Files
Genarrative/src/hooks/rpg-runtime-story/storyResponseOptions.test.ts
kdletters cbc27bad4a
Some checks failed
CI / verify (push) Has been cancelled
init with react+axum+spacetimedb
2026-04-26 18:06:23 +08:00

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([
'继续交谈',
'看看能交换什么',
]);
});
});