import { readFileSync } from 'node:fs' import { dirname, resolve } from 'node:path' import ts from 'typescript' import { describe, expect, it } from 'vitest' import { compileStyle, parse } from 'vue/compiler-sfc' import { adminNavigation, getNavigationLeaves } from '@/core/navigation/adminNavigation' // Approved entry/consumer inventory. Do not discover unrelated menus recursively. const cargoConsumers = [ 'ocean-bookings/pages/OceanBookingsView.vue', 'ocean-bookings/pages/OceanBookingDetailView.vue', 'ocean-bookings/components/OceanBookingDocumentsPanel.vue', 'ocean-bookings/components/OceanBookingPrintDialog.vue', 'trucking/pages/TruckAppointmentsView.vue', 'trucking/pages/TruckAppointmentCreateView.vue', 'trucking/pages/TruckAppointmentDetailView.vue', 'trucking/components/TruckAppointmentForm.vue', 'trucking/components/TruckAttachmentDialog.vue', 'container-loading/pages/ContainersView.vue', 'container-loading/pages/ContainerDetailView.vue', 'container-loading/pages/SmartLoadingWorkbenchView.vue', 'container-loading/components/OceanBookingLinkDialog.vue', 'container-loading/components/ContainerOverviewPanel.vue', 'container-loading/components/ContainerCustomsPanel.vue', 'container-loading/components/ContainerLastLegFeesPanel.vue', 'loading-lists/components/ContainerFeesPanel.vue', 'port-schedules/components/CarrierScheduleQueryPanel.vue', ] const overseasConsumers = [ 'overseas-warehouse/pages/LastMileOrdersView.vue', 'overseas-warehouse/pages/LastMileOrderDetailView.vue', 'overseas-warehouse/pages/InboundOrdersView.vue', 'overseas-warehouse/pages/InboundOrderDetailView.vue', 'overseas-warehouse/pages/DispatchesView.vue', 'overseas-warehouse/pages/DispatchDetailView.vue', 'overseas-warehouse/pages/OutboundsView.vue', 'overseas-warehouse/pages/OutboundDetailView.vue', 'overseas-warehouse/pages/DeliveryOrdersView.vue', 'overseas-warehouse/pages/DeliveryOrderDetailView.vue', 'overseas-warehouse/pages/RelabelOrdersView.vue', 'overseas-warehouse/pages/RelabelOrderDetailView.vue', 'overseas-warehouse/pages/PalletsView.vue', 'overseas-warehouse/pages/PalletDetailView.vue', 'overseas-warehouse/pages/InventoryView.vue', 'overseas-warehouse/pages/InventoryDetailView.vue', 'overseas-warehouse/pages/InventoryTransactionsView.vue', 'overseas-warehouse/pages/ReturnInventoryManagementView.vue', 'overseas-warehouse/components/WarehouseTransferReviewDialog.vue', 'overseas-warehouse/components/SmartDispatchDialog.vue', 'overseas-warehouse/components/DeliveryDocumentsDialog.vue', ] const overseasBusinessConsumers = [ 'overseas-business/pages/PurchaseOrdersView.vue', 'overseas-business/pages/PurchaseOrderDetailView.vue', 'overseas-business/pages/PurchaseOrderEditView.vue', 'overseas-business/pages/TransportOrdersView.vue', 'overseas-business/pages/TransportOrderFormView.vue', ] const customerServiceConsumers = [ 'overseas-warehouse/pages/ExceptionOrdersView.vue', 'overseas-warehouse/pages/CustomerVisitsView.vue', ] const financeConsumers = [ 'finance/pages/FinanceCustomerAccountsView.vue', 'finance/pages/FinanceCustomerArapView.vue', 'finance/pages/FinanceCustomerStatementsView.vue', 'finance/pages/FinanceFormalSettlementPage.vue', 'finance/pages/FinanceReceivableStatisticsView.vue', ] const generalCatalogConsumers = [ 'suppliers/pages/SuppliersView.vue', 'suppliers/pages/SupplierDetailView.vue', 'suppliers/pages/SupplierFormView.vue', 'suppliers/components/SupplierLicenseDialog.vue', 'overseas-master-data/pages/ExchangeRatesView.vue', 'overseas-master-data/pages/OverseasQuotesView.vue', 'overseas-master-data/pages/VehiclesView.vue', 'overseas-master-data/pages/VehicleDetailView.vue', 'overseas-master-data/pages/DriversView.vue', 'overseas-master-data/pages/TripsView.vue', 'overseas-master-data/pages/CapacityOffersView.vue', 'overseas-master-data/pages/CapacityOfferDetailView.vue', 'overseas-master-data/components/CapacityInquiryDialog.vue', 'overseas-master-data/components/CapacityInquiryTable.vue', 'overseas-master-data/components/CapacityOfferSettingsDialog.vue', 'overseas-master-data/pages/TripOperationsView.vue', 'overseas-master-data/pages/TripDetailView.vue', 'overseas-master-data/components/TripPlanDialog.vue', 'overseas-master-data/pages/DriverDetailView.vue', 'overseas-master-data/components/DriverEditorDialog.vue', 'overseas-master-data/components/DriverLicensePreview.vue', 'master-data/express-companies/pages/ExpressCompaniesView.vue', 'master-data/counterparties/pages/PartnersView.vue', 'fee-items/pages/FeeItemsView.vue', 'warehouses/pages/WarehousesView.vue', 'fba-pricing/carrier-rates/pages/CarrierRatesView.vue', 'fba-pricing/carrier-rates/pages/CarrierRateDetailView.vue', 'downloads/pages/DownloadCenterView.vue', ] const notificationConsumers = [ 'notifications/pages/PlatformNotificationsView.vue', 'notifications/pages/MessageManagementView.vue', 'notifications/pages/MessageTemplatesView.vue', 'notifications/components/MessageComposerDialog.vue', 'notifications/components/TemplateEditorDialog.vue', ] const systemConsumers = ['iam/pages/InternalUsersView.vue', 'iam/pages/RolesView.vue', 'iam/pages/PermissionsView.vue'] const consumers = [ 'overseas-business/components/FleetArrangementSummary.vue', ...notificationConsumers, ...systemConsumers, ...generalCatalogConsumers, ...financeConsumers, ...customerServiceConsumers, ...overseasBusinessConsumers, ...overseasConsumers, ...cargoConsumers, 'customers/pages/CustomersView.vue', 'customers/pages/CustomerDetailView.vue', 'customers/pages/OverseasStorageFeesView.vue', 'customers/components/CustomerBillingRulesPanel.vue', 'unified-orders/components/InboundFeeDetailManager.vue', 'unified-orders/components/OrderDeclarationEditorDialog.vue', 'customers/CustomerOverseasStorageFeePanel.vue', 'unified-orders/pages/UnifiedOrdersView.vue', 'inbounds/core/DropshipPalletPrintPanel.vue', 'inbounds/components/InboundTaskList.vue', 'inbounds/measurement/InboundMeasurementDialog.vue', 'inbounds/measurement/InboundMeasurementPanel.vue', 'inbounds/review/MeasurementReviewPanel.vue', 'inbounds/weight/components/WeightOcrPanel.vue', 'loading-lists/pages/PreloadPlanDetailView.vue', 'loading-lists/components/LoadingListsView.vue', 'loading-lists/components/LoadingListDetailDrawer.vue', 'loading-lists/components/PalletLabelPrintDialog.vue', 'warehouse-resources/locations/pages/LocationsView.vue', 'warehouse-resources/pallets/pages/PalletsView.vue', 'domestic-containers/pages/DomesticContainersView.vue', 'domestic-containers/pages/DomesticContainerInventoryDetailView.vue', 'container-loading/components/ContainerSimulationCreate.vue', 'audit/components/CustomerAuditLogsPanel.vue', ] const moduleCss = ['iam/iamDensity.css', 'finance/financeDensity.css', 'overseas-business/overseasBusinessDensity.css', 'customers/customerDensity.css', 'unified-orders/styles/orderDensity.css', 'inbounds/measurement/measurementDensity.css', 'domestic-containers/pages/domesticContainersSummary.css'] const read = (file: string) => readFileSync(resolve(process.cwd(), 'src', file), 'utf8') const domesticPages = [ { title: '订单列表', path: '/orders', file: 'unified-orders/pages/UnifiedOrdersView.vue' }, { title: '订单测量', path: '/inbounds/measurements', file: 'inbounds/components/InboundTaskList.vue', entry: 'inbounds/pages/InboundMeasurementsView.vue', component: 'InboundTaskList', mode: 'measurement' }, { title: '测量数据审核', path: '/inbounds/measurement-reviews', file: 'inbounds/components/InboundTaskList.vue', entry: 'inbounds/pages/MeasurementReviewsView.vue', component: 'InboundTaskList', mode: 'review' }, { title: '预装清单', path: '/loading-plans', file: 'loading-lists/components/LoadingListsView.vue', entry: 'loading-lists/pages/PreloadPlansView.vue', component: 'LoadingListsView', mode: 'preload' }, { title: '库位管理', path: '/master-data/locations', file: 'warehouse-resources/locations/pages/LocationsView.vue' }, { title: '托盘管理', path: '/master-data/pallets', file: 'warehouse-resources/pallets/pages/PalletsView.vue' }, { title: '整柜业务', path: '/domestic-containers', file: 'domestic-containers/pages/DomesticContainersView.vue' }, { title: '船期日历', path: '/sailing-calendar', file: 'sailing-calendar/pages/SailingCalendarView.vue' }, ] const customerPages = [ { title: '客户列表', path: '/customers', file: 'customers/pages/CustomersView.vue' }, { title: '海外仓阶梯费用', path: '/customers/overseas-storage-fees', file: 'customers/pages/OverseasStorageFeesView.vue' }, ] const densityPages = [...new Set([ ...notificationConsumers.filter(file => file.includes('/pages/')), ...systemConsumers, ...generalCatalogConsumers.filter(file => file.includes('/pages/')), ...financeConsumers, ...customerServiceConsumers, ...overseasBusinessConsumers, ...overseasConsumers.filter(file => !file.endsWith('PalletDetailView.vue')), ...cargoConsumers.filter(file => file.includes('/pages/') || ['ContainerOverviewPanel.vue', 'ContainerCustomsPanel.vue', 'ContainerLastLegFeesPanel.vue', 'ContainerFeesPanel.vue', 'OceanBookingDocumentsPanel.vue'].some(name => file.endsWith(name))), ...domesticPages.map(page => page.file), 'loading-lists/pages/PreloadPlanDetailView.vue', 'loading-lists/components/LoadingListDetailDrawer.vue', 'domestic-containers/pages/DomesticContainerInventoryDetailView.vue', ...customerPages.map(page => page.file), 'customers/pages/CustomerDetailView.vue', 'audit/components/CustomerAuditLogsPanel.vue', ])] type TemplateNode = NonNullable['descriptor']['template']>['ast']>['children'][number] type ElementNode = Extract const classesOf = (node: ElementNode, preload = true, dense = preload, workbench = 'DOMESTIC'): string[] => node.props.flatMap(prop => { if (prop.type === 6 && prop.name === 'class') return prop.value?.content.split(/\s+/) ?? [] if (prop.type !== 7 || prop.name !== 'bind' || prop.arg?.type !== 4 || prop.arg.content !== 'class' || prop.exp?.type !== 4 || !prop.exp.content.includes('density')) return [] // Evaluate only the authored density class binding with the shared component's two modes. const value = new Function('mode', 'isPreload', 'dense', 'isDomestic', 'isOverseas', `return (${prop.exp.content})`)(preload ? 'preload' : 'confirmed', preload, dense, workbench === 'DOMESTIC', workbench === 'OVERSEAS') as Record const flatten = (value: unknown): string[] => typeof value === 'string' ? value.split(/\s+/) : Array.isArray(value) ? value.flatMap(flatten) : value && typeof value === 'object' ? Object.entries(value).filter(([, enabled]) => enabled).flatMap(([names]) => names.split(/\s+/)) : [] return flatten(value) }) const styles = [ ...parse(read('shared/components/BatchImportDialog.vue')).descriptor.styles.map(style => ({ file: 'BatchImportDialog.vue', css: style.content })), ...consumers.flatMap(file => parse(read(`modules/${file}`)).descriptor.styles.map(style => ({ file, css: style.content }))), ...moduleCss.map(file => ({ file, css: read(`modules/${file}`) })), ] const shared = read('styles/adminDensity.css') const tokens = read('styles/adminDensityTokens.css') const cssRoot = (css: string) => { const result = compileStyle({ source: css, filename: 'density.css', id: 'density-contract', trim: false }) expect(result.errors).toEqual([]) return result.rawResult!.root } describe('approved Admin density ownership', () => { it('shares list status and category tab styles across registered list consumers', () => { for (const file of [ 'unified-orders/pages/UnifiedOrdersView.vue', 'overseas-warehouse/pages/LastMileOrdersView.vue', 'overseas-master-data/pages/VehiclesView.vue', 'suppliers/pages/SuppliersView.vue', 'domestic-containers/pages/DomesticContainersView.vue', 'container-loading/pages/ContainersView.vue', ]) { const page = parse(read(`modules/${file}`)).descriptor expect(page.template!.content).toMatch(/class="[^"]*density-status-tabs[^"]*" role="tablist"/) expect(page.styles.map((style) => style.content).join('\n')).not.toMatch(/\.(status-tabs?|supplier-tabs|container-scope-tabs)\s*[{:.]/) } expect(read('modules/overseas-warehouse/pages/ReturnInventoryManagementView.vue')).toContain('class="density-element-tabs density-status-tabs"') const selectors: string[] = [] cssRoot(shared).walkRules((rule) => { selectors.push(...rule.selectors) }) expect(selectors).toContain('.admin-density .density-status-tabs > button.is-active::after') expect(selectors).toContain('.admin-density .density-status-tabs > button:focus-visible') const element = '.admin-density .density-element-tabs.density-status-tabs > .el-tabs__header .el-tabs__nav .el-tabs__item' const rules: string[][] = [] cssRoot(shared).walkRules((rule) => { rules.push(rule.selectors) }) for (const suffix of ['', '::after', ':hover', ':focus-visible', '.is-active', '.is-active::after']) { expect(rules.some((rule) => rule.includes('.admin-density .density-status-tabs > button' + suffix) && rule.includes(element + suffix))).toBe(true) } }) it('keeps dashboard visualizations outside detail-field table styling', () => { const dashboard = read('modules/dashboard/pages/DashboardView.vue') expect(dashboard).toContain('class="dashboard-metrics"') expect(dashboard).toContain('class="dashboard-metric-mark"') expect(dashboard).not.toContain('density-fields') }) it('opts application-shell confirmations in even before a business page loads', () => { const entries = ['app/layouts/AppLayout.vue', 'app/components/WorkspaceTabs.vue', 'app/router/index.ts', 'main.ts'] let count = 0 for (const file of entries) { const source = read(file) expect(source, file).toContain("@/styles/adminDensity.css") const script = file.endsWith('.vue') ? parse(source).descriptor.scriptSetup!.content : source const tree = ts.createSourceFile(file, script, ts.ScriptTarget.Latest, true) const visit = (node: ts.Node) => { if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.expression.getText(tree) === 'ElMessageBox') { count += 1 const options = node.arguments[2] expect(options && ts.isObjectLiteralExpression(options), file).toBe(true) const customClass = options && ts.isObjectLiteralExpression(options) ? options.properties.find(prop => ts.isPropertyAssignment(prop) && prop.name.getText(tree) === 'customClass') : undefined expect(customClass && ts.isPropertyAssignment(customClass) && ts.isStringLiteral(customClass.initializer) && customClass.initializer.text, file).toBe('admin-density-confirm') } ts.forEachChild(node, visit) } visit(tree) } expect(count).toBe(5) }) it('checks dialog roots along actual static and dynamic Vue imports from approved consumers', () => { const pending = consumers.map(file => resolve(process.cwd(), 'src/modules', file)) const seen = new Set() while (pending.length) { const file = pending.shift()! if (seen.has(file)) continue seen.add(file) const descriptor = parse(readFileSync(file, 'utf8')).descriptor const enqueue = (reference: string) => { if (!reference.endsWith('.vue')) return if (reference.startsWith('@/')) pending.push(resolve(process.cwd(), 'src', reference.slice(2))) else if (reference.startsWith('.')) pending.push(resolve(dirname(file), reference)) } for (const script of [descriptor.script, descriptor.scriptSetup]) { if (!script) continue const ast = ts.createSourceFile(file, script.content, ts.ScriptTarget.Latest, true) const visitImports = (node: ts.Node) => { if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.expression.getText(ast) === 'ElMessageBox') { expect(node.arguments[2]?.getText(ast), file + ': reachable confirmation').toContain('admin-density-confirm') } if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) enqueue(node.moduleSpecifier.text) if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) { const reference = node.arguments[0] if (reference && ts.isStringLiteral(reference)) enqueue(reference.text) } ts.forEachChild(node, visitImports) } visitImports(ast) } if (['AppDialog.vue', 'AppDrawer.vue', 'BatchImportDialog.vue'].some(name => file.endsWith(name))) continue const visitTemplate = (nodes: TemplateNode[]) => { for (const node of nodes) { if (node.type !== 1) continue if (['AppDialog', 'el-dialog', 'BatchImportDialog'].includes(node.tag)) expect(classesOf(node), `${file}: reachable modal`).toContain('admin-density-dialog') if (['AppDrawer', 'el-drawer'].includes(node.tag)) expect(classesOf(node), `${file}: reachable drawer`).toContain('admin-density-drawer') visitTemplate(node.children) } } if (descriptor.template?.ast) visitTemplate(descriptor.template.ast.children) } }) it('keeps the supplier license dialog styled at its own teleported root', () => { const source = read('modules/suppliers/components/SupplierLicenseDialog.vue') expect(source).toContain(' { const element = '.admin-density .density-element-tabs > .el-tabs__header .el-tabs__nav .el-tabs__item' const rules: string[][] = [] cssRoot(shared).walkRules(rule => { rules.push(rule.selectors) }) for (const [nativeSuffix, elementSuffix] of [['', ''], ['::after', '::after'], [':hover', ':hover'], ['.active', '.is-active'], ['.active::after', '.is-active::after'], [':focus-visible', ':focus-visible']]) { expect(rules.some(selectors => selectors.includes('.admin-density .density-tabs > button' + nativeSuffix) && selectors.includes(element + elementSuffix))).toBe(true) } const domestic = read('modules/unified-orders/pages/UnifiedOrdersView.vue') const overseasShell = read('modules/overseas-warehouse/components/OverseasDetailShell.vue') for (const className of ['density-detail-header', 'density-detail-heading', 'density-detail-title']) { expect(domestic).toContain(className) expect(overseasShell).toContain(className) } expect(domestic).toContain('density-detail-overview') expect(read('modules/overseas-warehouse/pages/LastMileOrderDetailView.vue')).toContain('density-detail-overview') }) it('covers the approved overseas pages across the latest menu groups, details and both return inventory modes', () => { const paths = getNavigationLeaves(adminNavigation).map(node => node.path) expect(paths).toEqual(expect.arrayContaining(['last-mile-orders', 'inbound-orders', 'locations', 'dispatches', 'outbounds', 'delivery-orders', 'relabel-orders', 'pallets', 'inventory', 'return-inventory', 'return-cartons'].map(path => '/overseas-warehouse/' + path))) const routes = read('modules/overseas-warehouse/routes.ts') for (const mode of ['inventory', 'cartons']) expect(routes).toContain("mode: '" + mode + "'") for (const file of overseasConsumers) { const source = read('modules/' + file) expect(source, file).toContain('@/styles/adminDensity.css') expect(parse(source).errors, file).toEqual([]) if (file.endsWith('DetailView.vue') && !/OutboundDetail|DeliveryOrderDetail/.test(file)) { const root = parse(source).descriptor.template!.ast!.children.find(node => node.type === 1) as ElementNode expect(root.tag).toMatch(/^Overseas(?:DetailShell|ReadOnlyDetail)$/) expect(root.props.some(prop => prop.type === 6 && prop.name === 'dense')).toBe(true) expect(classesOf(root)).toContain('admin-density') } } }) it('keeps contained overseas tab content and external cargo tab bodies separate', () => { expect(shared).toContain('.density-element-tabs:not(.density-contained-tabs) > .el-tabs__content { display: none; }') for (const file of ['LastMileOrderDetailView', 'DeliveryOrderDetailView']) { const source = read('modules/overseas-warehouse/pages/' + file + '.vue') const ast = parse(source).descriptor.template!.ast! let panes = 0 const visit = (nodes: typeof ast.children) => { for (const node of nodes) { if (node.type !== 1) continue if (node.tag === 'el-tabs') expect(classesOf(node)).toContain('density-contained-tabs') if (node.tag === 'el-tab-pane') { panes++ expect(node.children.some(child => child.type === 1 && child.tag !== 'template'), file + ': pane retains body').toBe(true) expect(node.loc.source).toContain('density-tab-icon') } visit(node.children) } } visit(ast.children) expect(panes).toBe(file === 'LastMileOrderDetailView' ? 5 : 4) } }) it('opts shared overseas detail and relationship wrappers in only at approved callers', () => { for (const file of ['modules/overseas-warehouse/components/OverseasDetailShell.vue', 'modules/overseas-warehouse/components/OverseasReadOnlyDetail.vue', 'shared/components/InboundCodeRelationshipDialog.vue']) { const source = read(file) expect(source).toContain('dense?: boolean') const ast = parse(source).descriptor.template!.ast! const root = ast.children.find(node => node.type === 1) as ElementNode expect(classesOf(root, false, true).some(name => name.startsWith('admin-density')), file).toBe(true) expect(classesOf(root, false, false).some(name => name.startsWith('admin-density')), file).toBe(false) } expect(read('modules/overseas-warehouse/components/OverseasReadOnlyDetail.vue')).toContain(':dense="dense"') expect(read('modules/overseas-warehouse/pages/InventoryTransactionsView.vue')).toContain(' { const navigation = read('core/navigation/adminNavigation.ts') for (const path of ['/ocean-bookings', '/trucking', '/container-loading/domestic', '/container-loading/overseas']) expect(navigation).toContain(`path: '${path}'`) for (const [prefix, workbench] of [['Domestic', 'DOMESTIC'], ['Overseas', 'OVERSEAS']]) { for (const [suffix, component] of [['ContainersView', 'ContainersView'], ['ContainerDetailView', 'ContainerDetailView']]) { expect(read(`modules/container-loading/pages/${prefix}${suffix}.vue`)).toContain(`<${component} workbench="${workbench}"`) const ast = parse(read(`modules/container-loading/pages/${component}.vue`)).descriptor.template!.ast! const root = ast.children.find(node => node.type === 1) as ElementNode expect(classesOf(root, false, true, workbench)).toContain('admin-density') } } const detail = read('modules/container-loading/pages/ContainerDetailView.vue') for (const component of ['CarrierScheduleQueryPanel', 'LoadingListDetailDrawer', 'PalletLabelPrintDialog']) expect(detail).toContain(`<${component} dense`) expect(read('modules/container-loading/pages/ContainersView.vue')).toContain(' { for (const file of ['loading-lists/components/LoadingListDetailDrawer.vue', 'port-schedules/components/CarrierScheduleQueryPanel.vue']) { const ast = parse(read(`modules/${file}`)).descriptor.template!.ast! const root = ast.children.find(node => node.type === 1) as ElementNode expect(classesOf(root, false, true)).toContain('admin-density') expect(classesOf(root, false, false)).not.toContain('admin-density') if (file.includes('CarrierSchedule')) { const dialog = root.children.find(node => node.type === 1 && node.tag === 'AppDialog') as ElementNode expect(classesOf(dialog, false, true)).toContain('admin-density-dialog') expect(classesOf(dialog, false, false)).not.toContain('admin-density-dialog') } } }) it('keeps cargo native tables, row actions and detail tabs connected to shared styles', () => { for (const file of cargoConsumers) { const ast = parse(read(`modules/${file}`)).descriptor.template!.ast! const visit = (nodes: typeof ast.children, inCell = false, insideDensity = false) => { for (const node of nodes) { if (node.type !== 1) continue const classes = classesOf(node) const enabled = insideDensity || classes.some(name => ['admin-density', 'admin-density-dialog'].includes(name)) if (node.tag === 'table') { expect(classes, `${file}: native table`).toContain('density-table') expect(insideDensity, `${file}: native table ancestor`).toBe(true) } if (node.tag === 'el-tabs') expect(classes).toContain('density-element-tabs') if (inCell && node.tag === 'el-button') { expect(classes, `${file}: native row action`).toContain('density-action') expect(node.children.some(child => child.type === 2 && child.content.trim() || child.type === 5), `${file}: empty action`).toBe(true) } visit(node.children, inCell || node.tag === 'td', enabled) } } visit(ast.children) } }) it('keeps shared list actions and detail content inside a density-enabled page ancestor', () => { for (const file of densityPages) { const template = parse(read(`modules/${file}`)).descriptor.template!.ast! let checked = 0 const visit = (nodes: typeof template.children, insideDensity = false) => { for (const node of nodes) { if (node.type !== 1) continue const classes = classesOf(node) if (node.tag === 'DataTable' || node.tag === 'LoadingListDetailDrawer' || classes.some(name => ['density-action', 'density-actions', 'density-tabs', 'density-fields', 'density-tab-icon', 'density-back'].includes(name))) { expect(insideDensity, `${file}: ${classes.join(' ')} lacks its density ancestor`).toBe(true) checked += 1 } visit(node.children, insideDensity || classes.some(name => ['admin-density', 'admin-density-dialog'].includes(name))) } } visit(template.children) expect(checked, `${file}: no shared styles checked`).toBeGreaterThan(0) } }) it('covers approved domestic pages across the latest menu groups and loads density CSS on direct entry', () => { const paths = getNavigationLeaves(adminNavigation).map(node => node.path) expect(paths).toEqual(expect.arrayContaining(domesticPages.map(page => page.path))) for (const page of domesticPages) { if (page.entry) expect(read(`modules/${page.entry}`)).toContain(`<${page.component} mode="${page.mode}"`) } for (const file of densityPages) expect(read(`modules/${file}`), `${file}: direct entry CSS`).toContain('@/styles/adminDensity.css') }) it('covers both customer menu entries and their shared detail primitives', () => { const navigation = read('core/navigation/adminNavigation.ts') const customers = navigation.slice(navigation.indexOf("key: 'customers'"), navigation.indexOf("key: 'domestic-warehouse'")) expect([...customers.matchAll(/path: '/g)]).toHaveLength(customerPages.length) for (const page of customerPages) { expect(customers).toContain(`title: '${page.title}'`) expect(customers).toContain(`path: '${page.path}'`) } expect(read('modules/customers/pages/CustomerDetailView.vue')).toContain('class="back-button density-back"') }) it('uses shared outlined actions across domestic tables and explicit classes on teleported dialogs', () => { for (const file of [...new Set([...densityPages, ...consumers])]) { const template = parse(read(`modules/${file}`)).descriptor.template!.ast! const visit = (nodes: typeof template.children, actionSlot = false, parentTag = '') => { for (const node of nodes) { if (node.type !== 1) continue const isActionSlot = actionSlot || (parentTag === 'DataTable' && node.props.some(prop => prop.type === 7 && prop.name === 'slot' && prop.arg?.type === 4 && prop.arg.content === 'actions')) if (densityPages.includes(file) && isActionSlot && node.tag === 'el-button') expect(classesOf(node), `${file}: row action`).toContain('density-action') if (['AppDialog', 'el-dialog'].includes(node.tag)) expect(classesOf(node), `${file}: teleported dialog`).toContain('admin-density-dialog') if (['AppDrawer', 'el-drawer'].includes(node.tag)) expect(classesOf(node), `${file}: teleported drawer`).toContain('admin-density-drawer') visit(node.children, isActionSlot, node.tag) } } visit(template.children) } }) it('keeps tokens and shared rules opt-in and free of business selectors', () => { expect(shared).toContain("@import './adminDensityTokens.css'") for (const css of [tokens, shared]) cssRoot(css).walkRules(rule => { expect(rule.selector, `unscoped rule: ${rule.selector}`).toContain('.admin-density') expect(rule.selector).not.toMatch(/customer-|order-|inbound-|preload-/) }) }) it('defines every referenced density variable once, with local field width as the documented exception', () => { const definitions = [...tokens.matchAll(/(--admin-[\w-]+)\s*:/g)].map(match => match[1]) expect(new Set(definitions).size).toBe(definitions.length) const declared = new Set([...definitions, '--admin-field-label-width']) for (const { file, css } of [...styles, { file: 'adminDensity.css', css: shared }]) { for (const match of css.matchAll(/var\((--admin-[\w-]+)/g)) expect(declared.has(match[1]!), `${file}: ${match[1]}`).toBe(true) for (const match of css.matchAll(/(--admin-[\w-]+)\s*:/g)) expect(match[1], `${file}: token redefinition`).toBe('--admin-field-label-width') } }) it('connects configurable surface colors and semantic hover states to their consumers', () => { const root = cssRoot(shared) const rules = new Map>() root.walkRules(rule => { const declarations: Record = {} rule.walkDecls(decl => { declarations[decl.prop] = decl.value }) rules.set(rule.selector, declarations) }) expect(rules.get('.admin-density .el-table__body tr.el-table__row--striped td.el-table__cell')?.background).toBe('var(--admin-row-stripe)') expect(rules.get('.admin-density-dialog .el-descriptions__label.el-descriptions__cell')?.background).toBe('var(--admin-label-bg)') for (const tone of ['warning', 'purple', 'primary', 'success', 'danger']) { expect(rules.get(`:is(.admin-density, .admin-density-dialog) .density-action[data-tone="${tone}"]`)?.['--action-hover-bg']).toBe(`var(--admin-action-${tone}-hover)`) } expect(rules.get(':is(.admin-density, .admin-density-dialog) .el-button.density-action:hover:not(:disabled)')?.background).toBe('var(--action-hover-bg)') }) it('prevents page copies of action paint, tab primitives and desktop density dimensions', () => { for (const { file, css } of styles) cssRoot(css).walkRules(rule => { expect(rule.selector.trim(), file + ': empty selector').not.toBe('') expect(rule.selector, file).not.toMatch(/customer-action-button|order-action-button|\.density-action|\.density-tab-icon/) let desktop = true for (let parent: typeof rule.parent | ReturnType['parent'] = rule.parent; parent; parent = parent.parent) { if (parent.type === 'atrule' && /max-width|print/.test('params' in parent ? String(parent.params) : '')) desktop = false } rule.walkDecls(decl => { expect(decl.prop, file).not.toMatch(/^--el-button-/) if (desktop && decl.prop === 'font-size') expect(decl.value, `${file}: ${rule.selector}`).not.toMatch(/^(10|11|12|13|14|16)px$/) if (desktop && /^(height|min-height)$/.test(decl.prop)) expect(decl.value, `${file}: ${rule.selector}`).not.toMatch(/^(28|32|36|38|64)px$/) }) }) }) it('keeps desktop table toolbar dimensions owned by DataTable without cascade overrides', () => { const tableCss = parse(read('shared/components/DataTable.vue')).descriptor.styles.map(style => style.content).join('\n') const dimensions: Record = { padding: [], 'min-height': [] } cssRoot(tableCss).walkRules(rule => { if (!rule.selectors.includes('.data-table__toolbar') && !rule.selectors.includes('.data-table__toolbar.is-compact')) return // Preserve the existing mobile layout; the shared desktop dimensions have one owner. if (rule.parent?.type === 'atrule' && /max-width/.test(rule.parent.params)) return rule.walkDecls(decl => { if (dimensions[decl.prop]) dimensions[decl.prop].push(decl.value) }) }) expect(dimensions).toEqual({ padding: ['var(--admin-space-2, 6px) var(--admin-space-5, 12px)'], 'min-height': ['44px'] }) for (const { file, css } of [{ file: 'adminDensity.css', css: shared }, ...styles]) cssRoot(css).walkRules(rule => { if (!/\.data-table__toolbar(?:\b|\.)/.test(rule.selector)) return rule.walkDecls(decl => { expect(decl.prop, `${file}: toolbar dimensions belong in DataTable`).not.toMatch(/^(padding(?:-.+)?|height|min-height)$/) }) }) }) it('keeps conditional shared consumers opt-in instead of enabling other menus', () => { const actionWeights = new Map() cssRoot(read('styles/adminTextActions.css')).walkRules(rule => { rule.walkDecls('font-weight', declaration => { actionWeights.set(rule.selector, declaration.value) }) }) expect(actionWeights.get('.row-actions.admin-text-actions .el-button.el-button')).toBe('400') expect(actionWeights.get('.admin-text-actions.el-popper .el-dropdown-menu__item')).toBe('400') const dispatches = read('modules/overseas-warehouse/pages/DispatchesView.vue') expect(dispatches).toContain('appearance="integrated"') expect(dispatches).toContain('