收口前端平台组件库能力
新增 PlatformUiKit 通用弹窗、按钮、状态、空态、媒体、表单和标签等公共组件 迁移结果页、创作工作台、认证入口、RPG 暗色面板和运行态弹窗的重复 UI chrome 补充组件测试、页面回归测试、技术文档和 Hermes 共享决策记录
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { render, screen, waitFor, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { useState } from 'react';
|
||||
import { expect, test, vi } from 'vitest';
|
||||
@@ -204,8 +204,7 @@ const baseProfile = {
|
||||
'玩家以返乡守灯人继承者身份切入,首夜就撞见禁航区假航灯重亮,动机是阻止更多船只误入死潮。',
|
||||
coreConflict:
|
||||
'守潮盟与沉钟会争夺航路解释权,有人借假航灯持续清洗整片群岛的旧证据,玩家回港当夜就被卷进禁航区封锁。',
|
||||
keyRelationships:
|
||||
'玩家与沈砺旧友互疑,沈砺掌握沉船夜的关键视角。',
|
||||
keyRelationships: '玩家与沈砺旧友互疑,沈砺掌握沉船夜的关键视角。',
|
||||
hiddenLines:
|
||||
'沉钟异动和旧案灭口是同一条线,表面看像海雾自然失控,揭示节奏是先见异常,再见旧案,再见操盘者。',
|
||||
iconicElements:
|
||||
@@ -324,7 +323,8 @@ function ResultViewRehydratingHarness() {
|
||||
test('clicking新增可扮演角色 shows pending item, disables button, and marks result as new', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
let resolveGeneration: ((value: CustomWorldPlayableNpc) => void) | null = null;
|
||||
let resolveGeneration: ((value: CustomWorldPlayableNpc) => void) | null =
|
||||
null;
|
||||
mockedRpgCreationAssetClient.generatePlayableNpc.mockImplementation(
|
||||
() =>
|
||||
new Promise<CustomWorldPlayableNpc>((resolve) => {
|
||||
@@ -385,7 +385,8 @@ test('world tab generates opening cg only after manual click and writes it back
|
||||
mockedRpgCreationAssetClient.generateOpeningCg.mockResolvedValue({
|
||||
id: 'opening-cg-1',
|
||||
status: 'ready',
|
||||
storyboardImageSrc: '/generated-custom-world-scenes/world/opening/storyboard.png',
|
||||
storyboardImageSrc:
|
||||
'/generated-custom-world-scenes/world/opening/storyboard.png',
|
||||
storyboardAssetId: 'storyboard-1',
|
||||
videoSrc: '/generated-custom-world-scenes/world/opening/opening.mp4',
|
||||
videoAssetId: 'video-1',
|
||||
@@ -407,9 +408,9 @@ test('world tab generates opening cg only after manual click and writes it back
|
||||
await user.click(screen.getByRole('button', { name: '生成' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockedRpgCreationAssetClient.generateOpeningCg).toHaveBeenCalledTimes(
|
||||
1,
|
||||
);
|
||||
expect(
|
||||
mockedRpgCreationAssetClient.generateOpeningCg,
|
||||
).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
@@ -425,7 +426,8 @@ test('world tab keeps opening cg visible after parent rehydrates normalized prof
|
||||
mockedRpgCreationAssetClient.generateOpeningCg.mockResolvedValue({
|
||||
id: 'opening-cg-1',
|
||||
status: 'ready',
|
||||
storyboardImageSrc: '/generated-custom-world-scenes/world/opening/storyboard.png',
|
||||
storyboardImageSrc:
|
||||
'/generated-custom-world-scenes/world/opening/storyboard.png',
|
||||
storyboardAssetId: 'storyboard-1',
|
||||
videoSrc: '/generated-custom-world-scenes/world/opening/opening.mp4',
|
||||
videoAssetId: 'video-1',
|
||||
@@ -521,14 +523,18 @@ test('landmark tab previews every generated act image while keeping chapter deta
|
||||
);
|
||||
|
||||
expect(
|
||||
(screen.getByRole('img', {
|
||||
name: '沉钟栈桥-潮声逼近',
|
||||
}) as HTMLImageElement).getAttribute('src'),
|
||||
(
|
||||
screen.getByRole('img', {
|
||||
name: '沉钟栈桥-潮声逼近',
|
||||
}) as HTMLImageElement
|
||||
).getAttribute('src'),
|
||||
).toBe('/generated-custom-world-scenes/scene-act-1.png');
|
||||
expect(
|
||||
(screen.getByRole('img', {
|
||||
name: '沉钟栈桥-钟楼回响',
|
||||
}) as HTMLImageElement).getAttribute('src'),
|
||||
(
|
||||
screen.getByRole('img', {
|
||||
name: '沉钟栈桥-钟楼回响',
|
||||
}) as HTMLImageElement
|
||||
).getAttribute('src'),
|
||||
).toBe('/generated-custom-world-scenes/scene-act-2.png');
|
||||
});
|
||||
|
||||
@@ -580,9 +586,7 @@ test('agent result view shows error when entity generation returns no new profil
|
||||
await user.click(screen.getByRole('button', { name: /场景角色/u }));
|
||||
await user.click(screen.getByRole('button', { name: '新增场景角色' }));
|
||||
|
||||
expect(
|
||||
await screen.findByText(/结果页未收到新增内容/u),
|
||||
).toBeTruthy();
|
||||
expect(await screen.findByText(/结果页未收到新增内容/u)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('agent result view keeps publish-enter action clickable and hides sticky publish hints', () => {
|
||||
@@ -652,11 +656,9 @@ test('agent result view opens publish blocker dialog only when user clicks publi
|
||||
await user.click(screen.getByRole('button', { name: '发布并进入世界' }));
|
||||
|
||||
expect(screen.getByRole('dialog', { name: '发布作品' })).toBeTruthy();
|
||||
expect(screen.getByText('发布检查')).toBeTruthy();
|
||||
expect(screen.getByText('封面设置')).toBeTruthy();
|
||||
expect(
|
||||
screen.getByText(/仍有角色缺少正式主图或动作资产/u),
|
||||
).toBeTruthy();
|
||||
expect(screen.getByText('发布检查').className).toContain('tracking-[0.18em]');
|
||||
expect(screen.getByText('封面设置').className).toContain('tracking-[0.18em]');
|
||||
expect(screen.getByText(/仍有角色缺少正式主图或动作资产/u)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('agent result view keeps publish-enter action enabled when publish gate is clear', () => {
|
||||
@@ -693,3 +695,35 @@ test('agent result view keeps publish-enter action enabled when publish gate is
|
||||
});
|
||||
expect((actionButton as HTMLButtonElement).disabled).toBe(false);
|
||||
});
|
||||
|
||||
test('result view confirms full regeneration with unified dialog', async () => {
|
||||
const user = userEvent.setup();
|
||||
const handleRegenerate = vi.fn();
|
||||
|
||||
render(
|
||||
<RpgCreationResultView
|
||||
profile={baseProfile}
|
||||
previewCharacters={[]}
|
||||
isGenerating={false}
|
||||
progress={0}
|
||||
progressLabel=""
|
||||
error={null}
|
||||
onBack={() => {}}
|
||||
onProfileChange={() => {}}
|
||||
onRegenerate={handleRegenerate}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '重新生成' }));
|
||||
|
||||
const dialog = screen.getByRole('dialog', { name: '重新生成' });
|
||||
expect(screen.getByText(/确认重新生成“潮雾群岛”吗/u)).toBeTruthy();
|
||||
|
||||
await user.click(within(dialog).getByRole('button', { name: '取消' }));
|
||||
expect(handleRegenerate).not.toHaveBeenCalled();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '重新生成' }));
|
||||
await user.click(screen.getByRole('button', { name: '确认重新生成' }));
|
||||
|
||||
expect(handleRegenerate).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user