Files
admin-web/src/styles/adminDensity.test.ts
T
2026-10-20 17:59:13 +08:00

1438 lines
93 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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/SmartLoadingDialog.vue', 'container-loading/pages/GlobalSmartLoadingView.vue',
'loading-lists/pages/PreloadPlanDetailView.vue',
'container-loading/components/OceanBookingLinkDialog.vue', 'container-loading/components/ContainerOverviewPanel.vue',
'port-schedules/components/ContainerPortSchedulePanel.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/LocationsView.vue',
'overseas-warehouse/pages/CargoDiscardsView.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/TruckOrderDetailView.vue',
'overseas-business/pages/TransportOrderFormView.vue', 'overseas-business/components/ProcurementNodeActions.vue', 'overseas-business/components/ProcurementPhysicalFields.vue', 'overseas-business/components/ProcurementTransportFields.vue', 'overseas-business/components/ProcurementFinanceFields.vue', 'overseas-business/components/ProcurementChargeFields.vue', 'overseas-business/components/ProcurementControlFields.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/components/TransportRouteDialog.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/components/TripOperationsCharts.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 selfOperatedPages = ['self-operated/components/AnalyticsView.vue']
const selfOperatedConsumers = [...selfOperatedPages, 'self-operated/components/SkuTrend.vue', 'self-operated/pages/OverviewView.vue']
const consumers = [
...selfOperatedConsumers,
'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([
...selfOperatedPages,
...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<NonNullable<ReturnType<typeof parse>['descriptor']['template']>['ast']>['children'][number]
type ElementNode = Extract<TemplateNode, { type: 1 }>
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<string, boolean>
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('outlines only the three opted-in customer billing editors without overriding native focus or disabled styles', () => {
const rules = new Map<string, Record<string, string>>()
cssRoot(shared).walkRules(rule => {
const values: Record<string, string> = {}
rule.walkDecls(decl => { values[decl.prop] = decl.value })
rules.set(rule.selector, values)
})
expect(rules.get(':is(.admin-density, .admin-density-dialog) .density-table--editable .density-table-editor--outlined .el-input')).toEqual({
'--el-input-border-color': 'var(--el-color-primary-light-5)',
'--el-input-hover-border-color': 'var(--el-color-primary)',
'--el-input-bg-color': 'var(--admin-surface)',
})
const ast = parse(read('modules/unified-orders/pages/UnifiedOrdersView.vue')).descriptor.template!.ast!
const inputIds: string[] = []
const visit = (nodes: typeof ast.children) => { for (const node of nodes) {
if (node.type !== 1) continue
if (classesOf(node).includes('density-table-editor--outlined')) {
const input = node.children.find(child => child.type === 1 && child.tag === 'el-input-number') as ElementNode | undefined
expect(input).toBeDefined()
const id = input!.props.find(prop => prop.type === 6 && prop.name === 'id')
inputIds.push(id?.type === 6 ? id.value?.content || '' : '')
}
visit(node.children)
} }
visit(ast.children)
expect(inputIds).toEqual(['fee-confirm-weight', 'fee-confirm-volume', 'fee-confirm-unit-price'])
})
it('allows container filters and their final action group to wrap by available width', () => {
const source = read('modules/container-loading/pages/ContainersView.vue')
for (const style of parse(source).descriptor.styles) {
cssRoot(style.content).walkRules(rule => {
if (!rule.selector.includes('.query-toolbar__tail')) return
rule.walkDecls(decl => {
if (decl.prop === 'width') expect(decl.value).not.toBe('100%')
if (decl.prop === 'flex') expect(decl.value).not.toBe('0 0 100%')
})
})
}
})
it('keeps declaration order fields on the continuous cell-editor baseline', () => {
const source = read('modules/unified-orders/components/OrderDeclarationEditorDialog.vue')
const orderFields = source.slice(source.indexOf('<el-form label-position="left"'), source.indexOf('</el-form>') + '</el-form>'.length)
expect(orderFields).toContain('class="order-fields density-form"')
expect(orderFields).toContain('density-table density-table--editable')
expect(orderFields).not.toMatch(/<el-(?:row|col)\b/)
expect(orderFields).toContain('<span aria-label="渠道编码">')
expect(orderFields).not.toContain('<el-input :model-value="form.channelCode"')
})
it('shares the measurement palette through explicit dialog sections without changing other dialog bodies', () => {
const tokenValues: Record<string, string> = {}
cssRoot(tokens).walkDecls(decl => { tokenValues[decl.prop] = decl.value })
const rules = new Map<string, Record<string, string>>()
cssRoot(shared).walkRules(rule => {
const values: Record<string, string> = {}
rule.walkDecls(decl => { values[decl.prop] = decl.value })
rules.set(rule.selector, values)
})
for (const [tone, color] of [['summary', '#f4fbf8'], ['source', '#f7f9ff'], ['work', '#fffbf4']]) {
expect(tokenValues[`--admin-dialog-${tone}-bg`]).toBe(color)
expect(rules.get(`:is(.admin-density-dialog, .admin-density-drawer:where(.admin-density-drawer--detail, .admin-density-drawer--editor)) .density-dialog-section[data-tone="${tone}"]`)).toMatchObject({
background: `var(--admin-dialog-${tone}-bg)`,
'border-color': `var(--admin-dialog-${tone}-border)`,
})
}
expect(rules.get(':is(.admin-density-dialog, .admin-density-drawer:where(.admin-density-drawer--detail, .admin-density-drawer--editor)) .density-dialog-section')).not.toHaveProperty('height')
expect(rules.get(':is(.admin-density-dialog, .admin-density-drawer:where(.admin-density-drawer--detail, .admin-density-drawer--editor)) .density-dialog-note')).toMatchObject({ color: 'var(--cc-muted)', 'font-size': 'var(--admin-font-caption)' })
expect(rules.get(':is(.admin-density-dialog, .admin-density-drawer:where(.admin-density-drawer--editor)) .density-actions--footer')).toMatchObject({ 'justify-content': 'flex-end', 'flex-wrap': 'wrap', gap: 'var(--admin-space-3)' })
for (const [selector, declarations] of rules) {
if (selector.includes('density-dialog-section')) expect(Object.keys(declarations).filter(name => name.startsWith('border-left')), selector).toEqual([])
}
const editorRule = rules.get(':is(.admin-density, .admin-density-dialog) .density-table--editable :is(.el-input, .el-textarea)')
expect(editorRule).toMatchObject({ '--el-input-border-color': 'transparent', '--el-input-hover-border-color': 'var(--admin-table-border)' })
expect(editorRule).not.toHaveProperty('--el-input-focus-border-color')
expect(rules.get(':is(.admin-density, .admin-density-dialog) .density-table--editable .el-form-item')).toMatchObject({ 'margin-bottom': '0' })
expect(rules.get(':is(.admin-density, .admin-density-dialog) .density-table--editable .el-form-item.is-error')).toMatchObject({ 'margin-bottom': 'var(--admin-space-5)' })
for (const selector of rules.keys()) if (selector.includes('density-dialog-section')) expect(selector.startsWith(':is(.admin-density-dialog, .admin-density-drawer:where(.admin-density-drawer--detail, .admin-density-drawer--editor)) ')).toBe(true)
const measured = read('modules/inbounds/measurement/measurementDensity.css')
expect(measured).not.toMatch(/border-left(?:-color|-width)?\s*:/)
for (const color of ['#f4fbf8', '#f7f9ff', '#fffbf4']) expect(measured).not.toContain(color)
for (const file of ['unified-orders/pages/UnifiedOrdersView.vue', 'unified-orders/components/OrderDeclarationEditorDialog.vue', 'unified-orders/components/InboundFeeDetailManager.vue', 'inbounds/measurement/InboundMeasurementPanel.vue', 'inbounds/weight/components/WeightOcrPanel.vue', 'domestic-containers/pages/DomesticContainersView.vue', 'sailing-calendar/pages/SailingCalendarView.vue', 'ocean-bookings/components/OceanBookingPrintDialog.vue', 'ocean-bookings/pages/OceanBookingDetailView.vue', 'trucking/components/TruckAttachmentDialog.vue', 'container-loading/pages/ContainersView.vue', 'container-loading/pages/ContainerDetailView.vue', 'container-loading/components/OceanBookingLinkDialog.vue']) {
const source = read('modules/' + file)
expect(parse(source).errors, file).toEqual([])
const ast = parse(source).descriptor.template!.ast!
const visit = (nodes: typeof ast.children, inDialog = false) => { for (const node of nodes) {
if (node.type !== 1) continue
const inside = inDialog || ['AppDialog', 'el-dialog'].includes(node.tag)
if (classesOf(node).some(name => ['density-dialog-section', 'density-table--editable'].includes(name))) expect(inside, file + ': sections and cell editors belong to an explicit dialog').toBe(true)
visit(node.children, inside)
} }
visit(ast.children)
}
})
it('opts the audit detail drawer into the same palette without styling unselected drawers or clipping snapshots', () => {
const rules = new Map<string, Record<string, string>>()
cssRoot(shared).walkRules(rule => {
const declarations: Record<string, string> = {}
rule.walkDecls(decl => { declarations[decl.prop] = decl.value })
rules.set(rule.selector, declarations)
})
const snapshot = rules.get(':is(.admin-density-dialog, .admin-density-drawer:where(.admin-density-drawer--detail, .admin-density-drawer--editor)) .density-code-block')!
expect(snapshot).toMatchObject({ 'white-space': 'pre-wrap', 'overflow-wrap': 'anywhere' })
for (const key of ['height', 'min-height', 'max-height', 'overflow', 'overflow-y']) expect(snapshot).not.toHaveProperty(key)
for (const selector of rules.keys()) {
if (selector.includes('admin-density-drawer') && (selector.includes('density-dialog-section') || selector.includes('density-table'))) expect(selector).toContain('.admin-density-drawer:where(.admin-density-drawer--detail, .admin-density-drawer--editor)')
if (selector.includes('.el-drawer__header') || selector.includes('.el-drawer__body')) expect(selector).not.toContain('admin-density-drawer--detail')
}
const audit = parse(read('modules/audit/components/CustomerAuditLogsPanel.vue')).descriptor
const ast = audit.template!.ast!
const visit = (nodes: typeof ast.children, inside = false) => {
for (const node of nodes) {
if (node.type !== 1) continue
const inDrawer = inside || (node.tag === 'AppDrawer' && classesOf(node).includes('admin-density-drawer--detail') && classesOf(node).includes('admin-density-drawer'))
if (classesOf(node).some(value => ['density-dialog-section', 'density-table', 'density-code-block'].includes(value))) expect(inDrawer).toBe(true)
visit(node.children, inDrawer)
}
}
visit(audit.template!.ast!.children)
for (const style of audit.styles) expect(style.content).not.toMatch(/snapshot[^{}]*\{[^}]*max-height|border-left:/)
})
it('keeps review presentation in the shared dialog and resource editors on the shared form scale', () => {
const review = parse(read('modules/inbounds/review/MeasurementReviewPanel.vue')).descriptor
for (const style of review.styles) {
expect(style.content).not.toContain('linear-gradient')
expect(style.content).not.toMatch(/box-shadow:\s*inset/)
}
const host = parse(read('modules/inbounds/components/InboundTaskList.vue')).descriptor.template!.ast!
let reviewHostFound = false
const visit = (nodes: typeof host.children) => { for (const node of nodes) {
if (node.type !== 1) continue
if (classesOf(node).includes('inbound-task-dialog')) {
expect(node.tag).toBe('AppDialog')
expect(classesOf(node)).toContain('admin-density-dialog')
expect(node.props.some(prop => prop.type === 6 && prop.name === 'fullscreenable')).toBe(true)
expect(node.children.some(child => child.type === 1 && child.props.some(prop => prop.type === 7 && prop.name === 'slot' && prop.arg?.type === 4 && prop.arg.content === 'footer'))).toBe(true)
reviewHostFound = true
}
visit(node.children)
} }
visit(host.children)
expect(reviewHostFound).toBe(true)
for (const file of ['locations/pages/LocationsView.vue', 'pallets/pages/PalletsView.vue']) {
const source = read('modules/warehouse-resources/' + file)
expect(source).toContain('class="editor-form density-form density-form--flush-end"')
for (const style of parse(source).descriptor.styles) {
expect(style.content).not.toContain('.editor-form :deep(.el-form-item__label)')
expect(style.content).not.toContain('.editor-form :deep(.el-form-item)')
}
}
expect(shared).toContain('.admin-density-dialog .density-form--flush-end > .el-form-item:last-child')
expect(shared).not.toContain('.admin-density-dialog .density-form > .el-form-item:last-child')
})
it('keeps batch approval overflow tooltips single-line without forcing its headers or error tags to truncate', () => {
const view = parse(read('modules/unified-orders/pages/UnifiedOrdersView.vue')).descriptor.template!.ast!
const tooltipColumns: string[] = []
const visit = (nodes: typeof view.children, inBatch = false) => { for (const node of nodes) {
if (node.type !== 1) continue
const inside = inBatch || classesOf(node).includes('batch-approval-table')
if (inside && node.tag === 'el-table-column') {
const label = node.props.find(prop => prop.type === 6 && prop.name === 'label')
if (label?.type === 6 && ['客户', '中文品名'].includes(label.value?.content || '')) {
expect(node.props.some(prop => prop.type === 6 && prop.name === 'show-overflow-tooltip')).toBe(true)
tooltipColumns.push(label.value!.content)
}
}
visit(node.children, inside)
} }
visit(view.children)
expect(tooltipColumns).toEqual(['客户', '中文品名'])
const rules = new Map<string, Record<string, string>>()
cssRoot(read('modules/unified-orders/styles/orderDensity.css')).walkRules(rule => {
const declarations: Record<string, string> = {}
rule.walkDecls(decl => { declarations[decl.prop] = decl.value })
rules.set(rule.selector, declarations)
})
expect(rules.has('.admin-density-dialog.el-dialog .batch-approval-table .cell')).toBe(false)
expect(rules.get('.admin-density-dialog.el-dialog .batch-approval-table td .cell.el-tooltip')).toMatchObject({
'white-space': 'nowrap', 'text-overflow': 'ellipsis', overflow: 'hidden', 'overflow-wrap': 'normal',
})
expect(rules.get('.admin-density-dialog.el-dialog .batch-approval-table .el-tag')?.['white-space']).toBe('normal')
})
it('opts transfer approval into the shared centered confirmation appearance', () => {
const dialog = read('modules/overseas-warehouse/components/WarehouseTransferReviewDialog.vue')
const css = read('styles/adminDensity.css')
expect(dialog).toContain('class="admin-density-dialog density-confirm-dialog"')
expect(dialog).toContain('align-center')
expect(css).toContain('.admin-density-dialog.density-confirm-dialog.el-dialog:not(.is-fullscreen) { margin: auto; }')
expect(css).toContain('.admin-density-dialog.density-confirm-dialog.el-dialog .el-dialog__header { border-bottom: 0; }')
expect(css).toContain('.admin-density-dialog.density-confirm-dialog.el-dialog .el-dialog__footer { border-top: 0;')
})
it('keeps the simplified calendar in two columns with bounded previews and scrollable daily details', () => {
const view = parse(read('modules/appointment-calendar/AppointmentCalendarView.vue')).descriptor
const panel = parse(read('modules/appointment-calendar/AppointmentCalendarPanel.vue')).descriptor
expect(view.template!.content).toContain('<AppointmentCalendarPanel')
expect(view.scriptSetup!.content).not.toMatch(/getWorkSummary|getWorkCalendar|getWorkItems/)
const declarations = (selector: string) => {
const values: Record<string, string> = {}
cssRoot(panel.styles.map(style => style.content).join('\n')).walkRules(selector, rule => {
if (rule.parent?.type !== 'root') return
rule.walkDecls(decl => { values[decl.prop] = decl.value })
})
return values
}
expect(declarations('.appointment-calendar')['grid-template-columns']).toBe('minmax(0, 1fr) 360px')
expect(declarations('.calendar-day').height).toBe('146px')
expect(declarations('.week-grid .calendar-day').height).toBe('270px')
expect(declarations('.detail-events')['overflow-y']).toBe('auto')
expect(declarations('.detail-event dd')['overflow-wrap']).toBe('anywhere')
expect(panel.template!.content).toContain('.slice(0, 3)')
expect(panel.template!.content).toContain('v-if="canCreate"')
expect(panel.template!.content).toContain('新增预约')
})
it('keeps calendar creation, its two consumers and arrival editing on shared dialog sections', () => {
const view = parse(read('modules/appointment-calendar/AppointmentCalendarView.vue')).descriptor
const create = parse(read('modules/appointment-calendar/AppointmentCreatePanel.vue')).descriptor
const work = parse(read('modules/appointment-calendar/WorkCreateAppointmentForm.vue')).descriptor
expect(view.scriptSetup!.content).toContain("import '@/styles/adminDensity.css'")
const dialogs = view.template!.content.match(/<AppDialog\b[^>]+>/g)!
expect(dialogs).toHaveLength(3)
for (const dialog of dialogs) { expect(dialog).toContain('admin-density-dialog'); expect(dialog).toContain('fullscreenable') }
expect(view.template!.content).toContain('density-table density-table--editable')
expect(view.template!.content).toContain('label-position="left" label-width="110px"')
expect(view.styles.map(style => style.content).join('\n')).not.toContain('arrival-field')
expect(create.template!.content).toContain('aria-label="预约保存回执"')
expect(create.template!.content).not.toContain('<FbaAppointmentFields')
expect(create.template!.content).toContain('<LineClampText :content="row.customerName"')
expect(create.template!.content).toContain('data-tone="source"')
expect(work.template!.content).toContain('<FbaAppointmentFields continuous')
expect(work.template!.content).toContain('density-actions density-actions--footer')
for (const source of [create.template!.content, read('modules/appointment-calendar/WorkDetailPanel.vue')]) expect(source).toContain('<WorkCreateAppointmentForm')
expect(create.styles).toHaveLength(0); expect(work.styles).toHaveLength(0)
})
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',
'appointment-calendar/AppointmentCargoTabs.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('registers the self-operated dashboard with an explicit density root and chart layout', () => {
const source = read('modules/self-operated/pages/OverviewView.vue')
expect(source).toContain("@/styles/adminDensity.css")
expect(source).toContain('<AdminPage class="admin-density dashboard-page">')
expect(source).toContain('class="dashboard-kpis"')
expect(source).toContain('class="dashboard-main-charts"')
expect(source).not.toContain('density-fields')
expect(getNavigationLeaves(adminNavigation).map(node => node.path)).toContain('/self-operated/overview')
})
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<string>()
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 warehouse editors and import results on shared sections and cell editors', () => {
const source = read('modules/warehouses/pages/WarehousesView.vue')
expect(source).toContain('class="density-form density-dialog-sections"')
expect(source).toContain('density-table density-table--editable warehouse-editor-table')
expect(source).toContain('class="warehouse-import-dialog admin-density-dialog"')
expect(source).toContain('class="density-table warehouse-import-summary"')
expect(source).toContain('class="density-actions density-actions--footer"')
expect(source).not.toContain('<FormSection')
expect(source).not.toContain('<el-dialog')
expect(source).not.toContain(':global(.warehouse-editor-dialog .el-dialog__body)')
})
it('keeps role and permission editors on shared sections with one body scroll and fixed codes', () => {
const role = read('modules/iam/pages/RolesView.vue')
const permission = read('modules/iam/pages/PermissionsView.vue')
for (const source of [role, permission]) {
expect(source).toContain('fullscreenable :confirm-loading="saving"')
expect(source).toContain('density-table--editable')
expect(source).toContain('data-tone="work"')
expect(source).not.toContain('<FormSection')
expect(source).not.toContain('label-position="top"')
const css = parse(source).descriptor.styles.map(style => style.content).join('')
expect(css).not.toContain('el-dialog__body')
expect(css).not.toContain('border-left')
}
expect(role).toContain('data-tone="source"')
expect(role).not.toContain('max-height: 380px')
expect(role).toContain('align-items: start')
expect(permission).not.toContain('class="form-callout"')
})
it('keeps internal user forms on shared cells and native fullscreen with original geometry', () => {
const source = read('modules/iam/pages/InternalUsersView.vue')
expect(source).toContain('class="density-form density-dialog-sections"')
expect(source).toContain('class="density-table density-table--editable user-editor-table"')
expect(source).toContain('data-tone="work"')
expect(source).toContain('fullscreenable destroy-on-close')
expect(source).toContain('width="min(650px, calc(100vw - 28px))"')
expect(source).not.toContain('<FormSection')
expect(source).not.toContain('label-position="top"')
const css = parse(source).descriptor.styles.map(style => style.content).join('')
expect(css).not.toMatch(/font-size|background|border|el-dialog__body|el-dialog__footer/)
expect(css).toContain(':not(.is-fullscreen)')
})
it('opts fee item editing into drawer sections and existing cell editors without changing other drawers', () => {
const source = read('modules/fee-items/pages/FeeItemsView.vue')
const ast = parse(source).descriptor.template!.ast!
let editorFound = false
const visit = (nodes: typeof ast.children, inEditor = false) => { for (const node of nodes) {
if (node.type !== 1) continue
const inside = inEditor || node.tag === 'AppDrawer' && classesOf(node).includes('admin-density-drawer--editor')
if (node.tag === 'AppDrawer') {
editorFound = true
for (const name of ['admin-density', 'admin-density-drawer', 'admin-density-drawer--editor']) expect(classesOf(node)).toContain(name)
}
if (classesOf(node).some(name => ['density-dialog-section', 'density-table--editable'].includes(name))) expect(inside).toBe(true)
visit(node.children, inside)
} }
visit(ast.children)
expect(editorFound).toBe(true)
expect(source).not.toContain('<FormSection')
expect(source).not.toContain('label-position="top"')
expect(source).toContain('class="density-actions density-actions--footer"')
expect(source).toContain('size="min(620px, 94vw)"')
expect(shared).toContain(':where(.admin-density-drawer--detail, .admin-density-drawer--editor)')
const css = parse(source).descriptor.styles.map(style => style.content).join('')
expect(css).not.toContain('direction-grid')
expect(css).not.toContain('delete-message')
})
it('keeps download task detail on shared facts, error and footer primitives', () => {
const source = read('modules/downloads/pages/DownloadCenterView.vue')
const dialog = source.slice(source.indexOf('<AppDialog'), source.indexOf('</AppDialog>'))
expect(dialog).toContain('class="admin-density-dialog"')
expect(dialog).toContain('fullscreenable')
expect(dialog).toContain('data-tone="summary"')
expect(dialog).toContain('class="task-detail-grid density-fields"')
expect(dialog).toContain('<LoadErrorState v-if="detailError" compact')
expect(dialog).toContain('class="density-actions density-actions--footer"')
expect(dialog).not.toContain('requestedUsername || detailTask.requestedBy')
const css = parse(source).descriptor.styles.map(style => style.content).join('')
expect(css).not.toMatch(/\.task-(?:detail-summary|failure)\b|\.detail-load-error\b/)
})
it('keeps ship price book upload compact with shared table and confirmation styles', () => {
const list = read('modules/fba-pricing/carrier-rates/pages/CarrierRatesView.vue')
const detail = read('modules/fba-pricing/carrier-rates/pages/CarrierRateDetailView.vue')
const dialog = list.slice(list.indexOf('<AppDialog'), list.indexOf('</AppDialog>'))
expect(dialog).toContain('class="admin-density-dialog"')
expect(dialog).toContain('width="min(560px, calc(100vw - 28px))"')
expect(dialog).toContain(':confirm-loading="uploading"')
expect(dialog).toContain('class="density-table density-table--editable"')
expect(dialog).toContain('label-position="left"')
expect(dialog).toContain('class="density-dialog-note"')
expect(dialog).not.toContain('upload-scope-field')
expect(list).not.toContain('.upload-file-name strong')
for (const source of [list, detail]) expect(source).toContain("customClass: 'admin-density-confirm'")
})
it('uses shared frames, continuous tables and readonly facts for fleet quote dialogs', () => {
const editor = read('modules/overseas-master-data/components/FleetQuoteEditor.vue')
const importer = read('modules/overseas-master-data/components/FleetQuoteImport.vue')
for (const source of [editor, importer]) {
expect(source).toContain('class="admin-density-dialog" fullscreenable :confirm-loading="saving"')
expect(source).toContain('density-table--editable')
expect(source).toContain('data-tone="work"')
expect(source).toContain('data-tone="source"')
expect(source).toContain('class="density-actions density-actions--footer"')
expect(source).not.toContain('label-position="top"')
const css = parse(source).descriptor.styles.map(style => style.content).join('')
expect(css).not.toMatch(/font-size|background|border|el-dialog__body|max-height/)
}
expect(editor).toContain('v-if="readonly"')
expect(editor).toContain('class="density-fields fleet-quote-facts"')
expect(read('modules/overseas-master-data/pages/FleetQuotesView.vue')).toContain("customClass: 'admin-density-confirm'")
})
it('keeps pricing dialogs on shared tables and sections without local form paint', () => {
const rate = read('modules/overseas-master-data/pages/ExchangeRatesView.vue')
const quote = read('modules/overseas-master-data/pages/OverseasQuotesView.vue')
for (const source of [rate, quote]) {
const dialogs = parse(source).descriptor.template!.content.match(/<AppDialog[\s\S]*?<\/AppDialog>/g)!
expect(dialogs.length).toBeGreaterThan(0)
for (const dialog of dialogs) {
expect(dialog).toContain('class="admin-density-dialog"')
expect(dialog).not.toContain('label-position="top"')
expect(dialog).not.toContain('#header')
}
expect(source).toContain('density-table--editable')
expect(source).not.toContain('line-grid')
expect(source).not.toContain('form-grid')
}
expect(quote.match(/fullscreenable/g)).toHaveLength(2)
expect(quote).toContain('data-tone="summary"')
expect(quote).toContain('data-tone="source"')
expect(quote).toContain('data-tone="work"')
const css = parse(quote).descriptor.styles.map(style => style.content).join('')
expect(css).not.toMatch(/font-size|background|border|el-dialog__body|max-height/)
})
it('keeps the supplier license dialog styled at its own teleported root', () => {
const source = read('modules/suppliers/components/SupplierLicenseDialog.vue')
expect(source).toContain('<AppDialog class="admin-density-dialog"')
expect(source).toContain('width="min(560px, calc(100vw - 24px))"')
expect(source).toContain('aria-label="营业执照信息"')
expect(source).toContain('class="density-dialog-section" data-tone="source"')
expect(source).toContain('class="density-table license-info-table"')
expect(source).not.toContain('license-card')
expect(source).not.toContain('box-shadow')
})
it('uses identical authored tab paint for native domestic and Element overseas tabs', () => {
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' ? 7 : 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('<InboundCodeRelationshipDialog dense')
expect(read('modules/inbounds/pages/InboundOrdersView.vue')).toContain('<InboundCodeRelationshipDialog dense')
for (const file of ['CartonDetailView', 'ProductDetailView']) expect(read('modules/overseas-warehouse/pages/' + file + '.vue')).not.toContain(' dense')
})
it('covers five cargo menus, independent detail routes and both workbench modes', () => {
const navigation = read('core/navigation/adminNavigation.ts')
for (const path of ['/ocean-bookings', '/trucking', '/container-loading/domestic', '/loading-lists', '/loading-plans']) 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(suffix === 'ContainersView' ? `<ContainerWorkbenchView stage="${workbench}"` : `<${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('<ContainerSimulationCreate dense')
for (const file of cargoConsumers) expect(read(`modules/${file}`), `${file}: direct entry`).toContain('@/styles/adminDensity.css')
})
it('enables confirmed loading content and schedule overlays only for opted-in callers', () => {
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, insideEditor = 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))
const editable = insideEditor || classes.includes('density-table--editable')
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 && !editable && 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, editable)
}
}
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 (classes.includes('density-confirm-dialog')) {
expect(classes, `${file}: confirmation density`).toContain('admin-density-dialog')
checked += 1
}
if (node.tag === 'DataTable' || node.tag === 'LoadingListDetailDrawer' || classes.some(name => ['density-action', 'density-actions', 'density-tabs', 'density-fields', 'density-table', '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('keeps customer dialogs on shared sections, editors and preview geometry', () => {
const customers = read('modules/customers/pages/CustomersView.vue')
const detail = read('modules/customers/pages/CustomerDetailView.vue')
expect(customers).toContain('density-table density-table--editable customer-editor-table')
expect(customers).toContain('density-actions density-actions--footer')
expect(customers).not.toContain('<FormSection')
expect(customers).not.toContain('<el-dialog')
for (const source of [customers, detail]) {
expect(source).toContain('customer-qualification-preview')
expect(source).toContain('data-tone="source"')
expect(source).not.toMatch(/\.qualification-preview \{[^}]*overflow:auto/)
}
})
it('shares dense editable sections between both customer fee entries and billing rules', () => {
for (const file of ['customers/CustomerOverseasStorageFeePanel.vue', 'customers/pages/OverseasStorageFeesView.vue', 'customers/components/CustomerBillingRulesPanel.vue']) {
const source = read(`modules/${file}`)
expect(source, file).toContain('density-table density-table--editable')
expect(source, file).toContain('class="density-dialog-section" data-tone="work"')
expect(source, file).toContain('fullscreenable')
expect(source, file).not.toContain('class="dialog-actions"')
}
})
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) && !selfOperatedPages.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 the collection monitor in the Admin theme without legacy list controls', () => {
const page = read('modules/self-operated/pages/CollectionView.vue')
const monitor = read('modules/self-operated/components/CollectionAccountMonitor.vue')
expect(page).toContain('class="admin-density"')
expect(page).toContain('@/styles/adminDensity.css')
expect(page).toContain('<CollectionAccountMonitor')
expect(page).not.toContain('<DataTable')
expect(monitor).toContain('background: var(--el-bg-color)')
expect(monitor).not.toMatch(/--el-[\w-]+\s*:/)
})
it('opts the confirmed self-operated pages into integrated text actions without changing older outlined consumers', () => {
expect(getNavigationLeaves(adminNavigation).map(node => node.path)).toEqual(expect.arrayContaining([
'/self-operated/sales', '/self-operated/inventory', '/self-operated/collection',
]))
for (const file of selfOperatedPages) {
const template = parse(read(`modules/${file}`)).descriptor.template!.ast!
let tables = 0
const visit = (nodes: typeof template.children) => {
for (const node of nodes) {
if (node.type !== 1) continue
if (node.tag === 'DataTable') {
expect(node.props.some(prop => prop.type === 6 && prop.name === 'appearance' && prop.value?.content === 'integrated'), `${file}: integrated text actions`).toBe(true)
tables += 1
}
visit(node.children)
}
}
visit(template.children)
expect(tables).toBeGreaterThan(0)
}
})
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<string, Record<string, string>>()
root.walkRules(rule => {
const declarations: Record<string, string> = {}
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<typeof cssRoot>['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 measurement sections within the clipped panel and gives long references room', () => {
const dialog = parse(read('modules/inbounds/measurement/InboundMeasurementDialog.vue')).descriptor
const panelCss = parse(read('modules/inbounds/measurement/InboundMeasurementPanel.vue')).descriptor.styles.map(style => style.content).join('\n')
const differenceStyles = new Map<string, Record<string, string>>()
cssRoot(panelCss).walkRules(rule => {
const declarations: Record<string, string> = {}
rule.walkDecls(decl => { declarations[decl.prop] = decl.value })
differenceStyles.set(rule.selector, declarations)
})
expect(differenceStyles.get('.measurement-comparison__difference')?.color).toBe('var(--el-color-danger-dark-2)')
expect(differenceStyles.get('.measurement-deviation-alert')).toMatchObject({
'--el-color-warning': 'var(--el-color-danger-dark-2)',
'--el-color-warning-dark-2': 'var(--el-color-danger-dark-2)',
})
const layout = read('modules/inbounds/measurement/measurementDensity.css')
const summaryMargins: string[] = []
for (const css of [layout, ...dialog.styles.map(style => style.content)]) {
cssRoot(css).walkRules(rule => {
if (!/\.measurement-(summary-section|declaration|items-section)(?![\w-])/.test(rule.selector)) return
rule.walkDecls(decl => {
if (decl.prop.startsWith('margin')) {
expect(decl.value, `section must not escape the clipped panel: ${rule.selector}`).not.toMatch(/(^|\s)-\d/)
if (rule.selector.endsWith('.measurement-summary-section')) summaryMargins.push(decl.value)
}
})
})
}
expect(summaryMargins).toEqual(['0'])
const sectionStyles = new Map<string, Record<string, string>>()
cssRoot(layout).walkRules(rule => {
const declarations: Record<string, string> = {}
rule.walkDecls(decl => { declarations[decl.prop] = decl.value })
sectionStyles.set(rule.selector, declarations)
})
for (const [section, background] of [
['summary-section', 'var(--admin-dialog-summary-bg)'], ['declaration', 'var(--admin-dialog-source-bg)'], ['items-section', 'var(--admin-dialog-work-bg)'],
]) {
expect(sectionStyles.get(`.inbound-measurement-workspace-dialog.inbound-density .measurement-${section}`)).toMatchObject({
padding: 'var(--admin-space-4, 10px) var(--admin-space-5, 12px)',
background,
})
}
// Shared density must not reset the coloured sections' horizontal gutters.
expect(sectionStyles.get('.inbound-density.admin-density-dialog .measurement-section')).toEqual({
'padding-block': 'var(--admin-space-4, 10px)',
})
expect(layout).toContain('.measurement-document-number { display: flex; flex: 1;')
expect(layout).toContain('.measurement-platform-fields { display: flex; flex: 1 1 520px;')
expect(layout).toContain('.measurement-platform-fields > .measurement-expected-pallets { flex: 0 0 180px; }')
for (const style of dialog.styles) {
expect(style.content).not.toMatch(/\.measurement-(summary-section|metrics|declaration|items-section)\b/)
}
})
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<string, string[]> = { 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('gives simulation creation an opt-in comfortable editor frame', () => {
const source = read('modules/container-loading/components/ContainerSimulationCreate.vue')
expect(source).toContain("'admin-density-dialog--comfortable': dense")
expect(source).toContain('width="min(800px, calc(100vw - 32px))"')
expect(shared).toContain('.admin-density-dialog.admin-density-dialog--comfortable.el-dialog { --el-component-size: 40px; border-radius: 12px; }')
expect(shared).toContain('.admin-density-dialog.admin-density-dialog--comfortable.el-dialog .el-select__wrapper { min-height: 40px; }')
})
it('keeps conditional shared consumers opt-in instead of enabling other menus', () => {
const actionWeights = new Map<string, string>()
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('<template #filters><div class="integrated-query-group">')
expect(dispatches).toContain('class="density-status-tabs" role="tablist"')
expect(dispatches).not.toContain('class="surface-panel table-panel"')
const calendar = read('modules/sailing-calendar/pages/SailingCalendarView.vue')
expect(calendar).toContain('appearance="integrated" :data="[]" :columns="[]" column-settings-mode="hide"')
expect(calendar).toContain('<template #results>')
expect(calendar).not.toContain('class="calendar-toolbar surface-panel"')
expect(read('shared/components/DataTable.vue')).toContain('margin-left: var(--cc-list-action-sibling-margin, 8px)')
expect(shared).toContain('.density-actions { --cc-list-action-sibling-margin: 0px;')
expect(read('modules/loading-lists/components/LoadingListsView.vue')).toContain('class="business-page loading-lists-page admin-density"')
expect(read('modules/loading-lists/components/PalletLabelPrintDialog.vue')).toContain("'admin-density-dialog': dense")
expect(read('modules/container-loading/components/ContainerSimulationCreate.vue')).toContain("'admin-density-dialog': dense")
for (const file of ['loading-lists/components/LoadingListDetailDrawer.vue', 'loading-lists/components/PalletLabelPrintDialog.vue', 'container-loading/components/ContainerSimulationCreate.vue']) {
const template = parse(read(`modules/${file}`)).descriptor.template!.ast!
const visit = (nodes: typeof template.children) => {
for (const node of nodes) {
if (node.type !== 1) continue
expect(classesOf(node, false).filter(name => /^(admin-density|density-)/.test(name)), `${file}: unopted consumer`).toEqual([])
visit(node.children)
}
}
visit(template.children)
}
for (const file of ['customers/pages/CustomersView.vue', 'customers/pages/OverseasStorageFeesView.vue', 'unified-orders/pages/UnifiedOrdersView.vue']) {
expect(read(`modules/${file}`), file).toContain('density-action')
}
})
it('halves text action spacing through shared rules while retaining outlined action spacing', () => {
const declarations = new Map<string, Record<string, string>>()
cssRoot(read('styles/adminTextActions.css')).walkRules(rule => {
const values: Record<string, string> = {}
rule.walkDecls(decl => { values[decl.prop] = decl.value })
declarations.set(rule.selector, values)
})
expect(declarations.get('.row-actions.admin-text-actions')).toMatchObject({
'--cc-row-action-gap': 'calc(var(--admin-space-2, 6px) / 2)',
'--cc-list-action-sibling-margin': '0px',
gap: 'var(--cc-row-action-gap)',
})
const button = declarations.get('.row-actions.admin-text-actions .el-button.el-button')!
expect(button['padding-inline']).toBe('calc(var(--admin-space-4, 10px) / 2)')
expect(button).not.toHaveProperty('height')
expect(button).not.toHaveProperty('font-size')
const groups = new Map<string, string>()
cssRoot(shared).walkRules(rule => {
if (rule.selector.endsWith(' .density-actions')) {
rule.walkDecls('gap', decl => { groups.set(rule.selector, decl.value) })
}
})
expect([...groups.values()]).toEqual([
'var(--cc-row-action-gap, var(--admin-space-2))',
'var(--cc-row-action-gap, var(--admin-space-2))',
])
})
it('keeps active last-mile order dialogs on shared sections without internal vertical caps', () => {
const source = read('modules/overseas-warehouse/pages/LastMileOrdersView.vue')
for (const model of ['transferOpen', 'containerConvertOpen', 'inboundPlanOpen', 'holdOpen', 'manualNodesOpen', 'reworkOpen']) {
const frame = source.match(new RegExp('<AppDialog[^>]*v-model="' + model + '"[\\s\\S]*?(?=\\n <|\\n <|$)'))?.[0] || ''
expect(frame, model).toContain('fullscreenable')
}
expect(source).toContain('data-tone="summary"')
expect(source).toContain('data-tone="source"')
expect(source).toContain('data-tone="work"')
expect(source).toContain('class="frozen-pallet-count"')
expect(source).not.toContain('max-height: 48vh')
expect(source).not.toContain('.manual-node-table { max-height: 420px')
expect(source).not.toContain('class="container-preview__metric')
const detail = read('modules/overseas-warehouse/pages/LastMileOrderDetailView.vue')
expect(detail).toContain('density-table density-table--editable fee-editor-table')
expect(detail).toContain('label-position="left" label-width="110px" :disabled="saving"')
expect(detail).not.toContain('.fee-form-grid')
expect(read('modules/overseas-warehouse/components/WarehouseTransferReviewDialog.vue')).toContain('data-tone="source"')
})
it('keeps new dispatch settings and resources on shared continuous sections with body scrolling', () => {
const source = read('modules/overseas-warehouse/components/SmartDispatchDialog.vue')
const fields = read('modules/overseas-warehouse/components/DispatchResourceFields.vue')
expect(source).toContain('fullscreenable')
expect(source).toContain('density-table density-table--editable dispatch-create-table')
expect(source).toContain('<DispatchResourceFields continuous')
expect(source).toContain('label-position="left" label-width="130px"')
for (const tone of ['summary', 'source', 'work']) expect(source).toContain(`data-tone="${tone}"`)
expect(source).toContain('class="cargo-workspace"')
expect(source).toContain('<LineClampText')
expect(source).toContain('class="dispatch-source-group"')
expect(source).not.toContain('height="420"')
expect(source).not.toContain('class="create-grid"')
expect(fields).toContain('continuous?: boolean')
expect(fields).toContain('v-if="continuous"')
expect(fields).toContain('density-table density-table--editable dispatch-resource-table')
expect(fields).toContain('class="resource-grid"')
})
it('keeps dispatch workflow, delivery arrangements and owned evidence on shared compact sections', () => {
const workflow = read('modules/overseas-warehouse/components/DispatchWorkflowDialog.vue')
const arrangement = read('modules/overseas-warehouse/components/DispatchArrangeFields.vue')
const evidence = read('modules/overseas-warehouse/components/DispatchEvidenceFiles.vue')
expect(workflow).toContain(':fullscreenable="width !== \'560px\'"')
expect(workflow).toContain('<DispatchResourceFields continuous')
expect(workflow).toContain('density-table density-table--editable workflow-editor-table')
expect(workflow).toContain('label-position="left" label-width="130px"')
expect(workflow).toContain(':confirm-loading="busy || uploadingEvidence"')
expect(workflow).toContain('<LineClampText')
for (const tone of ['source', 'work']) {
expect(workflow).toContain(`data-tone="${tone}"`)
expect(arrangement).toContain(`data-tone="${tone}"`)
}
expect(workflow).not.toMatch(/max-height="(?:380|420)"/)
expect(arrangement).not.toContain('max-height="240"')
expect(arrangement).toContain('density-table density-table--editable arrange-editor-table')
expect(arrangement).toContain('本组新增费用')
expect(evidence).toContain('class="density-table dispatch-evidence-table"')
expect(evidence).toContain('fullscreenable :loading="previewLoading"')
expect(evidence).toContain('<LoadErrorState')
expect(evidence).toContain('<LineClampText')
for (const source of [workflow, arrangement, evidence]) {
const styles = parse(source).descriptor.styles.map(style => style.content).join('\n')
expect(styles).not.toMatch(/background:|border-left:|font-size:/)
}
})
it('keeps outbound creation modes compact with one body scroll and complete candidate controls', () => {
const source = read('modules/overseas-warehouse/pages/OutboundsView.vue')
expect(source).toContain('width="min(1760px, calc(100vw - 32px))" fullscreenable')
expect(source).toContain('label-position="left" label-width="120px" :disabled="createLocked"')
expect(source).toContain('density-table density-table--editable outbound-editor-table')
expect(source).toContain('data-tone="source"')
expect(source).toContain('data-tone="work"')
expect(source).toContain(':confirm-loading="saving || inventoryLoading"')
expect(source).toContain('aria-label="全选本页订单"')
expect(source).toContain('class="return-list-card density-dialog-section"')
expect(source).toContain(':page-sizes="[5, 10, 20, 50]"')
expect(source).toContain('<ApiErrorMeta :details="candidateError"')
expect(source).not.toMatch(/max-height="(?:360|380|400)"/)
const styles = parse(source).descriptor.styles.map(style => style.content).join('\n')
expect(styles).not.toMatch(/background:|border-left:|font-size:|max-height:|overflow: auto/)
})
it('keeps delivery operations and documents on compact sections without read-only inputs', () => {
const source = read('modules/overseas-warehouse/pages/DeliveryOrdersView.vue')
const documents = read('modules/overseas-warehouse/components/DeliveryDocumentsDialog.vue')
expect(source).toContain('density-table density-table--editable delivery-return-sources')
expect(source).toContain('label-position="left" label-width="100px"')
expect(source).toContain('label-position="left" label-width="110px"')
expect(source).toContain(':confirm-loading="returnSaving || returnLoading"')
expect(source).toContain('width="min(720px, calc(100vw - 28px))" fullscreenable')
expect(documents).toContain('width="min(1040px, calc(100vw - 40px))" fullscreenable')
expect(documents).toContain('data-tone="summary"')
expect(documents).toContain('data-tone="source"')
expect(documents).toContain('data-tone="work"')
expect(documents).toContain('<LineClampText :content="file.fileName"')
expect(documents).not.toContain(':model-value="deliveryNo" readonly')
expect(documents).not.toContain('max-height=')
const styles = parse(documents).descriptor.styles.map(style => style.content).join('\n')
expect(styles).not.toMatch(/background:|border-left:|font-size:|overflow: auto/)
})
it('shares relationship sections and fullscreen only through the explicit dense profile', () => {
const content = read('shared/components/InboundCodeRelationshipDialog.vue')
expect(content).toContain(':fullscreenable="dense"')
expect(content).toContain("{ 'density-dialog-sections': dense }")
expect(content.match(/'density-dialog-section': dense/g)).toHaveLength(3)
expect(content.match(/'density-dialog-section__title': dense/g)).toHaveLength(3)
expect(content).toContain(':data-tone="dense ? \'source\' : undefined"')
expect(content).toContain(':data-tone="dense ? \'summary\' : undefined"')
expect(content).not.toContain('.inbound-relationship-dialog.admin-density-dialog .relationship-section')
const styles = parse(content).descriptor.styles.map(style => style.content).join('\n')
cssRoot(styles).walkRules(rule => {
rule.walkDecls(decl => {
if (!['padding', 'background', 'border-radius', 'font-size', 'max-height'].includes(decl.prop)) return
expect(rule.selector, 'legacy visual declarations must stay outside dense').toMatch(/:not\(\.admin-density-dialog\)|:not\(\.density-dialog-section\)|:not\(\.density-fields\)/)
})
})
})
it('keeps return packing and carton dialogs on shared sections and one body scroll', () => {
const content = read('modules/overseas-warehouse/pages/ReturnInventoryManagementView.vue')
const dialogs = content.slice(content.indexOf('<AppDialog'))
expect(dialogs.match(/fullscreenable/g)).toHaveLength(3)
expect(dialogs).toContain('1380px')
expect(dialogs).toContain('1400px')
expect(dialogs).toContain('1080px')
expect(dialogs).toContain('label-position="left" label-width="110px"')
expect(dialogs).toContain('density-table density-table--editable')
expect(dialogs).toContain('data-tone="source"')
expect(dialogs).toContain('data-tone="summary"')
expect(dialogs).toContain('data-tone="work"')
expect(dialogs).not.toContain('<el-descriptions')
expect(dialogs).not.toContain('label-position="top"')
const styles = parse(content).descriptor.styles.map(style => style.content).join('\n')
expect(styles).not.toMatch(/max-height:|resize:|overflow: auto|border-radius:|font-size:|background:/)
expect(content).not.toContain('.packing-form :deep(.el-input__inner)')
})
it('keeps discard readonly sections and confirmation compact while retaining photo geometry', () => {
const content = read('modules/overseas-warehouse/pages/CargoDiscardsView.vue')
const dialog = content.slice(content.indexOf('<AppDialog'))
expect(dialog).toContain('class="admin-density-dialog"')
expect(dialog).toContain('fullscreenable')
expect(dialog).toContain('920px')
expect(dialog).toContain('data-tone="summary"')
expect(dialog).toContain('data-tone="source"')
expect(dialog).toContain('data-tone="work"')
expect(dialog).toContain('preview-teleported')
expect(dialog).toContain('<LineClampText')
expect(dialog).not.toContain('<el-timeline')
expect(content).toContain("customClass: 'admin-density-confirm'")
expect(content).toContain('height: 150px')
const styles = parse(content).descriptor.styles.map(style => style.content).join('\n')
expect(styles).not.toMatch(/background:|border-left:|font-size:|border-radius:/)
})
it('keeps overseas location editing and native confirmations on the shared dialog contract', () => {
const content = read('modules/overseas-warehouse/pages/LocationsView.vue')
const dialog = content.slice(content.indexOf('<AppDialog'))
expect(dialog).toContain('class="admin-density-dialog"')
expect(dialog).toContain('fullscreenable')
expect(dialog).toContain('density-table density-table--editable')
expect(dialog).toContain('label-position="left" label-width="110px" :disabled="formLocked"')
expect(dialog).toContain('data-tone="work"')
expect(dialog).not.toContain('label-position="top"')
expect(content).not.toContain('editor-grid')
expect(content.match(/customClass: 'admin-density-confirm'/g)).toHaveLength(2)
expect(parse(content).descriptor.styles).toHaveLength(0)
})
it('keeps overseas pallet editing on one shared continuous table with native fullscreen', () => {
const content = read('modules/overseas-warehouse/pages/PalletsView.vue')
const dialog = content.slice(content.indexOf('<AppDialog'))
expect(dialog).toContain('fullscreenable')
expect(dialog).toContain('label-position="left" label-width="120px" :disabled="formLocked"')
expect(dialog).toContain('density-table density-table--editable pallet-editor-table')
expect(dialog).toContain('data-tone="work"')
expect(dialog).not.toContain('label-position="top"')
expect(dialog).not.toContain('<el-row')
expect(content).not.toContain('.el-form :deep(')
const styles = parse(content).descriptor.styles.map(style => style.content).join('\n')
const editorRules = new Map<string, Record<string, string>>()
cssRoot(styles).walkRules(rule => {
if (!rule.selector.includes('pallet-editor-table')) return
const values: Record<string, string> = {}
rule.walkDecls(decl => { values[decl.prop] = decl.value })
editorRules.set(rule.selector, values)
})
expect([...editorRules]).toEqual([
['.pallet-editor-table', { 'table-layout': 'fixed' }],
['.pallet-editor-table td:not([colspan])', { width: '50%' }],
])
})
it('keeps relabel creation, review and PDF previews compact without replacing document sizing', () => {
const list = read('modules/overseas-warehouse/pages/RelabelOrdersView.vue')
const detail = read('modules/overseas-warehouse/pages/RelabelOrderDetailView.vue')
expect(list).toContain('width="min(760px, calc(100vw - 28px))" fullscreenable')
expect(list).toContain('density-table density-table--editable relabel-editor-table')
expect(list).toContain('aria-label="换标审核原始资料"')
for (const tone of ['summary', 'source', 'work']) expect(list).toContain(`data-tone="${tone}"`)
expect(list).not.toContain('label-position="top"')
expect(list).not.toContain('packing-plan-dialog')
expect(detail).toContain('width="min(900px, calc(100vw - 28px))" fullscreenable')
expect(detail).toContain(':loading="previewLoading"')
expect(detail).toContain('height:min(72vh,760px)')
expect(detail).toContain('<th class="relabel-pdf-actions">操作(明细PDF)</th>')
expect(detail).not.toContain('item.destinationFbaWarehouseCode || item.destinationWarehouseId')
expect(parse(list).descriptor.styles.map(style => style.content).join('\n')).not.toMatch(/background:|border-left:|font-size:|max-height:/)
})
it('keeps inbound appointment, plan and move editors on opt-in continuous forms', () => {
const list = read('modules/overseas-warehouse/pages/InboundOrdersView.vue')
const detail = read('modules/overseas-warehouse/pages/InboundOrderDetailView.vue')
const fields = read('modules/overseas-warehouse/components/FbaAppointmentFields.vue')
expect(list).toContain('<FbaAppointmentFields continuous')
expect(list.match(/fullscreenable/g)).toHaveLength(2)
expect(list).toContain('data-tone="summary"')
expect(list).toContain('data-tone="source"')
expect(list).toContain('data-tone="work"')
expect(list).not.toContain('v-model="planEditForm.version"')
expect(fields).toContain('continuous?: boolean')
expect(fields).toContain('v-if="continuous"')
expect(fields).toContain('density-table density-table--editable appointment-editor-table')
expect(fields).toContain('label-position="left" label-width="140px"')
expect(fields).toContain('label-position="top" class="appointment-fields density-form"')
const move = detail.slice(detail.indexOf('<AppDialog'))
expect(move).toContain('fullscreenable')
expect(move).toContain('aria-label="移库来源"')
expect(move).toContain('label-position="left" label-width="100px" :disabled="moveSaving || moveLocationsLoading"')
expect(move).not.toContain(':model-value="moveRow?.palletNo"')
})
it('keeps transfer allocation and receipt popups on shared sections and the native submit footer', () => {
const allocation = read('modules/overseas-warehouse/components/WarehouseTransferFulfillmentPanel.vue')
const receipt = read('modules/overseas-warehouse/components/WarehouseTransferReceiptPanel.vue')
for (const source of [allocation, receipt]) {
expect(source).toContain("import '@/styles/adminDensity.css'")
expect(source).toContain('class="density-dialog-sections"')
expect(source).toContain('data-tone="summary"')
expect(source).toContain('data-tone="source"')
expect(source).not.toMatch(/max-height:|overflow-y:/)
}
expect(allocation.match(/fullscreenable/g)).toHaveLength(1)
expect(receipt.match(/fullscreenable/g)).toHaveLength(3)
expect(allocation).toContain('row-key="inventoryPositionId" max-height=""')
expect(receipt).toContain('row-key="cartonMark" max-height=""')
expect(receipt).toContain('row-key="sku" max-height=""')
const forecast = receipt.split('v-model="increaseOpen"')[1]!.split('</AppDialog>')[0]!
expect(forecast).toContain(':confirm-loading="increasing"')
expect(forecast).toContain('@confirm="saveIncreaseForecast"')
expect(forecast).toContain('label-position="left" label-width="100px" :disabled="increasing"')
expect(forecast).toContain('density-table density-table--editable')
expect(forecast).not.toContain(':show-footer="false"')
expect(forecast).not.toContain('<el-button')
})
it('keeps settlement generation and cashiers on continuous forms with native fullscreen', () => {
const source = read('modules/finance/pages/FinanceFormalSettlementPage.vue')
const popups = source.slice(source.indexOf('<AppDialog'))
expect(popups.match(/fullscreenable/g)).toHaveLength(3)
expect(popups.match(/density-table density-table--editable/g)).toHaveLength(3)
expect(popups).toContain('label-position="left" label-width="120px" :disabled="createBusy"')
for (const flag of ['paymentBusy', 'receiptBusy']) expect(popups).toContain(`label-position="left" label-width="140px" :disabled="${flag}"`)
expect(popups).toContain('data-tone="summary"')
expect(popups).toContain('data-tone="work"')
expect(popups).toContain(':key="paymentUploadKey"')
expect(popups).toContain(':key="receiptUploadKey"')
expect(popups).not.toContain('label-position="top"')
expect(source).not.toContain('.remaining-preview')
expect(source).not.toContain('.settlement-operation-form { display: grid')
})
it('keeps fee and statement popup editors on the shared density and main body scroll', () => {
const arap = read('modules/finance/pages/FinanceCustomerArapView.vue').split('<AppDialog')[1]!
expect(arap).toContain('fullscreenable')
expect(arap).toContain('data-tone="source"')
expect(arap).toContain('data-tone="work"')
expect(arap).toContain('density-table density-table--editable')
expect(arap).toContain('label-position="left" label-width="110px" :disabled="reviewSaving"')
const statements = read('modules/finance/pages/FinanceCustomerStatementsView.vue')
const popups = statements.slice(statements.indexOf('<AppDialog'))
expect(popups.match(/fullscreenable/g)).toHaveLength(2)
expect(popups).toContain('label-position="left" label-width="90px" :disabled="createBusy"')
expect(popups).toContain('label-width="110px" :disabled="adjustBusy"')
expect(popups).toContain('<OperationErrorToast :details="adjustError"')
expect(popups).toContain('data-tone="summary"')
expect(popups).not.toContain('max-height="420"')
expect(popups).not.toContain('label-position="top"')
expect(statements).not.toContain('.create-period-row')
})
it('keeps wallet dialogs compact while preserving financial warnings and preview space', () => {
const content = read('modules/finance/pages/FinanceCustomerAccountsView.vue')
const popups = content.slice(content.indexOf('<AppDialog'))
expect(popups.match(/fullscreenable/g)).toHaveLength(4)
expect(popups.match(/class="density-table density-table--editable/g)).toHaveLength(3)
expect(popups.match(/label-position="left" label-width="100px"/g)).toHaveLength(3)
for (const flag of ['adjustmentSaving', 'creditLimitSaving', 'reviewSaving']) expect(popups).toContain(`:disabled="${flag}"`)
expect(popups).toContain('data-tone="summary"')
expect(popups).toContain('data-tone="source"')
expect(popups).toContain('data-tone="work"')
expect(popups).toContain('不足部分再使用可用授信')
expect(popups).toContain('次日授信失效')
expect(popups).not.toContain(' readonly')
expect(content).not.toContain('.wallet-form__grid')
expect(content).toContain('max-height: 70vh')
expect(content).toContain('onBeforeUnmount(clearVoucherPreview)')
})
it('keeps notification editors and detail drawers on the shared popup palette', () => {
for (const file of ['MessageComposerDialog', 'TemplateEditorDialog']) {
const content = read(`modules/notifications/components/${file}.vue`)
expect(content).toContain('fullscreenable :confirm-loading="submitting"')
expect(content).toContain('class="density-form density-dialog-sections" label-position="left"')
expect(content).toContain('density-table density-table--editable')
expect(content).toContain('density-actions density-actions--footer')
expect(content).toContain('data-tone="work"')
expect(content).toContain('<InlineHelp')
expect(content).not.toContain('class="dialog-actions"')
expect(content).not.toContain('model-value="站内信" disabled')
}
for (const file of ['PlatformNotificationsView', 'MessageManagementView']) {
const content = read(`modules/notifications/pages/${file}.vue`)
expect(content).toContain('admin-density-drawer--detail')
expect(content).toContain('data-tone="summary"')
expect(content).toContain('data-tone="source"')
expect(content).toContain('@retry="detailRow && openDetail(detailRow)"')
const styles = parse(content).descriptor.styles.map(style => style.content).join('\n')
expect(styles).not.toMatch(/background:|border-left:|font-size:|max-height:/)
}
})
it('keeps container evidence and customs summaries compact without duplicating field styles', () => {
const overview = read('modules/container-loading/components/ContainerOverviewPanel.vue')
const customs = read('modules/container-loading/components/ContainerCustomsPanel.vue')
expect(overview).toContain('class="density-fields pda-summary"')
expect(overview).toContain('height:112px;object-fit:contain')
expect(overview).not.toContain('aspect-ratio:4/3')
expect(customs).toContain('class="density-fields customs-summary"')
expect(customs).not.toContain('class="broker-submission-fact"')
expect(customs).toContain('class="confirmation-history-note"')
})
})