收口前端平台组件库能力
新增 PlatformUiKit 通用弹窗、按钮、状态、空态、媒体、表单和标签等公共组件 迁移结果页、创作工作台、认证入口、RPG 暗色面板和运行态弹窗的重复 UI chrome 补充组件测试、页面回归测试、技术文档和 Hermes 共享决策记录
This commit is contained in:
@@ -21,8 +21,118 @@ vi.mock('../../services/visual-novel-creation', () => ({
|
||||
|
||||
vi.mock('../../services/assetReadUrlService', () => ({
|
||||
resolveAssetReadUrl: vi.fn().mockResolvedValue(''),
|
||||
shouldResolveAssetReadUrl: vi.fn(() => false),
|
||||
}));
|
||||
|
||||
test('visual novel profile tab uses PlatformSubpanel shells', () => {
|
||||
const { container } = render(
|
||||
<VisualNovelResultView draft={mockVisualNovelDraft} onBack={() => {}} />,
|
||||
);
|
||||
|
||||
const profilePanels = Array.from(
|
||||
container.querySelectorAll('section.platform-subpanel'),
|
||||
).filter((panel) => panel.className.includes('rounded-[1.35rem]'));
|
||||
const workTitlePanel = screen.getByText('作品名称').closest('section');
|
||||
|
||||
expect(profilePanels).toHaveLength(2);
|
||||
for (const panel of profilePanels) {
|
||||
expect(panel.className).toContain('p-4');
|
||||
}
|
||||
expect(workTitlePanel?.className).toContain('platform-subpanel');
|
||||
expect(workTitlePanel?.className).toContain('rounded-[1.35rem]');
|
||||
});
|
||||
|
||||
test('visual novel profile media previews use PlatformMediaFrame aspects', () => {
|
||||
const draftWithCover = {
|
||||
...mockVisualNovelDraft,
|
||||
coverImageSrc: '/visual-novel/cover.png',
|
||||
};
|
||||
|
||||
const { container } = render(
|
||||
<VisualNovelResultView draft={draftWithCover} onBack={() => {}} />,
|
||||
);
|
||||
|
||||
const coverImages = Array.from(
|
||||
container.querySelectorAll('img[src="/visual-novel/cover.png"]'),
|
||||
);
|
||||
const mediaFrameClassNames = coverImages.map(
|
||||
(image) => image.closest('div.relative')?.className ?? '',
|
||||
);
|
||||
|
||||
expect(coverImages).toHaveLength(2);
|
||||
expect(mediaFrameClassNames).toContainEqual(
|
||||
expect.stringContaining('aspect-[4/3]'),
|
||||
);
|
||||
expect(mediaFrameClassNames).toContainEqual(
|
||||
expect.stringContaining('aspect-[16/9]'),
|
||||
);
|
||||
const assetPreviewFrameClassName = mediaFrameClassNames.find((className) =>
|
||||
className.includes('aspect-[16/9]'),
|
||||
);
|
||||
expect(assetPreviewFrameClassName).toBeTruthy();
|
||||
expect(assetPreviewFrameClassName).not.toContain(
|
||||
'bg-[var(--platform-subpanel-fill)]',
|
||||
);
|
||||
});
|
||||
|
||||
test('visual novel upload-only asset picker uses PlatformEmptyState chrome', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<VisualNovelResultView draft={mockVisualNovelDraft} onBack={() => {}} />,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '封面' }));
|
||||
|
||||
const pickerDialog = screen.getByRole('dialog', { name: '封面' });
|
||||
const uploadOnlyEmptyState =
|
||||
within(pickerDialog).getByText('选择本地文件上传');
|
||||
|
||||
expect(uploadOnlyEmptyState.className).toContain('border-dashed');
|
||||
expect(uploadOnlyEmptyState.className).toContain('rounded-[1.35rem]');
|
||||
expect(uploadOnlyEmptyState.className).toContain('min-h-24');
|
||||
expect(uploadOnlyEmptyState.className).toContain(
|
||||
'text-[var(--platform-text-base)]',
|
||||
);
|
||||
});
|
||||
|
||||
test('visual novel entity list items use interactive PlatformSubpanel shells', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<VisualNovelResultView draft={mockVisualNovelDraft} onBack={() => {}} />,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '角色' }));
|
||||
|
||||
const characterCard = screen.getByRole('button', { name: /林遥/u });
|
||||
|
||||
expect(characterCard.className).toContain('platform-subpanel');
|
||||
expect(characterCard.className).toContain('hover:bg-white');
|
||||
expect(characterCard.className).toContain('disabled:cursor-not-allowed');
|
||||
expect(characterCard.className).toContain('rounded-[1.25rem]');
|
||||
});
|
||||
|
||||
test('visual novel empty entity list uses PlatformSubpanel shell', async () => {
|
||||
const user = userEvent.setup();
|
||||
const emptyCharacterDraft = {
|
||||
...mockVisualNovelDraft,
|
||||
characters: [],
|
||||
};
|
||||
|
||||
render(
|
||||
<VisualNovelResultView draft={emptyCharacterDraft} onBack={() => {}} />,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '角色' }));
|
||||
|
||||
const emptyPanel = screen.getByText('暂无角色').closest('.platform-subpanel');
|
||||
|
||||
expect(emptyPanel?.className).toContain('platform-subpanel');
|
||||
expect(emptyPanel?.className).toContain('rounded-[1.25rem]');
|
||||
expect(emptyPanel?.className).toContain('min-h-32');
|
||||
});
|
||||
|
||||
test('visual novel result opens complex editors as a dialog', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
@@ -140,7 +250,9 @@ test('visual novel result uploads scene and character assets into platform refer
|
||||
test('visual novel result generates scene background from asset picker', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSaveDraft = vi.fn();
|
||||
const visualNovelCreation = await import('../../services/visual-novel-creation');
|
||||
const visualNovelCreation = await import(
|
||||
'../../services/visual-novel-creation'
|
||||
);
|
||||
const generateImageMock = vi.mocked(
|
||||
visualNovelCreation.generateVisualNovelImageAsset,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user