This commit is contained in:
2026-04-21 18:27:46 +08:00
parent 04bff9617d
commit 4372ab5be1
358 changed files with 30788 additions and 14737 deletions

View File

@@ -6,13 +6,35 @@ import { useState } from 'react';
import { expect, test, vi } from 'vitest';
import type { CustomWorldPlayableNpc, CustomWorldProfile } from '../types';
import { CustomWorldResultView } from './CustomWorldResultView';
import * as rpgCreationAssetClient from '../services/rpg-creation/rpgCreationAssetClient';
import { RpgCreationResultView } from './rpg-creation-result/RpgCreationResultView';
vi.mock('../services/aiService', () => ({
generateCustomWorldPlayableNpc: vi.fn(),
generateCustomWorldStoryNpc: vi.fn(),
generateCustomWorldLandmark: vi.fn(),
}));
vi.mock('../services/rpg-creation/rpgCreationAssetClient', () => {
const generatePlayableNpc = vi.fn();
const generateStoryNpc = vi.fn();
const generateLandmark = vi.fn();
const generateSceneImage = vi.fn();
const generateSceneNpc = vi.fn();
return {
rpgCreationAssetClient: {
generatePlayableNpc,
generateStoryNpc,
generateLandmark,
generateSceneImage,
generateSceneNpc,
},
generateCustomWorldPlayableNpc: generatePlayableNpc,
generateCustomWorldStoryNpc: generateStoryNpc,
generateCustomWorldLandmark: generateLandmark,
generateCustomWorldSceneImage: generateSceneImage,
generateCustomWorldSceneNpc: generateSceneNpc,
};
});
const mockedRpgCreationAssetClient = vi.mocked(
rpgCreationAssetClient.rpgCreationAssetClient,
);
vi.mock('./CharacterAnimator', () => ({
CharacterAnimator: () => <div></div>,
@@ -24,15 +46,11 @@ vi.mock('./CustomWorldNpcVisualEditor', () => ({
),
}));
vi.mock('./CustomWorldEntityEditorModal', () => ({
CustomWorldEntityEditorModal: () => null,
vi.mock('./rpg-creation-editor/RpgCreationEntityEditorModal', () => ({
RpgCreationEntityEditorModal: () => null,
default: () => null,
}));
async function loadAiService() {
return import('../services/aiService');
}
function createBackstoryReveal() {
return {
publicSummary: '公开背景',
@@ -259,7 +277,7 @@ function ResultViewHarness() {
const [profile, setProfile] = useState(baseProfile);
return (
<CustomWorldResultView
<RpgCreationResultView
profile={profile}
previewCharacters={[]}
isGenerating={false}
@@ -273,11 +291,10 @@ function ResultViewHarness() {
}
test('clicking新增可扮演角色 shows pending item, disables button, and marks result as new', async () => {
const aiService = await loadAiService();
const user = userEvent.setup();
let resolveGeneration: ((value: CustomWorldPlayableNpc) => void) | null = null;
vi.mocked(aiService.generateCustomWorldPlayableNpc).mockImplementation(
mockedRpgCreationAssetClient.generatePlayableNpc.mockImplementation(
() =>
new Promise<CustomWorldPlayableNpc>((resolve) => {
resolveGeneration = resolve;
@@ -346,7 +363,7 @@ test('playable tab prefers generated portrait over runtime preview placeholder',
} as CustomWorldProfile;
render(
<CustomWorldResultView
<RpgCreationResultView
profile={profile}
previewCharacters={[
{
@@ -403,7 +420,7 @@ test('readOnly result view hides edit and create actions for agent preview mode'
const user = userEvent.setup();
render(
<CustomWorldResultView
<RpgCreationResultView
profile={baseProfile}
previewCharacters={[]}
isGenerating={false}
@@ -425,3 +442,80 @@ test('readOnly result view hides edit and create actions for agent preview mode'
await user.click(screen.getByRole('button', { name: //u }));
expect(screen.queryByRole('button', { name: //u })).toBeNull();
});
test('agent result view shows publish blockers and disables publish-enter action', () => {
render(
<RpgCreationResultView
profile={baseProfile}
previewCharacters={[]}
isGenerating={false}
progress={0}
progressLabel=""
error={null}
onBack={() => {}}
onProfileChange={() => {}}
compactAgentResultMode
publishReady={false}
publishBlockers={[
'仍有角色缺少正式主图或动作资产,发布前需要先补齐。',
'营地还缺少正式场景图资产,发布前需要先确认营地图。',
]}
qualityFindings={[
{
id: 'role-assets-pending',
severity: 'warning',
code: 'role_assets_pending',
message: '仍有角色资产未完全补齐。',
},
]}
previewSourceLabel="服务端预览"
enterWorldActionLabel="发布并进入世界"
onEnterWorld={() => {}}
/>,
);
expect(screen.getByText(//u)).toBeTruthy();
expect(screen.getByText(/ 2 /u)).toBeTruthy();
expect(
screen.getByText(//u),
).toBeTruthy();
const actionButton = screen.getByRole('button', {
name: '发布并进入世界',
});
expect((actionButton as HTMLButtonElement).disabled).toBe(true);
});
test('agent result view keeps publish-enter action enabled when publish gate is clear', () => {
render(
<RpgCreationResultView
profile={baseProfile}
previewCharacters={[]}
isGenerating={false}
progress={0}
progressLabel=""
error={null}
onBack={() => {}}
onProfileChange={() => {}}
compactAgentResultMode
publishReady
publishBlockers={[]}
qualityFindings={[
{
id: 'scene-assets-pending',
severity: 'warning',
code: 'scene_assets_pending',
message: '仍有场景分幕图未补齐。',
},
]}
previewSourceLabel="服务端预览"
enterWorldActionLabel="发布并进入世界"
onEnterWorld={() => {}}
/>,
);
expect(screen.getByText(/ 1 warning /u)).toBeTruthy();
const actionButton = screen.getByRole('button', {
name: '发布并进入世界',
});
expect((actionButton as HTMLButtonElement).disabled).toBe(false);
});