Files
admin-web/src/modules/overseas-warehouse/pages/OutboundDetailView.test.ts
T
2026-10-19 17:07:55 +08:00

130 lines
7.8 KiB
TypeScript

import ElementPlus from 'element-plus'
import { flushPromises, mount } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { getOutbound } from '../api/outbounds'
import OutboundDetailView from './OutboundDetailView.vue'
import StatusBadge from '@/shared/components/StatusBadge.vue'
import { getPlatformWarehouse } from '@/modules/warehouses'
const canPermission = vi.hoisted(() => vi.fn((_code: string) => false))
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: canPermission }) }))
vi.mock('vue-router', () => ({ useRoute: () => ({ params: { outboundNo: 'OUT/1' } }), useRouter: () => ({ push: vi.fn() }) }))
vi.mock('../api/outbounds', () => ({ getOutbound: vi.fn(), cancelOutbound: vi.fn() }))
vi.mock('@/modules/warehouses', () => ({ getPlatformWarehouse: vi.fn() }))
describe('OutboundDetailView', () => {
it.each([
['WAIT_PICK', '待拣货', 'warning'], ['WAIT_OUTBOUND', '待出库', 'warning'], ['PICKING', '拣货中', 'info'],
['PICKED', '已拣货', 'warning'], ['OUTBOUNDED', '已出库', 'success'], ['CANCELLED', '已取消', 'danger'],
])('uses the same shared status tone as the list for %s', async (status, label, tone) => {
vi.mocked(getOutbound).mockResolvedValueOnce({ outbound: { id: '1', outboundNo: 'OUT/1', status }, pallets: [], deliveries: [] })
const wrapper = mount(OutboundDetailView, { global: { plugins: [ElementPlus] } })
await flushPromises()
const badge = wrapper.findComponent(StatusBadge)
expect(badge.text()).toBe(label)
expect(badge.classes()).toContain(`is-${tone}`)
wrapper.unmount()
})
beforeEach(() => {
vi.mocked(getPlatformWarehouse).mockReset().mockRejectedValue(new Error('Warehouse unavailable'))
canPermission.mockReset().mockReturnValue(false)
vi.mocked(getOutbound).mockReset().mockResolvedValue({
outbound: { id: '1', outboundNo: 'OUT/1', dispatchNo: 'PC-1', status: 'PICKED', palletCount: 2, packageCount: 20, createdAt: '2026-09-01T10:00:00' },
pallets: [{ palletNo: 'IP-ACY2-20260907-597057-P001', inboundCode: 'NYEXX2609060063', destinationWarehouseCode: 'ONT8', destinationWarehouseName: 'Amazon ONT8', skuCode: 'SKU-001', packageCount: 20 }],
deliveries: [
{ version: 2, allowedActions: ['START_DELIVERY'], id: '2', customerId: 'customer-1', deliveryNo: 'DEL-ONT8', outboundNo: 'OUT/1', dispatchNo: 'PC-1', status: 'WAIT_DELIVERY', destinationWarehouseCode: 'ONT8', palletCount: 1, packageCount: 10, signedPackages: null, createdAt: '2026-09-01T10:00:00' },
{ version: 2, allowedActions: ['START_DELIVERY'], id: '3', customerId: 'customer-1', deliveryNo: 'DEL-ACY2', outboundNo: 'OUT/1', dispatchNo: 'PC-1', status: 'WAIT_DELIVERY', destinationWarehouseCode: 'ACY2', palletCount: 1, packageCount: 10, signedPackages: null, createdAt: '2026-09-01T10:00:00' },
],
})
})
it('loads by encoded business outbound number and remains read-only without cancel permission', async () => {
const wrapper = mount(OutboundDetailView, { global: { plugins: [ElementPlus] } })
await flushPromises()
expect(getOutbound).toHaveBeenCalledWith('OUT/1')
expect(wrapper.text()).toContain('OUT/1')
expect(wrapper.text()).toContain('PC-1')
expect(wrapper.text()).toContain('DEL-ONT8')
expect(wrapper.text()).toContain('DEL-ACY2')
expect(wrapper.text()).toContain('ACY2')
expect(wrapper.text()).toContain('出库托盘')
expect(wrapper.text()).toContain('IP-ACY2-20260907-597057-P001')
expect(wrapper.text()).toContain('NYEXX2609060063')
expect(wrapper.text()).toContain('Amazon ONT8')
expect(wrapper.text()).toContain('SKU-001')
expect(wrapper.findAll('.outbound-pallet-table .data-table__single-line-cell').length).toBeGreaterThanOrEqual(6)
expect(wrapper.text()).not.toContain('取消出库单')
wrapper.unmount()
})
it('renders pallet missing values consistently and keeps the empty state in the table', async () => {
vi.mocked(getOutbound).mockResolvedValueOnce({
outbound: { id: '1', outboundNo: 'OUT/1', status: 'OUTBOUNDED' },
pallets: [{ palletNo: null, inboundCode: null, destinationWarehouseCode: null, destinationWarehouseName: null, skuCode: null, packageCount: null }],
deliveries: [],
})
const wrapper = mount(OutboundDetailView, { global: { plugins: [ElementPlus] } })
await flushPromises()
expect(wrapper.get('.outbound-pallet-table').text()).toContain('—')
expect(wrapper.text()).toContain('暂无关联派送单')
wrapper.unmount()
vi.mocked(getOutbound).mockResolvedValueOnce({
outbound: { id: '1', outboundNo: 'OUT/1', status: 'OUTBOUNDED' },
pallets: [],
deliveries: [],
})
const emptyWrapper = mount(OutboundDetailView, { global: { plugins: [ElementPlus] } })
await flushPromises()
expect(emptyWrapper.text()).toContain('暂无出库托盘')
emptyWrapper.unmount()
})
it('shows cancellation only before picking with the independent permission', async () => {
canPermission.mockImplementation((code: string) => code === 'platform:overseas-warehouse:outbound:cancel')
vi.mocked(getOutbound).mockResolvedValueOnce({ outbound: { id: '1', outboundNo: 'OUT/1', status: 'WAIT_PICK' }, pallets: [], deliveries: [] })
const wrapper = mount(OutboundDetailView, { global: { plugins: [ElementPlus] } })
await flushPromises()
expect(wrapper.text()).toContain('取消出库单')
wrapper.unmount()
})
it('resolves the warehouse for a discard outbound without deliveries', async () => {
vi.mocked(getOutbound).mockResolvedValueOnce({
outbound: { id: '1', outboundNo: 'OB-WH-20261019-151681', outboundType: 'DISCARD_OUTBOUND', overseasWarehouseId: 'warehouse-internal-id', packageCount: 1 },
deliveries: [],
})
vi.mocked(getPlatformWarehouse).mockResolvedValueOnce({
warehouse: { id: 'warehouse-internal-id', tenantId: '0', warehouseCode: 'LAX', internalCode: null, warehouseName: '洛杉矶仓', warehouseType: 'OVERSEAS', countryCode: 'US', timezone: 'America/Los_Angeles', contactName: null, contactPhone: null, contactEmail: null, status: 'ACTIVE', remark: null },
addresses: [], capabilities: [],
})
const wrapper = mount(OutboundDetailView, { global: { plugins: [ElementPlus] } })
await flushPromises()
expect(getPlatformWarehouse).toHaveBeenCalledWith('warehouse-internal-id')
const label = wrapper.findAll('.el-descriptions__label').find(item => item.text() === '海外仓')!
expect(label.element.nextElementSibling?.textContent?.trim()).toBe('洛杉矶仓')
expect(wrapper.text()).not.toContain('warehouse-internal-id')
wrapper.unmount()
})
it.each([
['warehouse-internal-id', '洛杉矶仓', '洛杉矶仓'],
['warehouse-internal-id', null, '—'],
['another-warehouse-id', '其他仓库', '—'],
])('uses a matching warehouse name and never displays internal IDs (%s, %s)', async (deliveryWarehouseId, name, expected) => {
vi.mocked(getOutbound).mockResolvedValueOnce({
outbound: { id: '1', outboundNo: 'OUT/1', overseasWarehouseId: 'warehouse-internal-id' },
deliveries: [{ id: '2', customerId: 'customer-1', deliveryNo: 'DEL-1', outboundNo: 'OUT/1', dispatchNo: 'PC-1', status: 'WAIT_DELIVERY', palletCount: 2, packageCount: 20, signedPackages: null, createdAt: '2026-09-01T10:00:00', version: 1, allowedActions: [], overseasWarehouseId: deliveryWarehouseId, overseasWarehouseName: name }],
})
const wrapper = mount(OutboundDetailView, { global: { plugins: [ElementPlus] } })
await flushPromises()
const labels = wrapper.findAll('.el-descriptions__label')
const label = labels.find(item => item.text() === '海外仓')!
expect(label.element.nextElementSibling?.textContent?.trim()).toBe(expected)
expect(wrapper.text()).not.toContain('warehouse-internal-id')
expect(wrapper.text()).not.toContain('another-warehouse-id')
wrapper.unmount()
})
})