182 lines
5.1 KiB
TypeScript
182 lines
5.1 KiB
TypeScript
/* @vitest-environment jsdom */
|
|
|
|
import { render, screen, waitFor } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { expect, test, vi } from 'vitest';
|
|
|
|
import type {
|
|
CustomWorldNpc,
|
|
CustomWorldPlayableNpc,
|
|
CustomWorldProfile,
|
|
} from '../types';
|
|
import { CustomWorldEntityEditorModal } from './CustomWorldEntityEditorModal';
|
|
|
|
vi.mock('./CharacterAnimator', () => ({
|
|
CharacterAnimator: () => <div>角色预览</div>,
|
|
}));
|
|
|
|
vi.mock('./CustomWorldNpcVisualEditor', () => ({
|
|
CustomWorldNpcPortrait: ({ npc }: { npc: { name: string } }) => (
|
|
<div>{npc.name}</div>
|
|
),
|
|
CustomWorldNpcVisualEditor: () => <div>预设形象编辑器</div>,
|
|
}));
|
|
|
|
vi.mock('./asset-studio/characterAssetWorkflowPersistence', () => ({
|
|
fetchCharacterWorkflowCache: vi.fn().mockResolvedValue({ cache: null }),
|
|
generateCharacterPromptBundle: vi.fn().mockResolvedValue({
|
|
visualPromptText: '自动生成的形象提示词',
|
|
animationPromptText: '自动生成的动作提示词',
|
|
}),
|
|
saveCharacterWorkflowCache: vi.fn().mockResolvedValue(undefined),
|
|
generateCharacterVisualCandidates: vi.fn(),
|
|
publishCharacterVisualAsset: vi.fn(),
|
|
generateCharacterAnimationDraft: vi.fn(),
|
|
publishCharacterAnimationAssets: vi.fn(),
|
|
}));
|
|
|
|
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: [],
|
|
initialItems: [],
|
|
templateCharacterId: 'knight-female-1',
|
|
};
|
|
}
|
|
|
|
function createStoryRole(id: string, name: string): CustomWorldNpc {
|
|
return {
|
|
...createPlayableRole(id, name),
|
|
initialAffinity: 6,
|
|
visual: undefined,
|
|
};
|
|
}
|
|
|
|
function createProfile(): CustomWorldProfile {
|
|
return {
|
|
id: 'world-1',
|
|
settingText: '潮雾群岛上的禁制与旧航道正在一起失衡。',
|
|
name: '潮雾群岛',
|
|
subtitle: '旧航道与沉钟回响',
|
|
summary: '一座正在被旧誓与新利益共同撕扯的群岛世界。',
|
|
tone: '压抑、潮湿、带着未解旧伤。',
|
|
playerGoal: '找到能让群岛重新稳定的关键节点。',
|
|
templateWorldType: 'WUXIA',
|
|
majorFactions: ['守潮盟', '沉钟会'],
|
|
coreConflicts: ['旧航道归属', '沉钟遗产争夺'],
|
|
attributeSchema: {},
|
|
playableNpcs: [createPlayableRole('playable-1', '沈砺')],
|
|
storyNpcs: [createStoryRole('story-1', '顾潮音')],
|
|
items: [],
|
|
camp: {
|
|
name: '潮灯居',
|
|
description: '玩家最初落脚的旧灯塔内院。',
|
|
dangerLevel: 'medium',
|
|
},
|
|
landmarks: [],
|
|
creatorIntent: null,
|
|
anchorPack: null,
|
|
lockState: null,
|
|
ownedSettingLayers: null,
|
|
generationMode: 'full',
|
|
generationStatus: 'complete',
|
|
} as unknown as CustomWorldProfile;
|
|
}
|
|
|
|
test('playable角色打开AI工坊后不会自动关闭', async () => {
|
|
const user = userEvent.setup();
|
|
const handleClose = vi.fn();
|
|
|
|
render(
|
|
<CustomWorldEntityEditorModal
|
|
profile={createProfile()}
|
|
target={{ kind: 'playable', mode: 'edit', id: 'playable-1' }}
|
|
onClose={handleClose}
|
|
onProfileChange={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
await user.click(screen.getByRole('button', { name: 'AI生成' }));
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('AI角色形象生成')).toBeTruthy();
|
|
});
|
|
|
|
expect(handleClose).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('场景角色打开AI工坊后不会自动关闭', async () => {
|
|
const user = userEvent.setup();
|
|
const handleClose = vi.fn();
|
|
|
|
render(
|
|
<CustomWorldEntityEditorModal
|
|
profile={createProfile()}
|
|
target={{ kind: 'story', mode: 'edit', id: 'story-1' }}
|
|
onClose={handleClose}
|
|
onProfileChange={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
await user.click(screen.getByRole('button', { name: 'AI生成' }));
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('AI角色形象生成')).toBeTruthy();
|
|
});
|
|
|
|
expect(handleClose).not.toHaveBeenCalled();
|
|
});
|