2026-05-29 14:51:18 +08:00
|
|
|
import assert from 'node:assert/strict'
|
|
|
|
|
import { readFileSync } from 'node:fs'
|
|
|
|
|
import { join } from 'node:path'
|
|
|
|
|
|
|
|
|
|
const root = process.cwd()
|
|
|
|
|
|
|
|
|
|
function readProjectFile(path) {
|
|
|
|
|
return readFileSync(join(root, path), 'utf8')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function testReceiptFolderViewSurface() {
|
|
|
|
|
const view = readProjectFile('web/src/views/ReceiptFolderView.vue')
|
|
|
|
|
|
2026-06-01 17:07:14 +08:00
|
|
|
assert.match(view, /activeStatus = ref\('all'\)/)
|
|
|
|
|
assert.match(view, /value: 'all'/)
|
2026-06-03 15:46:56 +08:00
|
|
|
assert.match(view, /value: 'unlinked'/)
|
|
|
|
|
assert.match(view, /value: 'linked'/)
|
2026-06-01 17:07:14 +08:00
|
|
|
assert.match(view, /openAssociateDialog/)
|
2026-06-03 15:46:56 +08:00
|
|
|
assert.match(view, /<EnterpriseDetailPage/)
|
|
|
|
|
assert.match(view, /variant="receipt-folder-detail"/)
|
|
|
|
|
assert.match(view, /<template #main>/)
|
|
|
|
|
assert.match(view, /<template #side>/)
|
|
|
|
|
assert.match(view, /<template #bottom>/)
|
|
|
|
|
assert.match(view, /receipt-preview-panel/)
|
|
|
|
|
assert.match(view, /receipt-ticket-info-panel/)
|
|
|
|
|
assert.match(view, /receipt-association-panel/)
|
|
|
|
|
assert.match(view, /receipt-edit-log-section/)
|
|
|
|
|
assert.match(view, /receipt-all-field-grid/)
|
|
|
|
|
assert.match(view, /receiptInfoEditing/)
|
|
|
|
|
assert.match(view, /startReceiptInfoEdit/)
|
|
|
|
|
assert.match(view, /cancelReceiptInfoEdit/)
|
|
|
|
|
assert.match(view, /receiptEditLogs/)
|
|
|
|
|
assert.match(view, /previewFrameUrl/)
|
2026-06-01 17:07:14 +08:00
|
|
|
assert.match(view, /previewTransform/)
|
2026-06-03 15:46:56 +08:00
|
|
|
assert.match(view, /String\(value \?\? ''\)\.trim\(\)/)
|
2026-06-01 17:07:14 +08:00
|
|
|
assert.match(view, /openAssociateDialogForCurrentReceipt/)
|
|
|
|
|
assert.match(view, /createReceiptDetailDashboardModel/)
|
2026-06-03 15:46:56 +08:00
|
|
|
assert.match(view, /createReceiptDetailFieldModel/)
|
|
|
|
|
assert.doesNotMatch(view, /receipt-detail-toolbar/)
|
|
|
|
|
assert.doesNotMatch(view, /receipt-side-stack/)
|
|
|
|
|
assert.doesNotMatch(view, /receipt-bottom-grid/)
|
|
|
|
|
assert.doesNotMatch(view, /receipt-status-panel/)
|
|
|
|
|
assert.doesNotMatch(view, /receipt-key-grid/)
|
|
|
|
|
assert.doesNotMatch(view, /receipt-other-collapse/)
|
|
|
|
|
assert.doesNotMatch(view, /ElCollapse/)
|
2026-05-30 15:46:51 +08:00
|
|
|
assert.doesNotMatch(view, /openSourceFile/)
|
2026-06-01 17:07:14 +08:00
|
|
|
assert.match(view, /back-label=/)
|
|
|
|
|
assert.match(view, /deleteCurrentReceipt/)
|
2026-05-29 14:51:18 +08:00
|
|
|
assert.match(view, /ElCheckboxGroup/)
|
|
|
|
|
assert.match(view, /fetchReceiptFolderItems\('all'\)/)
|
2026-06-06 17:19:07 +08:00
|
|
|
assert.match(view, /DocumentDropdownFilter/)
|
|
|
|
|
assert.match(view, /receiptFilterControls/)
|
|
|
|
|
assert.match(view, /clear-filter-btn/)
|
|
|
|
|
assert.match(view, /receiptFilters\[control\.key\]/)
|
|
|
|
|
assert.match(view, /clearReceiptFilters/)
|
|
|
|
|
assert.doesNotMatch(view, /class="filter-btn" type="button" @click="reloadReceipts"/)
|
2026-05-29 14:51:18 +08:00
|
|
|
assert.match(view, /buildReceiptFile\(item\)/)
|
|
|
|
|
assert.match(view, /source: selectedDraft \? 'detail' : 'receipt-folder'/)
|
|
|
|
|
assert.match(view, /emit\('open-assistant'/)
|
2026-06-24 10:42:50 +08:00
|
|
|
assert.match(view, /REIMBURSEMENT_LIST_PREVIEW_PARAMS/)
|
|
|
|
|
assert.match(view, /fetchExpenseClaims\(REIMBURSEMENT_LIST_PREVIEW_PARAMS\)/)
|
|
|
|
|
assert.doesNotMatch(view, /const claims = await fetchExpenseClaims\(\)/)
|
2026-05-29 14:51:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function testReceiptFolderServiceContract() {
|
|
|
|
|
const service = readProjectFile('web/src/services/receiptFolder.js')
|
|
|
|
|
const ocrService = readProjectFile('web/src/services/ocr.js')
|
|
|
|
|
const reimbursementService = readProjectFile('web/src/services/reimbursements.js')
|
|
|
|
|
|
|
|
|
|
assert.match(service, /\/receipt-folder\$\{buildStatusQuery\(status\)\}/)
|
|
|
|
|
assert.match(service, /\/receipt-folder\/\$\{encodeURIComponent/)
|
|
|
|
|
assert.match(service, /responseType: 'blob'/)
|
|
|
|
|
assert.match(service, /new File\(\[blob\], fileName/)
|
|
|
|
|
assert.match(service, /receiptId/)
|
|
|
|
|
assert.match(ocrService, /formData\.append\('receipt_ids'/)
|
|
|
|
|
assert.match(reimbursementService, /formData\.append\('receipt_id'/)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function testAppShellWiresReceiptFolder() {
|
|
|
|
|
const shell = readProjectFile('web/src/views/AppShellRouteView.vue')
|
2026-05-30 15:46:51 +08:00
|
|
|
const topBar = readProjectFile('web/src/components/layout/TopBar.vue')
|
2026-05-29 14:51:18 +08:00
|
|
|
|
|
|
|
|
assert.match(shell, /activeView === 'receiptFolder'/)
|
|
|
|
|
assert.match(shell, /ReceiptFolderView/)
|
|
|
|
|
assert.match(shell, /@open-assistant="openSmartEntry"/)
|
2026-05-30 15:46:51 +08:00
|
|
|
assert.match(shell, /@detail-open-change="receiptFolderDetailOpen = \$event"/)
|
|
|
|
|
assert.match(shell, /@detail-topbar-change="detailTopBarPayload = \$event"/)
|
2026-05-29 14:51:18 +08:00
|
|
|
assert.match(shell, /receipt-folder-workarea/)
|
2026-05-30 15:46:51 +08:00
|
|
|
assert.match(topBar, /receiptFolder/)
|
|
|
|
|
assert.match(topBar, /eyebrowLabel/)
|
2026-05-29 14:51:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function testSharedDocumentListStyleReuse() {
|
|
|
|
|
const receiptView = readProjectFile('web/src/views/ReceiptFolderView.vue')
|
|
|
|
|
const documentView = readProjectFile('web/src/views/DocumentsCenterView.vue')
|
|
|
|
|
const receiptStyles = readProjectFile('web/src/assets/styles/views/receipt-folder-view.css')
|
|
|
|
|
const sharedStyles = readProjectFile('web/src/assets/styles/components/document-list-shared.css')
|
|
|
|
|
|
|
|
|
|
assert.match(receiptView, /document-list-shared\.css/)
|
|
|
|
|
assert.match(documentView, /document-list-shared\.css/)
|
|
|
|
|
assert.match(sharedStyles, /\.table-wrap\b/)
|
|
|
|
|
assert.match(sharedStyles, /\.doc-kind-tag\b/)
|
|
|
|
|
assert.match(sharedStyles, /\.list-foot\b/)
|
2026-06-06 17:19:07 +08:00
|
|
|
assert.match(sharedStyles, /\.clear-filter-btn\b/)
|
|
|
|
|
assert.match(sharedStyles, /\.document-filter-menu\b/)
|
2026-05-29 14:51:18 +08:00
|
|
|
assert.doesNotMatch(receiptStyles, /\.table-wrap\b/)
|
|
|
|
|
assert.doesNotMatch(receiptStyles, /\.doc-kind-tag\b/)
|
|
|
|
|
assert.doesNotMatch(receiptStyles, /\.list-foot\b/)
|
2026-06-06 17:19:07 +08:00
|
|
|
assert.doesNotMatch(receiptStyles, /\.receipt-select-filter\b/)
|
|
|
|
|
assert.doesNotMatch(receiptStyles, /\.receipt-clear-filters\b/)
|
2026-05-29 14:51:18 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-30 15:46:51 +08:00
|
|
|
function testReceiptFolderDetailLayoutAdjustments() {
|
|
|
|
|
const receiptView = readProjectFile('web/src/views/ReceiptFolderView.vue')
|
|
|
|
|
const receiptStyles = readProjectFile('web/src/assets/styles/views/receipt-folder-view.css')
|
|
|
|
|
const fieldModel = readProjectFile('web/src/views/scripts/receiptFolderDetailFields.js')
|
2026-06-03 15:46:56 +08:00
|
|
|
const dashboardModel = readProjectFile('web/src/views/scripts/receiptFolderDetailDashboard.js')
|
|
|
|
|
const detailPage = readProjectFile('web/src/components/shared/EnterpriseDetailPage.vue')
|
2026-05-30 15:46:51 +08:00
|
|
|
|
|
|
|
|
assert.match(receiptView, /showStatusColumn/)
|
|
|
|
|
assert.match(receiptView, /<col v-if="showStatusColumn" class="col-status">/)
|
2026-06-01 17:07:14 +08:00
|
|
|
assert.match(receiptView, /document_date/)
|
2026-06-03 15:46:56 +08:00
|
|
|
assert.match(receiptView, /<td[^>]*>\s*<strong class="doc-id">/)
|
2026-05-30 15:46:51 +08:00
|
|
|
assert.match(receiptView, /buildDetailPayload\(\)/)
|
|
|
|
|
assert.match(receiptView, /receiptDetailSubtitle/)
|
|
|
|
|
assert.match(receiptView, /receiptDetailTopBarPayload/)
|
|
|
|
|
assert.match(receiptView, /detail-topbar-change/)
|
|
|
|
|
assert.doesNotMatch(receiptView, /<article v-else class="receipt-folder-detail/)
|
|
|
|
|
assert.doesNotMatch(receiptView, /class="back-btn"/)
|
2026-06-03 15:46:56 +08:00
|
|
|
assert.doesNotMatch(receiptView, /receipt-basic-panel/)
|
|
|
|
|
assert.doesNotMatch(receiptView, /receipt-ocr-panel/)
|
2026-05-30 15:46:51 +08:00
|
|
|
assert.match(receiptStyles, /\.receipt-folder-list th:first-child/)
|
2026-06-03 15:46:56 +08:00
|
|
|
assert.match(receiptStyles, /\.receipt-folder-detail :deep\(\.detail-scroll\)[\s\S]*display: flex/)
|
2026-05-30 15:46:51 +08:00
|
|
|
assert.match(receiptStyles, /\.receipt-folder-detail :deep\(\.detail-grid\)/)
|
2026-06-03 15:46:56 +08:00
|
|
|
assert.match(receiptStyles, /\.receipt-folder-detail :deep\(\.detail-bottom\)/)
|
2026-05-30 15:46:51 +08:00
|
|
|
assert.match(receiptStyles, /\.receipt-folder-detail :deep\(\.detail-actions\)/)
|
|
|
|
|
assert.match(receiptStyles, /\.receipt-folder-detail :deep\(\.enterprise-detail-card \.card-head\)/)
|
2026-06-03 15:46:56 +08:00
|
|
|
assert.match(receiptStyles, /\.receipt-preview-panel/)
|
|
|
|
|
assert.match(receiptStyles, /\.receipt-ticket-info-panel/)
|
|
|
|
|
assert.match(receiptStyles, /\.receipt-association-panel/)
|
|
|
|
|
assert.match(receiptStyles, /\.receipt-preview-box[\s\S]*height: clamp\(380px, 56vh, 640px\)/)
|
|
|
|
|
assert.match(receiptStyles, /\.receipt-all-field-grid/)
|
2026-06-03 17:40:52 +08:00
|
|
|
assert.match(receiptStyles, /\.receipt-all-field-grid[\s\S]*grid-template-columns: repeat\(auto-fit, minmax\(260px, 1fr\)\)/)
|
2026-06-03 15:46:56 +08:00
|
|
|
assert.match(receiptStyles, /\.receipt-edit-log-list/)
|
|
|
|
|
assert.doesNotMatch(receiptStyles, /\.receipt-detail-toolbar/)
|
|
|
|
|
assert.doesNotMatch(receiptStyles, /\.receipt-side-stack/)
|
|
|
|
|
assert.doesNotMatch(receiptStyles, /\.receipt-bottom-grid/)
|
|
|
|
|
assert.doesNotMatch(receiptStyles, /\.receipt-log-list/)
|
|
|
|
|
assert.match(detailPage, /\$slots\.bottom/)
|
|
|
|
|
assert.match(detailPage, /class="detail-bottom"/)
|
2026-05-30 15:46:51 +08:00
|
|
|
assert.match(fieldModel, /TRAIN_KEY_FIELD_DEFINITIONS/)
|
2026-06-01 17:07:14 +08:00
|
|
|
assert.match(fieldModel, /id: 'invoice_number'/)
|
|
|
|
|
assert.match(fieldModel, /id: 'invoice_date'/)
|
|
|
|
|
assert.match(fieldModel, /id: 'fare'/)
|
|
|
|
|
assert.match(fieldModel, /id: 'passenger_name'/)
|
2026-05-30 15:46:51 +08:00
|
|
|
assert.match(fieldModel, /syncEditableFieldsToTopLevel/)
|
2026-06-01 17:07:14 +08:00
|
|
|
assert.match(dashboardModel, /createReceiptDetailDashboardModel/)
|
|
|
|
|
assert.match(dashboardModel, /basicInfoItems/)
|
2026-06-03 15:46:56 +08:00
|
|
|
assert.match(dashboardModel, /linkedClaimItems/)
|
|
|
|
|
assert.doesNotMatch(dashboardModel, /operationLogs/)
|
|
|
|
|
assert.doesNotMatch(dashboardModel, /archiveInfoItems/)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function testAssistantUnlinkedReceiptPrompt() {
|
|
|
|
|
const submitComposer = readProjectFile('web/src/views/scripts/useTravelReimbursementSubmitComposer.js')
|
2026-06-24 10:42:50 +08:00
|
|
|
const attachmentFlow = readProjectFile('web/src/views/scripts/travelReimbursementSubmitAttachmentFlow.js')
|
|
|
|
|
const suggestedActions = readProjectFile('web/src/views/scripts/useTravelReimbursementSuggestedActions.js')
|
2026-06-03 15:46:56 +08:00
|
|
|
|
|
|
|
|
assert.match(submitComposer, /fetchReceiptFolderItems/)
|
|
|
|
|
assert.match(submitComposer, /promptUnlinkedReceiptFolderIfNeeded/)
|
2026-06-24 10:42:50 +08:00
|
|
|
assert.match(attachmentFlow, /fetchReceiptFolderItems\('unlinked'\)/)
|
|
|
|
|
assert.match(attachmentFlow, /skipReceiptFolderUnlinkedPrompt/)
|
|
|
|
|
assert.match(attachmentFlow, /open_receipt_folder/)
|
|
|
|
|
assert.match(attachmentFlow, /continue_upload_with_unlinked_receipts/)
|
|
|
|
|
assert.match(suggestedActions, /actionType === 'open_receipt_folder'/)
|
|
|
|
|
assert.match(suggestedActions, /router\.push\(\{ name: 'app-receiptFolder' \}\)/)
|
|
|
|
|
assert.match(suggestedActions, /actionType === 'continue_upload_with_unlinked_receipts'/)
|
|
|
|
|
assert.match(suggestedActions, /skipReceiptFolderUnlinkedPrompt: true/)
|
2026-05-30 15:46:51 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-29 14:51:18 +08:00
|
|
|
function run() {
|
|
|
|
|
testReceiptFolderViewSurface()
|
|
|
|
|
testReceiptFolderServiceContract()
|
|
|
|
|
testAppShellWiresReceiptFolder()
|
|
|
|
|
testSharedDocumentListStyleReuse()
|
2026-05-30 15:46:51 +08:00
|
|
|
testReceiptFolderDetailLayoutAdjustments()
|
2026-06-03 15:46:56 +08:00
|
|
|
testAssistantUnlinkedReceiptPrompt()
|
2026-05-29 14:51:18 +08:00
|
|
|
console.log('receipt folder view tests passed')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
run()
|