This commit is contained in:
184
src/hooks/rpg-runtime-story/storyResponseOptions.test.ts
Normal file
184
src/hooks/rpg-runtime-story/storyResponseOptions.test.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
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([
|
||||
'继续交谈',
|
||||
'看看能交换什么',
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user