2026-09-14 17:54:21 +08:00
import { readFileSync } from 'node:fs'
2026-09-15 09:58:15 +08:00
import { dirname , resolve } from 'node:path'
import ts from 'typescript'
2026-09-14 17:54:21 +08:00
import { describe , expect , it } from 'vitest'
import { compileStyle , parse } from 'vue/compiler-sfc'
2026-10-03 04:35:55 +08:00
import { adminNavigation , getNavigationLeaves } from '@/core/navigation/adminNavigation'
2026-09-14 17:54:21 +08:00
// Approved entry/consumer inventory. Do not discover unrelated menus recursively.
2026-09-14 18:34:34 +08:00
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' ,
]
2026-09-14 20:03:27 +08:00
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' ,
]
2026-09-15 09:58:15 +08:00
const overseasBusinessConsumers = [
'overseas-business/pages/PurchaseOrdersView.vue' , 'overseas-business/pages/PurchaseOrderDetailView.vue' ,
'overseas-business/pages/PurchaseOrderEditView.vue' , 'overseas-business/pages/TransportOrdersView.vue' ,
2026-10-10 02:19:39 +08:00
'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' ,
2026-09-15 09:58:15 +08:00
]
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' ,
2026-10-09 17:36:32 +08:00
'overseas-master-data/components/TransportRouteDialog.vue' ,
2026-10-03 04:35:55 +08:00
'overseas-master-data/pages/DriversView.vue' ,
'overseas-master-data/pages/TripsView.vue' ,
2026-10-07 16:46:57 +08:00
'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' ,
2026-10-07 12:24:21 +08:00
'overseas-master-data/pages/TripOperationsView.vue' ,
2026-10-03 04:35:55 +08:00
'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' ,
2026-09-15 09:58:15 +08:00
'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' ,
]
2026-09-15 10:38:03 +08:00
const systemConsumers = [ 'iam/pages/InternalUsersView.vue' , 'iam/pages/RolesView.vue' , 'iam/pages/PermissionsView.vue' ]
2026-10-13 17:29:45 +08:00
const selfOperatedPages = [ 'self-operated/components/AnalyticsView.vue' ]
2026-10-13 00:45:41 +08:00
const selfOperatedConsumers = [... selfOperatedPages , 'self-operated/components/SkuTrend.vue' , 'self-operated/pages/OverviewView.vue' ]
2026-09-14 17:54:21 +08:00
const consumers = [
2026-10-12 20:33:39 +08:00
... selfOperatedConsumers ,
2026-10-07 16:46:57 +08:00
'overseas-business/components/FleetArrangementSummary.vue' ,
2026-09-15 09:58:15 +08:00
... notificationConsumers ,
... systemConsumers ,
... generalCatalogConsumers ,
... financeConsumers ,
... customerServiceConsumers ,
... overseasBusinessConsumers ,
2026-09-14 20:03:27 +08:00
... overseasConsumers ,
2026-09-14 18:34:34 +08:00
... cargoConsumers ,
2026-09-14 17:54:21 +08:00
'customers/pages/CustomersView.vue' , 'customers/pages/CustomerDetailView.vue' ,
'customers/pages/OverseasStorageFeesView.vue' , 'customers/components/CustomerBillingRulesPanel.vue' ,
2026-09-15 09:58:15 +08:00
'unified-orders/components/InboundFeeDetailManager.vue' , 'unified-orders/components/OrderDeclarationEditorDialog.vue' ,
2026-09-14 17:54:21 +08:00
'customers/CustomerOverseasStorageFeePanel.vue' , 'unified-orders/pages/UnifiedOrdersView.vue' ,
2026-09-15 10:19:05 +08:00
'inbounds/core/DropshipPalletPrintPanel.vue' ,
2026-09-14 17:54:21 +08:00
'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' ,
]
2026-09-15 09:58:15 +08:00
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' ]
2026-09-14 17:54:21 +08:00
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' },
2026-09-28 15:24:33 +08:00
{ title : '船期日历' , path : '/sailing-calendar' , file : 'sailing-calendar/pages/SailingCalendarView.vue' },
2026-09-14 17:54:21 +08:00
]
2026-09-14 18:34:34 +08:00
const customerPages = [
{ title : '客户列表' , path : '/customers' , file : 'customers/pages/CustomersView.vue' },
{ title : '海外仓阶梯费用' , path : '/customers/overseas-storage-fees' , file : 'customers/pages/OverseasStorageFeesView.vue' },
]
2026-09-14 17:54:21 +08:00
const densityPages = [... new Set ([
2026-10-12 20:33:39 +08:00
... selfOperatedPages ,
2026-09-15 09:58:15 +08:00
... notificationConsumers . filter ( file => file . includes ( '/pages/' )),
... systemConsumers ,
... generalCatalogConsumers . filter ( file => file . includes ( '/pages/' )),
... financeConsumers ,
... customerServiceConsumers ,
... overseasBusinessConsumers ,
2026-09-14 20:03:27 +08:00
... overseasConsumers . filter ( file => ! file . endsWith ( 'PalletDetailView.vue' )),
2026-09-14 18:34:34 +08:00
... cargoConsumers . filter ( file => file . includes ( '/pages/' ) || [ 'ContainerOverviewPanel.vue' , 'ContainerCustomsPanel.vue' , 'ContainerLastLegFeesPanel.vue' , 'ContainerFeesPanel.vue' , 'OceanBookingDocumentsPanel.vue' ]. some ( name => file . endsWith ( name ))),
2026-09-14 17:54:21 +08:00
... domesticPages . map ( page => page . file ), 'loading-lists/pages/PreloadPlanDetailView.vue' ,
'loading-lists/components/LoadingListDetailDrawer.vue' , 'domestic-containers/pages/DomesticContainerInventoryDetailView.vue' ,
2026-09-14 18:34:34 +08:00
... customerPages . map ( page => page . file ), 'customers/pages/CustomerDetailView.vue' , 'audit/components/CustomerAuditLogsPanel.vue' ,
2026-09-14 17:54:21 +08:00
])]
type TemplateNode = NonNullable < NonNullable < ReturnType < typeof parse > [ 'descriptor' ][ 'template' ] > [ 'ast' ] >[ 'children' ][ number ]
type ElementNode = Extract < TemplateNode , { type : 1 }>
2026-09-14 18:34:34 +08:00
const classesOf = ( node : ElementNode , preload = true , dense = preload , workbench = 'DOMESTIC' ) : string [] => node . props . flatMap ( prop => {
2026-09-14 17:54:21 +08:00
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.
2026-09-14 18:34:34 +08:00
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 )
2026-09-14 17:54:21 +08:00
})
const styles = [
2026-09-15 09:58:15 +08:00
... parse ( read ( 'shared/components/BatchImportDialog.vue' )). descriptor . styles . map ( style => ({ file : 'BatchImportDialog.vue' , css : style.content })),
2026-09-14 17:54:21 +08:00
... 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' , () => {
2026-10-03 04:35:55 +08:00
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' ,
2026-10-12 11:45:00 +08:00
'appointment-calendar/AppointmentCargoTabs.vue' ,
2026-10-03 04:35:55 +08:00
]) {
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 )
}
})
2026-09-15 10:38:03 +08:00
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' )
})
2026-10-13 00:45:41 +08:00
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' )
})
2026-09-15 09:58:15 +08:00
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 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="营业执照信息"' )
})
2026-09-14 20:03:27 +08:00
2026-09-14 20:21:02 +08:00
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' )
})
2026-10-03 04:35:55 +08:00
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 )))
2026-09-14 20:03:27 +08:00
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 )
2026-10-09 15:25:19 +08:00
expect ( panes ). toBe ( file === 'LastMileOrderDetailView' ? 6 : 4 )
2026-09-14 20:03:27 +08:00
}
})
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' )
for ( const file of [ 'CartonDetailView' , 'ProductDetailView' ]) expect ( read ( 'modules/overseas-warehouse/pages/' + file + '.vue' )). not . toContain ( ' dense' )
})
2026-10-08 11:16:58 +08:00
it ( 'covers five cargo menus, independent detail routes and both workbench modes' , () => {
2026-09-14 18:34:34 +08:00
const navigation = read ( 'core/navigation/adminNavigation.ts' )
2026-10-08 11:16:58 +08:00
for ( const path of [ '/ocean-bookings' , '/trucking' , '/container-loading/domestic' , '/loading-lists' , '/loading-plans' ]) expect ( navigation ). toContain ( `path: ' ${ path } '` )
2026-09-14 18:34:34 +08:00
for ( const [ prefix , workbench ] of [[ 'Domestic' , 'DOMESTIC' ], [ 'Overseas' , 'OVERSEAS' ]]) {
for ( const [ suffix , component ] of [[ 'ContainersView' , 'ContainersView' ], [ 'ContainerDetailView' , 'ContainerDetailView' ]]) {
2026-10-08 11:16:58 +08:00
expect ( read ( `modules/container-loading/pages/ ${ prefix }${ suffix } .vue` )). toContain ( suffix === 'ContainersView' ? `<ContainerWorkbenchView stage=" ${ workbench } "` : `< ${ component } workbench=" ${ workbench } "` )
2026-09-14 18:34:34 +08:00
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 ) => {
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 )
}
})
2026-09-14 17:54:21 +08:00
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 )
2026-10-10 02:19:39 +08:00
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 ))) {
2026-09-14 17:54:21 +08:00
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 )
}
})
2026-10-03 04:35:55 +08:00
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 )))
2026-09-14 17:54:21 +08:00
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' )
})
2026-09-14 18:34:34 +08:00
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"' )
})
2026-09-14 17:54:21 +08:00
it ( 'uses shared outlined actions across domestic tables and explicit classes on teleported dialogs' , () => {
2026-09-14 18:34:34 +08:00
for ( const file of [... new Set ([... densityPages , ... consumers ])]) {
2026-09-14 17:54:21 +08:00
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' ))
2026-10-12 20:33:39 +08:00
if ( densityPages . includes ( file ) && ! selfOperatedPages . includes ( file ) && isActionSlot && node . tag === 'el-button' ) expect ( classesOf ( node ), ` ${ file } : row action` ). toContain ( 'density-action' )
2026-09-14 17:54:21 +08:00
if ([ 'AppDialog' , 'el-dialog' ]. includes ( node . tag )) expect ( classesOf ( node ), ` ${ file } : teleported dialog` ). toContain ( 'admin-density-dialog' )
2026-09-14 18:34:34 +08:00
if ([ 'AppDrawer' , 'el-drawer' ]. includes ( node . tag )) expect ( classesOf ( node ), ` ${ file } : teleported drawer` ). toContain ( 'admin-density-drawer' )
2026-09-14 17:54:21 +08:00
visit ( node . children , isActionSlot , node . tag )
}
}
visit ( template . children )
}
})
2026-10-13 17:29:45 +08:00
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*:/ )
})
2026-10-12 20:33:39 +08:00
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 )
}
})
2026-09-14 17:54:21 +08:00
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' )
}
})
2026-09-14 18:34:34 +08:00
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)' )
})
2026-09-14 17:54:21 +08:00
it ( 'prevents page copies of action paint, tab primitives and desktop density dimensions' , () => {
for ( const { file , css } of styles ) cssRoot ( css ). walkRules ( rule => {
2026-09-15 09:58:15 +08:00
expect ( rule . selector . trim (), file + ': empty selector' ). not . toBe ( '' )
2026-09-14 17:54:21 +08:00
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$/ )
})
})
})
2026-10-08 17:08:03 +08:00
it ( 'keeps measurement sections within the clipped panel and gives long references room' , () => {
const dialog = parse ( read ( 'modules/inbounds/measurement/InboundMeasurementDialog.vue' )). descriptor
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' , '#f4fbf8' ], [ 'declaration' , '#f7f9ff' ], [ 'items-section' , '#fffbf4' ],
]) {
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/ )
}
})
2026-09-15 10:38:03 +08:00
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)$/ )
})
})
})
2026-09-14 17:54:21 +08:00
it ( 'keeps conditional shared consumers opt-in instead of enabling other menus' , () => {
2026-09-30 14:01:58 +08:00
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"' )
2026-10-08 14:11:54 +08:00
expect ( dispatches ). toContain ( '<template #filters><div class="integrated-query-group">' )
expect ( dispatches ). toContain ( 'class="density-status-tabs" role="tablist"' )
2026-09-30 14:01:58 +08:00
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"' )
2026-09-14 18:34:34 +08:00
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;' )
2026-09-30 14:01:58 +08:00
expect ( read ( 'modules/loading-lists/components/LoadingListsView.vue' )). toContain ( 'class="business-page loading-lists-page admin-density"' )
2026-09-14 17:54:21 +08:00
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" )
2026-09-30 14:01:58 +08:00
for ( const file of [ 'loading-lists/components/LoadingListDetailDrawer.vue' , 'loading-lists/components/PalletLabelPrintDialog.vue' , 'container-loading/components/ContainerSimulationCreate.vue' ]) {
2026-09-14 18:34:34 +08:00
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 )
}
2026-09-14 17:54:21 +08:00
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' )
}
})
2026-10-09 11:11:20 +08:00
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"' )
})
2026-09-14 17:54:21 +08:00
})