Files
X-Financial/web/tests/workbench-ai-mode-expense-scene-action.test.mjs
caoxiaozhu 0cde1f8990 feat(web): 工作台 AI 模式与差旅/风险建议交互优化
- 新增 PersonalWorkbenchAiMode 组件、AI 侧边栏与 orb 机器人视觉资源
- 新增 aiApplicationDraftModel / aiExpenseDraftModel / aiWorkbenchConversationStore
  及业务准入 aiSidebarBusinessAccess,支撑 AI 模式下的申请与报销草稿
- 顶栏、侧边栏、工作台样式重构,适配 AI 模式切换与响应式布局
- 同步 steward plan/off_topic、差旅报销引导流、风险建议卡片等测试
2026-06-18 22:12:24 +08:00

81 lines
4.1 KiB
JavaScript

import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
import { fileURLToPath } from 'node:url'
import { buildExpenseSceneSelectionActions } from '../src/utils/expenseAssistantActions.js'
import { buildExpenseSceneSelectionMessage } from '../src/views/scripts/travelReimbursementConversationModel.js'
const aiMode = readFileSync(
fileURLToPath(new URL('../src/components/business/PersonalWorkbenchAiMode.vue', import.meta.url)),
'utf8'
)
test('expense scene selection message asks for type first and mentions application gate', () => {
const text = buildExpenseSceneSelectionMessage('帮我发起一笔报销,并检查需要准备哪些票据材料。')
assert.match(text, /先选|选择.*报销类型|报销场景/)
assert.match(text, /差旅|招待|申请单|关联申请单/)
})
test('expense scene actions mark travel and meal as requiring application', () => {
const actions = buildExpenseSceneSelectionActions('帮我发起一笔报销,并检查需要准备哪些票据材料。')
const travel = actions.find((action) => action.payload.expense_type === 'travel')
const meal = actions.find((action) => action.payload.expense_type === 'meal')
const transport = actions.find((action) => action.payload.expense_type === 'transport')
assert.equal(travel.payload.requires_application_before_reimbursement, true)
assert.equal(travel.payload.next_session_type, 'application')
assert.equal(meal.payload.requires_application_before_reimbursement, true)
assert.equal(meal.payload.next_session_type, 'application')
assert.equal(transport.payload.requires_application_before_reimbursement, false)
assert.equal(transport.payload.next_session_type, 'expense')
})
test('AI mode quick reimbursement card opens scene selection before steward plan', () => {
assert.match(
aiMode,
/function runAiModeAction\(item\) {[\s\S]{0,220}pushInlineExpenseSceneSelectionPrompt\(item\.prompt, item\.label\)/
)
})
test('AI mode expense scene selection stays in the inline conversation without opening the create view', () => {
assert.match(aiMode, /actionType === 'select_expense_type'/)
assert.doesNotMatch(aiMode, /emit\('open-assistant'/)
})
test('AI mode offers an inline application shortcut when no candidate application exists', () => {
assert.match(aiMode, /!candidates\.length/)
assert.match(aiMode, /ai_application_start_inline/)
assert.match(aiMode, /buildRequiredApplicationMissingText/)
assert.match(aiMode, /function startAiApplicationDraft/)
})
test('AI mode steward reimbursement action opens expense scene selection locally', () => {
assert.match(aiMode, /buildExpenseSceneSelectionMessage/)
assert.match(aiMode, /buildExpenseSceneSelectionActions/)
assert.match(aiMode, /SESSION_TYPE_EXPENSE/)
assert.match(aiMode, /function pushInlineExpenseSceneSelectionPrompt/)
assert.match(aiMode, /payload\?\.session_type[\s\S]*SESSION_TYPE_EXPENSE/)
assert.match(aiMode, /pushInlineExpenseSceneSelectionPrompt\(carryText, action\.label\)/)
assert.match(
aiMode,
/SESSION_TYPE_EXPENSE[\s\S]{0,140}pushInlineExpenseSceneSelectionPrompt\(carryText, action\.label\)[\s\S]{0,40}return/
)
})
test('AI mode attaches required application lookup result before steward planning', () => {
assert.match(aiMode, /async function attachAiRequiredApplicationGate\(planRequest, prompt\)/)
assert.match(aiMode, /fetchExpenseClaims\(\)/)
assert.match(aiMode, /filterRequiredApplicationCandidates\(claims, 'travel', currentUser\.value \|\| \{\}\)/)
assert.match(aiMode, /required_application_gate/)
assert.match(aiMode, /await attachAiRequiredApplicationGate\(planRequest, prompt\)/)
})
test('AI mode automatically continues required application gate decisions from steward plan', () => {
assert.match(aiMode, /function continueAiRequiredApplicationGateFromPlan\(normalizedPlan\)/)
assert.match(aiMode, /flow\.flowId === 'travel_application'[\s\S]*startAiApplicationDraft\('travel', '差旅费'/)
assert.match(aiMode, /flow\.flowId === 'travel_reimbursement'[\s\S]*startAiExpenseDraft\('travel', '差旅费', true/)
assert.match(aiMode, /continueAiRequiredApplicationGateFromPlan\(normalizedPlan\)/)
})