1
This commit is contained in:
@@ -8,6 +8,7 @@ import { beforeEach, expect, test, vi } from 'vitest';
|
||||
import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary';
|
||||
import type { CustomWorldAgentSessionSnapshot } from '../../../packages/shared/src/contracts/customWorldAgent';
|
||||
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
|
||||
import type { RpgCreationResultView } from '../../../packages/shared/src/contracts/rpgCreationResultView';
|
||||
import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes';
|
||||
import { ApiClientError } from '../../services/apiClient';
|
||||
import type { AuthUser } from '../../services/authService';
|
||||
@@ -32,6 +33,7 @@ import {
|
||||
createRpgCreationSession,
|
||||
executeRpgCreationAction,
|
||||
getRpgCreationOperation,
|
||||
getRpgCreationResultView,
|
||||
getRpgCreationSession,
|
||||
listRpgCreationWorks,
|
||||
streamRpgCreationMessage,
|
||||
@@ -101,6 +103,7 @@ vi.mock('../../services/rpg-creation', () => ({
|
||||
createRpgCreationSession: vi.fn(),
|
||||
executeRpgCreationAction: vi.fn(),
|
||||
getRpgCreationOperation: vi.fn(),
|
||||
getRpgCreationResultView: vi.fn(),
|
||||
getRpgCreationSession: vi.fn(),
|
||||
listRpgCreationWorks: vi.fn(),
|
||||
streamRpgCreationMessage: vi.fn(),
|
||||
@@ -523,6 +526,48 @@ const compiledAgentDraftSession: CustomWorldAgentSessionSnapshot = {
|
||||
},
|
||||
};
|
||||
|
||||
function buildResultViewForSession(
|
||||
session: CustomWorldAgentSessionSnapshot,
|
||||
): RpgCreationResultView {
|
||||
const profile = session.resultPreview?.preview ?? null;
|
||||
const isResultStage =
|
||||
session.stage === 'object_refining' ||
|
||||
session.stage === 'visual_refining' ||
|
||||
session.stage === 'long_tail_review' ||
|
||||
session.stage === 'ready_to_publish' ||
|
||||
session.stage === 'published';
|
||||
|
||||
return {
|
||||
session,
|
||||
profile,
|
||||
profileSource: profile ? 'result_preview' : 'none',
|
||||
targetStage: profile && isResultStage
|
||||
? 'custom-world-result'
|
||||
: session.stage === 'error'
|
||||
? 'custom-world-generating'
|
||||
: 'agent-workspace',
|
||||
generationViewSource: session.stage === 'error'
|
||||
? 'agent-draft-foundation'
|
||||
: null,
|
||||
resultViewSource: profile && isResultStage ? 'agent-draft' : null,
|
||||
canAutosaveLibrary: Boolean(profile && isResultStage),
|
||||
canSyncResultProfile:
|
||||
session.stage === 'object_refining' ||
|
||||
session.stage === 'visual_refining' ||
|
||||
session.stage === 'long_tail_review' ||
|
||||
session.stage === 'ready_to_publish',
|
||||
publishReady: Boolean(session.resultPreview?.publishReady),
|
||||
canEnterWorld: Boolean(session.resultPreview?.canEnterWorld),
|
||||
blockerCount: session.resultPreview?.blockers?.length ?? 0,
|
||||
recoveryAction: profile && isResultStage
|
||||
? 'open_result'
|
||||
: session.stage === 'error'
|
||||
? 'resume_generation'
|
||||
: 'continue_agent',
|
||||
recoveryReason: null,
|
||||
};
|
||||
}
|
||||
|
||||
type TestAuthValue = {
|
||||
user: AuthUser | null;
|
||||
canAccessProtectedData: boolean;
|
||||
@@ -573,8 +618,11 @@ function TestWrapper({
|
||||
onContinueGame?: (snapshot?: HydratedSavedGameSnapshot | null) => void;
|
||||
onSelectWorld?: RpgEntryFlowShellProps['handleCustomWorldSelect'];
|
||||
} = {}) {
|
||||
const [selectionStage, setSelectionStage] =
|
||||
useState<SelectionStage>('platform');
|
||||
const [selectionStage, setSelectionStage] = useState<SelectionStage>(() =>
|
||||
window.location.pathname === '/creation/rpg/agent'
|
||||
? 'agent-workspace'
|
||||
: 'platform',
|
||||
);
|
||||
|
||||
const content = (
|
||||
<RpgEntryFlowShell
|
||||
@@ -600,7 +648,7 @@ function TestWrapper({
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.resetAllMocks();
|
||||
window.history.replaceState(null, '', '/');
|
||||
window.sessionStorage.clear();
|
||||
window.localStorage.clear();
|
||||
@@ -667,6 +715,9 @@ beforeEach(() => {
|
||||
vi.mocked(createRpgCreationSession).mockResolvedValue({
|
||||
session: mockSession,
|
||||
});
|
||||
vi.mocked(getRpgCreationResultView).mockImplementation(async () =>
|
||||
buildResultViewForSession(mockSession),
|
||||
);
|
||||
vi.mocked(createBigFishCreationSession).mockResolvedValue({
|
||||
session: {
|
||||
sessionId: 'big-fish-session-1',
|
||||
@@ -757,9 +808,17 @@ beforeEach(() => {
|
||||
level: 1,
|
||||
name: '微光孢子',
|
||||
oneLineFantasy: '像发光尘埃一样在深海漂浮。',
|
||||
textDescription:
|
||||
'微光孢子是机械深海生态中的起始个体,体型最小,会先漂浮试探并寻找可吞并目标。',
|
||||
silhouetteDirection: '圆润微型机械球',
|
||||
sizeRatio: 1,
|
||||
visualDescription:
|
||||
'带有浅色发光核心的微型机械鱼苗或孢子体,轮廓圆润,表现出弱小但灵动的初始形象。',
|
||||
visualPromptSeed: 'deep sea glowing mechanical spore',
|
||||
idleMotionDescription:
|
||||
'待机时轻轻漂浮,身体和尾部做小幅摆动,像在适应深海水流。',
|
||||
moveMotionDescription:
|
||||
'移动时核心前探,尾部快速摆动推进,带出轻盈的游动轨迹。',
|
||||
motionPromptSeed: 'soft floating mechanical spore',
|
||||
mergeSourceLevel: null,
|
||||
preyWindow: [1],
|
||||
@@ -1168,6 +1227,9 @@ test('create tab opens compiled agent draft in result refinement page', async ()
|
||||
},
|
||||
]);
|
||||
vi.mocked(getRpgCreationSession).mockResolvedValue(compiledAgentDraftSession);
|
||||
vi.mocked(getRpgCreationResultView).mockResolvedValue(
|
||||
buildResultViewForSession(compiledAgentDraftSession),
|
||||
);
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
@@ -1271,6 +1333,13 @@ test('create tab resumes agent workspace when session has no draft profile even
|
||||
stage: 'clarifying',
|
||||
draftProfile: null,
|
||||
});
|
||||
vi.mocked(getRpgCreationResultView).mockResolvedValue(
|
||||
buildResultViewForSession({
|
||||
...mockSession,
|
||||
stage: 'clarifying',
|
||||
draftProfile: null,
|
||||
}),
|
||||
);
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
@@ -1316,13 +1385,13 @@ test('opening a compiled draft with a missing agent session falls back to create
|
||||
])
|
||||
.mockResolvedValueOnce([]);
|
||||
|
||||
vi.mocked(getRpgCreationSession).mockRejectedValueOnce(
|
||||
new ApiClientError({
|
||||
message: 'custom world agent session not found',
|
||||
status: 404,
|
||||
code: 'NOT_FOUND',
|
||||
}),
|
||||
);
|
||||
const missingSessionError = new ApiClientError({
|
||||
message: 'custom world agent session not found',
|
||||
status: 404,
|
||||
code: 'NOT_FOUND',
|
||||
});
|
||||
vi.mocked(getRpgCreationSession).mockRejectedValueOnce(missingSessionError);
|
||||
vi.mocked(getRpgCreationResultView).mockRejectedValueOnce(missingSessionError);
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
@@ -1699,6 +1768,22 @@ test('restoring an agent workspace ignores a stored session owned by another use
|
||||
expect(window.location.search).toBe('');
|
||||
});
|
||||
|
||||
test('restoring an agent workspace ignores explicit session pointer without local owner after login', async () => {
|
||||
window.history.replaceState(
|
||||
null,
|
||||
'',
|
||||
'/?customWorldSessionId=custom-world-agent-session-legacy',
|
||||
);
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(window.location.search).toBe('');
|
||||
});
|
||||
|
||||
expect(getRpgCreationSession).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('refreshing platform home ignores stored agent workspace pointer without explicit restore path', async () => {
|
||||
window.sessionStorage.setItem(
|
||||
'genarrative.custom-world-agent-ui.v1',
|
||||
@@ -2243,6 +2328,15 @@ test('failed draft work continues on generation progress view instead of agent w
|
||||
},
|
||||
]);
|
||||
vi.mocked(getRpgCreationSession).mockResolvedValue(mockSession);
|
||||
vi.mocked(getRpgCreationResultView).mockResolvedValue({
|
||||
...buildResultViewForSession({
|
||||
...mockSession,
|
||||
stage: 'error',
|
||||
}),
|
||||
targetStage: 'custom-world-generating',
|
||||
generationViewSource: 'agent-draft-foundation',
|
||||
recoveryAction: 'resume_generation',
|
||||
});
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
@@ -2267,6 +2361,9 @@ test('existing draft sessions open result page refinement instead of agent dialo
|
||||
error: null,
|
||||
});
|
||||
vi.mocked(getRpgCreationSession).mockResolvedValue(compiledAgentDraftSession);
|
||||
vi.mocked(getRpgCreationResultView).mockResolvedValue(
|
||||
buildResultViewForSession(compiledAgentDraftSession),
|
||||
);
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
@@ -2306,7 +2403,7 @@ test('agent result view shows publish blocker dialog before publish action when
|
||||
progress: 100,
|
||||
error: null,
|
||||
});
|
||||
vi.mocked(getRpgCreationSession).mockResolvedValue({
|
||||
const blockedSession = {
|
||||
...compiledAgentDraftSession,
|
||||
resultPreview: {
|
||||
...compiledAgentDraftSession.resultPreview!,
|
||||
@@ -2319,7 +2416,11 @@ test('agent result view shows publish blocker dialog before publish action when
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
} satisfies CustomWorldAgentSessionSnapshot;
|
||||
vi.mocked(getRpgCreationSession).mockResolvedValue(blockedSession);
|
||||
vi.mocked(getRpgCreationResultView).mockResolvedValue(
|
||||
buildResultViewForSession(blockedSession),
|
||||
);
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
@@ -2424,6 +2525,11 @@ test('agent draft result publishes to gallery from publish panel', async () => {
|
||||
vi.mocked(getRpgCreationSession).mockImplementation(async () =>
|
||||
hasPublishedWorld ? publishedSession : publishReadyDraftSession,
|
||||
);
|
||||
vi.mocked(getRpgCreationResultView).mockImplementation(async () =>
|
||||
buildResultViewForSession(
|
||||
hasPublishedWorld ? publishedSession : publishReadyDraftSession,
|
||||
),
|
||||
);
|
||||
|
||||
function PublishFlowWrapper() {
|
||||
const [selectionStage, setSelectionStage] =
|
||||
@@ -2482,7 +2588,7 @@ test('agent draft result test button enters current draft without publish gate',
|
||||
progress: 100,
|
||||
error: null,
|
||||
});
|
||||
vi.mocked(getRpgCreationSession).mockResolvedValue({
|
||||
const testDraftSession = {
|
||||
...compiledAgentDraftSession,
|
||||
stage: 'ready_to_publish',
|
||||
resultPreview: {
|
||||
@@ -2497,7 +2603,11 @@ test('agent draft result test button enters current draft without publish gate',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
} satisfies CustomWorldAgentSessionSnapshot;
|
||||
vi.mocked(getRpgCreationSession).mockResolvedValue(testDraftSession);
|
||||
vi.mocked(getRpgCreationResultView).mockResolvedValue(
|
||||
buildResultViewForSession(testDraftSession),
|
||||
);
|
||||
|
||||
function TestDraftWrapper() {
|
||||
const [selectionStage, setSelectionStage] =
|
||||
@@ -2582,7 +2692,7 @@ test('agent result view does not keep legacy publish blockers when preview uses
|
||||
progress: 100,
|
||||
error: null,
|
||||
});
|
||||
vi.mocked(getRpgCreationSession).mockResolvedValue({
|
||||
const publishGateSession = {
|
||||
...compiledAgentDraftSession,
|
||||
stage: 'ready_to_publish',
|
||||
resultPreview: {
|
||||
@@ -2650,7 +2760,11 @@ test('agent result view does not keep legacy publish blockers when preview uses
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
} satisfies CustomWorldAgentSessionSnapshot;
|
||||
vi.mocked(getRpgCreationSession).mockResolvedValue(publishGateSession);
|
||||
vi.mocked(getRpgCreationResultView).mockResolvedValue(
|
||||
buildResultViewForSession(publishGateSession),
|
||||
);
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
@@ -2809,6 +2923,9 @@ test('agent draft result back button returns to creation hub without syncing res
|
||||
},
|
||||
} satisfies CustomWorldAgentSessionSnapshot;
|
||||
vi.mocked(getRpgCreationSession).mockResolvedValue(resultSession);
|
||||
vi.mocked(getRpgCreationResultView).mockResolvedValue(
|
||||
buildResultViewForSession(resultSession),
|
||||
);
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
@@ -2840,7 +2957,7 @@ test('agent draft result back button returns to creation hub without syncing res
|
||||
expect(screen.queryByText('世界档案')).toBeNull();
|
||||
});
|
||||
|
||||
test('agent draft result auto-save persists the latest profile from session draft without result sync action', async () => {
|
||||
test('agent draft result auto-save syncs result profile before persisting backend result view', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
const syncedSession = {
|
||||
@@ -2940,6 +3057,35 @@ test('agent draft result auto-save persists the latest profile from session draf
|
||||
},
|
||||
} satisfies CustomWorldAgentSessionSnapshot;
|
||||
vi.mocked(getRpgCreationSession).mockResolvedValue(syncedSession);
|
||||
vi.mocked(getRpgCreationResultView).mockResolvedValue(
|
||||
buildResultViewForSession(syncedSession),
|
||||
);
|
||||
vi.mocked(executeRpgCreationAction).mockImplementation(async (_, payload) => ({
|
||||
operation: {
|
||||
operationId:
|
||||
payload.action === 'sync_result_profile'
|
||||
? 'operation-sync-result-profile-1'
|
||||
: 'operation-draft-foundation-1',
|
||||
type: payload.action,
|
||||
status: 'queued',
|
||||
phaseLabel: '已接收请求',
|
||||
phaseDetail:
|
||||
payload.action === 'sync_result_profile'
|
||||
? '正在同步结果页档案。'
|
||||
: '正在准备生成世界底稿。',
|
||||
progress: 10,
|
||||
error: null,
|
||||
},
|
||||
}));
|
||||
vi.mocked(getRpgCreationOperation).mockResolvedValue({
|
||||
operationId: 'operation-sync-result-profile-1',
|
||||
type: 'sync_result_profile',
|
||||
status: 'completed',
|
||||
phaseLabel: '结果页档案已同步',
|
||||
phaseDetail: '服务端已根据最新结果页档案刷新会话预览。',
|
||||
progress: 100,
|
||||
error: null,
|
||||
});
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
@@ -2978,7 +3124,7 @@ test('agent draft result auto-save persists the latest profile from session draf
|
||||
sessionId === 'custom-world-agent-session-1' &&
|
||||
payload?.action === 'sync_result_profile',
|
||||
),
|
||||
).toBe(false);
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('agent draft result can open from server result preview without embedded legacyResultProfile', async () => {
|
||||
@@ -3021,6 +3167,9 @@ test('agent draft result can open from server result preview without embedded le
|
||||
} satisfies CustomWorldAgentSessionSnapshot;
|
||||
|
||||
vi.mocked(getRpgCreationSession).mockResolvedValue(previewOnlySession);
|
||||
vi.mocked(getRpgCreationResultView).mockResolvedValue(
|
||||
buildResultViewForSession(previewOnlySession),
|
||||
);
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user