refactor: enforce 800 line source limits

This commit is contained in:
caoxiaozhu
2026-06-22 11:58:53 +08:00
parent 08a4fa3577
commit 6d33ba5742
150 changed files with 27413 additions and 23791 deletions

View File

@@ -23,6 +23,19 @@ const createViewScript = readFileSync(
fileURLToPath(new URL('../src/views/scripts/TravelReimbursementCreateView.js', import.meta.url)),
'utf8'
)
const createViewScriptSurface = [
'../src/views/scripts/TravelReimbursementCreateView.js',
'../src/views/scripts/useTravelReimbursementComposerTools.js',
'../src/views/scripts/useTravelReimbursementCreateViewControls.js',
'../src/views/scripts/useTravelReimbursementCreateViewDrawerControls.js',
'../src/views/scripts/useTravelReimbursementCreateViewLifecycle.js',
'../src/views/scripts/useTravelReimbursementCreateViewMessageHandlers.js',
'../src/views/scripts/useTravelReimbursementCreateViewState.js',
'../src/views/scripts/useTravelReimbursementCreateViewTravelCalculator.js',
'../src/views/scripts/useTravelReimbursementCreateViewUi.js',
'../src/views/scripts/useTravelReimbursementMessageActions.js',
'../src/views/scripts/useTravelReimbursementReviewActions.js'
].map((path) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')).join('\n')
const reviewPanelModelScript = readFileSync(
fileURLToPath(new URL('../src/views/scripts/travelReimbursementReviewPanelModel.js', import.meta.url)),
'utf8'
@@ -35,6 +48,11 @@ const insightPanelTemplate = readFileSync(
fileURLToPath(new URL('../src/components/travel/TravelReimbursementInsightPanel.vue', import.meta.url)),
'utf8'
)
const createViewTemplateSurface = [
createViewTemplate,
messageItemTemplate,
insightPanelTemplate
].join('\n')
const reimbursementService = readFileSync(
fileURLToPath(new URL('../src/services/reimbursements.js', import.meta.url)),
'utf8'
@@ -43,6 +61,10 @@ const reimbursementFlowScript = readFileSync(
fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementFlow.js', import.meta.url)),
'utf8'
)
const flowTimingScript = readFileSync(
fileURLToPath(new URL('../src/views/scripts/travelReimbursementFlowTiming.js', import.meta.url)),
'utf8'
)
const reviewActionsScript = readFileSync(
fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementReviewActions.js', import.meta.url)),
'utf8'
@@ -51,14 +73,26 @@ const reviewDrawerScript = readFileSync(
fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementReviewDrawer.js', import.meta.url)),
'utf8'
)
const submitComposerScript = readFileSync(
fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementSubmitComposer.js', import.meta.url)),
'utf8'
)
const submitComposerScript = [
'../src/views/scripts/travelReimbursementSubmitConstants.js',
'../src/views/scripts/travelReimbursementSubmitApplicationConflicts.js',
'../src/views/scripts/travelReimbursementSubmitApplicationPreview.js',
'../src/views/scripts/travelReimbursementSubmitLocalPreviewFlow.js',
'../src/views/scripts/travelReimbursementSubmitStewardDelegation.js',
'../src/views/scripts/travelReimbursementSubmitAttachmentFlow.js',
'../src/views/scripts/travelReimbursementSubmitDraftPreflight.js',
'../src/views/scripts/travelReimbursementSubmitRecognitionFlow.js',
'../src/views/scripts/travelReimbursementSubmitResponseModel.js',
'../src/views/scripts/useTravelReimbursementSubmitComposer.js'
].map((path) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')).join('\n')
const attachmentsScript = readFileSync(
fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementAttachments.js', import.meta.url)),
'utf8'
)
const attachmentSyncScript = readFileSync(
fileURLToPath(new URL('../src/utils/expenseClaimAttachmentSync.js', import.meta.url)),
'utf8'
)
const sessionStateScript = readFileSync(
fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementSessionState.js', import.meta.url)),
'utf8'
@@ -85,31 +119,31 @@ const insightPanelStyles = readFileSync(
)
test('review drawer tools expose the default review tab before conditional document and risk tabs', () => {
assert.match(createViewTemplate, /v-if="activeReviewPayload && reviewOverviewDrawerAvailable"[\s\S]*title="报销识别核对"[\s\S]*@click="switchToReviewOverviewDrawer"/)
assert.match(createViewTemplate, /v-if="activeReviewPayload && reviewDocumentDrawerAvailable"[\s\S]*title="单据识别"/)
assert.match(createViewTemplate, /v-if="activeReviewPayload && reviewRiskDrawerAvailable"[\s\S]*title="显示风险"/)
assert.match(createViewTemplate, /title="调用流程"/)
assert.match(createViewTemplateSurface, /v-if="ui\.activeReviewPayload && ui\.reviewOverviewDrawerAvailable"[\s\S]*title="报销识别核对"[\s\S]*@click="ui\.switchToReviewOverviewDrawer"/)
assert.match(createViewTemplateSurface, /v-if="ui\.activeReviewPayload && ui\.reviewDocumentDrawerAvailable"[\s\S]*title="单据识别"/)
assert.match(createViewTemplateSurface, /v-if="ui\.activeReviewPayload && ui\.reviewRiskDrawerAvailable"[\s\S]*title="显示风险"/)
assert.match(createViewTemplateSurface, /title="调用流程"/)
assert.ok(
createViewTemplate.indexOf('title="报销识别核对"') < createViewTemplate.indexOf('title="单据识别"'),
createViewTemplateSurface.indexOf('title="报销识别核对"') < createViewTemplateSurface.indexOf('title="单据识别"'),
'default review button should be placed before the document recognition button'
)
})
test('review drawer tool buttons switch modes instead of toggling the active mode closed', () => {
assert.match(createViewScript, /const isReviewOverviewDrawer = computed\(\(\) => reviewDrawerMode\.value === REVIEW_DRAWER_MODE_REVIEW\)/)
assert.match(createViewScript, /function switchReviewDrawerMode\(mode\) \{[\s\S]*if \(reviewDrawerMode\.value === mode\) \{[\s\S]*return[\s\S]*\}/)
assert.match(createViewScript, /function switchToReviewOverviewDrawer\(\) \{[\s\S]*switchReviewDrawerMode\(REVIEW_DRAWER_MODE_REVIEW\)/)
assert.match(createViewScript, /function toggleReviewDocumentDrawer\(\) \{[\s\S]*switchReviewDrawerMode\(REVIEW_DRAWER_MODE_DOCUMENTS\)/)
assert.match(createViewScript, /function toggleReviewRiskDrawer\(\) \{[\s\S]*switchReviewDrawerMode\(REVIEW_DRAWER_MODE_RISK\)/)
assert.match(createViewScript, /function toggleReviewFlowDrawer\(\) \{[\s\S]*switchReviewDrawerMode\(REVIEW_DRAWER_MODE_FLOW\)/)
assert.doesNotMatch(createViewScript, /REVIEW_DRAWER_MODE_DOCUMENTS\s*\?\s*REVIEW_DRAWER_MODE_REVIEW/)
assert.doesNotMatch(createViewScript, /REVIEW_DRAWER_MODE_RISK\s*\?\s*REVIEW_DRAWER_MODE_REVIEW/)
assert.doesNotMatch(createViewScript, /REVIEW_DRAWER_MODE_FLOW\s*\?\s*REVIEW_DRAWER_MODE_REVIEW/)
assert.match(createViewScriptSurface, /const isReviewOverviewDrawer = computed\(\(\) => reviewDrawerMode\.value === REVIEW_DRAWER_MODE_REVIEW\)/)
assert.match(createViewScriptSurface, /function switchReviewDrawerMode\(mode\) \{[\s\S]*if \(reviewDrawerMode\.value === mode\) \{[\s\S]*return[\s\S]*\}/)
assert.match(createViewScriptSurface, /function switchToReviewOverviewDrawer\(\) \{[\s\S]*switchReviewDrawerMode\(REVIEW_DRAWER_MODE_REVIEW\)/)
assert.match(createViewScriptSurface, /function toggleReviewDocumentDrawer\(\) \{[\s\S]*switchReviewDrawerMode\(REVIEW_DRAWER_MODE_DOCUMENTS\)/)
assert.match(createViewScriptSurface, /function toggleReviewRiskDrawer\(\) \{[\s\S]*switchReviewDrawerMode\(REVIEW_DRAWER_MODE_RISK\)/)
assert.match(createViewScriptSurface, /function toggleReviewFlowDrawer\(\) \{[\s\S]*switchReviewDrawerMode\(REVIEW_DRAWER_MODE_FLOW\)/)
assert.doesNotMatch(createViewScriptSurface, /REVIEW_DRAWER_MODE_DOCUMENTS\s*\?\s*REVIEW_DRAWER_MODE_REVIEW/)
assert.doesNotMatch(createViewScriptSurface, /REVIEW_DRAWER_MODE_RISK\s*\?\s*REVIEW_DRAWER_MODE_REVIEW/)
assert.doesNotMatch(createViewScriptSurface, /REVIEW_DRAWER_MODE_FLOW\s*\?\s*REVIEW_DRAWER_MODE_REVIEW/)
})
test('document review drawer fills sidebar height and preview dialog is centered', () => {
assert.match(createViewTemplate, /class="insight-body"[\s\S]*:class="\{ 'document-review-body': isReviewDocumentDrawer \}"/)
assert.match(createViewTemplateSurface, /class="insight-body"[\s\S]*:class="\{ 'document-review-body': ui\.isReviewDocumentDrawer \}"/)
assert.match(createViewBaseStyles, /\.insight-panel-shell\s*\{[\s\S]*display:\s*flex;[\s\S]*min-height:\s*0;/)
assert.match(createViewPart2Styles, /\.insight-body\.document-review-body\s*\{[\s\S]*display:\s*flex;[\s\S]*overflow:\s*hidden;/)
assert.match(createViewPart2Styles, /\.review-ticket-drawer\s*\{[\s\S]*grid-template-rows:\s*auto minmax\(0,\s*1fr\);[\s\S]*height:\s*100%;[\s\S]*overflow:\s*hidden;/)
@@ -146,8 +180,8 @@ test('document review OCR result card header keeps copy and navigation separated
})
test('document preview avoids restored stale object urls', () => {
assert.match(createViewTemplate, /v-if="documentPreviewDialog\.kind === 'image'"[\s\S]*:key="documentPreviewDialog\.renderKey"/)
assert.match(createViewTemplate, /v-else-if="documentPreviewDialog\.kind === 'pdf'"[\s\S]*:key="documentPreviewDialog\.renderKey"/)
assert.match(createViewTemplateSurface, /v-if="documentPreviewDialog\.kind === 'image'"[\s\S]*:key="documentPreviewDialog\.renderKey"/)
assert.match(createViewTemplateSurface, /v-else-if="documentPreviewDialog\.kind === 'pdf'"[\s\S]*:key="documentPreviewDialog\.renderKey"/)
assert.match(reviewDrawerScript, /renderKey:\s*''/)
assert.match(reviewDrawerScript, /renderKey:\s*\[[\s\S]*Date\.now\(\)[\s\S]*\]\.join\('__'\)/)
assert.match(attachmentsScript, /isTemporaryPreviewUrl/)
@@ -182,8 +216,8 @@ test('local transport review no longer uses the travel hotel template', () => {
reviewPanelModelScript,
/key:\s*'customer_name'[\s\S]{0,220}placeholder:\s*'请输入客户名称'[\s\S]{0,80}\},\s*\{[\s\S]{0,80}key:\s*'attachments'/
)
assert.match(createViewTemplate, /placeholder="例如:出租车\/网约车票据 \/ 火车\/高铁票"/)
assert.doesNotMatch(createViewTemplate, /票据场景[\s\S]{0,260}例如:业务招待费 \/ 差旅费/)
assert.match(createViewTemplateSurface, /placeholder="例如:出租车\/网约车票据 \/ 火车\/高铁票"/)
assert.doesNotMatch(createViewTemplateSurface, /票据场景[\s\S]{0,260}例如:业务招待费 \/ 差旅费/)
})
test('local save of changed reimbursement category updates edit fields too', () => {
@@ -259,19 +293,19 @@ test('next step action uses rich text guidance and confirm dialog instead of foo
)
assert.doesNotMatch(highRiskCopy, /\[继续下一步\]\(#review-next-step\)/)
assert.match(createViewTemplate, /class="review-next-step-rich-copy message-answer-markdown"[\s\S]*renderMarkdown\(buildReviewNextStepRichCopyForMessage\(message\)\)/)
assert.match(createViewTemplate, /class="message-bubble" :class="buildMessageBubbleClass\(message\)"/)
assert.match(createViewTemplate, /:open="nextStepConfirmDialog\.open"[\s\S]*title="确认提交当前单据?"[\s\S]*confirm-text="确认提交"/)
assert.match(createViewScript, /const REVIEW_NEXT_STEP_HREF = '#review-next-step'/)
assert.match(createViewScript, /buildReviewRiskLevelCounts/)
assert.match(createViewScript, /function buildMessageBubbleClass\(message\)/)
assert.match(createViewScript, /message-bubble-review-risk-high/)
assert.match(createViewScript, /message-bubble-review-risk-medium/)
assert.match(createViewScript, /message-bubble-review-risk-low/)
assert.match(createViewScript, /function openReviewNextStepConfirm\(message\)/)
assert.match(createViewScript, /async function confirmReviewNextStepSubmit\(\)/)
assert.match(createViewScript, /href === REVIEW_NEXT_STEP_HREF[\s\S]*openReviewNextStepConfirm\(message\)/)
assert.match(createViewScript, /href\.startsWith\(REVIEW_RISK_PANEL_HREF_PREFIX\)[\s\S]*switchReviewDrawerMode\(REVIEW_DRAWER_MODE_RISK\)/)
assert.match(createViewTemplateSurface, /class="review-next-step-rich-copy message-answer-markdown"[\s\S]*ui\.renderMarkdown\(ui\.buildReviewNextStepRichCopyForMessage\(message\)\)/)
assert.match(createViewTemplateSurface, /class="message-bubble"[\s\S]*:class="ui\.buildMessageBubbleClass\(message\)"/)
assert.match(createViewTemplateSurface, /:open="nextStepConfirmDialog\.open"[\s\S]*title="确认提交当前单据?"[\s\S]*confirm-text="确认提交"/)
assert.match(createViewScriptSurface, /const REVIEW_NEXT_STEP_HREF = '#review-next-step'/)
assert.match(createViewScriptSurface, /buildReviewRiskLevelCounts/)
assert.match(createViewScriptSurface, /function buildMessageBubbleClass\(message\)/)
assert.match(createViewScriptSurface, /message-bubble-review-risk-high/)
assert.match(createViewScriptSurface, /message-bubble-review-risk-medium/)
assert.match(createViewScriptSurface, /message-bubble-review-risk-low/)
assert.match(createViewScriptSurface, /function openReviewNextStepConfirm\(message\)/)
assert.match(createViewScriptSurface, /async function confirmReviewNextStepSubmit\(\)/)
assert.match(createViewScriptSurface, /href === REVIEW_NEXT_STEP_HREF[\s\S]*openReviewNextStepConfirm\(message\)/)
assert.match(createViewScriptSurface, /href\.startsWith\(REVIEW_RISK_PANEL_HREF_PREFIX\)[\s\S]*switchReviewDrawerMode\(REVIEW_DRAWER_MODE_RISK\)/)
assert.match(createViewBaseStyles, /\.message-bubble-review-risk-low\s*\{[\s\S]*border-color:\s*rgba\(37,\s*99,\s*235,/)
assert.match(createViewBaseStyles, /\.message-bubble-review-risk-medium\s*\{[\s\S]*border-color:\s*rgba\(217,\s*119,\s*6,/)
assert.match(createViewBaseStyles, /\.message-bubble-review-risk-high\s*\{[\s\S]*border-color:\s*rgba\(220,\s*38,\s*38,/)
@@ -286,11 +320,11 @@ test('review risk drawer lists risk briefs without score and posts details into
const riskItemsBlock = reviewPanelModelScript.match(/function buildReviewRiskItems\(reviewPayload\) \{[\s\S]*?\n\}\n\nexport function buildReviewRiskConversationText/)
assert.ok(riskItemsBlock, 'risk item builder should be present')
assert.doesNotMatch(createViewTemplate, /review-side-risk-score/)
assert.doesNotMatch(createViewTemplate, /风险评分/)
assert.doesNotMatch(createViewTemplate, /暂无风险评分/)
assert.doesNotMatch(createViewScript, /function buildReviewRiskScore/)
assert.doesNotMatch(createViewScript, /const reviewRiskScore/)
assert.doesNotMatch(createViewTemplateSurface, /review-side-risk-score/)
assert.doesNotMatch(createViewTemplateSurface, /风险评分/)
assert.doesNotMatch(createViewTemplateSurface, /暂无风险评分/)
assert.doesNotMatch(createViewScriptSurface, /function buildReviewRiskScore/)
assert.doesNotMatch(createViewScriptSurface, /const reviewRiskScore/)
assert.doesNotMatch(riskItemsBlock[0], /\.slice\(0,\s*6\)/)
assert.match(reviewPanelModelScript, /const DEPRECATED_REVIEW_RISK_TITLE_KEYWORDS = \[[\s\S]*'历史报销画像'[\s\S]*'制度注意事项'/)
assert.match(
@@ -299,11 +333,11 @@ test('review risk drawer lists risk briefs without score and posts details into
)
assert.match(
createViewTemplate,
/class="review-side-risk-item"[\s\S]*@click="appendReviewRiskBriefToConversation\(item\)"/
createViewTemplateSurface,
/class="review-side-risk-item"[\s\S]*@click="ui\.appendReviewRiskBriefToConversation\(item\)"/
)
assert.doesNotMatch(createViewTemplate, /\{\{\s*item\.levelLabel\s*\}\}/)
assert.match(createViewTemplate, /class="review-side-risk-icon" :title="item\.levelLabel"/)
assert.doesNotMatch(createViewTemplateSurface, /\{\{\s*item\.levelLabel\s*\}\}/)
assert.match(createViewTemplateSurface, /class="review-side-risk-icon" :title="item\.levelLabel"/)
assert.match(reviewPanelModelScript, /info:\s*\{[\s\S]*label:\s*'提示'/)
assert.match(reviewPanelModelScript, /medium:\s*\{[\s\S]*label:\s*'中风险'/)
assert.match(reviewPanelModelScript, /low:\s*\{[\s\S]*label:\s*'低风险'/)
@@ -315,22 +349,22 @@ test('review risk drawer lists risk briefs without score and posts details into
assert.match(reviewPanelModelScript, /\.replace\(\/\(高风险\|中风险\|低风险\)\/g,\s*''\)/)
assert.match(reviewPanelModelScript, /sourceLabel:\s*meta\.label/)
assert.doesNotMatch(reviewPanelModelScript, /normalizedTitle\.includes\('AI预审'\)/)
assert.match(createViewScript, /metaTone:\s*item\.level \|\| 'low'/)
assert.doesNotMatch(createViewTemplate, /@click="openReviewRiskDetail\(item\)"/)
assert.doesNotMatch(createViewTemplate, /review-risk-detail-modal/)
assert.doesNotMatch(createViewScript, /reviewRiskDetailDialog/)
assert.doesNotMatch(createViewScript, /function openReviewRiskDetail/)
assert.match(createViewScriptSurface, /metaTone:\s*item\.level \|\| 'low'/)
assert.doesNotMatch(createViewTemplateSurface, /@click="openReviewRiskDetail\(item\)"/)
assert.doesNotMatch(createViewTemplateSurface, /review-risk-detail-modal/)
assert.doesNotMatch(createViewScriptSurface, /reviewRiskDetailDialog/)
assert.doesNotMatch(createViewScriptSurface, /function openReviewRiskDetail/)
assert.match(
createViewScript,
createViewScriptSurface,
/function appendReviewRiskBriefToConversation\(item\) \{[\s\S]*messages\.value\.push\(createMessage\('assistant'/
)
assert.match(reviewPanelModelScript, /function buildReviewRiskConversationText\(item, detailTarget = \{\}\)/)
assert.match(createViewScript, /function resolveReviewDetailTarget\(message = null\) \{[\s\S]*router\.resolve\(\{[\s\S]*name: 'app-document-detail'/)
assert.match(createViewScript, /function resolveReviewRiskDetailTarget\(\) \{[\s\S]*return resolveReviewDetailTarget\(\)/)
assert.match(createViewScript, /进入 \$\{claimNo\} 详情重新填写/)
assert.match(createViewTemplate, /class="expense-query-risk-row"[\s\S]*appendExpenseQueryRiskToConversation\(record, risk\)/)
assert.match(createViewScript, /function appendExpenseQueryRiskToConversation\(record, risk\) \{[\s\S]*进入 \$\{claimNo\} 详情重新填写/)
assert.match(createViewScriptSurface, /function resolveReviewDetailTarget\(message = null\) \{[\s\S]*router\.resolve\(\{[\s\S]*name: 'app-document-detail'/)
assert.match(createViewScriptSurface, /function resolveReviewRiskDetailTarget\(\) \{[\s\S]*return resolveReviewDetailTarget\(\)/)
assert.match(createViewScriptSurface, /进入 \$\{claimNo\} 详情重新填写/)
assert.match(createViewTemplateSurface, /class="expense-query-risk-row"[\s\S]*ui\.appendExpenseQueryRiskToConversation\(record, risk\)/)
assert.match(createViewScriptSurface, /function appendExpenseQueryRiskToConversation\(record, risk\) \{[\s\S]*进入 \$\{claimNo\} 详情重新填写/)
})
test('review drawer default mode is scoped by the current action and travel overview uses travel-specific fields', () => {
@@ -340,15 +374,15 @@ test('review drawer default mode is scoped by the current action and travel over
assert.match(reviewDrawerScript, /scope === 'risk' && hasRisks[\s\S]*REVIEW_DRAWER_MODE_RISK/)
assert.match(reviewDrawerScript, /scope === 'overview'[\s\S]*REVIEW_DRAWER_MODE_REVIEW/)
assert.match(reviewPanelModelScript, /function normalizeReviewPanelScope\(scope\)/)
assert.match(createViewScript, /canExposeReviewPanelScope\(item\.reviewPanelScope\)/)
assert.match(createViewScript, /currentInsight\.value\.intent === 'agent' && agent[\s\S]*return null/)
assert.match(createViewScriptSurface, /canExposeReviewPanelScope\(item\.reviewPanelScope\)/)
assert.match(createViewScriptSurface, /currentInsight\.value\.intent === 'agent' && agent[\s\S]*return null/)
assert.match(reviewPanelModelScript, /function isTravelReviewPayload\(reviewPayload/)
assert.match(reviewPanelModelScript, /function resolveReviewTravelTransportType\(reviewPayload/)
assert.match(reviewPanelModelScript, /label: '交通类型'[\s\S]*modelKey: 'transport_type'/)
assert.match(reviewPanelModelScript, /label: '酒店名称'[\s\S]*modelKey: 'merchant_name'/)
assert.match(reviewPanelModelScript, /label: '出差事宜'[\s\S]*editor: 'textarea'[\s\S]*wide: true/)
assert.match(createViewTemplate, /item\.editor === 'textarea'[\s\S]*<textarea/)
assert.match(createViewTemplate, /wide: item\.wide/)
assert.match(createViewTemplateSurface, /item\.editor === 'textarea'[\s\S]*<textarea/)
assert.match(createViewTemplateSurface, /wide: item\.wide/)
})
test('submit composer scopes the side panel to intent overview, document upload, or triggered risk only', () => {
@@ -356,17 +390,17 @@ test('submit composer scopes the side panel to intent overview, document upload,
assert.match(submitComposerScript, /fileCount > 0 && documentCount > 0[\s\S]*return 'documents'/)
assert.match(submitComposerScript, /riskCount > 0 && \(asksRisk \|\| \['next_step', 'submit', 'submit_claim'\]\.includes\(normalizedAction\)\)[\s\S]*return 'risk'/)
assert.match(submitComposerScript, /!normalizedAction && fileCount === 0[\s\S]*return 'overview'/)
assert.match(submitComposerScript, /reviewPanelScope: resolveReviewPanelScope\(\{/)
assert.match(submitComposerScript, /reviewPanelScope: stewardDelegated[\s\S]*resolveReviewPanelScope\(\{/)
assert.match(submitComposerScript, /nextInsight\.agent\.reviewPanelScope = assistantMessage\.reviewPanelScope/)
})
test('expense query answers keep one clear result structure with document center jump link', () => {
assert.match(createViewTemplate, /!message\.reviewPayload && !message\.queryPayload && message\.meta\?\.length/)
assert.match(createViewTemplate, /!message\.reviewPayload && !message\.queryPayload && message\.suggestedActions\?\.length/)
assert.match(createViewTemplate, /!message\.reviewPayload && !message\.queryPayload && message\.citations\?\.length/)
assert.match(createViewTemplate, /message\.queryPayload\.title \|\| \(message\.queryPayload\.selectionMode === 'draft_association' \? '选择关联草稿' : '最近 5 条筛选结果'\)/)
assert.match(createViewTemplate, /v-html="renderMarkdown\(buildExpenseQueryHint\(message\.queryPayload\)\)"/)
assert.match(createViewScript, /href\.startsWith\('\/app\/'\)[\s\S]*router\.push\(href\)/)
assert.doesNotMatch(createViewTemplateSurface, /message\.meta\?\.length/)
assert.match(createViewTemplateSurface, /!message\.reviewPayload && !message\.queryPayload && message\.suggestedActions\?\.length/)
assert.match(createViewTemplateSurface, /!message\.reviewPayload && !message\.queryPayload && message\.citations\?\.length/)
assert.match(createViewTemplateSurface, /message\.queryPayload\.title \|\| \(message\.queryPayload\.selectionMode === 'draft_association' \? '选择关联草稿' : '最近 5 条筛选结果'\)/)
assert.match(createViewTemplateSurface, /v-html="ui\.renderMarkdown\(ui\.buildExpenseQueryHint\(message\.queryPayload\)\)"/)
assert.match(createViewScriptSurface, /href\.startsWith\('\/app\/'\)[\s\S]*router\.push\(href\)/)
})
test('backend query response suppresses generic query actions and supports archived filter title', () => {
@@ -384,36 +418,36 @@ test('backend query response suppresses generic query actions and supports archi
assert.match(queryScript, /EXPENSE_QUERY_PREVIEW_LIMIT = 5/)
assert.match(queryScript, /"归档"[\s\S]*"archived"/)
assert.match(queryScript, /ExpenseClaim\.approval_stage\.ilike\("%归档%"\)/)
assert.match(queryScript, /"title": f"最近 \{len\(preview_claims\)\} 条\{scope_label\}"/)
assert.match(queryScript, /"title": \([\s\S]*f"最近 \{len\(preview_claims\)\} 条\{scope_label\}"/)
})
test('closing the assistant while OCR is running defers unmount until the current flow finishes', () => {
assert.match(createViewScript, /const closeAfterBusy = ref\(false\)/)
assert.match(createViewScript, /function isWorkbenchBusy\(\) \{[\s\S]*submitting\.value \|\| reviewActionBusy\.value \|\| sessionSwitchBusy\.value/)
assert.match(createViewScript, /function maybeFinalizeDeferredClose\(\) \{[\s\S]*!closeAfterBusy\.value \|\| workbenchVisible\.value \|\| isWorkbenchBusy\(\)/)
assert.match(createViewScript, /function requestCloseWorkbench\(\) \{[\s\S]*closeAfterBusy\.value = isWorkbenchBusy\(\)[\s\S]*workbenchVisible\.value = false/)
assert.match(createViewScript, /function emitCloseAfterLeave\(\) \{[\s\S]*closeAfterBusy\.value && isWorkbenchBusy\(\)[\s\S]*return/)
assert.match(createViewScript, /\[submitting\.value, reviewActionBusy\.value, sessionSwitchBusy\.value, workbenchVisible\.value\][\s\S]*maybeFinalizeDeferredClose\(\)/)
assert.match(createViewScriptSurface, /const closeAfterBusy = ref\(false\)/)
assert.match(createViewScriptSurface, /function isWorkbenchBusy\(\) \{[\s\S]*submitting\.value \|\| reviewActionBusy\.value \|\| sessionSwitchBusy\.value/)
assert.match(createViewScriptSurface, /function maybeFinalizeDeferredClose\(\) \{[\s\S]*!closeAfterBusy\.value \|\| workbenchVisible\.value \|\| isWorkbenchBusy\(\)/)
assert.match(createViewScriptSurface, /function requestCloseWorkbench\(\) \{[\s\S]*closeAfterBusy\.value = isWorkbenchBusy\(\)[\s\S]*workbenchVisible\.value = false/)
assert.match(createViewScriptSurface, /function emitCloseAfterLeave\(\) \{[\s\S]*closeAfterBusy\.value && isWorkbenchBusy\(\)[\s\S]*return/)
assert.match(createViewScriptSurface, /\[submitting\.value, reviewActionBusy\.value, sessionSwitchBusy\.value, workbenchVisible\.value\][\s\S]*maybeFinalizeDeferredClose\(\)/)
})
test('composer exposes travel calculator and posts spreadsheet-backed result into conversation', () => {
assert.match(createViewTemplate, /v-if="canShowTravelCalculator" class="travel-calculator-anchor"/)
assert.match(createViewTemplate, /class="tool-btn composer-side-btn travel-calculator-trigger"[\s\S]*差旅计算器/)
assert.match(createViewTemplate, /class="travel-calculator-popover"[\s\S]*v-model="travelCalculatorForm\.days"[\s\S]*v-model="travelCalculatorForm\.location"/)
assert.doesNotMatch(createViewTemplate, /travel-calculator-modal/)
assert.doesNotMatch(createViewTemplate, /travelCalculatorResult\.total_amount/)
assert.match(createViewScript, /calculateTravelReimbursement/)
assert.match(createViewScript, /const canShowTravelCalculator = computed\(\(\) => activeSessionType\.value === SESSION_TYPE_EXPENSE\)/)
assert.match(createViewScript, /function openTravelCalculator\(\) \{[\s\S]*!canShowTravelCalculator\.value[\s\S]*closeTravelCalculator\(\)/)
assert.match(createViewScript, /function toggleTravelCalculator\(\)/)
assert.match(createViewScript, /function toggleTravelCalculator\(\) \{[\s\S]*!canShowTravelCalculator\.value[\s\S]*closeTravelCalculator\(\)/)
assert.match(createViewScript, /watch\(canShowTravelCalculator,[\s\S]*closeTravelCalculator\(\)/)
assert.match(createViewScript, /function submitTravelCalculator\(\) \{[\s\S]*calculateTravelReimbursement\(\{[\s\S]*grade: String\(user\.grade/)
assert.match(createViewScript, /根据您输入的地点和天数/)
assert.match(createViewScript, /匹配到您要出差的地区为/)
assert.match(createViewScript, /参考可报销合计/)
assert.match(createViewScript, /住宿费:\$\{hotelRate\} × \$\{days\} = \$\{hotelAmount\} 元/)
assert.match(createViewScript, /messages\.value\.push\(createMessage\('assistant', buildTravelCalculatorResultText\(payload\)/)
assert.match(createViewTemplateSurface, /v-if="canShowTravelCalculator" class="travel-calculator-anchor"/)
assert.match(createViewTemplateSurface, /class="tool-btn composer-side-btn travel-calculator-trigger"[\s\S]*差旅计算器/)
assert.match(createViewTemplateSurface, /class="travel-calculator-popover"[\s\S]*v-model="travelCalculatorForm\.days"[\s\S]*v-model="travelCalculatorForm\.location"/)
assert.doesNotMatch(createViewTemplateSurface, /travel-calculator-modal/)
assert.doesNotMatch(createViewTemplateSurface, /travelCalculatorResult\.total_amount/)
assert.match(createViewScriptSurface, /calculateTravelReimbursement/)
assert.match(createViewScriptSurface, /const canShowTravelCalculator = computed\(\(\) => activeSessionType\.value === SESSION_TYPE_EXPENSE\)/)
assert.match(createViewScriptSurface, /function openTravelCalculator\(\) \{[\s\S]*!canShowTravelCalculator\.value[\s\S]*closeTravelCalculator\(\)/)
assert.match(createViewScriptSurface, /function toggleTravelCalculator\(\)/)
assert.match(createViewScriptSurface, /function toggleTravelCalculator\(\) \{[\s\S]*!canShowTravelCalculator\.value[\s\S]*closeTravelCalculator\(\)/)
assert.match(createViewScriptSurface, /watch\(canShowTravelCalculator,[\s\S]*closeTravelCalculator\(\)/)
assert.match(createViewScriptSurface, /function submitTravelCalculator\(\) \{[\s\S]*calculateTravelReimbursement\(\{[\s\S]*grade: String\(user\.grade/)
assert.match(createViewScriptSurface, /根据您输入的地点和天数/)
assert.match(createViewScriptSurface, /匹配到您要出差的地区为/)
assert.match(createViewScriptSurface, /参考可报销合计/)
assert.match(createViewScriptSurface, /住宿费:\$\{hotelRate\} × \$\{days\} = \$\{hotelAmount\} 元/)
assert.match(createViewScriptSurface, /messages\.value\.push\(createMessage\('assistant', buildTravelCalculatorResultText\(payload\)/)
assert.match(reimbursementService, /export function calculateTravelReimbursement\(payload = \{\}\) \{[\s\S]*\/reimbursements\/travel-calculator/)
})
@@ -421,11 +455,11 @@ test('continuing receipt upload preserves prior review form context', () => {
assert.match(reviewPanelModelScript, /function buildReviewFormContextFromPayload\(reviewPayload, inlineState = null\)/)
assert.match(reviewPanelModelScript, /function buildBusinessTimeContextFromReviewValues\(values = \{\}\)/)
assert.match(
createViewScript,
submitComposerScript,
/resolvedUploadDisposition === 'continue_existing'[\s\S]*buildReviewFormContextFromPayload\([\s\S]*activeReviewPayload\.value[\s\S]*reviewInlineForm\.value[\s\S]*extraContext\.review_form_values/s
)
assert.match(
createViewScript,
submitComposerScript,
/inheritedReviewContext\.business_time_context[\s\S]*extraContext\.business_time_context = inheritedReviewContext\.business_time_context/s
)
})
@@ -465,27 +499,31 @@ test('review form context emits ontology fields instead of local aliases', () =>
})
test('review drawer save action is disabled while receipt recognition is submitting', () => {
assert.match(createViewScript, /const submitting = ref\(false\)/)
assert.match(createViewScriptSurface, /const submitting = ref\(false\)/)
assert.match(
createViewScript,
/submitting\.value = true[\s\S]*recognizeOcrFiles\(files\)[\s\S]*submitting\.value = false/s
submitComposerScript,
/submitting\.value = true[\s\S]*handleSubmitRecognitionFlow\(\{[\s\S]*recognizeOcrFiles[\s\S]*submitting\.value = false/s
)
assert.match(
createViewTemplate,
/class="review-side-save-pill"[\s\S]*:disabled="reviewActionBusy \|\| submitting"[\s\S]*@click="saveInlineReviewChanges"/
submitComposerScript,
/collectReceiptFiles\(\{[\s\S]*files,[\s\S]*recognizeOcrFiles[\s\S]*\}\)/
)
assert.match(
createViewScript,
createViewTemplateSurface,
/class="review-side-save-pill"[\s\S]*:disabled="ui\.reviewActionBusy \|\| ui\.submitting"[\s\S]*@click="ui\.saveInlineReviewChanges"/
)
assert.match(
createViewScriptSurface,
/async function handleReviewAction\(message, action\) \{[\s\S]*if \(!actionType \|\| submitting\.value \|\| reviewActionBusy\.value \|\| sessionSwitchBusy\.value\) return/
)
assert.match(
createViewScript,
createViewScriptSurface,
/function saveInlineReviewChanges\(\) \{[\s\S]*\|\| submitting\.value[\s\S]*\|\| sessionSwitchBusy\.value[\s\S]*\) return/
)
})
test('flow run detail refresh has timeout so composer submit is not held open', () => {
assert.match(reimbursementFlowScript, /FLOW_RUN_DETAIL_REFRESH_TIMEOUT_MS\s*=\s*3000/)
assert.match(flowTimingScript, /FLOW_RUN_DETAIL_REFRESH_TIMEOUT_MS\s*=\s*3000/)
assert.match(
reimbursementFlowScript,
/await Promise\.race\(\[[\s\S]*fetchAgentRunDetail\(flowRunId\.value\)[\s\S]*globalThis\.setTimeout\(\(\) => resolve\(null\), FLOW_RUN_DETAIL_REFRESH_TIMEOUT_MS\)/
@@ -509,10 +547,10 @@ test('draft creation keeps detail-scoped attachment persistence alive before clo
)
assert.match(submitComposerScript, /source: 'detail-smart-entry-attachment-sync'/)
assert.match(submitComposerScript, /uploadedCount: Number\(syncResult\?\.uploadedCount \|\| 0\)/)
assert.match(attachmentsScript, /function normalizeAttachmentMatchName\(value\)/)
assert.match(attachmentsScript, /const normalizedMatchBuckets = new Map\(\)/)
assert.match(attachmentSyncScript, /function normalizeAttachmentMatchName\(value\)/)
assert.match(attachmentSyncScript, /const normalizedMatchBuckets = new Map\(\)/)
assert.match(
attachmentsScript,
attachmentSyncScript,
/nextExactMatch \|\| nextNormalizedMatch \|\| fallbackMatch \|\| emptyFallbackMatch/
)
})
@@ -593,8 +631,8 @@ test('detail smart-entry receipt sync uploads files to existing empty items and
test('review summary renders markdown and save draft relies on backend response only', () => {
assert.match(
createViewTemplate,
/message\.text && message\.role === 'assistant' && message\.reviewPayload[\s\S]*v-html="renderMarkdown\(message\.text\)"/
createViewTemplateSurface,
/message\.text && message\.role === 'assistant' && message\.reviewPayload[\s\S]*v-html="ui\.renderMarkdown\(ui\.buildReviewMainMessageText\(message\)\)"/
)
assert.doesNotMatch(
reviewActionsScript,
@@ -623,13 +661,13 @@ test('saved draft review messages stop showing the save-draft prompt', () => {
assert.doesNotMatch(followup.summary, /当前草稿待完善|必须/)
assert.doesNotMatch(followup.summary, /点击|点“草稿”|保存为草稿|临时保存|暂存/)
assert.match(messageItemTemplate, /buildReviewPlainFollowupForMessage\(message\)/)
assert.match(createViewScript, /function isDraftSavedReviewMessage\(message\)/)
assert.match(createViewScript, /function canUseInlineSaveDraft\(message\)[\s\S]*isDraftSavedReviewMessage\(message\)/)
assert.match(createViewScriptSurface, /function isDraftSavedReviewMessage\(message\)/)
assert.match(createViewScriptSurface, /function canUseInlineSaveDraft\(message\)[\s\S]*isDraftSavedReviewMessage\(message\)/)
})
test('guided save draft emits refresh and exposes reimbursement draft detail card', () => {
assert.match(
createViewScript,
createViewScriptSurface,
/emitDraftSaved:\s*\(payload\)\s*=>\s*emit\('draft-saved', payload\)/
)
assert.match(submitComposerScript, /function emitSavedDraftRefresh\(draftPayload\)/)
@@ -637,10 +675,10 @@ test('guided save draft emits refresh and exposes reimbursement draft detail car
submitComposerScript,
/emitSavedDraftRefresh\(payload\?\.result\?\.draft_payload \|\| null\)/
)
assert.match(createViewScript, /function shouldShowDraftSavedCard\(message\)/)
assert.match(createViewScript, /function canOpenDraftDetail\(message\)/)
assert.match(createViewScript, /function resolveReimbursementDraftClaimNo\(draftPayload\)/)
assert.doesNotMatch(createViewScript, /function buildReimbursementDraftSummaryItems\(draftPayload\)/)
assert.match(createViewScriptSurface, /function shouldShowDraftSavedCard\(message\)/)
assert.match(createViewScriptSurface, /function canOpenDraftDetail\(message\)/)
assert.match(createViewScriptSurface, /function resolveReimbursementDraftClaimNo\(draftPayload\)/)
assert.doesNotMatch(createViewScriptSurface, /function buildReimbursementDraftSummaryItems\(draftPayload\)/)
assert.match(messageItemTemplate, /v-if="ui\.canOpenDraftDetail\(message\)"/)
assert.match(messageItemTemplate, /class="reimbursement-draft-link"/)
assert.match(messageItemTemplate, /reimbursement-draft-pending-detail/)