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: '
' }, } } }) 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>) .mockResolvedValueOnce({ records: [{ id: 'customer-b', tenantName: '柏悦物流', status: 'ACTIVE' }], page: 2, size: 200, total: 201, pages: 2 } as Awaited>) 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() }) })