/** @vitest-environment jsdom */ import { act, render } from '@testing-library/react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { CustomWorldAgentSessionSnapshot } from '../../../packages/shared/src/contracts/customWorldAgent'; import type { CustomWorldWorkSummary } from '../../../packages/shared/src/contracts/customWorldWorkSummary'; import { WorldType, type CustomWorldProfile } from '../../types'; import { executeRpgCreationAction, upsertRpgWorldProfile, } from '../../services/rpg-creation'; import { useRpgCreationResultAutosave } from './useRpgCreationResultAutosave'; import { useRpgEntryLibraryDetail } from './useRpgEntryLibraryDetail'; vi.mock('../../services/rpg-creation', () => ({ executeRpgCreationAction: vi.fn(), getRpgCreationOperation: vi.fn(), upsertRpgWorldProfile: vi.fn(), })); vi.mock('../../services/rpg-entry', () => ({ deleteRpgEntryWorldProfile: vi.fn(), getRpgEntryWorldGalleryDetail: vi.fn(), listRpgEntryWorldLibrary: vi.fn(), publishRpgEntryWorldProfile: vi.fn(), unpublishRpgEntryWorldProfile: vi.fn(), })); function buildProfile(name: string): CustomWorldProfile { return { id: `profile-${name}`, settingText: name, name, subtitle: name, summary: name, tone: '测试', playerGoal: '测试', templateWorldType: WorldType.WUXIA, compatibilityTemplateWorldType: WorldType.WUXIA, majorFactions: [], coreConflicts: [], attributeSchema: { id: `schema-${name}`, worldId: `profile-${name}`, schemaVersion: 1, generatedFrom: { worldType: WorldType.CUSTOM, worldName: name, settingSummary: name, tone: '测试', conflictCore: '测试', }, slots: [], }, playableNpcs: [], storyNpcs: [], items: [], landmarks: [], generationMode: 'full', generationStatus: 'complete', }; } function buildSession( overrides: Partial = {}, ): CustomWorldAgentSessionSnapshot { return { sessionId: 'agent-session-1', currentTurn: 1, anchorContent: { worldPromise: null, playerFantasy: null, themeBoundary: null, playerEntryPoint: null, coreConflict: null, keyRelationships: [], hiddenLines: null, iconicElements: null, }, progressPercent: 20, lastAssistantReply: '继续补齐世界草稿。', stage: 'clarifying', focusCardId: null, creatorIntent: null, creatorIntentReadiness: { isReady: false, completedKeys: [], missingKeys: [], }, anchorPack: null, lockState: null, draftProfile: null, messages: [], draftCards: [], pendingClarifications: [], suggestedActions: [], recommendedReplies: [], qualityFindings: [], assetCoverage: { roleAssets: [], sceneAssets: [], allRoleAssetsReady: false, allSceneAssetsReady: false, }, resultPreview: null, updatedAt: '2026-04-25T00:00:00.000Z', ...overrides, }; } describe('RPG Agent 草稿恢复', () => { beforeEach(() => { vi.clearAllMocks(); }); it('作品摘要已有对象数量但 session 没有 draftProfile 时恢复 Agent 页面', async () => { const syncAgentSessionSnapshot = vi.fn(async () => buildSession({ stage: 'clarifying', draftProfile: null, }), ); const setSelectionStage = vi.fn(); const persistAgentUiState = vi.fn(); const setGeneratedCustomWorldProfile = vi.fn(); const setCustomWorldResultViewSource = vi.fn(); const suppressAgentDraftResultAutoOpen = vi.fn(); let openWork: | ((work: CustomWorldWorkSummary) => Promise) | null = null; function Harness() { openWork = useRpgEntryLibraryDetail({ userId: 'user-1', selectedDetailEntry: null, setSelectedDetailEntry: vi.fn(), savedCustomWorldEntries: [], setSavedCustomWorldEntries: vi.fn(), setGeneratedCustomWorldProfile, setCustomWorldError: vi.fn(), setCustomWorldAutoSaveError: vi.fn(), setCustomWorldAutoSaveState: vi.fn(), setCustomWorldGenerationViewSource: vi.fn(), setCustomWorldResultViewSource, setSelectionStage, setPlatformTabToCreate: vi.fn(), setPlatformError: vi.fn(), appendBrowseHistoryEntry: vi.fn(async () => {}), refreshCustomWorldWorks: vi.fn(async () => []), refreshPublishedGallery: vi.fn(async () => []), persistAgentUiState, syncAgentSessionSnapshot, buildDraftResultProfile: (session) => (session?.draftProfile as CustomWorldProfile | null) ?? null, suppressAgentDraftResultAutoOpen, releaseAgentDraftResultAutoOpenSuppression: vi.fn(), resetAutoSaveTrackingToIdle: vi.fn(), markAutoSavedProfile: vi.fn(), }).handleOpenCreationWork; return null; } render(); await act(async () => { await openWork?.({ workId: 'draft:agent-session-1', sourceType: 'agent_session', status: 'draft', title: '未生成草稿作品', subtitle: '', summary: '', updatedAt: '2026-04-25T00:00:00.000Z', stage: 'clarifying', stageLabel: '澄清中', playableNpcCount: 2, landmarkCount: 3, sessionId: 'agent-session-1', canResume: true, canEnterWorld: false, }); }); expect(syncAgentSessionSnapshot).toHaveBeenCalledWith('agent-session-1'); expect(suppressAgentDraftResultAutoOpen).toHaveBeenCalled(); expect(persistAgentUiState).toHaveBeenCalledWith('agent-session-1', null); expect(setGeneratedCustomWorldProfile).toHaveBeenLastCalledWith(null); expect(setCustomWorldResultViewSource).toHaveBeenLastCalledWith(null); expect(setSelectionStage).toHaveBeenLastCalledWith('agent-workspace'); expect(setSelectionStage).not.toHaveBeenCalledWith('custom-world-result'); }); it('Agent 结果页自动保存只刷新 session draftProfile,不触发 sync_result_profile', async () => { const oldProfile = buildProfile('旧前端快照'); const latestProfile = { ...buildProfile('服务端草稿快照'), summary: '自动保存应保存这份 session 最新草稿。', }; const latestSession = buildSession({ stage: 'object_refining', draftProfile: latestProfile as unknown as Record, }); const syncAgentSessionSnapshot = vi.fn(async () => latestSession); vi.mocked(upsertRpgWorldProfile).mockResolvedValue({ entry: { ownerUserId: 'user-1', profileId: latestProfile.id, publicWorkCode: null, authorPublicUserCode: null, profile: latestProfile, visibility: 'draft', publishedAt: null, updatedAt: '2026-04-25T00:00:00.000Z', authorDisplayName: '测试玩家', worldName: latestProfile.name, subtitle: latestProfile.subtitle, summaryText: latestProfile.summary, coverImageSrc: null, themeMode: 'tide', playableNpcCount: 0, landmarkCount: 0, }, entries: [], }); function Harness() { useRpgCreationResultAutosave({ selectionStage: 'custom-world-result', activeAgentSessionId: 'agent-session-1', agentSession: buildSession({ stage: 'object_refining', draftProfile: oldProfile as unknown as Record, resultPreview: { publishReady: false, blockers: [], qualityFindings: [], sourceLabel: '旧预览', } as never, }), generatedCustomWorldProfile: oldProfile, isAgentDraftResultView: true, userId: 'user-1', setGeneratedCustomWorldProfile: vi.fn(), setAgentOperation: vi.fn(), setSavedCustomWorldEntries: vi.fn(), setSelectedDetailEntry: vi.fn(), refreshCustomWorldWorks: vi.fn(async () => []), persistAgentUiState: vi.fn(), syncAgentSessionSnapshot, buildDraftResultProfile: (session) => (session?.draftProfile as CustomWorldProfile | null) ?? null, }); return null; } vi.useFakeTimers(); render(); await act(async () => { await vi.advanceTimersByTimeAsync(650); }); vi.useRealTimers(); expect(syncAgentSessionSnapshot).toHaveBeenCalledWith('agent-session-1'); expect(upsertRpgWorldProfile).toHaveBeenCalledWith(latestProfile, { sourceAgentSessionId: 'agent-session-1', }); expect( vi.mocked(executeRpgCreationAction).mock.calls.some( ([, payload]) => payload?.action === 'sync_result_profile', ), ).toBe(false); }); });