Files
admin-web/src/modules/overseas-warehouse/pages/OutboundsView.selection.test.ts
T
2026-10-08 20:25:25 +08:00

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()
})
})