收口前端平台组件库能力
新增 PlatformUiKit 通用弹窗、按钮、状态、空态、媒体、表单和标签等公共组件 迁移结果页、创作工作台、认证入口、RPG 暗色面板和运行态弹窗的重复 UI chrome 补充组件测试、页面回归测试、技术文档和 Hermes 共享决策记录
This commit is contained in:
119
src/components/CharacterDetailModal.test.tsx
Normal file
119
src/components/CharacterDetailModal.test.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { expect, test, vi } from 'vitest';
|
||||
|
||||
import { AnimationState, type Character, WorldType } from '../types';
|
||||
import { CharacterDetailModal } from './CharacterDetailModal';
|
||||
|
||||
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>
|
||||
),
|
||||
}));
|
||||
|
||||
function createCharacter(): Character {
|
||||
return {
|
||||
id: 'sword-princess',
|
||||
name: '剑之公主',
|
||||
title: '王庭剑姬',
|
||||
gender: 'female',
|
||||
description: '以迅疾剑技和正面压制见长。',
|
||||
backstory: '王庭旁支出身,正在追回失落誓剑。',
|
||||
avatar: '/roles/sword-princess.png',
|
||||
portrait: '/roles/sword-princess.png',
|
||||
assetFolder: 'roles',
|
||||
assetVariant: 'generated',
|
||||
attributes: {
|
||||
strength: 12,
|
||||
agility: 14,
|
||||
intelligence: 8,
|
||||
spirit: 10,
|
||||
},
|
||||
personality: '外冷内热,做决定时很少犹豫。',
|
||||
skills: [
|
||||
{
|
||||
id: 'oath-slash',
|
||||
name: '誓剑斩',
|
||||
animation: AnimationState.SKILL1,
|
||||
damage: 18,
|
||||
manaCost: 6,
|
||||
cooldownTurns: 2,
|
||||
range: 1,
|
||||
style: 'burst',
|
||||
},
|
||||
],
|
||||
adventureOpenings: {
|
||||
[WorldType.WUXIA]: {
|
||||
reason: '踏入王庭旧案。',
|
||||
goal: '追回誓剑。',
|
||||
monologue: '旧誓仍在。',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function findPanelForText(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('角色详情装备背包和旅程信息复用暗色平台子面板', () => {
|
||||
render(
|
||||
<CharacterDetailModal
|
||||
character={createCharacter()}
|
||||
worldType={WorldType.WUXIA}
|
||||
onClose={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const candidateBadge = screen.getByText('候选人');
|
||||
const genderBadge = screen.getByText('性别: 女');
|
||||
|
||||
expect(candidateBadge.className).toContain('rounded-full');
|
||||
expect(candidateBadge.className).toContain('bg-sky-500/10');
|
||||
expect(genderBadge.className).toContain('rounded-full');
|
||||
expect(genderBadge.className).toContain('bg-black/20');
|
||||
|
||||
for (const text of [
|
||||
'王庭剑',
|
||||
'武斗牌',
|
||||
'踏入王庭旧案。',
|
||||
'追回誓剑。',
|
||||
'王庭旁支出身,正在追回失落誓剑。',
|
||||
'外冷内热,做决定时很少犹豫。',
|
||||
]) {
|
||||
const panel = findPanelForText(text);
|
||||
|
||||
expect(panel?.className).toContain('border-white/10');
|
||||
expect(panel?.className).toContain('bg-black/25');
|
||||
expect(panel?.className).toContain('rounded-[1rem]');
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user