优化
This commit is contained in:
@@ -2,8 +2,11 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { flushPromises, mount, type VueWrapper } from '@vue/test-utils'
|
||||
import { createRequire } from 'node:module'
|
||||
import DomesticContainersView from './DomesticContainersView.vue'
|
||||
import DataTable from '@/shared/components/DataTable.vue'
|
||||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
import QueryToolbar from '@/shared/components/QueryToolbar.vue'
|
||||
import * as api from '../api/domesticContainers'
|
||||
import type { InventoryRecord } from '../types'
|
||||
import type { InventoryPage, InventoryRecord } from '../types'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
// Use Node's supported require export so the validator retains its constructor interop.
|
||||
@@ -31,11 +34,16 @@ describe('DomesticContainersView', () => {
|
||||
})
|
||||
it('keeps desktop filters and actions on one row with explicit field widths', () => { expect(source).toContain("key: 'warehouseId', type: 'select', width: 210"); expect(source).toContain("key: 'orderNo', type: 'input', width: 220"); expect(source).toContain("key: 'containerNo', type: 'input', width: 220"); expect(source).toContain("key: 'cargoName', type: 'input', width: 220"); expect(source).toContain("key: 'status', type: 'select', width: 180") })
|
||||
it('uses the shared query toolbar flow without a page-level layout override', () => { expect(source).not.toContain('.query-toolbar__filters') })
|
||||
it('keeps all four inventory facts in the approved compact summary', () => {
|
||||
for (const fact of ['summary.inboundBoxes', 'summary.outboundBoxes', 'summary.availableBoxes', 'summary.inboundVolumeCbm']) expect(source).toContain(fact)
|
||||
it('keeps all four inventory facts and units in shared compact metric cards', () => {
|
||||
for (const fact of ['summary?.inboundBoxes', 'summary?.outboundBoxes', 'summary?.availableBoxes', 'summary?.inboundVolumeCbm']) expect(source).toContain(fact)
|
||||
expect(source).toContain("import './domesticContainersSummary.css'")
|
||||
expect(summaryStyles).toContain('display: flex; flex-wrap: wrap')
|
||||
expect(summaryStyles).toContain('font-size: var(--admin-font-section, 14px)')
|
||||
const summary = source.match(/<section\b[^>]*class="inventory-summary"[\s\S]*?<\/section>/)?.[0] || ''
|
||||
expect(summary).not.toContain('v-if=')
|
||||
expect(summary.match(/class="workbench-metric workbench-tone is-compact"/g)).toHaveLength(4)
|
||||
expect(summary.match(/<span>箱<\/span>/g)).toHaveLength(3)
|
||||
expect(summary).toContain("summary?.inboundVolumeCbm.toFixed(2) ?? '—' }}</strong><span>m³</span>")
|
||||
expect(summaryStyles).toContain('grid-template-columns: repeat(4, minmax(0, 1fr))')
|
||||
expect(summaryStyles).toContain('gap: var(--workbench-grid-gap)')
|
||||
expect(summaryStyles).not.toContain('position: absolute')
|
||||
})
|
||||
it('does not offer an inventory detail action before an inbound has formed inventory', () => { expect(source).toContain("canViewInventory(row as ContainerOrder | InventoryRecord)"); expect(source).toContain("row.status === 'COMPLETED'") })
|
||||
@@ -49,10 +57,14 @@ vi.mock('@/modules/warehouses', () => ({ listAllPlatformWarehouses: vi.fn().mock
|
||||
vi.mock('../api/domesticContainers', () => ({ createDomesticContainerInbound: vi.fn(), createDomesticContainerOutbound: vi.fn(), getDomesticContainerInventory: vi.fn(), listDomesticContainerInbounds: vi.fn(), listDomesticContainerInventories: vi.fn(), listDomesticContainerOutbounds: vi.fn() }))
|
||||
const inventory: InventoryRecord = { id: 'inventory-1', warehouseId: 'warehouse-1', warehouseName: '测试仓', containerNo: 'MSCU1234567', cargoName: '测试货物', availableBoxes: 8, inboundBoxes: 10, outboundBoxes: 2, inboundVolumeCbm: 2.5, inventoryStatus: 'PARTIALLY_OUTBOUND', version: 3, updatedAt: '2026-09-30T10:00:00' }
|
||||
let mounted: VueWrapper | undefined
|
||||
async function openCreate(label: string) {
|
||||
async function mountView() {
|
||||
const view = mount(DomesticContainersView, { attachTo: document.body, global: { plugins: [ElementPlus] } })
|
||||
mounted = view
|
||||
await flushPromises()
|
||||
return view
|
||||
}
|
||||
async function openCreate(label: string) {
|
||||
const view = await mountView()
|
||||
if (label === '新建出库单') await view.findAll('[role="tab"]').find(tab => tab.text() === '整柜出库单')!.trigger('click')
|
||||
await flushPromises()
|
||||
await view.findAll('button').find(button => button.text() === label)!.trigger('click')
|
||||
@@ -64,7 +76,7 @@ async function submitCreate() {
|
||||
document.querySelector<HTMLButtonElement>('.el-dialog__footer .el-button--primary')!.click()
|
||||
await flushPromises()
|
||||
}
|
||||
describe('DomesticContainersView create workflows', () => {
|
||||
describe('DomesticContainersView workflows', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
const empty = { records: [], page: 1, size: 20, total: 0, pages: 0 }
|
||||
@@ -75,6 +87,60 @@ describe('DomesticContainersView create workflows', () => {
|
||||
})
|
||||
afterEach(() => { mounted?.unmount(); mounted = undefined; document.body.innerHTML = '' })
|
||||
|
||||
it('loads the overview on entry and keeps it above the list across all three tabs', async () => {
|
||||
const view = await mountView()
|
||||
const overview = view.get('.inventory-summary')
|
||||
expect(view.element.firstElementChild).toBe(overview.element)
|
||||
expect(overview.findAll('strong').map(value => value.text())).toEqual(['10', '2', '8', '2.50'])
|
||||
expect(api.listDomesticContainerInventories).toHaveBeenCalledTimes(1)
|
||||
for (const label of ['整柜出库单', '整柜库存', '整柜入库单']) {
|
||||
const requests = vi.mocked(api.listDomesticContainerInventories).mock.calls.length
|
||||
await view.findAll('[role="tab"]').find(tab => tab.text() === label)!.trigger('click')
|
||||
await flushPromises()
|
||||
expect(view.element.firstElementChild).toBe(overview.element)
|
||||
expect(overview.findAll('strong').map(value => value.text())).toEqual(['10', '2', '8', '2.50'])
|
||||
expect(api.listDomesticContainerInventories).toHaveBeenCalledTimes(requests + 1)
|
||||
}
|
||||
})
|
||||
|
||||
it.each(['整柜入库单', '整柜出库单'])('uses common filters for the overview on %s without passing order-only filters', async label => {
|
||||
const view = await mountView()
|
||||
await view.findAll('[role="tab"]').find(tab => tab.text() === label)!.trigger('click')
|
||||
await flushPromises()
|
||||
const filters = { warehouseId: 'warehouse-1', containerNo: 'MSCU1234567', cargoName: '测试货物', orderNo: 'ORDER-01', status: 'PENDING' }
|
||||
view.findComponent(QueryToolbar).vm.$emit('search', filters)
|
||||
await flushPromises()
|
||||
expect(api.listDomesticContainerInventories).toHaveBeenLastCalledWith({ page: 1, size: 1, warehouseId: filters.warehouseId, containerNo: filters.containerNo, cargoName: filters.cargoName })
|
||||
const list = label === '整柜入库单' ? api.listDomesticContainerInbounds : api.listDomesticContainerOutbounds
|
||||
expect(list).toHaveBeenLastCalledWith({ ...filters, page: 1, size: 20 })
|
||||
})
|
||||
|
||||
it('shows unknown summary values on failure while keeping the order list available and allowing retry', async () => {
|
||||
vi.mocked(api.listDomesticContainerInventories).mockRejectedValueOnce(new Error('统计失败'))
|
||||
const view = await mountView()
|
||||
expect(view.findAll('.inventory-summary strong').map(value => value.text())).toEqual(['—', '—', '—', '—'])
|
||||
expect(view.findComponent(DataTable).props('resultsVisible')).toBe(true)
|
||||
const error = view.findComponent(LoadErrorState)
|
||||
expect(error.props('title')).toBe('整柜库存统计加载失败')
|
||||
error.vm.$emit('retry')
|
||||
await flushPromises()
|
||||
expect(view.findComponent(LoadErrorState).exists()).toBe(false)
|
||||
expect(view.findAll('.inventory-summary strong').map(value => value.text())).toEqual(['10', '2', '8', '2.50'])
|
||||
})
|
||||
|
||||
it('ignores a stale overview response after switching tabs', async () => {
|
||||
let resolveFirst!: (value: InventoryPage) => void
|
||||
vi.mocked(api.listDomesticContainerInventories).mockImplementationOnce(() => new Promise(resolve => { resolveFirst = resolve }))
|
||||
const view = await mountView()
|
||||
expect(view.get('.inventory-summary').attributes('aria-busy')).toBe('true')
|
||||
await view.findAll('[role="tab"]').find(tab => tab.text() === '整柜出库单')!.trigger('click')
|
||||
await flushPromises()
|
||||
resolveFirst({ records: [], page: 1, size: 1, total: 0, pages: 0, summary: { inboundBoxes: 99, outboundBoxes: 0, availableBoxes: 99, inboundVolumeCbm: 99 } })
|
||||
await flushPromises()
|
||||
expect(view.findAll('.inventory-summary strong').map(value => value.text())).toEqual(['10', '2', '8', '2.50'])
|
||||
expect(view.get('.inventory-summary').attributes('aria-busy')).toBe('false')
|
||||
})
|
||||
|
||||
it('keeps validation, normalized input and the idempotency key when retrying an inbound', async () => {
|
||||
vi.mocked(api.createDomesticContainerInbound).mockRejectedValueOnce(new Error('临时失败')).mockResolvedValueOnce({ id: 'order-internal', orderNo: 'ZGRK-01', replayed: false })
|
||||
const { form } = await openCreate('新建入库单')
|
||||
|
||||
Reference in New Issue
Block a user