fix: preserve rpg custom world detail profiles

This commit is contained in:
kdletters
2026-05-22 03:14:11 +08:00
parent a9d23a8a44
commit d74457faa2
19 changed files with 2726 additions and 109 deletions

View File

@@ -340,4 +340,136 @@ describe('useRpgCreationEnterWorld', () => {
'published-profile',
);
});
it('正式进入世界回读结果页字段更少时不降级当前完整 profile', async () => {
const resultProfile = {
...buildProfile({
id: 'draft-profile-rich-assets',
name: '星砂废都',
imageSrc: '/generated-characters/draft-role/portrait.png',
}),
cover: {
sourceType: 'generated' as const,
imageSrc: '/generated-custom-world-covers/star-waste/cover.webp',
characterRoleIds: ['draft-profile-rich-assets-role'],
},
openingCg: {
id: 'opening-cg-stardust',
status: 'ready' as const,
storyboardImageSrc: '/generated-custom-world-scenes/opening/storyboard.png',
videoSrc: '/generated-custom-world-scenes/opening/opening.mp4',
imageModel: 'gpt-image-2' as const,
videoModel: 'doubao-seedance-2-0-fast-260128',
aspectRatio: '16:9' as const,
imageSize: '2k' as const,
videoResolution: '480p' as const,
durationSeconds: 15 as const,
pointCost: 80 as const,
estimatedWaitMinutes: 10 as const,
updatedAt: '2026-05-21T00:00:00.000Z',
},
sceneChapterBlueprints: [
{
id: 'scene-chapter-stardust',
sceneId: 'landmark-stardust',
title: '钟楼第一夜',
summary: '钟楼第一夜。',
sceneTaskDescription: '进入钟楼。',
linkedThreadIds: [],
linkedLandmarkIds: ['landmark-stardust'],
acts: [
{
id: 'act-stardust-opening',
sceneId: 'landmark-stardust',
title: '第一幕',
summary: '砂眠带玩家进入坠星钟楼。',
stageCoverage: ['opening' as const],
backgroundImageSrc:
'/assets/custom-world/act-stardust-opening.png',
backgroundAssetId: 'asset-act-stardust-opening',
encounterNpcIds: ['draft-profile-rich-assets-role'],
primaryNpcId: 'draft-profile-rich-assets-role',
oppositeNpcId: 'draft-profile-rich-assets-role',
eventDescription: '钟楼旧铃忽然自鸣。',
linkedThreadIds: [],
advanceRule: 'after_primary_contact' as const,
actGoal: '进入钟楼。',
transitionHook: '星砂开始倒流。',
},
],
},
],
} satisfies CustomWorldProfile;
const stalePublishedProfile = {
...resultProfile,
name: '星砂废都',
cover: null,
openingCg: null,
playableNpcs: [],
sceneChapterBlueprints: null,
} satisfies CustomWorldProfile;
const handleCustomWorldSelect = vi.fn();
const setGeneratedCustomWorldProfile = vi.fn();
const syncAgentDraftResultProfile = vi.fn(async () => ({
profile: resultProfile,
view: buildResultView({
stage: 'ready_to_publish',
profile: resultProfile,
canEnterWorld: false,
}),
}));
const executePublishWorld = vi.fn(async () => buildSession('published'));
const syncAgentCreationResultView = vi.fn(async () =>
buildResultView({
stage: 'published',
profile: stalePublishedProfile,
canEnterWorld: true,
}),
);
function Harness() {
const { enterWorldFromCurrentResult } = useRpgCreationEnterWorld({
isAgentDraftResultView: true,
activeAgentSessionId: 'session-1',
currentAgentSessionStage: 'ready_to_publish',
generatedCustomWorldProfile: resultProfile,
handleCustomWorldSelect,
syncAgentDraftResultProfile,
executePublishWorld,
syncAgentCreationResultView,
setGeneratedCustomWorldProfile,
});
return (
<button
type="button"
onClick={() => void enterWorldFromCurrentResult()}
>
</button>
);
}
const { getByText } = render(<Harness />);
await act(async () => {
getByText('进入世界').click();
});
const launchedProfile = handleCustomWorldSelect.mock.calls[0]?.[0];
expect(launchedProfile?.id).toBe('draft-profile-rich-assets');
expect(launchedProfile?.cover?.imageSrc).toBe(
'/generated-custom-world-covers/star-waste/cover.webp',
);
expect(launchedProfile?.openingCg?.videoSrc).toBe(
'/generated-custom-world-scenes/opening/opening.mp4',
);
expect(launchedProfile?.playableNpcs[0]?.imageSrc).toBe(
'/generated-characters/draft-role/portrait.png',
);
expect(
launchedProfile?.sceneChapterBlueprints?.[0]?.acts[0]
?.backgroundImageSrc,
).toBe('/assets/custom-world/act-stardust-opening.png');
});
});