185 lines
5.3 KiB
TypeScript
185 lines
5.3 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import { AnimationState, type StoryOption } from '../../types';
|
|
import { resolveStoryResponseOptions } from './storyResponseOptions';
|
|
|
|
function createOption(
|
|
functionId: string,
|
|
actionText: string,
|
|
priority = 0,
|
|
interaction?: StoryOption['interaction'],
|
|
): StoryOption {
|
|
return {
|
|
functionId,
|
|
actionText,
|
|
text: actionText,
|
|
priority,
|
|
interaction,
|
|
visuals: {
|
|
playerAnimation: AnimationState.IDLE,
|
|
playerMoveMeters: 0,
|
|
playerOffsetY: 0,
|
|
playerFacing: 'right',
|
|
scrollWorld: false,
|
|
monsterChanges: [],
|
|
},
|
|
};
|
|
}
|
|
|
|
describe('storyResponseOptions', () => {
|
|
it('keeps rewritten actionText when camp companion follow-up uses available options', () => {
|
|
const availableOptions = [
|
|
createOption('npc_chat', '先聊聊营地安排', 3),
|
|
createOption('npc_gift', '把旧礼物递给你', 2),
|
|
createOption('camp_travel_home_scene', '前往旧地点', 1),
|
|
];
|
|
const responseOptions = [
|
|
createOption('npc_chat', '顺着你刚才的话继续问下去', 3),
|
|
createOption('npc_gift', '把刚挑好的礼物正式交给你', 2),
|
|
createOption('camp_travel_home_scene', '前往云河渡', 1),
|
|
];
|
|
|
|
const resolved = resolveStoryResponseOptions({
|
|
responseOptions,
|
|
availableOptions,
|
|
getSanitizedOptions: () => {
|
|
throw new Error('available options branch should not sanitize');
|
|
},
|
|
});
|
|
|
|
expect(resolved.map((option) => option.actionText)).toEqual([
|
|
'顺着你刚才的话继续问下去',
|
|
'把刚挑好的礼物正式交给你',
|
|
'前往云河渡',
|
|
]);
|
|
});
|
|
|
|
it('preserves interaction metadata when AI rewrites provided npc options', () => {
|
|
const availableOptions = [
|
|
createOption('npc_chat', '继续交谈', 3, {
|
|
kind: 'npc',
|
|
npcId: 'npc-camp',
|
|
action: 'chat',
|
|
}),
|
|
createOption('camp_travel_home_scene', '前往旧地点', 1),
|
|
];
|
|
const responseOptions = [
|
|
createOption('npc_chat', '顺着你刚才那句提醒继续追问', 3),
|
|
createOption('camp_travel_home_scene', '先回云河渡', 1),
|
|
];
|
|
|
|
const resolved = resolveStoryResponseOptions({
|
|
responseOptions,
|
|
availableOptions,
|
|
getSanitizedOptions: () => {
|
|
throw new Error('available options branch should not sanitize');
|
|
},
|
|
});
|
|
|
|
expect(resolved[0]).toEqual(
|
|
expect.objectContaining({
|
|
functionId: 'npc_chat',
|
|
actionText: '顺着你刚才那句提醒继续追问',
|
|
interaction: {
|
|
kind: 'npc',
|
|
npcId: 'npc-camp',
|
|
action: 'chat',
|
|
},
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('falls back to available options when the response omits them entirely', () => {
|
|
const availableOptions = [
|
|
createOption('npc_chat', '继续交谈', 2),
|
|
createOption('camp_travel_home_scene', '前往山门', 1),
|
|
];
|
|
|
|
const resolved = resolveStoryResponseOptions({
|
|
responseOptions: [],
|
|
availableOptions,
|
|
getSanitizedOptions: () => {
|
|
throw new Error('available options fallback should not sanitize');
|
|
},
|
|
});
|
|
|
|
expect(resolved.map((option) => option.actionText)).toEqual([
|
|
'继续交谈',
|
|
'前往山门',
|
|
]);
|
|
});
|
|
|
|
it('keeps only AI-selected options when optionCatalog is used for reasoned follow-ups', () => {
|
|
const optionCatalog = [
|
|
createOption('npc_chat', '继续交谈', 3, {
|
|
kind: 'npc',
|
|
npcId: 'npc-camp',
|
|
action: 'chat',
|
|
}),
|
|
createOption('npc_help', '请求援手', 2, {
|
|
kind: 'npc',
|
|
npcId: 'npc-camp',
|
|
action: 'help',
|
|
}),
|
|
createOption('npc_trade', '看看能交换什么', 1, {
|
|
kind: 'npc',
|
|
npcId: 'npc-camp',
|
|
action: 'trade',
|
|
}),
|
|
];
|
|
const responseOptions = [
|
|
createOption('npc_help', '顺着刚才的话请他搭把手', 3),
|
|
createOption('npc_chat', '追问他刚才为什么突然沉默', 2),
|
|
];
|
|
|
|
const resolved = resolveStoryResponseOptions({
|
|
responseOptions,
|
|
optionCatalog,
|
|
getSanitizedOptions: () => {
|
|
throw new Error('option catalog branch should not sanitize');
|
|
},
|
|
});
|
|
|
|
expect(resolved).toEqual([
|
|
expect.objectContaining({
|
|
functionId: 'npc_help',
|
|
actionText: '顺着刚才的话请他搭把手',
|
|
interaction: {
|
|
kind: 'npc',
|
|
npcId: 'npc-camp',
|
|
action: 'help',
|
|
},
|
|
}),
|
|
expect.objectContaining({
|
|
functionId: 'npc_chat',
|
|
actionText: '追问他刚才为什么突然沉默',
|
|
interaction: {
|
|
kind: 'npc',
|
|
npcId: 'npc-camp',
|
|
action: 'chat',
|
|
},
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it('falls back to the raw catalog only when the AI omits optionCatalog results entirely', () => {
|
|
const optionCatalog = [
|
|
createOption('npc_chat', '继续交谈', 2),
|
|
createOption('npc_trade', '看看能交换什么', 1),
|
|
];
|
|
|
|
const resolved = resolveStoryResponseOptions({
|
|
responseOptions: [],
|
|
optionCatalog,
|
|
getSanitizedOptions: () => {
|
|
throw new Error('option catalog fallback should not sanitize');
|
|
},
|
|
});
|
|
|
|
expect(resolved.map((option) => option.actionText)).toEqual([
|
|
'继续交谈',
|
|
'看看能交换什么',
|
|
]);
|
|
});
|
|
});
|