147 lines
6.2 KiB
TypeScript
147 lines
6.2 KiB
TypeScript
import { describe, expect, test } from 'vitest';
|
|
import type { CustomWorldAgentSessionSnapshot } from './customWorldAgentSession';
|
|
import type { CustomWorldResultPreviewEnvelope } from './customWorldResultPreview';
|
|
import type { CustomWorldWorkSummary } from './customWorldWorkSummary';
|
|
|
|
import {
|
|
createRpgAgentFoundationDraftProfileFixture,
|
|
createRpgAgentSupportedActionsFixture,
|
|
createRpgAgentSessionFixture,
|
|
createRpgCreationAnchorContentFixture,
|
|
createRpgCreationPreviewEnvelopeFixture,
|
|
createRpgCreationPublishedProfileFixture,
|
|
createRpgCreationWorksResponseFixture,
|
|
createRpgWorldLibraryEntryFixture,
|
|
} from './rpgCreationFixtures';
|
|
|
|
describe('RPG 创作共享契约 fixture', () => {
|
|
test('旧命名兼容分文件可以直接承接新 fixture 的类型消费', () => {
|
|
const legacySession: CustomWorldAgentSessionSnapshot =
|
|
createRpgAgentSessionFixture();
|
|
const legacyPreview: CustomWorldResultPreviewEnvelope =
|
|
createRpgCreationPreviewEnvelopeFixture();
|
|
const legacyWork: CustomWorldWorkSummary =
|
|
createRpgCreationWorksResponseFixture().items[0]!;
|
|
|
|
expect(legacySession.stage).toBe('ready_to_publish');
|
|
expect(legacySession.resultPreview?.source).toBe(legacyPreview.source);
|
|
expect(legacyWork.status).toBe('draft');
|
|
});
|
|
|
|
test('anchor fixture 与 foundation draft fixture 保持最小创作真相源对应关系', () => {
|
|
const anchors = createRpgCreationAnchorContentFixture();
|
|
const draftProfile = createRpgAgentFoundationDraftProfileFixture();
|
|
|
|
expect(anchors.worldPromise).toContain('旧航路群岛');
|
|
expect(draftProfile.worldHook).toContain('旧航路群岛');
|
|
expect(draftProfile.playableNpcs).toHaveLength(1);
|
|
expect(draftProfile.storyNpcs).toHaveLength(1);
|
|
expect(draftProfile.sceneChapters[0]?.acts[0]?.backgroundImageSrc).toContain(
|
|
'/generated-custom-world-scenes/landmark-1/scene-act-1/scene.png',
|
|
);
|
|
});
|
|
|
|
test('session fixture 同时暴露 supportedActions 与 resultPreview', () => {
|
|
const session = createRpgAgentSessionFixture();
|
|
|
|
expect(session.sessionId).toBe('rpg-session-fixture');
|
|
expect(session.stage).toBe('ready_to_publish');
|
|
expect(session.checkpoints?.[0]?.checkpointId).toBe(
|
|
'checkpoint-foundation-v1',
|
|
);
|
|
expect(session.supportedActions?.map((entry) => entry.action)).toEqual(
|
|
expect.arrayContaining(['draft_foundation', 'generate_role_assets', 'publish_world']),
|
|
);
|
|
expect(session.resultPreview?.source).toBe('session_preview');
|
|
expect(session.resultPreview?.blockers).toEqual([]);
|
|
});
|
|
|
|
test('preview fixture 保持预览来源、质量结论与 profile 载体三层边界', () => {
|
|
const preview = createRpgCreationPreviewEnvelopeFixture();
|
|
|
|
expect(preview.source).toBe('session_preview');
|
|
expect(preview.preview.previewId).toBe('preview-fixture-1');
|
|
expect(preview.preview.sessionId).toBe('rpg-session-fixture');
|
|
expect(preview.qualityFindings?.[0]).toMatchObject({
|
|
severity: 'info',
|
|
code: 'scene_asset_ready',
|
|
});
|
|
});
|
|
|
|
test('supported actions fixture 明确区分可执行能力矩阵,而不是让前端自行猜测按钮状态', () => {
|
|
const supportedActions = createRpgAgentSupportedActionsFixture();
|
|
|
|
expect(supportedActions).toEqual([
|
|
{ action: 'draft_foundation', enabled: true },
|
|
{ action: 'generate_role_assets', enabled: true },
|
|
{ action: 'publish_world', enabled: true },
|
|
]);
|
|
});
|
|
|
|
test('published profile fixture 能稳定承载作品库与结果页所需的封面、场景幕与角色资产字段', () => {
|
|
const profile = createRpgCreationPublishedProfileFixture();
|
|
|
|
expect(profile.id).toBe('rpg-profile-fixture');
|
|
expect(profile.playableNpcs).toHaveLength(1);
|
|
expect(profile.landmarks).toHaveLength(1);
|
|
expect(profile.sceneChapterBlueprints).toHaveLength(1);
|
|
expect(
|
|
(profile.sceneChapterBlueprints as Array<{ acts?: Array<{ backgroundImageSrc?: string }> }>)[0]
|
|
?.acts?.[0]?.backgroundImageSrc,
|
|
).toContain('/generated-custom-world-scenes/landmark-1/scene-act-1/scene.png');
|
|
});
|
|
|
|
test('regression: session preview 与 published profile 需要共同保留角色动作资产和分幕背景字段', () => {
|
|
const session = createRpgAgentSessionFixture();
|
|
const publishedProfile = createRpgCreationPublishedProfileFixture();
|
|
const preview = createRpgCreationPreviewEnvelopeFixture();
|
|
|
|
expect(
|
|
((session.draftProfile as { playableNpcs?: Array<{ animationMap?: { run?: { basePath?: string } } }> })
|
|
.playableNpcs?.[0]?.animationMap?.run?.basePath ?? ''),
|
|
).toContain('/generated-characters/playable-1/animations/run');
|
|
expect(
|
|
((preview.preview.playableNpcs as Array<{ generatedAnimationSetId?: string }>)[0]
|
|
?.generatedAnimationSetId ?? ''),
|
|
).toBe('animation-set-playable-1');
|
|
expect(
|
|
((publishedProfile.sceneChapterBlueprints as Array<{
|
|
acts?: Array<{ backgroundAssetId?: string }>;
|
|
}>)[0]?.acts?.[0]?.backgroundAssetId ?? ''),
|
|
).toBe('scene-asset-runtime');
|
|
});
|
|
|
|
test('works fixture 与 library fixture 对齐同一 published profile', () => {
|
|
const works = createRpgCreationWorksResponseFixture();
|
|
const libraryEntry = createRpgWorldLibraryEntryFixture();
|
|
|
|
const publishedWork = works.items.find((entry) => entry.status === 'published');
|
|
|
|
expect(publishedWork?.profileId).toBe(libraryEntry.profileId);
|
|
expect(publishedWork?.title).toBe(libraryEntry.worldName);
|
|
expect(publishedWork?.canEnterWorld).toBe(true);
|
|
expect(libraryEntry.profile.id).toBe(libraryEntry.profileId);
|
|
});
|
|
|
|
test('regression: works fixture 需要稳定保留草稿与发布态的作品门槛字段', () => {
|
|
const works = createRpgCreationWorksResponseFixture();
|
|
const draftWork = works.items.find((entry) => entry.status === 'draft');
|
|
const publishedWork = works.items.find((entry) => entry.status === 'published');
|
|
|
|
expect(draftWork).toMatchObject({
|
|
stage: 'ready_to_publish',
|
|
stageLabel: '准备发布',
|
|
canResume: true,
|
|
canEnterWorld: false,
|
|
roleVisualReadyCount: 2,
|
|
roleAnimationReadyCount: 2,
|
|
});
|
|
expect(publishedWork).toMatchObject({
|
|
stage: 'published',
|
|
stageLabel: '已发布',
|
|
canResume: false,
|
|
canEnterWorld: true,
|
|
});
|
|
});
|
|
});
|