import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' import { fileURLToPath } from 'node:url' const detailTemplate = readFileSync( fileURLToPath(new URL('../src/views/TravelRequestDetailView.vue', import.meta.url)), 'utf8' ) const detailScript = readFileSync( fileURLToPath(new URL('../src/views/scripts/TravelRequestDetailView.js', import.meta.url)), 'utf8' ) const detailStyles = readFileSync( fileURLToPath(new URL('../src/assets/styles/views/travel-request-detail-view.css', import.meta.url)), 'utf8' ) const approvalDialog = readFileSync( fileURLToPath(new URL('../src/components/travel/TravelRequestApprovalDialog.vue', import.meta.url)), 'utf8' ) const confirmDialog = readFileSync( fileURLToPath(new URL('../src/components/shared/ConfirmDialog.vue', import.meta.url)), 'utf8' ) const budgetAnalysisComponent = readFileSync( fileURLToPath(new URL('../src/components/travel/TravelRequestBudgetAnalysis.vue', import.meta.url)), 'utf8' ) const reimbursementService = readFileSync( fileURLToPath(new URL('../src/services/reimbursements.js', import.meta.url)), 'utf8' ) const appShellScript = readFileSync( fileURLToPath(new URL('../src/composables/useAppShell.js', import.meta.url)), 'utf8' ) function extractFunction(source, name) { const signatureIndex = source.indexOf(`function ${name}(`) assert.notEqual(signatureIndex, -1, `${name} should exist`) const bodyStart = source.indexOf('{', signatureIndex) assert.notEqual(bodyStart, -1, `${name} should have a body`) let depth = 0 for (let index = bodyStart; index < source.length; index += 1) { const char = source[index] if (char === '{') { depth += 1 } else if (char === '}') { depth -= 1 if (depth === 0) { return source.slice(signatureIndex, index + 1) } } } assert.fail(`${name} body should be closed`) } test('approval-mode detail collects leader opinion inside confirm dialog before API call', () => { assert.match(detailScript, /approvalMode:/) assert.match(detailScript, /const leaderOpinion = ref\(''\)/) assert.match(detailScript, /const approveConfirmDialogOpen = ref\(false\)/) assert.match(detailScript, /const approvalRiskConfirmed = ref\(false\)/) assert.match(detailScript, /const approvalRiskConfirmItems = computed/) assert.match(detailScript, /const approvalRiskConfirmRequired = computed/) assert.match(detailScript, /const canApproveRequest = computed/) assert.match(detailScript, /canApproveLeaderExpenseClaims/) assert.match(detailScript, /canApproveBudgetExpenseApplications/) assert.match(detailScript, /isCurrentDirectManagerForRequest/) assert.match(detailScript, /isCurrentRequestApplicant/) assert.match(detailScript, /isFinanceApprovalStage/) assert.match(detailScript, /const isBudgetApprovalStage = computed/) assert.match(detailScript, /const showBudgetAnalysis = computed/) assert.match(detailScript, /const isCurrentApplicant = computed/) assert.match(detailScript, /const isCurrentDirectManagerApprover = computed/) assert.match(detailScript, /const canProcessFinanceApprovalStage = computed/) assert.match(detailScript, /const canProcessBudgetApprovalStage = computed/) assert.match(detailScript, /const canProcessCurrentApprovalStage = computed/) assert.match(detailScript, /approvalOpinionTitle/) assert.match(detailScript, /approvalConfirmDescription/) assert.doesNotMatch(detailScript, /approvalNextStage/) assert.doesNotMatch(detailScript, /showApplicationLeaderOpinionInput/) assert.doesNotMatch(detailScript, /showLeaderApprovalPanel/) assert.match(detailScript, /const budgetApprovalOpinionRequired = computed/) assert.match(detailScript, /const requiresApprovalOpinion = computed\(\(\) => budgetApprovalOpinionRequired\.value\)/) assert.match(detailScript, /hasBudgetApprovalWarning\(request\.value\)/) assert.match(detailScript, /return '预算审批意见'/) assert.match(detailScript, /buildLeaderApprovalEvents/) assert.match(detailScript, /buildLeaderApprovalInfo/) assert.match(detailScript, /const leaderApprovalEvents = computed/) assert.match(detailScript, /const hasLeaderApprovalEvents = computed/) assert.match(detailScript, /const hasSingleLeaderApprovalEvent = computed\(\(\) => leaderApprovalEvents\.value\.length === 1\)/) assert.match( detailScript, /const showApplicationLeaderOpinion = computed\(\(\) => \(\s*isApplicationDocument\.value\s*&& hasLeaderApprovalEvents\.value\s*\)\)/ ) assert.match(detailScript, /isDirectManagerApprovalStage\.value\)[\s\S]*return isCurrentDirectManagerApprover\.value/) assert.match(detailScript, /if \(isDirectManagerApprovalStage\.value\) \{[\s\S]*return isCurrentDirectManagerApprover\.value/) assert.match(detailScript, /canProcessFinanceApprovalStage\.value/) assert.match(detailScript, /canProcessBudgetApprovalStage\.value/) assert.match(detailScript, /const canApproveRequest = computed\(\(\) =>\s*request\.value\.approvalKey === 'in_progress'[\s\S]*&& canProcessCurrentApprovalStage\.value\s*\)/) assert.doesNotMatch( detailScript, /const canApproveRequest = computed\(\(\) =>\s*\(Boolean\(props\.approvalMode\) \|\| isApplicationDocument\.value\)/ ) assert.doesNotMatch(detailScript, /leaderApprovalReadonlyText/) assert.match(detailScript, /resolveGeneratedDraftClaimNo/) assert.match(detailScript, /resolveApproveErrorMessage/) assert.match(detailScript, /当前部门未配置 P8 预算审批人,请联系管理员配置后再审批。/) assert.match(detailScript, /预算已超过警戒值,请填写预算审批意见后再通过。/) assert.match(detailScript, /approveActionLabel/) assert.match(detailScript, /approveExpenseClaim\(request\.value\.claimId, \{[\s\S]*opinion: leaderOpinion\.value\.trim\(\) \|\| '同意'/) assert.match(detailScript, /报销草稿 \$\{generatedDraftClaimNo\} 已生成/) assert.match(detailScript, /按预算与风险结果决定下一步/) assert.match(detailScript, /无风险且预算充足将直接完成申请/) assert.doesNotMatch(detailTemplate, /v-if="showLeaderApprovalPanel"/) assert.doesNotMatch(detailTemplate, /showApplicationLeaderOpinionInput/) assert.doesNotMatch(detailTemplate, /class="leader-approval-card/) assert.doesNotMatch(detailTemplate, /class="inline-leader-opinion/) assert.match(detailTemplate, /v-if="showApplicationLeaderOpinion"/) assert.match(detailTemplate, /class="application-leader-opinion"/) assert.match(detailTemplate, /v-if="hasLeaderApprovalEvents"/) assert.match(detailTemplate, /class="application-leader-opinion-timeline"/) assert.match(detailTemplate, /:class="\{ 'is-single': hasSingleLeaderApprovalEvent \}"/) assert.match(detailTemplate, /v-for="event in leaderApprovalEvents"/) assert.match(detailTemplate, /class="application-leader-opinion-event"/) assert.match(detailTemplate, /event\.type === 'returned'/) assert.match(detailTemplate, /class="application-leader-opinion-event-status"/) assert.match(detailTemplate, /class="application-leader-opinion-event-body"/) assert.match(detailTemplate, /审批意见/) assert.match(detailTemplate, /class="application-leader-opinion-event-foot"/) assert.match(detailTemplate, /class="application-leader-opinion-operator"/) assert.doesNotMatch(detailTemplate, /leaderApprovalReadonlyText/) assert.doesNotMatch(detailTemplate, /\u5f85\u76f4\u5c5e\u9886\u5bfc\u586b\u5199\u5ba1\u6279\u610f\u89c1/) assert.match(detailTemplate, /领导意见/) assert.match(detailTemplate, /