import { describe, expect, it } from 'vitest'; import { WorldType, type CustomWorldProfile } from '../types'; import { resolveCustomWorldCoverPresentation } from './customWorldCover'; function createBaseProfile(): CustomWorldProfile { return { id: 'custom-world-cover-test', settingText: '潮雾群岛', name: '潮雾群岛', subtitle: '封面规则测试', summary: '用于验证默认封面优先级。', tone: '潮湿、压抑', playerGoal: '查明旧航道真相。', templateWorldType: WorldType.WUXIA, compatibilityTemplateWorldType: WorldType.WUXIA, majorFactions: [], coreConflicts: [], attributeSchema: { id: 'schema:test', worldId: 'CUSTOM', schemaVersion: 1, generatedFrom: { worldType: WorldType.CUSTOM, worldName: '潮雾群岛', settingSummary: '封面规则测试', tone: '潮湿、压抑', conflictCore: '旧航道真相', }, slots: [], }, playableNpcs: [ { id: 'playable-1', name: '林潮', title: '守潮人', role: '可扮演角色', description: '负责守住第一道进港口。', backstory: '他在港口旧案里失去过同伴。', personality: '谨慎克制。', motivation: '想查清货船去向。', combatStyle: '借地形换位。', initialAffinity: 20, relationshipHooks: ['旧案'], tags: ['港口'], backstoryReveal: { publicSummary: '他对港口格外熟悉。', chapters: [], }, skills: [], initialItems: [], imageSrc: '/images/roles/linchao.webp', }, ], storyNpcs: [], items: [], camp: { id: 'camp-1', name: '守夜营地', description: '潮线后的临时据点。', imageSrc: '/images/camp/camp.webp', sceneNpcIds: [], connections: [], }, landmarks: [ { id: 'landmark-1', name: '潮汐码头', description: '涨潮时会吞掉半截栈桥。', imageSrc: '/images/landmark/docks.webp', sceneNpcIds: [], connections: [], }, ], sceneChapterBlueprints: [ { id: 'scene-chapter-1', sceneId: 'landmark-1', title: '潮汐码头', summary: '第一章开局场景。', sceneTaskDescription: '追查潮汐码头失踪案的第一条线索。', linkedThreadIds: [], linkedLandmarkIds: ['landmark-1'], acts: [ { id: 'act-1', sceneId: 'landmark-1', title: '雾里靠岸', summary: '玩家第一次进入港口。', stageCoverage: ['opening'], backgroundImageSrc: '/images/scene/act-1.webp', backgroundAssetId: 'asset-scene-act-1', encounterNpcIds: [], primaryNpcId: 'playable-1', oppositeNpcId: 'playable-1', eventDescription: '玩家第一次进入港口,发现涨潮后的异常痕迹。', linkedThreadIds: [], advanceRule: 'after_primary_contact', actGoal: '拿到第一句真话。', transitionHook: '下一幕将进入封锁区。', }, ], }, ], }; } describe('resolveCustomWorldCoverPresentation', () => { it('优先使用开局场景第一幕图片作为默认封面底图', () => { const profile = createBaseProfile(); const result = resolveCustomWorldCoverPresentation(profile); expect(result.imageSrc).toBe('/images/scene/act-1.webp'); expect(result.renderMode).toBe('scene_with_roles'); expect(result.characterImageSrcs).toEqual(['/images/roles/linchao.webp']); }); it('当第一幕图片缺失时按营地图与地标图顺序回退', () => { const profile = createBaseProfile(); const firstSceneChapter = profile.sceneChapterBlueprints?.[0]; const firstSceneAct = firstSceneChapter?.acts[0]; if (!firstSceneChapter || !firstSceneAct) { throw new Error('expected base profile to provide an opening scene chapter'); } profile.sceneChapterBlueprints = [ { ...firstSceneChapter, acts: [ { ...firstSceneAct, backgroundImageSrc: null, backgroundAssetId: null, }, ], }, ]; const fallbackToCamp = resolveCustomWorldCoverPresentation(profile); expect(fallbackToCamp.imageSrc).toBe('/images/camp/camp.webp'); profile.camp = { ...profile.camp!, imageSrc: '', }; const fallbackToLandmark = resolveCustomWorldCoverPresentation(profile); expect(fallbackToLandmark.imageSrc).toBe('/images/landmark/docks.webp'); }); });