收口前端平台组件库能力

新增 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

@@ -18,6 +18,23 @@ vi.mock('../ResolvedAssetImage', () => ({
}) => (src ? <img src={src} alt={alt} className={className} /> : null),
}));
function findNearestClassName(
element: HTMLElement,
classNamePart: string,
): HTMLElement | null {
let current: HTMLElement | null = element;
while (current) {
if (current.className.includes(classNamePart)) {
return current;
}
current = current.parentElement;
}
return null;
}
function createSession(): BigFishSessionSnapshotResponse {
return {
sessionId: 'big-fish-session-1',
@@ -151,8 +168,65 @@ describe('BigFishResultView', () => {
);
expect(screen.getByText('主图 已生成')).toBeTruthy();
expect(screen.getByAltText('荧潮幼体')).toBeTruthy();
expect(screen.getByAltText('深海谜境 场地背景')).toBeTruthy();
const levelImage = screen.getByAltText('荧潮幼体');
expect(levelImage).toBeTruthy();
const levelFrame = findNearestClassName(levelImage, 'relative');
expect(levelFrame?.className).toContain('aspect-square');
expect(levelFrame?.className).toContain('radial-gradient');
expect(levelFrame?.className).toContain('linear-gradient');
expect(levelFrame?.className).not.toContain(
'bg-[var(--platform-subpanel-fill)]',
);
const backgroundImage = screen.getByAltText('深海谜境 场地背景');
expect(backgroundImage).toBeTruthy();
const backgroundFrame = findNearestClassName(backgroundImage, 'relative');
expect(backgroundFrame?.className).toContain('aspect-[9/16]');
expect(backgroundFrame?.className).toContain('radial-gradient');
expect(backgroundFrame?.className).toContain('linear-gradient');
expect(backgroundFrame?.className).not.toContain(
'bg-[var(--platform-subpanel-fill)]',
);
expect(
findNearestClassName(screen.getByText('荧潮幼体'), 'platform-subpanel')
?.className,
).toContain('rounded-[1.5rem]');
expect(
findNearestClassName(screen.getByText('场地背景'), 'platform-subpanel')
?.className,
).toContain('rounded-[1.5rem]');
expect(
findNearestClassName(screen.getByText('发布校验'), 'platform-subpanel')
?.className,
).toContain('rounded-[1.5rem]');
});
test('uses platform pill badge for ready publish status', () => {
render(
<BigFishResultView
session={{
...createSession(),
publishReady: true,
assetCoverage: {
levelMainImageReadyCount: 1,
levelMotionReadyCount: 2,
backgroundReady: true,
requiredLevelCount: 1,
publishReady: true,
blockers: [],
},
}}
onBack={() => {}}
onExecuteAction={() => {}}
onStartTestRun={() => {}}
/>,
);
const readyBadge = screen.getByText('已达到发布条件');
expect(readyBadge.tagName).toBe('SPAN');
expect(readyBadge.className).toContain('rounded-full');
expect(readyBadge.className).toContain('border-emerald-200');
expect(readyBadge.className).toContain('bg-emerald-50');
});
test('uses level descriptions as default prompt content in asset studio', () => {
@@ -166,8 +240,21 @@ describe('BigFishResultView', () => {
);
fireEvent.click(screen.getByRole('button', { name: '主图' }));
expect(screen.getByText('PROMPT').className).toContain('tracking-[0.18em]');
const studioPreviewFrame = findNearestClassName(
screen.getByAltText('Lv.1 主图工坊'),
'relative',
);
expect(studioPreviewFrame?.className).toContain('aspect-[9/5]');
expect(studioPreviewFrame?.className).toContain('border-dashed');
expect(studioPreviewFrame?.className).toContain('bg-cyan-50/40');
expect(studioPreviewFrame?.className).not.toContain(
'bg-[var(--platform-subpanel-fill)]',
);
expect(
screen.getByText('带有浅青色荧光纹路的小型鱼苗,轮廓圆润,呈现弱小但灵动的开局形象。'),
screen.getByText(
'带有浅青色荧光纹路的小型鱼苗,轮廓圆润,呈现弱小但灵动的开局形象。',
),
).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: '关闭' }));
@@ -183,6 +270,33 @@ describe('BigFishResultView', () => {
).toBeTruthy();
});
test('uses PlatformActionButton chrome for white surface asset actions', () => {
render(
<BigFishResultView
session={createSession()}
onBack={() => {}}
onExecuteAction={() => {}}
onStartTestRun={() => {}}
/>,
);
for (const actionName of ['主图', '待机', '移动', '生成背景']) {
const action = screen.getByRole('button', { name: actionName });
expect(action.className).toContain('platform-button');
expect(action.className).toContain('rounded-full');
}
fireEvent.click(screen.getByRole('button', { name: '主图' }));
for (const actionName of ['关闭', '生成并应用正式图']) {
const action = screen.getByRole('button', { name: actionName });
expect(action.className).toContain('platform-button');
expect(action.className).toContain('rounded-full');
}
});
test('shows publish failures in a dismissible modal', () => {
const onDismissError = vi.fn();
@@ -202,6 +316,13 @@ describe('BigFishResultView', () => {
expect(
screen.getByText('big_fish 发布校验未通过:还缺少 16 个基础动作'),
).toBeTruthy();
const iconBadge = screen.getByLabelText('发布失败提示');
expect(iconBadge.className).toContain(
'bg-[var(--platform-button-danger-fill)]',
);
expect(iconBadge.className).toContain(
'text-[var(--platform-button-danger-text)]',
);
fireEvent.click(screen.getByRole('button', { name: '知道了' }));
expect(onDismissError).toHaveBeenCalledTimes(1);
@@ -234,6 +355,11 @@ describe('BigFishResultView', () => {
const publishedButton = screen.getByRole('button', { name: '已发布' });
expect((publishedButton as HTMLButtonElement).disabled).toBe(true);
expect(screen.getAllByText('已发布').length).toBeGreaterThan(0);
const publishedBadge = screen
.getAllByText('已发布')
.find((element) => element.tagName === 'SPAN');
expect(publishedBadge?.className).toContain('border-emerald-200');
expect(publishedBadge?.className).toContain('bg-emerald-50');
fireEvent.click(publishedButton);
expect(onExecuteAction).not.toHaveBeenCalled();
});