1
This commit is contained in:
278
src/components/rpg-entry/useRpgEntryAgentDraftRestore.test.tsx
Normal file
278
src/components/rpg-entry/useRpgEntryAgentDraftRestore.test.tsx
Normal file
@@ -0,0 +1,278 @@
|
||||
/** @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> = {},
|
||||
): 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<void>)
|
||||
| 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(<Harness />);
|
||||
|
||||
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<string, unknown>,
|
||||
});
|
||||
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<string, unknown>,
|
||||
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(<Harness />);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user