2026-05-24 21:44:17 +08:00
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
import { fileURLToPath } from 'node:url'
const documentsCenterView = readFileSync (
fileURLToPath ( new URL ( '../src/views/DocumentsCenterView.vue' , import . meta . url ) ) ,
'utf8'
)
const documentsCenterStyles = readFileSync (
fileURLToPath ( new URL ( '../src/assets/styles/views/documents-center-view.css' , import . meta . url ) ) ,
'utf8'
)
2026-06-02 14:01:51 +08:00
const documentListSharedStyles = readFileSync (
fileURLToPath ( new URL ( '../src/assets/styles/components/document-list-shared.css' , import . meta . url ) ) ,
'utf8'
)
2026-05-24 21:44:17 +08:00
test ( 'documents center keeps only the top scope tabs and renders status as a dropdown filter' , ( ) => {
assert . match ( documentsCenterView , /<nav class="status-tabs document-scope-tabs"/ )
assert . doesNotMatch ( documentsCenterView , /<nav class="status-tabs document-state-tabs"/ )
assert . match ( documentsCenterView , /class="document-status-filter"[\s\S]*class="document-filter status-dropdown-filter"/ )
assert . match (
documentsCenterView ,
/<div class="filter-set">[\s\S]*<div class="list-search">[\s\S]*<div class="document-status-filter"[\s\S]*<div v-if="showDocumentTypeFilter" class="document-filter">/
)
assert . match ( documentsCenterView , /v-for="option in statusFilterOptions"/ )
assert . match ( documentsCenterView , /@click="selectStatusTab\(option\.value\)"/ )
} )
2026-05-25 13:35:39 +08:00
test ( 'documents center top tabs start from all and show document category labels' , ( ) => {
assert . match ( documentsCenterView , /const DOCUMENT_SCOPE_ALL = '全部'/ )
2026-05-24 21:44:17 +08:00
assert . match ( documentsCenterView , /const DOCUMENT_SCOPE_APPLICATION = '申请单'/ )
assert . match ( documentsCenterView , /const DOCUMENT_SCOPE_REIMBURSEMENT = '报销单'/ )
assert . match ( documentsCenterView , /const DOCUMENT_SCOPE_REVIEW = '审核单'/ )
assert . match ( documentsCenterView , /const DOCUMENT_SCOPE_ARCHIVE = '归档'/ )
2026-05-25 13:35:39 +08:00
assert . match ( documentsCenterView , /const activeScopeTab = ref\(readDocumentScope\(DOCUMENT_SCOPE_ALL, scopeTabs\)\)/ )
2026-05-24 21:44:17 +08:00
assert . match (
documentsCenterView ,
2026-05-25 13:35:39 +08:00
/const scopeTabs = \[[\s\S]*DOCUMENT_SCOPE_ALL[\s\S]*DOCUMENT_SCOPE_APPLICATION[\s\S]*DOCUMENT_SCOPE_REIMBURSEMENT[\s\S]*DOCUMENT_SCOPE_REVIEW[\s\S]*DOCUMENT_SCOPE_ARCHIVE[\s\S]*\]/
2026-05-24 21:44:17 +08:00
)
} )
test ( 'documents center category tabs map to the intended row sources' , ( ) => {
2026-05-26 09:15:14 +08:00
assert . match ( documentsCenterView , /excludeArchivedDocumentRows/ )
assert . match ( documentsCenterView , /approvalRows\.value = excludeArchivedDocumentRows/ )
2026-05-25 13:35:39 +08:00
assert . match ( documentsCenterView , /const nonArchivedRows = computed\(\(\) => mergeDocumentRows\(\[\.\.\.ownedRows\.value, \.\.\.approvalRows\.value\]\)\)/ )
2026-05-26 09:15:14 +08:00
assert . match ( documentsCenterView , /activeScopeTab\.value !== DOCUMENT_SCOPE_ARCHIVE && isArchivedDocumentRow\(row\)/ )
2026-05-24 21:44:17 +08:00
assert . match (
documentsCenterView ,
2026-05-25 13:35:39 +08:00
/activeScopeTab\.value === DOCUMENT_SCOPE_ALL[\s\S]*return nonArchivedRows\.value/
)
assert . match (
documentsCenterView ,
2026-05-27 14:35:17 +08:00
/activeScopeTab\.value === DOCUMENT_SCOPE_APPLICATION[\s\S]*return applicationScopeRows\.value/
2026-05-24 21:44:17 +08:00
)
assert . match (
documentsCenterView ,
/activeScopeTab\.value === DOCUMENT_SCOPE_REIMBURSEMENT[\s\S]*ownedRows\.value\.filter/
)
assert . match (
documentsCenterView ,
/activeScopeTab\.value === DOCUMENT_SCOPE_REVIEW[\s\S]*return approvalRows\.value/
)
assert . match (
documentsCenterView ,
/activeScopeTab\.value === DOCUMENT_SCOPE_ARCHIVE[\s\S]*return archiveRows\.value/
)
2026-05-25 13:35:39 +08:00
assert . match ( documentsCenterView , /return nonArchivedRows\.value/ )
} )
test ( 'documents center preserves application document type from mapped requests' , ( ) => {
assert . match (
documentsCenterView ,
/const documentTypeCode = normalized\.documentTypeCode \|\| DOCUMENT_TYPE_REIMBURSEMENT/
)
assert . match (
documentsCenterView ,
/documentTypeCode === DOCUMENT_TYPE_APPLICATION \? '申请单' : '报销单'/
)
assert . doesNotMatch (
documentsCenterView ,
/documentTypeCode:\s*DOCUMENT_TYPE_REIMBURSEMENT,[\s\S]*documentTypeLabel:\s*'报销单'/
)
2026-05-24 21:44:17 +08:00
} )
test ( 'documents center list shows created time and conditional stay time columns' , ( ) => {
assert . match ( documentsCenterView , /import \{[\s\S]*formatDocumentListTime[\s\S]*resolveDocumentStayTimeDisplay[\s\S]*\} from '..\/utils\/documentCenterTime\.js'/ )
assert . match ( documentsCenterView , /<col class="col-created">/ )
assert . match ( documentsCenterView , /<col v-if="showStayTimeColumn" class="col-stay">/ )
2026-05-27 17:31:27 +08:00
assert . match ( documentsCenterView , /<col class="col-initiator">/ )
2026-05-24 21:44:17 +08:00
assert . match ( documentsCenterView , /<th>单号<\/th>[\s\S]*<th>创建时间<\/th>[\s\S]*<th v-if="showStayTimeColumn">停留时间<\/th>/ )
2026-05-27 17:31:27 +08:00
assert . match ( documentsCenterView , /<th>费用场景<\/th>[\s\S]*<th>发起人<\/th>[\s\S]*<th>事项<\/th>/ )
2026-06-02 14:01:51 +08:00
assert . match ( documentsCenterView , /<td data-label="创建时间">\{\{ row\.createdAtDisplay \}\}<\/td>/ )
assert . match ( documentsCenterView , /<td v-if="showStayTimeColumn" data-label="停留时间">\{\{ row\.stayTimeDisplay \}\}<\/td>/ )
assert . match ( documentsCenterView , /<td data-label="发起人">\{\{ row\.initiatorName \}\}<\/td>/ )
2026-05-24 21:44:17 +08:00
assert . match (
documentsCenterView ,
/const showStayTimeColumn = computed\(\(\) =>[\s\S]*DOCUMENT_SCOPE_APPLICATION[\s\S]*DOCUMENT_SCOPE_REVIEW/
)
assert . match ( documentsCenterView , /createdAtDisplay: formatDocumentListTime\(createdAtSource\)/ )
assert . match ( documentsCenterView , /stayTimeDisplay: resolveDocumentStayTimeDisplay\(normalized\)/ )
2026-05-27 17:31:27 +08:00
assert . match ( documentsCenterView , /initiatorName,/ )
assert . match ( documentsCenterView , /row\.initiatorName/ )
2026-05-24 21:44:17 +08:00
} )
test ( 'documents center action buttons are scoped to application and reimbursement tabs' , ( ) => {
assert . match (
documentsCenterView ,
/v-if="\[DOCUMENT_SCOPE_APPLICATION, DOCUMENT_SCOPE_REIMBURSEMENT\]\.includes\(activeScopeTab\)"[\s\S]*class="document-actions"/
)
assert . match (
documentsCenterView ,
/v-if="activeScopeTab === DOCUMENT_SCOPE_APPLICATION"[\s\S]*@click="emit\('create-application'\)"[\s\S]*发起申请/
)
assert . match (
documentsCenterView ,
/v-if="activeScopeTab === DOCUMENT_SCOPE_REIMBURSEMENT"[\s\S]*@click="emit\('create-request'\)"[\s\S]*发起报销/
)
assert . doesNotMatch ( documentsCenterView , /create-request-btn secondary/ )
} )
test ( 'documents center category tabs render bubble counts for new documents' , ( ) => {
2026-05-25 13:35:39 +08:00
assert . match ( documentsCenterView , /readViewedDocumentKeys/ )
assert . match ( documentsCenterView , /const viewedDocumentKeys = ref\(readViewedDocumentKeys\(\)\)/ )
2026-05-24 21:44:17 +08:00
assert . match ( documentsCenterView , /v-for="tab in scopeTabItems"/ )
assert . match ( documentsCenterView , /<span v-if="tab\.badgeCount > 0" class="scope-tab-badge"/ )
assert . match ( documentsCenterView , /tab\.badgeCount > 99 \? '99\+' : tab\.badgeCount/ )
assert . match ( documentsCenterView , /const scopeNewCountMap = computed\(\(\) => \(\{/ )
2026-05-25 13:35:39 +08:00
assert . match ( documentsCenterView , /\[DOCUMENT_SCOPE_ALL\]: countNewDocuments\(nonArchivedRows\.value, viewedDocumentKeys\.value\)/ )
assert . match (
documentsCenterView ,
2026-05-27 14:35:17 +08:00
/const applicationScopeRows = computed\(\(\) => prepareApplicationScopeRows\(ownedRows\.value\)\)/
)
assert . match (
documentsCenterView ,
/\[DOCUMENT_SCOPE_APPLICATION\]: countNewDocuments\(filterApplicationScopeNewRows\(applicationScopeRows\.value\), viewedDocumentKeys\.value\)/
2026-05-25 13:35:39 +08:00
)
2026-05-24 21:44:17 +08:00
assert . match (
documentsCenterView ,
2026-05-25 13:35:39 +08:00
/\[DOCUMENT_SCOPE_REIMBURSEMENT\]: countNewDocuments\(ownedRows\.value\.filter\(\(row\) => row\.documentTypeCode === DOCUMENT_TYPE_REIMBURSEMENT\), viewedDocumentKeys\.value\)/
2026-05-24 21:44:17 +08:00
)
2026-05-25 13:35:39 +08:00
assert . match ( documentsCenterView , /\[DOCUMENT_SCOPE_REVIEW\]: countNewDocuments\(approvalRows\.value, viewedDocumentKeys\.value\)/ )
assert . match ( documentsCenterView , /\[DOCUMENT_SCOPE_ARCHIVE\]: countNewDocuments\(archiveRows\.value, viewedDocumentKeys\.value\)/ )
2026-05-24 21:44:17 +08:00
assert . match (
documentsCenterView ,
/const scopeTabItems = computed\(\(\) =>[\s\S]*badgeCount: scopeNewCountMap\.value\[tab\] \|\| 0/
)
} )
2026-05-25 13:35:39 +08:00
test ( 'documents center rows show NEW marker until the row is opened' , ( ) => {
assert . match ( documentsCenterView , /<span v-if="row\.isNewDocument" class="new-document-badge">NEW<\/span>/ )
2026-06-02 14:01:51 +08:00
assert . match ( documentsCenterView , /isNewDocument: archived\s*\?\s*false\s*:\s*isNewDocument\(/ )
2026-05-25 13:35:39 +08:00
assert . match (
documentsCenterView ,
/function openDocument\(row\) \{[\s\S]*writeDocumentScope\(activeScopeTab\.value, scopeTabs\)[\s\S]*viewedDocumentKeys\.value = markDocumentViewed\(row, viewedDocumentKeys\.value\)[\s\S]*emit\('open-document', row\.rawRequest \|\| row\)/
)
assert . match ( documentsCenterStyles , /\.new-document-badge\s*\{[\s\S]*background:\s*#fff5f5;/ )
assert . match ( documentsCenterStyles , /\.new-document-badge\s*\{[\s\S]*border:\s*1px solid #fecaca;/ )
assert . match ( documentsCenterStyles , /\.new-document-badge::before\s*\{[\s\S]*background:\s*#ef4444;/ )
assert . doesNotMatch ( documentsCenterStyles , /newDocumentPulse/ )
} )
2026-05-27 09:17:57 +08:00
test ( 'documents center empty states follow theme tone across all scope tabs' , ( ) => {
2026-05-25 13:35:39 +08:00
const emptyStateBlock = documentsCenterView . match ( /const emptyState = computed\(\(\) => \{[\s\S]*?\n\}\)/ ) ? . [ 0 ] || ''
2026-05-27 09:17:57 +08:00
assert . match ( emptyStateBlock , /eyebrow: '申请单'[\s\S]*tone: 'theme'/ )
assert . match ( emptyStateBlock , /title: filtered \? '没有符合当前条件的单据'[\s\S]*tone: 'theme'/ )
assert . doesNotMatch ( emptyStateBlock , /tone:\s*'emerald'/ )
2026-05-25 13:35:39 +08:00
assert . doesNotMatch ( emptyStateBlock , /tone:\s*'sky'/ )
assert . doesNotMatch ( emptyStateBlock , /tone:\s*'slate'/ )
assert . doesNotMatch ( emptyStateBlock , /tone:\s*'amber'/ )
} )
test ( 'documents center empty states do not render small action buttons' , ( ) => {
const emptyStateBlock = documentsCenterView . match ( /const emptyState = computed\(\(\) => \{[\s\S]*?\n\}\)/ ) ? . [ 0 ] || ''
assert . match ( emptyStateBlock , /actionLabel:\s*''/ )
assert . match ( emptyStateBlock , /actionIcon:\s*''/ )
assert . doesNotMatch ( emptyStateBlock , /actionLabel:\s*filtered/ )
assert . doesNotMatch ( emptyStateBlock , /actionIcon:\s*filtered/ )
assert . doesNotMatch ( emptyStateBlock , /actionLabel:\s*'发起申请'/ )
assert . doesNotMatch ( emptyStateBlock , /actionLabel:\s*'发起报销'/ )
assert . doesNotMatch ( emptyStateBlock , /actionLabel:\s*'清空筛选'/ )
} )
2026-05-24 21:44:17 +08:00
test ( 'documents center switches filter conditions by category tab' , ( ) => {
assert . match ( documentsCenterView , /const FILTER_CONFIG_BY_SCOPE = \{/ )
2026-05-25 13:35:39 +08:00
assert . match (
documentsCenterView ,
/\[DOCUMENT_SCOPE_ALL\]: \{[\s\S]*sceneFallbackLabel: '单据场景'[\s\S]*statusTitle: '单据状态'[\s\S]*showDocumentType: true/
)
2026-05-24 21:44:17 +08:00
assert . match (
documentsCenterView ,
/\[DOCUMENT_SCOPE_APPLICATION\]: \{[\s\S]*sceneFallbackLabel: '申请场景'[\s\S]*statusTitle: '申请状态'[\s\S]*showDocumentType: false/
)
assert . match (
documentsCenterView ,
/\[DOCUMENT_SCOPE_REIMBURSEMENT\]: \{[\s\S]*statusTitle: '报销状态'[\s\S]*showDocumentType: false/
)
assert . match (
documentsCenterView ,
/\[DOCUMENT_SCOPE_REVIEW\]: \{[\s\S]*sceneFallbackLabel: '审核场景'[\s\S]*statusTitle: '审核状态'[\s\S]*statusTabs: \['全部', '审批中', '待补充', '已完成'\]/
)
assert . match (
documentsCenterView ,
2026-05-28 12:09:49 +08:00
/\[DOCUMENT_SCOPE_ARCHIVE\]: \{[\s\S]*dateLabel: '归档时间'[\s\S]*statusTitle: '归档状态'[\s\S]*statusTabs: \['全部', '已付款', '已完成'\]/
2026-05-24 21:44:17 +08:00
)
assert . match ( documentsCenterView , /v-if="showDocumentTypeFilter" class="document-filter"/ )
assert . match ( documentsCenterView , /:placeholder="activeFilterConfig\.searchPlaceholder"/ )
assert . match ( documentsCenterView , /class="document-status-filter" :aria-label="activeFilterConfig\.statusTitle"/ )
assert . doesNotMatch ( documentsCenterView , /class="status-filter-label"/ )
assert . match (
documentsCenterView ,
2026-05-27 09:17:57 +08:00
/watch\(activeFilterConfig, \(\) => \{[\s\S]*openFilterKey\.value = ''[\s\S]*datePopover\.value = false/
2026-05-24 21:44:17 +08:00
)
2026-05-27 09:17:57 +08:00
assert . match ( documentsCenterView , /<EnterpriseSelect v-model="pageSize"[\s\S]*:options="pageSizeOptions"/ )
assert . doesNotMatch ( documentsCenterView , /pageSizeOpen/ )
2026-05-24 21:44:17 +08:00
} )
test ( 'documents center status dropdown derives labels and closes after selection' , ( ) => {
assert . match ( documentsCenterView , /const statusFilterOptions = computed\(\(\) =>/ )
assert . match ( documentsCenterView , /activeFilterConfig\.value\.statusTabs\.map/ )
assert . match ( documentsCenterView , /label: tab === '全部' \? '全部状态' : tab/ )
assert . match ( documentsCenterView , /const statusFilterLabel = computed\(\(\) =>/ )
assert . match (
documentsCenterView ,
/function selectStatusTab\(value\) \{[\s\S]*activeStatusTab\.value = value[\s\S]*openFilterKey\.value = ''[\s\S]*\}/
)
} )
test ( 'documents center status dropdown uses compact filter styling' , ( ) => {
assert . match ( documentsCenterStyles , /\.documents-list\s*\{[\s\S]*grid-template-rows:\s*auto auto minmax\(0,\s*1fr\) auto;/ )
2026-06-02 14:01:51 +08:00
assert . match ( documentListSharedStyles , /\.status-tabs button\s*\{[\s\S]*display:\s*inline-flex;/ )
assert . match ( documentListSharedStyles , /\.scope-tab-badge\s*\{[\s\S]*border-radius:\s*999px;/ )
assert . match ( documentListSharedStyles , /min-width:\s*1420px;/ )
2026-05-24 21:44:17 +08:00
assert . match ( documentsCenterStyles , /\.col-created\s*\{\s*width:\s*10%;\s*\}/ )
assert . match ( documentsCenterStyles , /\.col-stay\s*\{\s*width:\s*9%;\s*\}/ )
2026-05-27 17:31:27 +08:00
assert . match ( documentsCenterStyles , /\.col-initiator\s*\{\s*width:\s*8%;\s*\}/ )
2026-05-24 21:44:17 +08:00
assert . match ( documentsCenterStyles , /\.document-status-filter\s*\{[\s\S]*display:\s*inline-flex;/ )
assert . match ( documentsCenterStyles , /\.document-status-filter\s*\{[\s\S]*min-height:\s*38px;/ )
assert . match ( documentsCenterStyles , /\.status-filter-trigger\s*\{[\s\S]*min-width:\s*154px;/ )
assert . match ( documentsCenterStyles , /\.status-filter-menu\s*\{[\s\S]*min-width:\s*154px;/ )
assert . doesNotMatch ( documentsCenterStyles , /\.document-state-tabs\s*\{/ )
assert . doesNotMatch ( documentsCenterStyles , /\.document-status-filter\s*\{[^}]*margin-top:/ )
} )