import { expect, test } from 'vitest'; import type { CustomWorldAgentSessionSnapshot } from '../../../packages/shared/src/contracts/customWorldAgent'; import { buildRpgCreationPreviewFromResultPreview, buildRpgCreationPreviewFromSession, } from './rpgCreationPreviewAdapter'; const sessionWithPreview: CustomWorldAgentSessionSnapshot = { sessionId: 'session-preview-1', currentTurn: 3, anchorContent: { worldPromise: null, playerFantasy: null, themeBoundary: null, playerEntryPoint: null, coreConflict: null, keyRelationships: null, hiddenLines: null, iconicElements: null, }, progressPercent: 100, lastAssistantReply: '第一版世界底稿已经准备好了。', stage: 'object_refining', focusCardId: null, creatorIntent: null, creatorIntentReadiness: { isReady: true, completedKeys: [], missingKeys: [], }, anchorPack: null, lockState: null, draftProfile: { id: 'draft-profile-1', settingText: '草稿 profile 直接进入游戏。', name: '只作为 fallback 的本地草稿名', subtitle: 'fallback', summary: 'fallback', tone: 'fallback', playerGoal: 'fallback', templateWorldType: 'WUXIA', majorFactions: [], coreConflicts: [], attributeSchema: { id: 'schema:draft:test', worldId: 'custom:草稿', schemaVersion: 1, schemaName: '草稿六维', generatedFrom: { worldType: 'CUSTOM', worldName: '只作为 fallback 的本地草稿名', settingSummary: '草稿 profile 直接进入游戏。', tone: 'fallback', conflictCore: '验证草稿直读链路', }, slots: [ { slotId: 'axis_a', name: '稿骨', definition: '草稿承压维度。', positiveSignals: ['承压'], negativeSignals: ['虚浮'], combatUseText: '顶住正面压力。', socialUseText: '稳住对话姿态。', explorationUseText: '维持探索状态。', }, { slotId: 'axis_b', name: '稿步', definition: '草稿换位维度。', positiveSignals: ['灵动'], negativeSignals: ['迟滞'], combatUseText: '快速换位。', socialUseText: '顺势接话。', explorationUseText: '穿越复杂路径。', }, { slotId: 'axis_c', name: '稿识', definition: '草稿洞察维度。', positiveSignals: ['洞察'], negativeSignals: ['误判'], combatUseText: '看破破绽。', socialUseText: '识别隐藏动机。', explorationUseText: '整理线索。', }, { slotId: 'axis_d', name: '稿魄', definition: '草稿推进维度。', positiveSignals: ['果断'], negativeSignals: ['犹疑'], combatUseText: '推进突破口。', socialUseText: '关键时刻定调。', explorationUseText: '面对未知继续前探。', }, { slotId: 'axis_e', name: '稿契', definition: '草稿关系维度。', positiveSignals: ['协同'], negativeSignals: ['疏离'], combatUseText: '形成协同收益。', socialUseText: '建立信任交换。', explorationUseText: '从关系打开线索。', }, { slotId: 'axis_f', name: '稿澜', definition: '草稿续航维度。', positiveSignals: ['回稳'], negativeSignals: ['紊乱'], combatUseText: '久战不乱。', socialUseText: '情绪稳定。', explorationUseText: '长线保持行动力。', }, ], }, playableNpcs: [ { id: 'draft-playable-1', name: '草稿角色', title: '直读测试', role: '可扮演角色', description: '从 draftProfile 直接进入角色选择页。', backstory: '草稿角色的背景不经过 resultPreview 转换。', personality: '直接、清醒', motivation: '验证草稿直读链路', combatStyle: '以直读链路破局', initialAffinity: 18, relationshipHooks: ['来自草稿'], tags: ['draft-profile'], skills: [], initialItems: [], imageSrc: '/generated-characters/draft-playable-1/portrait.png', }, ], storyNpcs: [], items: [], landmarks: [], }, messages: [], draftCards: [ { id: 'world-foundation', kind: 'world', title: '世界底稿', subtitle: '阶段三预览', summary: '测试服务端 result preview 优先级。', status: 'warning', linkedIds: [], warningCount: 0, }, ], pendingClarifications: [], suggestedActions: [], recommendedReplies: [], qualityFindings: [], assetCoverage: { roleAssets: [], sceneAssets: [], allRoleAssetsReady: false, allSceneAssetsReady: false, }, resultPreview: { source: 'session_preview', preview: { id: 'preview-profile-1', settingText: '被海雾吞没的旧航路群岛', name: '服务端结果预览', subtitle: '优先于前端 fallback', summary: '结果页应该优先消费 session.resultPreview。', tone: '压抑、潮湿、悬疑', playerGoal: '查清沉船与禁航区异动的真相。', templateWorldType: 'WUXIA', majorFactions: ['守灯会', '航运公会'], coreConflicts: ['守灯会与航运公会争夺旧航路控制权'], playableNpcs: [], storyNpcs: [], items: [], landmarks: [], generationMode: 'full', generationStatus: 'complete', sessionId: 'session-preview-1', }, generatedAt: '2026-04-21T10:00:00.000Z', qualityFindings: [], blockers: [], }, updatedAt: '2026-04-21T10:00:00.000Z', }; test('buildRpgCreationPreviewFromResultPreview normalizes server preview envelope', () => { const profile = buildRpgCreationPreviewFromResultPreview( sessionWithPreview.resultPreview, ); expect(profile?.name).toBe('服务端结果预览'); expect(profile?.subtitle).toBe('优先于前端 fallback'); expect(profile?.id).toBe('preview-profile-1'); expect(profile?.settingText).toBe('被海雾吞没的旧航路群岛'); }); test('buildRpgCreationPreviewFromSession prefers agent draft profile', () => { const profile = buildRpgCreationPreviewFromSession(sessionWithPreview); expect(profile?.name).toBe('只作为 fallback 的本地草稿名'); expect(profile?.summary).toBe('fallback'); expect(profile?.id).toBe('draft-profile-1'); expect(profile?.playableNpcs[0]?.id).toBe('draft-playable-1'); }); test('buildRpgCreationPreviewFromSession does not require resultPreview', () => { const profile = buildRpgCreationPreviewFromSession({ ...sessionWithPreview, resultPreview: null, }); expect(profile?.name).toBe('只作为 fallback 的本地草稿名'); expect(profile?.playableNpcs[0]?.imageSrc).toBe( '/generated-characters/draft-playable-1/portrait.png', ); expect(profile?.attributeSchema.slots.map((slot) => slot.name)).toEqual([ '稿骨', '稿步', '稿识', '稿魄', '稿契', '稿澜', ]); });