Files
Genarrative/src/services/rpg-creation/rpgCreationPreviewAdapter.test.ts
2026-04-28 20:25:37 +08:00

233 lines
6.8 KiB
TypeScript

import { expect, test } from 'vitest';
import type { CustomWorldAgentSessionSnapshot } from '../../../packages/shared/src/contracts/customWorldAgent';
import {
buildRpgCreationPreviewFromResultPreview,
buildRpgCreationPreviewFromResultView,
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,
generatedFrom: {
worldType: 'CUSTOM',
worldName: '只作为 fallback 的本地草稿名',
settingSummary: '草稿 profile 直接进入游戏。',
tone: 'fallback',
conflictCore: '验证草稿直读链路',
},
slots: [
{
slotId: 'axis_a',
name: '稿骨',
},
{
slotId: 'axis_b',
name: '稿步',
},
{
slotId: 'axis_c',
name: '稿识',
},
{
slotId: 'axis_d',
name: '稿魄',
},
{
slotId: 'axis_e',
name: '稿契',
},
{
slotId: 'axis_f',
name: '稿澜',
},
],
},
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 server result preview', () => {
const profile = buildRpgCreationPreviewFromSession(sessionWithPreview);
expect(profile?.name).toBe('服务端结果预览');
expect(profile?.summary).toBe('结果页应该优先消费 session.resultPreview。');
expect(profile?.id).toBe('preview-profile-1');
expect(profile?.playableNpcs).toEqual([]);
});
test('buildRpgCreationPreviewFromSession no longer reads draft legacy result profile', () => {
const profile = buildRpgCreationPreviewFromSession({
...sessionWithPreview,
resultPreview: null,
draftProfile: {
...sessionWithPreview.draftProfile,
legacyResultProfile: {
...sessionWithPreview.resultPreview!.preview,
id: 'legacy-result-profile-1',
name: '草稿内嵌结果页',
summary: 'resultPreview 缺失时继续使用 draft 内嵌的结果页快照。',
},
},
});
expect(profile).toBeNull();
});
test('buildRpgCreationPreviewFromResultView consumes backend-selected profile', () => {
const profile = buildRpgCreationPreviewFromResultView({
session: {
...sessionWithPreview,
resultPreview: null,
},
profile: {
...sessionWithPreview.resultPreview!.preview,
id: 'backend-selected-profile-1',
name: '后端结果页真相',
summary: 'legacy 兼容只允许在后端 result-view 内完成。',
},
profileSource: 'draft_profile',
targetStage: 'custom-world-result',
generationViewSource: null,
resultViewSource: 'agent-draft',
canAutosaveLibrary: true,
canSyncResultProfile: true,
publishReady: false,
canEnterWorld: false,
blockerCount: 0,
recoveryAction: 'open_result',
});
expect(profile?.name).toBe('后端结果页真相');
expect(profile?.summary).toBe('legacy 兼容只允许在后端 result-view 内完成。');
expect(profile?.id).toBe('backend-selected-profile-1');
});
test('buildRpgCreationPreviewFromSession does not treat draftProfile as runtime profile', () => {
const profile = buildRpgCreationPreviewFromSession({
...sessionWithPreview,
resultPreview: null,
});
expect(profile).toBeNull();
});