新增 PlatformUiKit 通用弹窗、按钮、状态、空态、媒体、表单和标签等公共组件 迁移结果页、创作工作台、认证入口、RPG 暗色面板和运行态弹窗的重复 UI chrome 补充组件测试、页面回归测试、技术文档和 Hermes 共享决策记录
173 lines
4.7 KiB
TypeScript
173 lines
4.7 KiB
TypeScript
/* @vitest-environment jsdom */
|
|
|
|
import { render, screen } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { expect, test, vi } from 'vitest';
|
|
|
|
import {
|
|
AnimationState,
|
|
type Character,
|
|
type CompanionRenderState,
|
|
type EquipmentLoadout,
|
|
WorldType,
|
|
} from '../types';
|
|
import { CharacterPanel } from './CharacterPanel';
|
|
|
|
vi.mock('./CharacterAnimator', () => ({
|
|
CharacterAnimator: () => <div>角色动画</div>,
|
|
}));
|
|
|
|
vi.mock('./MedievalNpcAnimator', () => ({
|
|
MedievalNpcAnimator: () => <div>NPC 动画</div>,
|
|
}));
|
|
|
|
vi.mock('./PixelCloseButton', () => ({
|
|
PixelCloseButton: ({
|
|
label,
|
|
onClick,
|
|
}: {
|
|
label: string;
|
|
onClick: () => void;
|
|
}) => (
|
|
<button type="button" onClick={onClick}>
|
|
{label}
|
|
</button>
|
|
),
|
|
}));
|
|
|
|
vi.mock('./PixelIcon', () => ({
|
|
PixelIcon: ({ className }: { className?: string }) => (
|
|
<span className={className}>像素图标</span>
|
|
),
|
|
}));
|
|
|
|
vi.mock('./ResolvedAssetImage', () => ({
|
|
ResolvedAssetImage: ({ alt }: { alt: string }) => <img alt={alt} />,
|
|
}));
|
|
|
|
function createCharacter(id: string, name: string): Character {
|
|
return {
|
|
id,
|
|
name,
|
|
title: `${name}称号`,
|
|
gender: 'female',
|
|
description: `${name}描述`,
|
|
backstory: `${name}背景故事`,
|
|
avatar: `/${id}.png`,
|
|
portrait: `/${id}.png`,
|
|
assetFolder: 'roles',
|
|
assetVariant: 'generated',
|
|
attributes: {
|
|
strength: 10,
|
|
agility: 10,
|
|
intelligence: 10,
|
|
spirit: 10,
|
|
},
|
|
personality: `${name}性格`,
|
|
skills: [],
|
|
adventureOpenings: {},
|
|
};
|
|
}
|
|
|
|
function findSharedDarkPanelForText(text: string) {
|
|
let current: HTMLElement | null = screen.getByText(text);
|
|
|
|
while (current) {
|
|
if (
|
|
current.className.includes('border-white/10') &&
|
|
current.className.includes('bg-black/25')
|
|
) {
|
|
return current;
|
|
}
|
|
current = current.parentElement;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
test('角色面板详情静态信息复用暗色平台子面板和胶囊标签', async () => {
|
|
const user = userEvent.setup();
|
|
const playerCharacter = createCharacter('hero', '沈行');
|
|
const companionCharacter = createCharacter('sword-princess', '闻雪');
|
|
const companionRenderState: CompanionRenderState = {
|
|
npcId: 'npc-companion-1',
|
|
character: companionCharacter,
|
|
hp: 42,
|
|
maxHp: 60,
|
|
mana: 18,
|
|
maxMana: 30,
|
|
skillCooldowns: {},
|
|
animationState: AnimationState.IDLE,
|
|
slot: 'upper',
|
|
};
|
|
|
|
render(
|
|
<CharacterPanel
|
|
worldType={WorldType.WUXIA}
|
|
playerCharacter={playerCharacter}
|
|
playerHp={80}
|
|
playerMaxHp={100}
|
|
playerMana={25}
|
|
playerMaxMana={40}
|
|
playerEquipment={{} as EquipmentLoadout}
|
|
companionRenderStates={[companionRenderState]}
|
|
companionArcStates={[
|
|
{
|
|
characterId: companionCharacter.id,
|
|
arcTheme: '潮声里的信任',
|
|
currentStage: 'opening',
|
|
activeConflictTags: [],
|
|
pendingEventIds: [],
|
|
resolvedEventIds: [],
|
|
},
|
|
]}
|
|
companionResolutions={[
|
|
{
|
|
characterId: companionCharacter.id,
|
|
resolutionType: 'bonded',
|
|
summary: '闻雪与主角完成潮声誓约。',
|
|
relatedThreadIds: ['thread-tide'],
|
|
},
|
|
]}
|
|
/>,
|
|
);
|
|
|
|
const multiplierBadge = screen.getAllByText(/适配 x/u)[0];
|
|
expect(multiplierBadge?.className).toContain('rounded-full');
|
|
expect(multiplierBadge?.className).toContain('bg-emerald-500/10');
|
|
|
|
await user.click(screen.getByRole('button', { name: /沈行/u }));
|
|
|
|
const levelProgressPanel = screen.getByTestId(
|
|
'character-panel-level-progress',
|
|
);
|
|
|
|
expect(levelProgressPanel.className).toContain('border-amber-300/18');
|
|
expect(levelProgressPanel.className).toContain('bg-amber-500/8');
|
|
expect(levelProgressPanel.className).toContain('rounded-xl');
|
|
expect(findSharedDarkPanelForText('沈行背景故事')?.className).toContain(
|
|
'bg-black/25',
|
|
);
|
|
expect(findSharedDarkPanelForText('沈行性格')?.className).toContain(
|
|
'bg-black/25',
|
|
);
|
|
|
|
await user.click(screen.getByRole('button', { name: '关闭角色详情' }));
|
|
await user.click(screen.getByRole('button', { name: /闻雪/u }));
|
|
|
|
expect(findSharedDarkPanelForText('个人线阶段')?.className).toContain(
|
|
'bg-black/25',
|
|
);
|
|
expect(findSharedDarkPanelForText('潮声里的信任')?.className).toContain(
|
|
'bg-black/25',
|
|
);
|
|
const resolutionPanel = screen.getByTestId('character-panel-resolution');
|
|
|
|
expect(resolutionPanel.className).toContain('border-emerald-400/18');
|
|
expect(resolutionPanel.className).toContain('bg-emerald-500/8');
|
|
expect(resolutionPanel.className).toContain('rounded-xl');
|
|
expect(findSharedDarkPanelForText('王庭剑')?.className).toContain(
|
|
'bg-black/25',
|
|
);
|
|
});
|