收口前端平台组件库能力

新增 PlatformUiKit 通用弹窗、按钮、状态、空态、媒体、表单和标签等公共组件
迁移结果页、创作工作台、认证入口、RPG 暗色面板和运行态弹窗的重复 UI chrome
补充组件测试、页面回归测试、技术文档和 Hermes 共享决策记录
This commit is contained in:
2026-06-10 10:24:18 +08:00
parent a4ee6ff698
commit 1ad25e30f8
226 changed files with 23364 additions and 7825 deletions

View 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]');
}
});