style(admin): unify dialog layouts and dense readonly presentation
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
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 * as api from '../api/domesticContainers'
|
||||
import type { 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.
|
||||
const ElementPlus = (createRequire(import.meta.url)('element-plus') as typeof import('element-plus')).default
|
||||
const source = readFileSync(resolve(process.cwd(), 'src/modules/domestic-containers/pages/DomesticContainersView.vue'), 'utf8')
|
||||
const summaryStyles = readFileSync(resolve(process.cwd(), 'src/modules/domestic-containers/pages/domesticContainersSummary.css'), 'utf8')
|
||||
describe('DomesticContainersView', () => {
|
||||
@@ -22,3 +29,86 @@ describe('DomesticContainersView', () => {
|
||||
it('matches backend numeric and container-number input boundaries before submit', () => { expect(source).toContain("pattern: /^[A-Za-z0-9-]{4,32}$/"); expect(source).toContain(':max="999999999"'); expect(source).toContain(':max="999999999999.9999"') })
|
||||
it('requires a refreshed inventory version for completed outbound submission', () => { expect(source).toContain("outboundForm.inventoryVersion == null"); expect(source).toContain('库存版本未就绪,请重新选择集装箱') })
|
||||
})
|
||||
|
||||
vi.mock('vue-router', () => ({ useRouter: () => ({ push: vi.fn() }) }))
|
||||
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => true }) }))
|
||||
vi.mock('@/modules/warehouses', () => ({ listAllPlatformWarehouses: vi.fn().mockResolvedValue([{ id: 'warehouse-1', warehouseCode: 'WH-01', warehouseName: '测试仓', warehouseType: 'DOMESTIC', status: 'ACTIVE' }]) }))
|
||||
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) {
|
||||
const view = mount(DomesticContainersView, { attachTo: document.body, global: { plugins: [ElementPlus] } })
|
||||
mounted = view
|
||||
await flushPromises()
|
||||
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')
|
||||
await flushPromises()
|
||||
const form = view.findComponent({ name: 'ElForm' })
|
||||
return { view, form }
|
||||
}
|
||||
async function submitCreate() {
|
||||
document.querySelector<HTMLButtonElement>('.el-dialog__footer .el-button--primary')!.click()
|
||||
await flushPromises()
|
||||
}
|
||||
describe('DomesticContainersView create workflows', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
const empty = { records: [], page: 1, size: 20, total: 0, pages: 0 }
|
||||
vi.mocked(api.listDomesticContainerInbounds).mockResolvedValue(empty)
|
||||
vi.mocked(api.listDomesticContainerOutbounds).mockResolvedValue(empty)
|
||||
vi.mocked(api.listDomesticContainerInventories).mockResolvedValue({ ...empty, records: [inventory], summary: { inboundBoxes: 10, outboundBoxes: 2, availableBoxes: 8, inboundVolumeCbm: 2.5 } })
|
||||
vi.mocked(api.getDomesticContainerInventory).mockResolvedValue({ inventory: { ...inventory, availableBoxes: 6, version: 9 }, inboundOrderCount: 1, outboundOrderCount: 1, transactions: [] })
|
||||
})
|
||||
afterEach(() => { mounted?.unmount(); mounted = undefined; document.body.innerHTML = '' })
|
||||
|
||||
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('新建入库单')
|
||||
await submitCreate()
|
||||
expect(api.createDomesticContainerInbound).not.toHaveBeenCalled()
|
||||
const fields = form.findAllComponents({ name: 'ElFormItem' })
|
||||
const warehouse = fields.find(item => item.props('prop') === 'warehouseId')!.findComponent({ name: 'ElSelect' })
|
||||
warehouse.vm.$emit('update:modelValue', 'warehouse-1')
|
||||
await fields.find(item => item.props('prop') === 'cargoName')!.find('input').setValue(' 测试货物 ')
|
||||
await fields.find(item => item.props('prop') === 'containerNo')!.find('input').setValue('mscu1234567')
|
||||
fields.find(item => item.props('prop') === 'boxCount')!.findComponent({ name: 'ElInputNumber' }).vm.$emit('update:modelValue', 4)
|
||||
fields.find(item => item.props('prop') === 'volumeCbm')!.findComponent({ name: 'ElInputNumber' }).vm.$emit('update:modelValue', 1.2345)
|
||||
await flushPromises()
|
||||
await submitCreate()
|
||||
expect(document.body.textContent).toContain('整柜单据创建失败')
|
||||
await submitCreate()
|
||||
const calls = vi.mocked(api.createDomesticContainerInbound).mock.calls
|
||||
expect(calls).toHaveLength(2)
|
||||
expect(calls[0][0]).toEqual(calls[1][0])
|
||||
expect(calls[1][0]).toMatchObject({ warehouseId: 'warehouse-1', cargoName: '测试货物', containerNo: 'MSCU1234567', boxCount: 4, volumeCbm: 1.2345, status: 'COMPLETED', idempotencyKey: expect.any(String) })
|
||||
})
|
||||
|
||||
it('shows inventory facts as text and submits the refreshed version only within available stock', async () => {
|
||||
vi.mocked(api.createDomesticContainerOutbound).mockResolvedValue({ id: 'order-internal', orderNo: 'ZGCK-01', replayed: false })
|
||||
const { form } = await openCreate('新建出库单')
|
||||
const fields = form.findAllComponents({ name: 'ElFormItem' })
|
||||
const warehouse = fields.find(item => item.props('prop') === 'warehouseId')!.findComponent({ name: 'ElSelect' })
|
||||
warehouse.vm.$emit('update:modelValue', 'warehouse-1')
|
||||
warehouse.vm.$emit('change', 'warehouse-1')
|
||||
await flushPromises()
|
||||
const selection = fields.find(item => item.props('prop') === 'containerNo')!.findComponent({ name: 'ElSelect' })
|
||||
selection.vm.$emit('update:modelValue', 'inventory-1')
|
||||
selection.vm.$emit('change', 'inventory-1')
|
||||
await flushPromises()
|
||||
const cargo = fields.find(item => item.props('label') === '货物名称')!
|
||||
expect(cargo.text()).toContain('测试货物')
|
||||
expect(cargo.find('input').exists()).toBe(false)
|
||||
expect(fields.find(item => item.props('label') === '可用库存')!.text()).toContain('6 箱')
|
||||
const quantity = fields.find(item => item.props('prop') === 'boxCount')!.findComponent({ name: 'ElInputNumber' })
|
||||
quantity.vm.$emit('update:modelValue', 7)
|
||||
await flushPromises()
|
||||
await submitCreate()
|
||||
expect(api.createDomesticContainerOutbound).not.toHaveBeenCalled()
|
||||
expect(document.body.textContent).toContain('库存不足,当前可用 6 箱')
|
||||
quantity.vm.$emit('update:modelValue', 5)
|
||||
await flushPromises()
|
||||
await submitCreate()
|
||||
expect(api.createDomesticContainerOutbound).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({ warehouseId: 'warehouse-1', containerNo: 'MSCU1234567', boxCount: 5, inventoryVersion: 9, idempotencyKey: expect.any(String) }))
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user