import ElementPlus from 'element-plus' import { defineComponent, h } from 'vue' import { enableAutoUnmount, flushPromises, mount, type VueWrapper } from '@vue/test-utils' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import WarehouseTransferReceiptPanel from './WarehouseTransferReceiptPanel.vue' import AppDialog from '@/shared/components/AppDialog.vue' import DataTable from '@/shared/components/DataTable.vue' import LoadErrorState from '@/shared/components/LoadErrorState.vue' import { ApiResponseError } from '@/shared/api/http' import { getWarehouseTransfer, getWarehouseTransferCartonLabel, increaseWarehouseTransferForecast, type WarehouseTransferDetailDto } from '../api/warehouseTransfers' vi.mock('../api/warehouseTransfers', () => ({ getWarehouseTransfer: vi.fn(), getWarehouseTransferCartonLabel: vi.fn(), increaseWarehouseTransferForecast: vi.fn() })) const can = vi.hoisted(() => vi.fn(() => true)) vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can }) })) enableAutoUnmount(afterEach) const DataTableStub = defineComponent({ props: ['data', 'columns'], setup(props, { slots }) { return () => h('div', { class: 'test-table' }, [JSON.stringify(props.data), ...(props.data || []).flatMap((row: object) => [slots.boxAction?.({ row }), slots.quantityDifference?.({ row }), slots.boxDifference?.({ row })])]) }, }) const DialogStub = defineComponent({ props: ['modelValue', 'confirmText', 'confirmDisabled', 'confirmLoading'], emits: ['confirm'], setup(props, { slots, emit }) { return () => props.modelValue ? h('section', { 'data-test': 'box-dialog' }, [slots.default?.(), props.confirmText ? h('button', { disabled: props.confirmDisabled || props.confirmLoading, onClick: () => emit('confirm') }, props.confirmText) : null]) : null } }) const render = (transferNo = 'WT-SOURCE') => mount(WarehouseTransferReceiptPanel, { props: { customerId: 'CUSTOMER', transferNo }, global: { plugins: [ElementPlus], stubs: { DataTable: DataTableStub, AppDialog: DialogStub } } }) function fixture(): WarehouseTransferDetailDto { return { id: '1', transferNo: 'WT-SOURCE', status: 'WAIT_CUSTOMER_CONFIRMATION', needRelabel: false, packageCount: 4, items: [{ id: '10', skuCode: 'SKU-A', productNameZh: '产品 A', quantity: '120', packageCount: 3 }, { id: '20', skuCode: 'SKU-B', productNameZh: '产品 B', quantity: 30, packageCount: 1 }], cartons: [ { cartonMark: 'BOX-1', cartonIndex: 1, totalCartons: 4, status: 'COUNTED', transferItemId: '10', transferPalletId: '100', quantity: '8', length: 100, width: 20, height: 50, weight: '2.5', countedAt: '2026-10-15T12:00:00' }, { cartonMark: 'BOX-2', cartonIndex: 2, totalCartons: 4, status: 'COUNTED', transferItemId: '10', transferPalletId: '100', quantity: 12, length: 100, width: 20, height: 50, weight: 3.5 }, { cartonMark: 'BOX-3', cartonIndex: 3, totalCartons: 4, status: 'COUNTED', transferItemId: '20', transferPalletId: '200', quantity: 7, length: 100, width: 20, height: 50, weight: 4 }, { cartonMark: 'BOX-MISSING', cartonIndex: 4, totalCartons: 4, status: 'NOT_RECEIVED', transferItemId: '10', quantity: 999, weight: 999 }, ], pallets: [{ id: '100', sequenceNo: 1, palletNo: 'PALLET-A', cartonCount: 2, grossWeightKg: 6, volumeCbm: .2, status: 'COMPLETED' }, { id: '200', sequenceNo: 2, palletNo: 'PALLET-B', cartonCount: 1, grossWeightKg: 4, volumeCbm: .1, status: 'COMPLETED' }], relabelBindings: [], } } beforeEach(() => { can.mockReset().mockReturnValue(true); vi.mocked(getWarehouseTransfer).mockReset(); vi.mocked(getWarehouseTransferCartonLabel).mockReset(); vi.mocked(increaseWarehouseTransferForecast).mockReset() }) afterEach(() => vi.restoreAllMocks()) describe('转运预报与实际签收明细', () => { it('keeps historical receipt totals after discard or shipment and leaves unknown declaration differences blank', async () => { const value = fixture() value.items[0]!.quantity = null value.cartons[0]!.status = 'DISCARD_PENDING' value.cartons[1]!.status = 'SHIPPED' value.cartons[2]!.status = 'DISCARDED' vi.mocked(getWarehouseTransfer).mockResolvedValue(value) const wrapper = render(); await flushPromises() expect(wrapper.emitted('summary')!.at(-1)![0]).toMatchObject({ boxes: 3, quantity: 27, skus: 2, forecastQuantity: null }) const rows = wrapper.findAllComponents(DataTableStub)[0]!.props('data') expect(rows[0]).toMatchObject({ receivedQuantity: 20, receivedBoxes: 2, quantityDifference: null }) expect(wrapper.findAll('.test-table')[0]!.text()).toContain('未记录') }) it('shows every SKU in a mixed box and counts its physical measurements once', async () => { const value = fixture() value.cartons = [{ ...value.cartons[0]!, quantity: 10, items: [{ transferItemId: '10', skuCode: 'SKU-A', quantity: 3 }, { transferItemId: '20', skuCode: 'SKU-B', quantity: 7 }] }] value.pallets = [value.pallets[0]!] vi.mocked(getWarehouseTransfer).mockResolvedValue(value) const wrapper = render(); await flushPromises() expect(wrapper.emitted('summary')!.at(-1)![0]).toMatchObject({ boxes: 1, skus: 2, quantity: 10, weight: 2.5, volume: .1 }) const comparison = wrapper.findAllComponents(DataTableStub)[0]!.props('data') expect(comparison[0]).toMatchObject({ receivedQuantity: 3, receivedBoxes: 1 }) expect(comparison[1]).toMatchObject({ receivedQuantity: 7, receivedBoxes: 1 }) expect(wrapper.findAll('.test-table')[1]!.text()).toContain('SKU-A × 3 件;SKU-B × 7 件') await wrapper.findAll('button').find(button => button.text() === '查看箱号明细')!.trigger('click') await wrapper.findAll('button').find(button => button.text() === '箱内明细')!.trigger('click') const contents = wrapper.findAll('[data-test="box-dialog"]')[1]!.text() expect(contents).toContain('SKU-A'); expect(contents).toContain('SKU-B') expect(contents).toContain('产品 A'); expect(contents).toContain('产品 B') }) it('keeps declarations separate and totals only counted boxes across pallets', async () => { vi.mocked(getWarehouseTransfer).mockResolvedValue(fixture()) const wrapper = render(); await flushPromises() expect(getWarehouseTransfer).toHaveBeenCalledWith('CUSTOMER', 'WT-SOURCE') const summary = wrapper.emitted('summary')!.at(-1)![0] expect(summary).toMatchObject({ forecastBoxes: 4, boxes: 3, quantity: 27, weight: 10 }) expect((summary as { volume: number }).volume).toBeCloseTo(.3) const rows = wrapper.findAllComponents(DataTableStub)[0]!.props('data') expect(rows[0]).toMatchObject({ quantity: '120', receivedQuantity: 20, receivedBoxes: 2, quantityDifference: -100, boxDifference: -1 }) expect(rows[1]).toMatchObject({ receivedQuantity: 7, quantityDifference: -23, boxDifference: 0 }) const tables = wrapper.findAll('.test-table') expect(tables[0]!.text()).toContain('120') expect(tables[1]!.text()).toContain('SKU-A × 20 件'); expect(tables[1]!.text()).toContain('SKU-B × 7 件') expect(tables).toHaveLength(2) await wrapper.findAll('button').find(button => button.text() === '查看箱号明细')!.trigger('click') expect(wrapper.findAll('.test-table')[2]!.text()).not.toContain('BOX-MISSING') await wrapper.findAll('button').find(button => button.text() === '箱内明细')!.trigger('click') const box = wrapper.findAll('[data-test="box-dialog"]')[1]! expect(box.text()).toContain('BOX-1'); expect(box.text()).toContain('产品 A'); expect(box.text()).toContain('"quantity":"8"') expect(box.text()).not.toContain('120') }) it('shows zero receipts before signing without treating forecast cartons as actual receipts', async () => { const value = fixture(); value.pallets = []; value.cartons.forEach(box => { box.status = 'EXPECTED' }) vi.mocked(getWarehouseTransfer).mockResolvedValue(value) const wrapper = render(); await flushPromises() expect(wrapper.emitted('summary')!.at(-1)![0]).toMatchObject({ boxes: 0, quantity: 0 }) expect(wrapper.findAll('.test-table')[0]!.text()).toContain('待签收') expect(wrapper.findAll('.test-table')).toHaveLength(2) expect(wrapper.findAll('button').find(button => button.text() === '查看箱号明细')!.attributes('disabled')).toBeDefined() }) it('shows signed positive and zero differences against each forecast row', async () => { const value = fixture(); value.items[0]!.quantity = 10; value.items[0]!.packageCount = 2 vi.mocked(getWarehouseTransfer).mockResolvedValue(value) const wrapper = render(); await flushPromises() const table = wrapper.findAll('.test-table')[0]! expect(table.text()).toContain('+10') expect(table.findAll('.receipt-difference').map(cell => cell.text())).toEqual(['+10', '-23']) expect(wrapper.findAllComponents(DataTableStub)[0]!.props('data')[0]).toMatchObject({ quantityDifference: 10, boxDifference: 0 }) }) it('prints server label HTML with the selected customer and source reference', async () => { vi.mocked(getWarehouseTransfer).mockResolvedValue(fixture()) vi.mocked(getWarehouseTransferCartonLabel).mockResolvedValue({ transferNo: 'WT-SOURCE', cartons: fixture().cartons, printableHtml: 'SERVER-BOX-LABEL' }) const popup = { opener: window, document: { open: vi.fn(), write: vi.fn(), close: vi.fn() }, focus: vi.fn(), print: vi.fn(), close: vi.fn() } vi.spyOn(window, 'open').mockReturnValue(popup as unknown as Window) const wrapper = render(); await flushPromises() await wrapper.findAll('button').find(button => button.text() === '打印箱唛')!.trigger('click'); await flushPromises() expect(getWarehouseTransferCartonLabel).toHaveBeenCalledExactlyOnceWith('CUSTOMER', 'WT-SOURCE') expect(popup.document.write).toHaveBeenCalledWith('SERVER-BOX-LABEL') expect(popup.print).toHaveBeenCalledOnce() expect(popup.opener).toBeNull() }) it('closes the print window and leaves receipt details available when labels fail', async () => { vi.mocked(getWarehouseTransfer).mockResolvedValue(fixture()) vi.mocked(getWarehouseTransferCartonLabel).mockRejectedValue(new Error('标签暂不可用')) const popup = { close: vi.fn(), print: vi.fn() } vi.spyOn(window, 'open').mockReturnValue(popup as unknown as Window) const wrapper = render(); await flushPromises() await wrapper.findAll('button').find(button => button.text() === '打印箱唛')!.trigger('click'); await flushPromises() expect(popup.close).toHaveBeenCalledOnce(); expect(popup.print).not.toHaveBeenCalled() expect(wrapper.text()).toContain('箱唛加载失败,请重试') expect(wrapper.text()).toContain('签收箱号明细(3 箱)') }) it('does not request another order when the source reference is missing', async () => { const wrapper = render(''); await flushPromises() expect(getWarehouseTransfer).not.toHaveBeenCalled() expect(wrapper.text()).toContain('缺少客户或来源转运单号') }) it('discards a stale response after switching transfers', async () => { let resolveOld!: (value: WarehouseTransferDetailDto) => void vi.mocked(getWarehouseTransfer).mockImplementationOnce(() => new Promise(resolve => { resolveOld = resolve })).mockResolvedValueOnce({ ...fixture(), transferNo: 'WT-NEW', packageCount: 9 }) const wrapper = render(); await wrapper.setProps({ transferNo: 'WT-NEW' }); await flushPromises() resolveOld(fixture()); await flushPromises() expect(wrapper.emitted('summary')!.at(-1)![0]).toMatchObject({ forecastBoxes: 9 }) }) }) // 完成原因直接展示,多个箱保存同一原因时只显示一次。 it('displays saved discrepancy reason once without opening box details', async () => { const value = fixture() value.cartons.forEach(box => { box.discrepancyReason = '短收一箱,客户漏发' }) vi.mocked(getWarehouseTransfer).mockResolvedValue(value) const wrapper = render(); await flushPromises() expect(wrapper.findAll('.receipt-discrepancy-reason')).toHaveLength(1) expect(wrapper.find('.receipt-discrepancy-reason').text()).toBe('实收差异原因:短收一箱,客户漏发') }) /** 平台只填新增箱数,保留SKU预报并按原箱数校验。 */ it('平台可补整单预报箱数,无需选择SKU', async () => { const value = { ...fixture(), inboundCode: 'WT-SOURCE', status: 'WAIT_PDA_RECEIVING' } vi.mocked(getWarehouseTransfer).mockResolvedValue(value) vi.mocked(increaseWarehouseTransferForecast).mockResolvedValue({ ...value, packageCount: 6 }) const wrapper = render(); await flushPromises() await wrapper.findAll('button').find(button => button.text() === '增加预报箱数')!.trigger('click') const number = wrapper.findComponent({ name: 'ElInputNumber' }); await number.vm.$emit('update:modelValue', 2) await wrapper.findAll('button').find(button => button.text() === '确认增加')!.trigger('click'); await flushPromises() expect(increaseWarehouseTransferForecast).toHaveBeenCalledWith('CUSTOMER', 'WT-SOURCE', { expectedPackageCount: 4, packageCount: 6 }) expect(wrapper.text()).toContain('预报箱数 6 箱') }) async function nativeReceipt() { const wrapper = mount(WarehouseTransferReceiptPanel, { attachTo: document.body, props: { customerId: 'CUSTOMER', transferNo: 'WT-SOURCE' }, global: { plugins: [ElementPlus], stubs: { transition: false } } }) await flushPromises() return wrapper } function popup(wrapper: VueWrapper, title: string) { return wrapper.findAllComponents(AppDialog).find(dialog => dialog.props('title').startsWith(title))! } async function openNativeForecast() { const wrapper = await nativeReceipt() await wrapper.findAll('button').find(button => button.text() === '增加预报箱数')!.trigger('click'); await flushPromises() return { wrapper, dialog: popup(wrapper, '增加预报箱数') } } function forecastFixture() { return { ...fixture(), inboundCode: 'WT-SOURCE', status: 'WAIT_PDA_RECEIVING' } } describe('receipt native popup integration', () => { it('keeps counted boxes and mixed SKU contents through two independent fullscreen frames', async () => { const value = fixture() value.cartons[0] = { ...value.cartons[0]!, quantity: 10, discrepancyReason: '少收两件,客户确认', items: [{ transferItemId: '10', skuCode: 'SKU-A', quantity: 3 }, { transferItemId: '20', skuCode: 'SKU-B', quantity: 7 }] } vi.mocked(getWarehouseTransfer).mockResolvedValue(value) const wrapper = await nativeReceipt() await wrapper.findAll('button').find(button => button.text() === '查看箱号明细')!.trigger('click'); await flushPromises() const cartonsDialog = popup(wrapper, '签收箱号明细') expect(cartonsDialog.props('title')).toBe('签收箱号明细 · WT-SOURCE') expect(cartonsDialog.get('[aria-label="已清点箱号"]').findAll('.el-table__body-wrapper .el-table__row')).toHaveLength(3) expect(cartonsDialog.text()).not.toContain('BOX-MISSING') await cartonsDialog.get('button[aria-label="展开全屏"]').trigger('click'); await flushPromises() await cartonsDialog.findAll('.el-table__body-wrapper button').find(button => button.text() === '箱内明细')!.trigger('click'); await flushPromises() const boxDialog = popup(wrapper, '箱内清点明细') expect(boxDialog.props('title')).toBe('箱内清点明细 · BOX-1') await boxDialog.get('button[aria-label="展开全屏"]').trigger('click'); await flushPromises() expect(boxDialog.findComponent({ name: 'ElDialog' }).props('fullscreen')).toBe(true) expect(boxDialog.findComponent(DataTable).props('data')).toEqual([{ sku: 'SKU-A', name: '产品 A', quantity: 3 }, { sku: 'SKU-B', name: '产品 B', quantity: 7 }]) expect(boxDialog.text()).toContain('少收两件,客户确认') expect(boxDialog.get('[aria-label="当前签收箱"]').text()).toContain('2026-10-15') await boxDialog.get('.el-dialog__headerbtn').trigger('click'); await new Promise(resolve => setTimeout(resolve, 80)); await flushPromises() expect(boxDialog.props('modelValue')).toBe(false) expect(cartonsDialog.props('modelValue')).toBe(true) expect(cartonsDialog.findComponent({ name: 'ElDialog' }).props('fullscreen')).toBe(true) expect(getWarehouseTransfer).toHaveBeenCalledExactlyOnceWith('CUSTOMER', 'WT-SOURCE') }) it('retains every counted box in the main dialog body without a nested height cap', async () => { const value = fixture() value.cartons = Array.from({ length: 30 }, (_, index) => ({ ...value.cartons[0]!, cartonMark: `BOX-${index + 1}`, cartonIndex: index + 1, totalCartons: 30 })) vi.mocked(getWarehouseTransfer).mockResolvedValue(value) const wrapper = await nativeReceipt() await wrapper.findAll('button').find(button => button.text() === '查看箱号明细')!.trigger('click'); await flushPromises() const dialog = popup(wrapper, '签收箱号明细') expect(dialog.get('[aria-label="实收箱数核对"]').text()).toContain('30 箱') expect(dialog.findAll('.el-table__body-wrapper .el-table__row')).toHaveLength(30) expect(dialog.text()).toContain('BOX-30') expect(dialog.findComponent(DataTable).props('maxHeight')).toBe('') expect((dialog.get('.el-table__body-wrapper .el-scrollbar__wrap').element as HTMLElement).style.maxHeight).toBe('') }) it('freezes the original forecast count through fullscreen and sends only the scoped count command', async () => { const value = forecastFixture() vi.mocked(getWarehouseTransfer).mockResolvedValue(value) vi.mocked(increaseWarehouseTransferForecast).mockResolvedValue({ ...value, packageCount: 6 }) const { wrapper, dialog } = await openNativeForecast() expect(dialog.props('title')).toBe('增加预报箱数 · WT-SOURCE') expect(dialog.get('[aria-label="预报箱数核对"]').findAll('input')).toHaveLength(0) dialog.findComponent({ name: 'ElInputNumber' }).vm.$emit('update:modelValue', 2); await flushPromises() await dialog.get('button[aria-label="展开全屏"]').trigger('click'); await flushPromises() expect(dialog.get('[aria-label="预报箱数核对"]').text()).toContain('增加后6 箱') expect(dialog.findComponent({ name: 'ElInputNumber' }).props('modelValue')).toBe(2) await dialog.findAll('button').find(button => button.text() === '确认增加')!.trigger('click'); await flushPromises() expect(increaseWarehouseTransferForecast).toHaveBeenCalledExactlyOnceWith('CUSTOMER', 'WT-SOURCE', { expectedPackageCount: 4, packageCount: 6 }) expect(dialog.props('modelValue')).toBe(false) expect(wrapper.text()).toContain('预报箱数 6 箱') }) it('locks pending edits and closing, preserves the failed count and retries the same snapshot', async () => { const value = forecastFixture() vi.mocked(getWarehouseTransfer).mockResolvedValue(value) let reject!: (reason: unknown) => void vi.mocked(increaseWarehouseTransferForecast).mockReturnValueOnce(new Promise((_, no) => { reject = no })).mockResolvedValueOnce({ ...value, packageCount: 6 }) const { dialog } = await openNativeForecast() dialog.findComponent({ name: 'ElInputNumber' }).vm.$emit('update:modelValue', 2); await flushPromises() dialog.vm.$emit('confirm'); dialog.vm.$emit('confirm'); await flushPromises() expect(increaseWarehouseTransferForecast).toHaveBeenCalledTimes(1) expect(dialog.findComponent({ name: 'ElDialog' }).props()).toMatchObject({ showClose: false, closeOnPressEscape: false }) expect(dialog.get('input[placeholder="请输入新增箱数"]').attributes('disabled')).toBeDefined() expect(dialog.findAll('button').find(button => button.text() === '取消')!.attributes('disabled')).toBeDefined() reject(new ApiResponseError('箱数已被其他操作修改', 'A0409', 409, null, 'forecast-frame-request')); await flushPromises() expect(dialog.props('modelValue')).toBe(true) expect(dialog.findComponent({ name: 'ElInputNumber' }).props('modelValue')).toBe(2) expect(dialog.text()).toContain('forecast-frame-request') expect(dialog.get('[aria-label="预报箱数核对"]').text()).toContain('原预报箱数4 箱') await dialog.findAll('button').find(button => button.text() === '重试本次增加')!.trigger('click'); await flushPromises() expect(vi.mocked(increaseWarehouseTransferForecast).mock.calls).toEqual([ ['CUSTOMER', 'WT-SOURCE', { expectedPackageCount: 4, packageCount: 6 }], ['CUSTOMER', 'WT-SOURCE', { expectedPackageCount: 4, packageCount: 6 }], ]) }) it('keeps whole-box and 5000-box limits at both the frame and submit handler', async () => { const value = { ...forecastFixture(), packageCount: 4999 } vi.mocked(getWarehouseTransfer).mockResolvedValue(value) vi.mocked(increaseWarehouseTransferForecast).mockResolvedValue({ ...value, packageCount: 5000 }) const { dialog } = await openNativeForecast() const input = dialog.findComponent({ name: 'ElInputNumber' }) expect(input.props('max')).toBe(1) for (const count of [0, -1, 1.5, Number.NaN, 2]) { input.vm.$emit('update:modelValue', count) dialog.vm.$emit('confirm'); await flushPromises() expect(increaseWarehouseTransferForecast).not.toHaveBeenCalled() input.vm.$emit('update:modelValue', 1); await flushPromises() } input.vm.$emit('update:modelValue', 1); await flushPromises() expect(dialog.props('confirmDisabled')).toBe(false) dialog.vm.$emit('confirm'); await flushPromises() expect(increaseWarehouseTransferForecast).toHaveBeenCalledExactlyOnceWith('CUSTOMER', 'WT-SOURCE', { expectedPackageCount: 4999, packageCount: 5000 }) }) it('resets a cancelled draft and its failure context when reopened', async () => { vi.mocked(getWarehouseTransfer).mockResolvedValue(forecastFixture()) vi.mocked(increaseWarehouseTransferForecast).mockRejectedValue(new ApiResponseError('增加失败', 'A0503', 503, null, 'old-forecast-request')) const { wrapper, dialog } = await openNativeForecast() dialog.findComponent({ name: 'ElInputNumber' }).vm.$emit('update:modelValue', 2); await flushPromises() dialog.vm.$emit('confirm'); await flushPromises() expect(dialog.text()).toContain('old-forecast-request') await dialog.findAll('button').find(button => button.text() === '取消')!.trigger('click'); await flushPromises() await wrapper.findAll('button').find(button => button.text() === '增加预报箱数')!.trigger('click'); await flushPromises() expect(dialog.findComponent({ name: 'ElInputNumber' }).props('modelValue')).toBe(1) expect(dialog.findComponent(LoadErrorState).exists()).toBe(false) expect(increaseWarehouseTransferForecast).toHaveBeenCalledTimes(1) }) it('closes the old forecast frame on a scope change and ignores a late failure', async () => { vi.mocked(getWarehouseTransfer).mockResolvedValueOnce(forecastFixture()).mockResolvedValueOnce({ ...forecastFixture(), transferNo: 'WT-NEW', inboundCode: 'WT-NEW', packageCount: 8 }) let reject!: (reason: unknown) => void vi.mocked(increaseWarehouseTransferForecast).mockReturnValueOnce(new Promise((_, no) => { reject = no })) const { wrapper, dialog } = await openNativeForecast() dialog.findComponent({ name: 'ElInputNumber' }).vm.$emit('update:modelValue', 2); await flushPromises() dialog.vm.$emit('confirm'); await flushPromises() await wrapper.setProps({ customerId: 'CUSTOMER-NEW', transferNo: 'WT-NEW' }); await flushPromises() expect(dialog.props('modelValue')).toBe(false) reject(new ApiResponseError('旧票响应', 'A0503', 503, null, 'stale-forecast-request')); await flushPromises() expect(wrapper.text()).toContain('预报箱数 8 箱') expect(wrapper.text()).not.toContain('stale-forecast-request') await wrapper.findAll('button').find(button => button.text() === '增加预报箱数')!.trigger('click'); await flushPromises() expect(dialog.props('title')).toBe('增加预报箱数 · WT-NEW') expect(dialog.get('[aria-label="预报箱数核对"]').text()).toContain('原预报箱数8 箱') expect(dialog.findComponent({ name: 'ElInputNumber' }).props('modelValue')).toBe(1) expect(increaseWarehouseTransferForecast).toHaveBeenCalledExactlyOnceWith('CUSTOMER', 'WT-SOURCE', { expectedPackageCount: 4, packageCount: 6 }) }) it('keeps forecast modification unavailable without approval permission', async () => { can.mockReturnValue(false) vi.mocked(getWarehouseTransfer).mockResolvedValue(forecastFixture()) const wrapper = await nativeReceipt() expect(wrapper.findAll('button').some(button => button.text() === '增加预报箱数')).toBe(false) popup(wrapper, '增加预报箱数').vm.$emit('confirm'); await flushPromises() expect(increaseWarehouseTransferForecast).not.toHaveBeenCalled() }) })