refactor: enforce 800 line source limits
This commit is contained in:
@@ -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/)
|
||||
|
||||
Reference in New Issue
Block a user