import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' import { fileURLToPath } from 'node:url' const sidebar = readFileSync( fileURLToPath(new URL('../src/components/layout/SidebarRail.vue', import.meta.url)), 'utf8' ) const sidebarStyles = readFileSync( fileURLToPath(new URL('../src/assets/styles/components/sidebar-rail.css', import.meta.url)), 'utf8' ) const topbar = readFileSync( fileURLToPath(new URL('../src/components/layout/TopBar.vue', import.meta.url)), 'utf8' ) const topbarStyles = readFileSync( fileURLToPath(new URL('../src/assets/styles/components/top-bar.css', import.meta.url)), 'utf8' ) const appShellRouteView = readFileSync( fileURLToPath(new URL('../src/views/AppShellRouteView.vue', import.meta.url)), 'utf8' ) const documentInbox = readFileSync( fileURLToPath(new URL('../src/composables/useDocumentCenterInbox.js', import.meta.url)), 'utf8' ) const documentNewState = readFileSync( fileURLToPath(new URL('../src/utils/documentCenterNewState.js', import.meta.url)), 'utf8' ) const notificationStatesService = readFileSync( fileURLToPath(new URL('../src/services/notificationStates.js', import.meta.url)), 'utf8' ) const topbarNotificationStates = readFileSync( fileURLToPath(new URL('../src/composables/useTopBarNotificationStates.js', import.meta.url)), 'utf8' ) test('sidebar no longer renders document center unread indicators', () => { assert.doesNotMatch(sidebar, /useDocumentCenterInbox/) assert.doesNotMatch(sidebar, /hasUnread: documentInboxHasUnread/) assert.doesNotMatch(sidebar, /hasNewMessage/) assert.doesNotMatch(sidebar, /nav-label-text/) assert.doesNotMatch(sidebar, /nav-unread-dot/) assert.match(sidebar, /\{\{ item\.displayLabel \}\}<\/span>/) assert.doesNotMatch(sidebarStyles, /\.nav-label-text\s*\{/) assert.doesNotMatch(sidebarStyles, /\.nav-unread-dot/) assert.match(sidebarStyles, /\.nav-label\s*\{[\s\S]*overflow:\s*hidden;[\s\S]*text-overflow:\s*ellipsis;/) }) test('topbar bell owns document center unread notifications', () => { assert.match(topbar, /useDocumentCenterInbox/) assert.match(topbar, /useTopBarNotificationStates/) assert.match(topbar, /resolveDocumentNotificationId/) assert.match(topbar, /notificationRows: documentInboxNotificationRows/) assert.match(topbar, /const documentNotificationItems = computed/) assert.match(topbar, /title: `\$\{row\.documentTypeLabel \|\| '单据'\} \$\{row\.documentNo \|\| row\.claimId \|\| '待生成'\}`/) assert.match(topbar, /description: resolveDocumentNotificationDescription\(row\)/) assert.match(topbar, /const unread = Boolean\(row\.isUnread\) && !isNotificationRead\(id\)/) assert.match(topbar, /markDocumentInboxRowRead\(item\.documentRow\)/) assert.match(topbar, /markNotificationStateRead\(item\)/) assert.match(topbar, /markDocumentInboxRowsRead\(documentRows\)/) assert.match(topbar, /hideNotificationStates\(currentItems\)/) assert.match(topbar, /loadNotificationStates\(\)/) assert.match(topbar, /const topbarNotificationCount = computed\(\(\) => \{[\s\S]*const count = unreadNotifications\.value\.length/) assert.doesNotMatch(topbar, /document-center-unread/) assert.doesNotMatch(topbar, /target: \{ type: 'documents-center' \}/) assert.doesNotMatch(topbar, /emit\('navigate', 'documents'\)/) assert.match(appShellRouteView, /@navigate="handleNavigate"/) assert.match(topbar, /startDocumentInboxPolling\(\)/) assert.match(topbar, /stopDocumentInboxPolling\(\)/) assert.match(topbar, /class="notification-clear-btn"/) assert.match(topbar, /function clearAllNotifications\(\)/) assert.match(topbar, /function markNotificationRead\(item\)/) assert.match(topbar, /class="notification-type-icon" :class="item\.tone"/) assert.match(topbarStyles, /\.notification-head-copy\s*\{[\s\S]*display:\s*grid;/) assert.match(topbarStyles, /\.notification-head-actions\s*\{[\s\S]*display:\s*inline-flex;/) assert.match(topbarStyles, /\.notification-popover\s*\{[\s\S]*max-height:\s*min\(520px,\s*calc\(100vh - 96px\)\);/) assert.match(topbarStyles, /\.notification-list\s*\{[\s\S]*max-height:\s*min\(336px,\s*calc\(100vh - 226px\)\);[\s\S]*overflow-y:\s*auto;/) assert.match(topbarStyles, /\.notification-copy small\s*\{[\s\S]*-webkit-line-clamp:\s*2;/) assert.match(topbarStyles, /@media \(max-width: 640px\)[\s\S]*\.notification-popover\s*\{[\s\S]*position:\s*fixed;/) assert.match(topbarStyles, /\.notification-tabs button em\s*\{[\s\S]*border-radius:\s*4px;/) assert.match(topbarStyles, /\.notification-row\s*\{[\s\S]*grid-template-columns:\s*34px minmax\(0,\s*1fr\) 16px;/) assert.doesNotMatch(topbarStyles, /\.notification-dot/) }) test('topbar notification state is persisted through backend API with local fallback', () => { assert.match(notificationStatesService, /apiRequest\('\/notification-states'\)/) assert.match(notificationStatesService, /apiRequest\('\/notification-states',\s*\{[\s\S]*method:\s*'POST'/) assert.match(topbarNotificationStates, /fetchNotificationStates/) assert.match(topbarNotificationStates, /patchNotificationStates/) assert.match(topbarNotificationStates, /NOTIFICATION_READ_STORAGE_KEY/) assert.match(topbarNotificationStates, /NOTIFICATION_HIDDEN_STORAGE_KEY/) assert.match(topbarNotificationStates, /applyRemoteStates/) assert.match(topbarNotificationStates, /markNotificationStateRead/) assert.match(topbarNotificationStates, /hideNotificationStates/) }) test('document inbox reuses document center viewed-key state', () => { assert.match(documentInbox, /DOCUMENT_VIEWED_KEYS_CHANGE_EVENT/) assert.match(documentInbox, /fetchNotificationStates/) assert.match(documentInbox, /mergeNotificationStatesIntoViewedDocumentKeys/) assert.match(documentInbox, /readViewedDocumentKeys/) assert.match(documentInbox, /countNewDocuments\(documentRows\.value, viewedDocumentKeys\.value\)/) assert.match(documentInbox, /const notificationRows = computed/) assert.match(documentInbox, /isUnread: isNewDocument\(row, viewedDocumentKeys\.value\)/) assert.match(documentInbox, /function markDocumentInboxRowRead\(row\)/) assert.match(documentInbox, /function markDocumentInboxRowsRead\(rows = documentRows\.value\)/) assert.match(documentInbox, /fetchExpenseClaims/) assert.match(documentInbox, /fetchApprovalExpenseClaims/) assert.match(documentInbox, /fetchArchivedExpenseClaims/) assert.match(documentInbox, /window\.addEventListener\(DOCUMENT_VIEWED_KEYS_CHANGE_EVENT, refreshViewedDocumentKeys\)/) assert.match(documentNewState, /export const DOCUMENT_VIEWED_KEYS_CHANGE_EVENT/) assert.match(documentNewState, /resolveDocumentNotificationId/) assert.match(documentNewState, /buildDocumentsViewedStatePatches/) assert.match(documentNewState, /window\.dispatchEvent\(new CustomEvent\(DOCUMENT_VIEWED_KEYS_CHANGE_EVENT\)\)/) })