244
src/components/CustomWorldResultView.test.tsx
Normal file
244
src/components/CustomWorldResultView.test.tsx
Normal file
@@ -0,0 +1,244 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { useState } from 'react';
|
||||
import { expect, test, vi } from 'vitest';
|
||||
|
||||
import type { CustomWorldPlayableNpc, CustomWorldProfile } from '../types';
|
||||
import { CustomWorldResultView } from './CustomWorldResultView';
|
||||
|
||||
vi.mock('../services/aiService', () => ({
|
||||
generateCustomWorldPlayableNpc: vi.fn(),
|
||||
generateCustomWorldStoryNpc: vi.fn(),
|
||||
generateCustomWorldLandmark: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./CharacterAnimator', () => ({
|
||||
CharacterAnimator: () => <div>角色预览</div>,
|
||||
}));
|
||||
|
||||
vi.mock('./CustomWorldNpcVisualEditor', () => ({
|
||||
CustomWorldNpcPortrait: ({ npc }: { npc: { name: string } }) => (
|
||||
<div>{npc.name}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('./CustomWorldEntityEditorModal', () => ({
|
||||
CustomWorldEntityEditorModal: () => null,
|
||||
}));
|
||||
|
||||
async function loadAiService() {
|
||||
return import('../services/aiService');
|
||||
}
|
||||
|
||||
function createBackstoryReveal() {
|
||||
return {
|
||||
publicSummary: '公开背景',
|
||||
chapters: [
|
||||
{
|
||||
id: 'surface',
|
||||
title: '表层来意',
|
||||
affinityRequired: 6,
|
||||
teaser: '表层来意',
|
||||
content: '表层来意内容',
|
||||
contextSnippet: '表层来意摘要',
|
||||
},
|
||||
{
|
||||
id: 'scar',
|
||||
title: '旧事裂痕',
|
||||
affinityRequired: 12,
|
||||
teaser: '旧事裂痕',
|
||||
content: '旧事裂痕内容',
|
||||
contextSnippet: '旧事裂痕摘要',
|
||||
},
|
||||
{
|
||||
id: 'hidden',
|
||||
title: '隐藏执念',
|
||||
affinityRequired: 18,
|
||||
teaser: '隐藏执念',
|
||||
content: '隐藏执念内容',
|
||||
contextSnippet: '隐藏执念摘要',
|
||||
},
|
||||
{
|
||||
id: 'final',
|
||||
title: '最终底牌',
|
||||
affinityRequired: 24,
|
||||
teaser: '最终底牌',
|
||||
content: '最终底牌内容',
|
||||
contextSnippet: '最终底牌摘要',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function createPlayableRole(id: string, name: string): CustomWorldPlayableNpc {
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
title: '同行者',
|
||||
role: '协作战力',
|
||||
description: `${name}的定位描述`,
|
||||
backstory: `${name}的背景`,
|
||||
personality: `${name}的性格`,
|
||||
motivation: `${name}的动机`,
|
||||
combatStyle: `${name}的战斗风格`,
|
||||
initialAffinity: 18,
|
||||
relationshipHooks: ['关系钩子'],
|
||||
relations: [],
|
||||
tags: ['测试'],
|
||||
backstoryReveal: createBackstoryReveal(),
|
||||
skills: [
|
||||
{
|
||||
id: `${id}-skill-1`,
|
||||
name: '技能一',
|
||||
summary: '技能说明一',
|
||||
style: '起手压制',
|
||||
},
|
||||
{
|
||||
id: `${id}-skill-2`,
|
||||
name: '技能二',
|
||||
summary: '技能说明二',
|
||||
style: '机动周旋',
|
||||
},
|
||||
{
|
||||
id: `${id}-skill-3`,
|
||||
name: '技能三',
|
||||
summary: '技能说明三',
|
||||
style: '爆发终结',
|
||||
},
|
||||
],
|
||||
initialItems: [
|
||||
{
|
||||
id: `${id}-item-1`,
|
||||
name: '物品一',
|
||||
category: '武器',
|
||||
quantity: 1,
|
||||
rarity: 'rare',
|
||||
description: '物品说明一',
|
||||
tags: ['测试'],
|
||||
},
|
||||
{
|
||||
id: `${id}-item-2`,
|
||||
name: '物品二',
|
||||
category: '专属物品',
|
||||
quantity: 1,
|
||||
rarity: 'rare',
|
||||
description: '物品说明二',
|
||||
tags: ['测试'],
|
||||
},
|
||||
{
|
||||
id: `${id}-item-3`,
|
||||
name: '物品三',
|
||||
category: '消耗品',
|
||||
quantity: 2,
|
||||
rarity: 'uncommon',
|
||||
description: '物品说明三',
|
||||
tags: ['测试'],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const baseProfile = {
|
||||
id: 'world-1',
|
||||
settingText: '潮雾群岛上的禁制与旧航道正在一起失衡。',
|
||||
name: '潮雾群岛',
|
||||
subtitle: '旧航道与沉钟回响',
|
||||
summary: '一座正在被旧誓与新利益共同撕扯的群岛世界。',
|
||||
tone: '压抑、潮湿、带着未解旧伤。',
|
||||
playerGoal: '找到能让群岛重新稳定的关键节点。',
|
||||
templateWorldType: 'WUXIA',
|
||||
majorFactions: ['守潮盟', '沉钟会'],
|
||||
coreConflicts: ['旧航道归属', '沉钟遗产争夺'],
|
||||
attributeSchema: {},
|
||||
playableNpcs: [createPlayableRole('playable-1', '沈砺')],
|
||||
storyNpcs: [
|
||||
{
|
||||
...createPlayableRole('story-1', '顾潮音'),
|
||||
initialAffinity: 6,
|
||||
},
|
||||
],
|
||||
items: [],
|
||||
camp: {
|
||||
name: '潮灯居',
|
||||
description: '玩家最初落脚的旧灯塔内院。',
|
||||
dangerLevel: 'medium',
|
||||
},
|
||||
landmarks: [
|
||||
{
|
||||
id: 'landmark-1',
|
||||
name: '沉钟栈桥',
|
||||
description: '旧钟与潮声常年相撞的码头栈桥。',
|
||||
dangerLevel: 'medium',
|
||||
sceneNpcIds: ['story-1'],
|
||||
connections: [],
|
||||
},
|
||||
],
|
||||
creatorIntent: null,
|
||||
anchorPack: null,
|
||||
lockState: null,
|
||||
ownedSettingLayers: null,
|
||||
generationMode: 'full',
|
||||
generationStatus: 'complete',
|
||||
} as unknown as CustomWorldProfile;
|
||||
|
||||
function ResultViewHarness() {
|
||||
const [profile, setProfile] = useState(baseProfile);
|
||||
|
||||
return (
|
||||
<CustomWorldResultView
|
||||
profile={profile}
|
||||
previewCharacters={[]}
|
||||
isGenerating={false}
|
||||
progress={0}
|
||||
progressLabel=""
|
||||
error={null}
|
||||
onBack={() => {}}
|
||||
onProfileChange={setProfile}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
() =>
|
||||
new Promise<CustomWorldPlayableNpc>((resolve) => {
|
||||
resolveGeneration = resolve;
|
||||
}),
|
||||
);
|
||||
|
||||
render(<ResultViewHarness />);
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /可扮演角色/u }));
|
||||
await user.click(screen.getByRole('button', { name: '新增可扮演角色' }));
|
||||
|
||||
expect(screen.getByText('新可扮演角色')).toBeTruthy();
|
||||
expect(screen.getByText('正在整理世界上下文')).toBeTruthy();
|
||||
|
||||
const createButton = screen.getByRole('button', { name: '新增可扮演角色' });
|
||||
expect((createButton as HTMLButtonElement).disabled).toBe(true);
|
||||
|
||||
const finishGeneration = resolveGeneration;
|
||||
if (!finishGeneration) {
|
||||
throw new Error('expected pending playable generation resolver');
|
||||
}
|
||||
|
||||
(finishGeneration as (value: CustomWorldPlayableNpc) => void)(
|
||||
createPlayableRole('playable-2', '云止'),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('云止')).toBeTruthy();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('新可扮演角色')).toBeNull();
|
||||
});
|
||||
|
||||
expect(screen.getAllByText('新').length).toBeGreaterThan(0);
|
||||
});
|
||||
Reference in New Issue
Block a user