/* @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: () =>
角色预览
, })); vi.mock('./CustomWorldNpcVisualEditor', () => ({ CustomWorldNpcPortrait: ({ npc }: { npc: { name: string } }) => (
{npc.name}
), CustomWorldNpcVisualEditor: () =>
预设形象编辑器
, })); 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( , ); 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( , ); await user.click(screen.getByRole('button', { name: 'AI生成' })); await waitFor(() => { expect(screen.getByText('AI角色形象生成')).toBeTruthy(); }); expect(handleClose).not.toHaveBeenCalled(); });