import { describe, expect, test } from 'vitest'; import type { JumpHopSessionSnapshotResponse, JumpHopWorkspaceCreateRequest, } from '../../../packages/shared/src/contracts/jumpHop'; import type { Match3DAgentSessionSnapshot, Match3DAnchorPackResponse, } from '../../../packages/shared/src/contracts/match3dAgent'; import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks'; import type { PuzzleAgentActionRequest } from '../../../packages/shared/src/contracts/puzzleAgentActions'; import type { PuzzleAnchorPack, PuzzleDraftLevel, } from '../../../packages/shared/src/contracts/puzzleAgentDraft'; import type { CreatePuzzleAgentSessionRequest, PuzzleAgentSessionSnapshot, } from '../../../packages/shared/src/contracts/puzzleAgentSession'; import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary'; import type { WoodenFishSessionSnapshotResponse, WoodenFishWorkspaceCreateRequest, } from '../../../packages/shared/src/contracts/woodenFish'; import { buildJumpHopDraftActionPayload, buildMatch3DFormPayloadFromSession, buildMatch3DFormPayloadFromWork, buildPendingMatch3DDraftMetadata, buildPendingPuzzleDraftMetadata, buildPuzzleCompileActionFromFormPayload, buildPuzzleFormPayloadFromAction, buildPuzzleFormPayloadFromSession, buildPuzzleFormPayloadFromWork, buildPuzzleWorkUpdatePayloadFromDraft, buildWoodenFishDraftActionPayload, isEmptyPuzzleFormOnlyDraft, isPuzzleFormOnlyDraft, } from './platformMiniGameDraftPayloadModel'; function buildPuzzleAnchorPack(): PuzzleAnchorPack { const item = { key: 'theme', label: '主题', value: '星桥机关', status: 'confirmed' as const, }; return { themePromise: item, visualSubject: item, visualMood: item, compositionHooks: item, tagsAndForbidden: item, }; } function buildPuzzleLevel( overrides: Partial = {}, ): PuzzleDraftLevel { return { levelId: 'level-1', levelName: '星桥机关', pictureDescription: '关卡画面描述', candidates: [], selectedCandidateId: null, coverImageSrc: null, coverAssetId: null, generationStatus: 'idle', ...overrides, }; } function buildPuzzleWork( overrides: Partial = {}, ): PuzzleWorkSummary { return { workId: 'puzzle-work-1', profileId: 'puzzle-profile-1', ownerUserId: 'user-1', authorDisplayName: '玩家', workTitle: ' 星桥拼图 ', workDescription: ' 修复星桥机关。 ', levelName: '星桥机关', summary: '把碎片拼回原位。', themeTags: ['星桥'], coverImageSrc: '/cover.png', coverAssetId: null, publicationStatus: 'draft', updatedAt: '2026-06-01T10:00:00.000Z', publishedAt: null, publishReady: false, levels: [buildPuzzleLevel()], ...overrides, }; } function buildPuzzleSession( overrides: Partial = {}, ): PuzzleAgentSessionSnapshot { const anchorPack = buildPuzzleAnchorPack(); return { sessionId: 'puzzle-session-1', seedText: '种子描述', currentTurn: 1, progressPercent: 20, stage: 'collecting_anchors', anchorPack, draft: { workTitle: '会话标题', workDescription: '会话描述', levelName: '星桥机关', summary: '会话摘要', themeTags: ['星桥'], forbiddenDirectives: [], creatorIntent: null, anchorPack, candidates: [], selectedCandidateId: null, coverImageSrc: null, coverAssetId: null, generationStatus: 'idle', levels: [buildPuzzleLevel()], formDraft: { workTitle: '表单标题', workDescription: '表单描述', pictureDescription: '表单画面', }, }, messages: [], lastAssistantReply: null, publishedProfileId: null, suggestedActions: [], resultPreview: null, updatedAt: '2026-06-01T10:00:00.000Z', ...overrides, }; } function buildMatch3DAnchorPack( overrides: Partial = {}, ): Match3DAnchorPackResponse { return { theme: { key: 'theme', label: '主题', value: '海岛玩具', status: 'confirmed', }, clearCount: { key: 'clearCount', label: '消除次数', value: '12', status: 'confirmed', }, difficulty: { key: 'difficulty', label: '难度', value: '3', status: 'confirmed', }, ...overrides, }; } function buildMatch3DSession( overrides: Partial = {}, ): Match3DAgentSessionSnapshot { return { sessionId: 'match3d-session-1', currentTurn: 1, progressPercent: 20, stage: 'collecting', anchorPack: buildMatch3DAnchorPack(), config: null, draft: null, messages: [], lastAssistantReply: null, publishedProfileId: null, updatedAt: '2026-06-01T11:00:00.000Z', ...overrides, }; } function buildMatch3DWork( overrides: Partial = {}, ): Match3DWorkSummary { return { workId: 'match3d-work-1', profileId: 'match3d-profile-1', ownerUserId: 'user-1', gameName: '海岛抓大鹅', themeText: ' 海岛玩具 ', summary: '收集海岛玩具。', tags: ['海岛'], coverImageSrc: '/match3d-cover.png', referenceImageSrc: '/match3d-reference.png', clearCount: 12, difficulty: 3, publicationStatus: 'draft', playCount: 0, updatedAt: '2026-06-01T11:00:00.000Z', publishedAt: null, publishReady: false, ...overrides, }; } function buildJumpHopDraft( overrides: Partial> = {}, ): NonNullable { return { templateId: 'jump-hop', templateName: '跳一跳', profileId: 'jump-hop-profile-1', themeText: '草稿主题', workTitle: '草稿跳一跳', workDescription: '从草稿恢复。', themeTags: ['草稿'], difficulty: 'standard', stylePreset: 'paper-toy', characterPrompt: '草稿角色', tilePrompt: '草稿平台', endMoodPrompt: '草稿终点', characterAsset: null, tileAtlasAsset: null, tileAssets: [], path: null, coverComposite: null, generationStatus: 'draft', ...overrides, }; } function buildJumpHopPayload( overrides: Partial = {}, ): JumpHopWorkspaceCreateRequest { return { templateId: 'jump-hop', themeText: '表单主题', workTitle: '表单跳一跳', workDescription: '从表单提交。', themeTags: ['表单'], difficulty: 'advanced', stylePreset: 'neon-glass', characterPrompt: '表单角色', tilePrompt: '表单平台', endMoodPrompt: '表单终点', ...overrides, }; } function buildWoodenFishDraft( overrides: Partial< NonNullable > = {}, ): NonNullable { return { templateId: 'wooden-fish', templateName: '敲木鱼', profileId: 'wooden-fish-profile-1', workTitle: '草稿木鱼', workDescription: '从草稿恢复。', themeTags: ['草稿'], hitObjectPrompt: '草稿敲击物', hitObjectReferenceImageSrc: '/draft-hit-ref.png', hitSoundPrompt: null, floatingWords: ['草稿 +1'], hitObjectAsset: null, backgroundAsset: null, backButtonAsset: null, hitSoundAsset: null, coverImageSrc: null, generationStatus: 'draft', ...overrides, }; } function buildWoodenFishPayload( overrides: Partial = {}, ): WoodenFishWorkspaceCreateRequest { return { templateId: 'wooden-fish', workTitle: '表单木鱼', workDescription: '从表单提交。', themeTags: ['表单'], hitObjectPrompt: '表单敲击物', hitObjectReferenceImageSrc: '/form-hit-ref.png', hitSoundPrompt: null, hitSoundAsset: null, floatingWords: ['表单 +1'], ...overrides, }; } describe('platformMiniGameDraftPayloadModel', () => { test('builds puzzle form payload from work with fallback description priority', () => { expect( buildPuzzleFormPayloadFromWork( buildPuzzleWork({ workDescription: ' ', summary: ' 摘要描述 ', levelName: ' 关卡标题 ', }), ), ).toEqual({ seedText: '摘要描述', workTitle: '星桥拼图', workDescription: '摘要描述', pictureDescription: '摘要描述', referenceImageSrc: null, referenceImageSrcs: [], referenceImageAssetObjectId: null, referenceImageAssetObjectIds: [], imageModel: null, aiRedraw: true, }); }); test('builds puzzle work update payload from result draft', () => { const draft = buildPuzzleSession().draft!; expect(buildPuzzleWorkUpdatePayloadFromDraft(draft)).toEqual({ workTitle: '会话标题', workDescription: '会话描述', levelName: '星桥机关', summary: '会话摘要', themeTags: ['星桥'], coverImageSrc: null, coverAssetId: null, levels: [buildPuzzleLevel()], }); expect( buildPuzzleWorkUpdatePayloadFromDraft({ ...draft, levels: undefined, }).levels, ).toEqual([]); }); test('builds jump hop draft action payload from payload or draft', () => { expect( buildJumpHopDraftActionPayload('compile-draft', { payload: buildJumpHopPayload(), draft: buildJumpHopDraft(), }), ).toEqual({ actionType: 'compile-draft', workTitle: '表单跳一跳', workDescription: '从表单提交。', themeTags: ['表单'], difficulty: 'advanced', stylePreset: 'neon-glass', characterPrompt: '表单角色', tilePrompt: '表单平台', endMoodPrompt: '表单终点', }); expect( buildJumpHopDraftActionPayload('regenerate-tiles', { draft: buildJumpHopDraft(), }), ).toMatchObject({ actionType: 'regenerate-tiles', workTitle: '草稿跳一跳', tilePrompt: '草稿平台', }); }); test('builds wooden fish draft action payload from payload or draft', () => { expect( buildWoodenFishDraftActionPayload('compile-draft', { payload: buildWoodenFishPayload(), draft: buildWoodenFishDraft(), }), ).toEqual({ actionType: 'compile-draft', workTitle: '表单木鱼', workDescription: '从表单提交。', themeTags: ['表单'], hitObjectPrompt: '表单敲击物', hitObjectReferenceImageSrc: '/form-hit-ref.png', hitSoundAsset: null, floatingWords: ['表单 +1'], }); expect( buildWoodenFishDraftActionPayload('regenerate-hit-object', { draft: buildWoodenFishDraft(), }), ).toMatchObject({ actionType: 'regenerate-hit-object', workTitle: '草稿木鱼', hitObjectPrompt: '草稿敲击物', floatingWords: ['草稿 +1'], }); }); test('builds puzzle form payload from session form draft and fallbacks', () => { expect(buildPuzzleFormPayloadFromSession(buildPuzzleSession())).toEqual({ seedText: '表单画面', workTitle: '表单标题', workDescription: '表单描述', pictureDescription: '表单画面', referenceImageSrc: null, referenceImageSrcs: [], referenceImageAssetObjectId: null, referenceImageAssetObjectIds: [], imageModel: null, aiRedraw: true, }); expect( buildPuzzleFormPayloadFromSession( buildPuzzleSession({ draft: { ...buildPuzzleSession().draft!, formDraft: null, levels: [buildPuzzleLevel({ pictureDescription: '关卡优先' })], }, }), ).pictureDescription, ).toBe('关卡优先'); }); test('resolves puzzle form-only draft state for empty and filled forms', () => { const baseDraft = buildPuzzleSession().draft!; const emptySession = buildPuzzleSession({ seedText: ' ', draft: { ...baseDraft, formDraft: { workTitle: ' ', workDescription: ' ', pictureDescription: ' ', }, }, }); expect(isPuzzleFormOnlyDraft(emptySession)).toBe(true); expect(isEmptyPuzzleFormOnlyDraft(emptySession)).toBe(true); expect(isPuzzleFormOnlyDraft(buildPuzzleSession())).toBe(true); expect(isEmptyPuzzleFormOnlyDraft(buildPuzzleSession())).toBe(false); expect( isPuzzleFormOnlyDraft(buildPuzzleSession({ stage: 'ready_to_publish' })), ).toBe(false); }); test('builds puzzle compile action and restores form payload from action', () => { const payload: CreatePuzzleAgentSessionRequest = { seedText: '种子', workTitle: ' 标题 ', workDescription: '', pictureDescription: ' 画面 ', referenceImageSrc: '/ref.png', referenceImageSrcs: ['/ref-a.png'], referenceImageAssetObjectId: 'asset-ref', referenceImageAssetObjectIds: ['asset-ref-a'], imageModel: 'image-model', aiRedraw: false, }; const action = buildPuzzleCompileActionFromFormPayload(payload); expect(action).toEqual({ action: 'compile_puzzle_draft', promptText: '画面', workTitle: '标题', workDescription: '画面', pictureDescription: '画面', referenceImageSrc: '/ref.png', referenceImageSrcs: ['/ref-a.png'], referenceImageAssetObjectId: 'asset-ref', referenceImageAssetObjectIds: ['asset-ref-a'], imageModel: 'image-model', aiRedraw: false, candidateCount: 1, }); expect(buildPuzzleFormPayloadFromAction(action)).toEqual({ seedText: '画面', workTitle: '标题', workDescription: '画面', pictureDescription: '画面', referenceImageSrc: '/ref.png', referenceImageSrcs: ['/ref-a.png'], referenceImageAssetObjectId: 'asset-ref', referenceImageAssetObjectIds: ['asset-ref-a'], imageModel: 'image-model', aiRedraw: false, }); expect( buildPuzzleFormPayloadFromAction({ action: 'publish_puzzle_work', } as PuzzleAgentActionRequest), ).toBeNull(); }); test('builds pending puzzle metadata from non-empty payload fields', () => { expect( buildPendingPuzzleDraftMetadata({ workTitle: ' 标题 ', workDescription: ' ', pictureDescription: ' 画面 ', seedText: '种子', }), ).toEqual({ title: '标题', summary: '画面', }); expect(buildPendingPuzzleDraftMetadata(null)).toEqual({}); }); test('builds match3d form payload from session config, draft and anchors', () => { expect( buildMatch3DFormPayloadFromSession( buildMatch3DSession({ config: { themeText: ' 配置主题 ', referenceImageSrc: '/config-ref.png', clearCount: 9, difficulty: 4, assetStyleId: 'style-1', assetStyleLabel: '手办', assetStylePrompt: '软陶手办', generateClickSound: true, }, draft: { profileId: 'profile-1', gameName: '草稿标题', themeText: '草稿主题', tags: [], referenceImageSrc: '/draft-ref.png', clearCount: 6, difficulty: 2, }, }), ), ).toEqual({ seedText: '配置主题', themeText: '配置主题', referenceImageSrc: '/config-ref.png', clearCount: 9, difficulty: 4, assetStyleId: 'style-1', assetStyleLabel: '手办', assetStylePrompt: '软陶手办', generateClickSound: true, }); expect( buildMatch3DFormPayloadFromSession( buildMatch3DSession({ anchorPack: buildMatch3DAnchorPack({ clearCount: { key: 'clearCount', label: '消除次数', value: 'not-number', status: 'confirmed', }, }), }), ), ).toMatchObject({ seedText: '海岛玩具', clearCount: undefined, difficulty: 3, }); }); test('builds match3d form payload from work and pending metadata', () => { expect( buildMatch3DFormPayloadFromWork( buildMatch3DWork({ themeText: ' ', }), ), ).toEqual({ seedText: '海岛抓大鹅', themeText: '海岛抓大鹅', referenceImageSrc: '/match3d-reference.png', clearCount: 12, difficulty: 3, }); expect( buildPendingMatch3DDraftMetadata({ themeText: ' ', seedText: ' 海岛抓大鹅 ', }), ).toEqual({ title: '海岛抓大鹅', summary: '海岛抓大鹅', }); }); });