Files
Genarrative/src/components/platform-entry/platformPuzzleDraftRecoveryModel.test.ts
T

196 lines
5.5 KiB
TypeScript

import { describe, expect, test } from 'vitest';
import type {
PuzzleAnchorPack,
PuzzleDraftLevel,
PuzzleGeneratedImageCandidate,
PuzzleResultDraft,
} from '../../../packages/shared/src/contracts/puzzleAgentDraft';
import type { PuzzleAgentSessionSnapshot } from '../../../packages/shared/src/contracts/puzzleAgentSession';
import {
hasRecoverableGeneratedPuzzleDraft,
normalizeRecoveredPuzzleDraftSession,
} from './platformPuzzleDraftRecoveryModel';
function buildAnchorPack(): PuzzleAnchorPack {
const item = {
key: 'theme',
label: '主题',
value: '星桥机关',
status: 'confirmed' as const,
};
return {
themePromise: item,
visualSubject: item,
visualMood: item,
compositionHooks: item,
tagsAndForbidden: item,
};
}
function buildCandidate(
overrides: Partial<PuzzleGeneratedImageCandidate> = {},
): PuzzleGeneratedImageCandidate {
return {
candidateId: 'candidate-1',
imageSrc: '/candidate-cover.png',
assetId: 'asset-candidate-cover',
prompt: '星桥机关',
sourceType: 'generated',
selected: true,
...overrides,
};
}
function buildLevel(overrides: Partial<PuzzleDraftLevel> = {}): PuzzleDraftLevel {
return {
levelId: 'level-1',
levelName: '星桥机关',
pictureDescription: '星桥机关画面',
candidates: [buildCandidate()],
selectedCandidateId: null,
coverImageSrc: null,
coverAssetId: null,
generationStatus: 'generating',
...overrides,
};
}
function buildDraft(overrides: Partial<PuzzleResultDraft> = {}): PuzzleResultDraft {
const anchorPack = buildAnchorPack();
return {
workTitle: '星桥拼图',
workDescription: '修复星桥机关。',
levelName: '星桥机关',
summary: '把碎片拼回原位。',
themeTags: ['星桥', '机关', '修复'],
forbiddenDirectives: [],
creatorIntent: null,
anchorPack,
candidates: [],
selectedCandidateId: null,
coverImageSrc: null,
coverAssetId: null,
generationStatus: 'generating',
levels: [buildLevel()],
...overrides,
};
}
function buildSession(
overrides: Partial<PuzzleAgentSessionSnapshot> = {},
): PuzzleAgentSessionSnapshot {
const anchorPack = buildAnchorPack();
return {
sessionId: 'puzzle-session-1',
seedText: '星桥',
currentTurn: 1,
progressPercent: 100,
stage: 'draft_ready',
anchorPack,
draft: buildDraft(),
messages: [],
lastAssistantReply: null,
publishedProfileId: null,
suggestedActions: [],
resultPreview: null,
updatedAt: '2026-06-01T10:00:00.000Z',
...overrides,
};
}
function withCompleteLevelAssets(
overrides: Partial<PuzzleDraftLevel> = {},
): PuzzleDraftLevel {
return buildLevel({
levelSceneImageSrc: '/level-scene.png',
uiSpritesheetImageSrc: '/ui-spritesheet.png',
levelBackgroundImageSrc: '/level-background.png',
...overrides,
});
}
describe('platformPuzzleDraftRecoveryModel', () => {
test('normalizes and marks recovered puzzle draft ready when asset pack is complete', () => {
const normalized = normalizeRecoveredPuzzleDraftSession(
buildSession({
draft: buildDraft({
levels: [withCompleteLevelAssets()],
}),
}),
);
expect(hasRecoverableGeneratedPuzzleDraft(normalized)).toBe(true);
expect(normalized.draft).toMatchObject({
coverImageSrc: '/candidate-cover.png',
coverAssetId: 'asset-candidate-cover',
selectedCandidateId: 'candidate-1',
generationStatus: 'ready',
});
expect(normalized.draft?.levels?.[0]).toMatchObject({
coverImageSrc: '/candidate-cover.png',
coverAssetId: 'asset-candidate-cover',
selectedCandidateId: 'candidate-1',
generationStatus: 'ready',
});
});
test('keeps half-finished draft generating when only cover candidate exists', () => {
const normalized = normalizeRecoveredPuzzleDraftSession(buildSession());
expect(hasRecoverableGeneratedPuzzleDraft(normalized)).toBe(false);
expect(normalized.draft).toMatchObject({
coverImageSrc: '/candidate-cover.png',
generationStatus: 'generating',
});
expect(normalized.draft?.levels?.[0]).toMatchObject({
coverImageSrc: '/candidate-cover.png',
generationStatus: 'generating',
});
});
test('requires level scene, ui spritesheet and level background assets together', () => {
expect(
hasRecoverableGeneratedPuzzleDraft(
buildSession({
draft: buildDraft({
coverImageSrc: '/draft-cover.png',
levels: [
withCompleteLevelAssets({
uiSpritesheetImageSrc: null,
uiSpritesheetImageObjectKey: null,
}),
],
}),
}),
),
).toBe(false);
});
test('accepts object keys as recovered asset references', () => {
expect(
hasRecoverableGeneratedPuzzleDraft(
buildSession({
draft: buildDraft({
coverImageSrc: '/draft-cover.png',
levels: [
buildLevel({
levelSceneImageObjectKey: 'level-scene.png',
uiSpritesheetImageObjectKey: 'ui-spritesheet.png',
levelBackgroundImageObjectKey: 'level-background.png',
}),
],
}),
}),
),
).toBe(true);
});
test('leaves sessions without draft unchanged and unrecoverable', () => {
const session = buildSession({ draft: null });
expect(normalizeRecoveredPuzzleDraftSession(session)).toBe(session);
expect(hasRecoverableGeneratedPuzzleDraft(session)).toBe(false);
});
});