import { afterEach, beforeEach, expect, it, vi } from 'vitest' import { shallowMount, flushPromises, type VueWrapper } from '@vue/test-utils' import FleetQuoteImport from './FleetQuoteImport.vue' import { ApiResponseError } from '@/shared/api/http' import { fleetQuoteCsvHeader } from '../fleetQuoteCsv' import * as api from '../api/fleetQuotes' vi.mock('../api/fleetQuotes', async original => ({ ...(await original()), importFleetQuotes: vi.fn() })) const wrappers: VueWrapper[] = [] async function mount() { const wrapper = shallowMount(FleetQuoteImport, { props: { modelValue: true, kind: 'SALE' }, global: { stubs: { AppDialog: { template: '
' }, FleetQuotePartySelect: { name: 'FleetQuotePartySelect', props: ['modelValue', 'kind', 'disabled'], emits: ['update:modelValue'], template: '
' }, ElButton: { props: ['disabled', 'loading'], template: '' }, ElAlert: { props: ['title'], template: '

{{ title }}

' }, ApiErrorMeta: true, } } }); wrappers.push(wrapper) wrapper.findComponent({ name: 'FleetQuotePartySelect' }).vm.$emit('update:modelValue', '101'); await flushPromises(); return wrapper } async function pick(wrapper: VueWrapper, city = 'QA-FIRST', size = 100, text?: () => Promise) { Object.defineProperty(wrapper.find('input[type="file"]').element, 'files', { configurable: true, value: [{ size, text: text || (async () => `${fleetQuoteCsvHeader.join(',')}\n${city},QA-DEST,10,USD,,0,12,2026-01-01,2100-01-01,`) }] }) await wrapper.find('input[type="file"]').trigger('change'); await flushPromises() } async function save(wrapper: VueWrapper) { await wrapper.findAll('button').find(b => ['确认导入', '按原请求重试'].includes(b.text()))!.trigger('click'); await flushPromises() } beforeEach(() => vi.clearAllMocks()); afterEach(() => wrappers.splice(0).forEach(w => w.unmount())) it('definite import rejection unlocks file selection and corrected rows use a new key', async () => { const wrapper = await mount(); await pick(wrapper) vi.mocked(api.importFleetQuotes).mockRejectedValueOnce(new ApiResponseError('组合重复', 'A0409', 200)); await save(wrapper) const first = vi.mocked(api.importFleetQuotes).mock.calls[0]! expect(wrapper.find('input[type="file"]').attributes('disabled')).toBeUndefined() await pick(wrapper, 'QA-CORRECTED'); vi.mocked(api.importFleetQuotes).mockResolvedValue({ importedRows: 1 }); await save(wrapper) const second = vi.mocked(api.importFleetQuotes).mock.calls[1]! expect(second[0]).not.toBe(first[0]); expect(second[1][0]?.originCity).toBe('QA-CORRECTED') }) it('unknown result preserves original purpose, key and rows across closing and category changes', async () => { const wrapper = await mount(); await pick(wrapper) vi.mocked(api.importFleetQuotes).mockRejectedValueOnce(new Error('network')); await save(wrapper) const call = vi.mocked(api.importFleetQuotes).mock.calls[0]! const first = [call[0], call[1].map(row => ({ ...row }))] await wrapper.setProps({ modelValue: false, kind: 'PURCHASE' }); await wrapper.setProps({ modelValue: true }); await flushPromises() expect(wrapper.findComponent({ name: 'FleetQuotePartySelect' }).props('kind')).toBe('SALE') expect(wrapper.find('input[type="file"]').attributes('disabled')).toBeDefined() vi.mocked(api.importFleetQuotes).mockResolvedValue({ importedRows: 1 }); await save(wrapper) expect(vi.mocked(api.importFleetQuotes).mock.calls[1]).toEqual(first) }) it('selecting an oversized replacement file cannot accidentally submit the previous valid file', async () => { const wrapper = await mount(); await pick(wrapper); await pick(wrapper, 'TOO-LARGE', 500001) expect(wrapper.findAll('button').find(b => b.text() === '确认导入')!.attributes('disabled')).toBeDefined() expect(wrapper.text()).not.toContain('QA-FIRST'); expect(api.importFleetQuotes).not.toHaveBeenCalled() }) it('a late old file read cannot replace the most recently selected CSV', async () => { const wrapper = await mount(); let release!: (value: string) => void const slow = new Promise(resolve => { release = resolve }) await pick(wrapper, 'SLOW', 100, () => slow) await pick(wrapper, 'QA-LATEST') release(`${fleetQuoteCsvHeader.join(',')}\nQA-OLD,QA-DEST,10,USD,,0,12,2026-01-01,2100-01-01,`); await flushPromises() expect(wrapper.text()).toContain('QA-LATEST'); expect(wrapper.text()).not.toContain('QA-OLD') vi.mocked(api.importFleetQuotes).mockResolvedValue({ importedRows: 1 }); await save(wrapper) expect(vi.mocked(api.importFleetQuotes).mock.calls[0]?.[1][0]?.originCity).toBe('QA-LATEST') })