169 lines
9.3 KiB
TypeScript
169 lines
9.3 KiB
TypeScript
import ElementPlus, { ElCheckbox, ElInput, ElPagination, ElSelect } from 'element-plus'
|
|
import { flushPromises, mount, type VueWrapper } from '@vue/test-utils'
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import OutboundsView from './OutboundsView.vue'
|
|
import { listPlatformTenants } from '@/modules/iam'
|
|
|
|
const api = vi.hoisted(() => ({ candidates: vi.fn(), create: vi.fn(), warehouses: vi.fn() }))
|
|
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => true }) }))
|
|
vi.mock('vue-router', () => ({ useRouter: () => ({ push: vi.fn() }) }))
|
|
vi.mock('@/modules/iam', () => ({ listPlatformTenants: vi.fn().mockResolvedValue({ records: [] }) }))
|
|
vi.mock('@/modules/warehouses', () => ({ listPlatformWarehouses: api.warehouses }))
|
|
vi.mock('../api/outbounds', () => ({
|
|
listOutbounds: vi.fn().mockResolvedValue({ records: [], total: 0 }),
|
|
listOneClickOutboundCandidates: api.candidates,
|
|
createOneClickDropshipOutbound: api.create,
|
|
cancelOutbound: vi.fn(), createReturnOutbound: vi.fn(), listReturnOutboundCandidates: vi.fn(),
|
|
}))
|
|
|
|
const fixtures = Array.from({ length: 7 }, (_, index) => ({
|
|
inventoryPositionId: `position-${index}`, orderNo: `ORDER-${index}`, customerId: 'customer-a',
|
|
customerName: '客户甲', overseasWarehouseId: 'warehouse-a', palletNo: `PALLET-${index}`,
|
|
inboundCode: `IN-${index}`, skuCode: index < 6 ? 'MATCH' : 'OTHER', productName: '测试品名', availablePackages: index + 1,
|
|
grossWeightKg: (index + 1) * 10, volumeWeightKg: (index + 1) * 20, volumeCbm: index + 1,
|
|
}))
|
|
let wrapper: VueWrapper
|
|
async function clickButton(text: string) {
|
|
const button = wrapper.findAll('button').find(item => item.text().includes(text))
|
|
expect(button, text).toBeDefined()
|
|
await button!.trigger('click')
|
|
await flushPromises()
|
|
}
|
|
async function openCreate() {
|
|
wrapper = mount(OutboundsView, { global: { plugins: [ElementPlus], stubs: {
|
|
AppDialog: { props: ['modelValue'], emits: ['confirm'], template: '<section v-if="modelValue"><slot /><button data-test="submit-outbound" @click="$emit(\'confirm\')">提交出库</button></section>' },
|
|
} } })
|
|
await flushPromises()
|
|
await clickButton('新建出库单')
|
|
}
|
|
function candidatePagination() { return wrapper.findAllComponents(ElPagination).find(item => item.props('total') === 7)! }
|
|
function pageCheckbox() { return wrapper.findAllComponents(ElCheckbox).find(item => item.props('ariaLabel') === '全选本页订单')! }
|
|
async function selectBothPages() {
|
|
pageCheckbox().vm.$emit('change', true)
|
|
await flushPromises()
|
|
candidatePagination().vm.$emit('update:current-page', 2)
|
|
await flushPromises()
|
|
pageCheckbox().vm.$emit('change', true)
|
|
await flushPromises()
|
|
}
|
|
|
|
describe('outbound candidate search and selection', () => {
|
|
beforeEach(() => {
|
|
vi.mocked(listPlatformTenants).mockReset().mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 0 })
|
|
vi.stubGlobal('ResizeObserver', class { observe() {} unobserve() {} disconnect() {} })
|
|
vi.stubGlobal('localStorage', { getItem: () => null, setItem: () => undefined, removeItem: () => undefined })
|
|
api.candidates.mockReset().mockResolvedValue(fixtures.map(item => ({ ...item })))
|
|
api.create.mockReset().mockResolvedValue({})
|
|
api.warehouses.mockReset().mockResolvedValue({ records: [
|
|
{ id: 'warehouse-a', warehouseName: '洛杉矶仓', warehouseCode: 'US-LAX-01', warehouseType: 'OVERSEAS', status: 'ACTIVE' },
|
|
{ id: 'warehouse-b', warehouseName: '纽约仓', warehouseCode: 'US-NY-01', warehouseType: 'OVERSEAS', status: 'ACTIVE' },
|
|
] })
|
|
})
|
|
afterEach(() => { wrapper?.unmount(); vi.unstubAllGlobals() })
|
|
|
|
it('offers enabled customers from later pages when creating a return outbound', async () => {
|
|
vi.mocked(listPlatformTenants)
|
|
.mockResolvedValueOnce({ records: [{ id: 'customer-a', tenantName: '柏悦物流demo2', status: 'ACTIVE' }], page: 1, size: 200, total: 201, pages: 2 } as Awaited<ReturnType<typeof listPlatformTenants>>)
|
|
.mockResolvedValueOnce({ records: [{ id: 'customer-b', tenantName: '柏悦物流', status: 'ACTIVE' }], page: 2, size: 200, total: 201, pages: 2 } as Awaited<ReturnType<typeof listPlatformTenants>>)
|
|
await openCreate()
|
|
expect(listPlatformTenants).toHaveBeenNthCalledWith(1, { page: 1, size: 200, status: 'ACTIVE' })
|
|
expect(listPlatformTenants).toHaveBeenNthCalledWith(2, { page: 2, size: 200, status: 'ACTIVE' })
|
|
const type = wrapper.findComponent({ name: 'ElRadioGroup' })
|
|
type.vm.$emit('update:modelValue', 'RETURN_OUTBOUND')
|
|
type.vm.$emit('change', 'RETURN_OUTBOUND')
|
|
await flushPromises()
|
|
const customer = wrapper.findAllComponents(ElSelect).find(select => select.props('placeholder') === '请选择客户')!
|
|
expect(customer.findAllComponents({ name: 'ElOption' }).map(option => option.props('label'))).toEqual(['柏悦物流demo2', '柏悦物流'])
|
|
customer.vm.$emit('update:modelValue', 'customer-b')
|
|
await flushPromises()
|
|
expect(customer.props('modelValue')).toBe('customer-b')
|
|
})
|
|
|
|
it('selects and deselects the current page while preserving other pages, then submits all selected quantities', async () => {
|
|
await openCreate()
|
|
expect(wrapper.text()).toContain('可出毛重(kg)')
|
|
expect(wrapper.text()).toContain('可出材积重(kg)')
|
|
expect(wrapper.text()).toContain('可出体积(m³)')
|
|
pageCheckbox().vm.$emit('change', true)
|
|
await flushPromises()
|
|
expect(wrapper.text()).toContain('已选择 5 条')
|
|
candidatePagination().vm.$emit('update:current-page', 2)
|
|
await flushPromises()
|
|
expect(pageCheckbox().props('modelValue')).toBe(false)
|
|
pageCheckbox().vm.$emit('change', true)
|
|
await flushPromises()
|
|
expect(wrapper.text()).toContain('已选择 7 条')
|
|
pageCheckbox().vm.$emit('change', false)
|
|
await flushPromises()
|
|
expect(wrapper.text()).toContain('已选择 5 条')
|
|
await wrapper.get('[data-test="submit-outbound"]').trigger('click')
|
|
await flushPromises()
|
|
expect(api.create).toHaveBeenCalledWith('customer-a', expect.objectContaining({
|
|
items: fixtures.slice(0, 5).map(item => ({ inventoryPositionId: item.inventoryPositionId, packages: item.availablePackages, sku: item.skuCode, cartonMarks: [] })),
|
|
}))
|
|
})
|
|
|
|
it('keeps searches on one row without bulk buttons and preserves filtered selection across pages', async () => {
|
|
await openCreate()
|
|
expect(wrapper.get('.dropship-search-row').findAll('.el-form-item')).toHaveLength(2)
|
|
expect(wrapper.find('.candidate-selection').exists()).toBe(false)
|
|
expect(wrapper.text()).not.toContain('全选筛选结果')
|
|
expect(wrapper.text()).not.toContain('清空选择')
|
|
const query = wrapper.findAllComponents(ElInput).find(item => item.props('ariaLabel') === '搜索可出库订单')!
|
|
await query.get('input').setValue('MATCH')
|
|
await flushPromises()
|
|
expect(candidatePagination().props('total')).toBe(7)
|
|
await query.get('input').trigger('keyup', { key: 'Enter' })
|
|
await flushPromises()
|
|
const filteredPagination = wrapper.findAllComponents(ElPagination).find(item => item.props('total') === 6)!
|
|
expect(filteredPagination).toBeDefined()
|
|
pageCheckbox().vm.$emit('change', true)
|
|
await flushPromises()
|
|
filteredPagination.vm.$emit('update:current-page', 2)
|
|
await flushPromises()
|
|
pageCheckbox().vm.$emit('change', true)
|
|
await flushPromises()
|
|
expect(wrapper.text()).toContain('已选择 6 条')
|
|
await wrapper.get('.candidate-query').findAll('button').find(item => item.text() === '重置')!.trigger('click')
|
|
await flushPromises()
|
|
candidatePagination().vm.$emit('update:current-page', 2)
|
|
await flushPromises()
|
|
expect(pageCheckbox().props('indeterminate')).toBe(true)
|
|
pageCheckbox().vm.$emit('change', false)
|
|
await flushPromises()
|
|
expect(wrapper.get('.candidate-pagination').text()).toContain('已选择 5 条')
|
|
})
|
|
|
|
it('resets the page after page-size changes and clears selection when changing searchable warehouses', async () => {
|
|
await openCreate()
|
|
await selectBothPages()
|
|
const pagination = candidatePagination()
|
|
pagination.vm.$emit('update:current-page', 2)
|
|
await flushPromises()
|
|
pagination.vm.$emit('update:page-size', 20)
|
|
pagination.vm.$emit('size-change', 20)
|
|
await flushPromises()
|
|
expect(pagination.props('currentPage')).toBe(1)
|
|
expect(wrapper.text()).toContain('第 1 / 1 页')
|
|
expect(wrapper.text()).toContain('已选择 7 条')
|
|
const warehouse = wrapper.findAllComponents(ElSelect).find(item => item.props('ariaLabel') === '搜索海外仓')!
|
|
expect(warehouse.props('filterable')).toBe(true)
|
|
warehouse.vm.$emit('update:modelValue', 'warehouse-b')
|
|
warehouse.vm.$emit('change', 'warehouse-b')
|
|
await flushPromises()
|
|
expect(api.candidates).toHaveBeenLastCalledWith(undefined, { warehouseId: 'warehouse-b' })
|
|
expect(wrapper.text()).toContain('已选择 0 条')
|
|
})
|
|
|
|
it('warns about a multi-customer bulk selection and preserves the submit restriction', async () => {
|
|
api.candidates.mockResolvedValue([{ ...fixtures[0] }, { ...fixtures[1], customerId: 'customer-b', customerName: '客户乙' }])
|
|
await openCreate()
|
|
pageCheckbox().vm.$emit('change', true)
|
|
await flushPromises()
|
|
expect(wrapper.text()).toContain('当前选择包含多个客户')
|
|
await wrapper.get('[data-test="submit-outbound"]').trigger('click')
|
|
await flushPromises()
|
|
expect(api.create).not.toHaveBeenCalled()
|
|
})
|
|
})
|