Files
admin-web/src/modules/overseas-warehouse/layoutAcceptance.test.ts
T
2026-10-20 23:34:11 +08:00

101 lines
7.0 KiB
TypeScript

import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
const page = (name: string) => readFileSync(resolve(process.cwd(), 'src/modules/overseas-warehouse/pages', name), 'utf8')
const component = (name: string) => readFileSync(resolve(process.cwd(), 'src/modules/overseas-warehouse/components', name), 'utf8')
describe('overseas warehouse 1920px layout acceptance', () => {
it('keeps action columns first and wide enough for every visible action', () => {
const widths: Record<string, number> = {
'LastMileOrdersView.vue': 330,
'InboundOrdersView.vue': 300,
// Three compact RowActions plus the overflow menu; long business numbers use the saved space.
'DispatchesView.vue': 260,
'OutboundsView.vue': 180,
'DeliveryOrdersView.vue': 300,
'RelabelOrdersView.vue': 180,
'PalletsView.vue': 180,
'InventoryView.vue': 110,
}
for (const [name, width] of Object.entries(widths)) {
const source = page(name)
const columns = source.slice(source.indexOf('const columns'), source.indexOf('async function', source.indexOf('const columns')))
expect(columns).toMatch(new RegExp(`key: 'actions',[\\s\\S]*?label: '操作',[\\s\\S]*?width: ${width},[\\s\\S]*?fixed: 'left'`))
expect(columns.indexOf("key: 'actions'")).toBeLessThan(columns.indexOf("label:", columns.indexOf("key: 'actions'") + 20))
}
})
it('keeps all inbound-order row actions on one line', () => {
const source = page('InboundOrdersView.vue')
expect(source).toContain('<template #actions="{ row }"><div class="row-actions density-actions admin-text-actions">')
expect(source).toContain('.row-actions { min-width: max-content; flex-wrap: nowrap; white-space: nowrap; }')
expect(source).toContain('.row-actions :deep(.el-button) { flex: 0 0 auto; }')
expect(readFileSync(resolve(process.cwd(), 'src/styles/adminDensity.css'), 'utf8')).toContain('.density-actions > :is(.el-button, .el-dropdown) { margin-left: 0; }')
})
it('assigns explicit desktop widths to dense query inputs', () => {
for (const name of ['LastMileOrdersView.vue', 'InboundOrdersView.vue', 'OutboundsView.vue', 'DeliveryOrdersView.vue', 'RelabelOrdersView.vue', 'PalletsView.vue', 'InventoryView.vue']) {
const source = page(name)
const fieldLines = source.split(/\r?\n/).filter((line) => /type: '(input|select|daterange)'/.test(line))
expect(fieldLines.length, name).toBeGreaterThan(0)
expect(fieldLines.every((line) => line.includes('width:')), `${name} has a query field without width`).toBe(true)
}
})
it('preserves semantic placeholders for query fields while forms use their labels', () => {
for (const name of ['LastMileOrdersView.vue', 'InboundOrdersView.vue', 'OutboundsView.vue', 'DeliveryOrdersView.vue', 'RelabelOrdersView.vue', 'PalletsView.vue', 'InventoryView.vue']) {
const source = page(name)
const queryFields = [...source.matchAll(/\{ key: '[^']+', type: '(?:input|select)'[^\r\n]*/g)].map(([field]) => field)
expect(queryFields.length, name).toBeGreaterThan(0)
expect(queryFields.every((field) => field.includes('placeholder:')), `${name} has a query field without placeholder`).toBe(true)
}
})
it('uses four-field desktop density and full-width form controls', () => {
const sharedDetail = component('OverseasReadOnlyDetail.vue')
expect(sharedDetail).toContain('grid-template-columns: repeat(2, minmax(0, 1fr))')
expect(page('LastMileOrderDetailView.vue')).toContain('density-fields')
expect(page('InboundOrderDetailView.vue')).toContain('density-fields')
expect(page('DispatchDetailView.vue')).toContain('class="density-element-tabs density-contained-tabs"')
expect(page('DispatchDetailView.vue').match(/<el-tab-pane name=/g)).toHaveLength(4)
expect(page('DispatchDetailView.vue')).toContain('density-fields')
expect(page('DeliveryOrderDetailView.vue')).toContain('density-fields')
expect(page('InventoryDetailView.vue')).toContain('density-fields')
expect(page('InventoryDetailView.vue').match(/class="inventory-table-wrap"/g)).toHaveLength(3)
expect(page('InventoryDetailView.vue')).toContain('.inventory-table-wrap { padding:0 var(--admin-space-5) var(--admin-space-4); }')
const outbounds = page('OutboundsView.vue')
expect(outbounds).toContain('density-table density-table--editable outbound-editor-table')
const density = readFileSync(resolve(process.cwd(), 'src/styles/adminDensity.css'), 'utf8')
expect(density).toContain('.density-form :is(.el-select, .el-input-number, .el-date-editor) { width: 100%; }')
expect(outbounds).not.toContain('.create-outbound-form :deep(.el-select)')
expect(outbounds).toContain('.candidate-pagination :deep(.el-pagination__sizes .el-select), .return-pagination :deep(.el-pagination__sizes .el-select) { width: 128px; }')
expect(page('RelabelOrdersView.vue')).toContain('density-table density-table--editable relabel-editor-table')
expect(page('PalletsView.vue')).toContain('density-table density-table--editable pallet-editor-table')
expect(page('RelabelOrdersView.vue')).toContain('label-position="left" label-width="120px"')
expect(page('PalletsView.vue')).toContain('label-position="left" label-width="120px"')
const lastMileDetail = page('LastMileOrderDetailView.vue')
expect(lastMileDetail).toContain('density-table density-table--editable fee-editor-table')
expect(lastMileDetail).toContain('filterable allow-create clearable default-first-option')
expect(lastMileDetail).toContain('.fee-editor-table { table-layout: fixed; }')
expect(lastMileDetail).toContain('.fee-editor-table td:not([colspan]) { width: 50%; }')
expect(lastMileDetail).toContain("field.label === '入仓码' || field.label === '集装箱号'")
expect(lastMileDetail).toContain(':title="isSingleLineBusinessField(field) ? String(field.value) : undefined"')
expect(lastMileDetail).toContain('.detail-field-value--business { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }')
})
it('keeps the inbound detail compact and puts pallet facts before appointment metadata', () => {
const inbound = page('InboundOrderDetailView.vue')
expect(inbound).toContain('grid-template-columns: repeat(5, minmax(0, 1fr))')
expect(inbound).toContain('class="section-heading"')
expect(inbound).toContain('.detail-section { min-width: 0; padding: 0;')
expect(inbound).toContain(':deep(.density-detail-header) { border-bottom: 1px solid var(--workbench-border); }')
expect(inbound).toContain('.appointment-fields { grid-template-columns:repeat(4,minmax(0,1fr)); }')
expect(inbound).toContain('class="compact-section-heading"')
expect(inbound.match(/class="detail-table-wrap"/g)).toHaveLength(2)
expect(inbound).toContain('.detail-table-wrap { padding:var(--admin-space-3) var(--admin-space-5) var(--admin-space-4); }')
expect(inbound.indexOf('class="detail-section pallet-section"')).toBeLessThan(inbound.indexOf('class="detail-section appointment-section"'))
expect(inbound).not.toContain("fields: ['状态', '预约状态'")
})
})