diff --git a/src/modules/overseas-master-data/pages/DriversBehavior.test.ts b/src/modules/overseas-master-data/pages/DriversBehavior.test.ts index 24106b5e..8e12686c 100644 --- a/src/modules/overseas-master-data/pages/DriversBehavior.test.ts +++ b/src/modules/overseas-master-data/pages/DriversBehavior.test.ts @@ -29,7 +29,7 @@ const layout = { template: '
' } const button = { props: ['disabled', 'loading'], template: '' } const dialog = { name: 'AppDialog', props: ['modelValue'], emits: ['confirm', 'update:modelValue'], template: '
' } const input = { name: 'ElInput', props: ['modelValue'], emits: ['update:modelValue'], template: '' } -const table = { name: 'DataTable', props: ['data', 'page', 'pageSize', 'total'], emits: ['update:page', 'update:pageSize'], template: '
{{row.driverNo}}
' } +const table = { name: 'DataTable', props: ['data', 'page', 'pageSize', 'total', 'loading'], emits: ['update:page', 'update:pageSize'], template: '
{{row.driverNo}}
' } const stubs = { AdminPage: layout, ListPageLayout: layout, DetailPageLayout: layout, FormSection: layout, DataTable: table, QueryToolbar: true, RowActions: true, StatusBadge: true, LoadErrorState: true, AppDialog: dialog, DriverEditorDialog: true, DriverLicensePreview: true, ElButton: button, ElForm: layout, ElFormItem: layout, ElInput: input, ElSelect: { ...input, name: 'ElSelect' }, ElOption: true, ElDatePicker: { ...input, name: 'ElDatePicker' } } const wrappers: VueWrapper[] = [] function mount(component: object, props: Record = {}): VueWrapper { @@ -63,6 +63,65 @@ beforeEach(() => { afterEach(() => { wrappers.splice(0).forEach(wrapper => wrapper.unmount()); vi.unstubAllGlobals() }) describe('driver page workflows', () => { + it.each(['success', 'failure'] as const)('keeps B after cached A detail arrives with late %s', async outcome => { + let resolveA!: (value: DriverDetail) => void, rejectA!: (reason: Error) => void + const pendingA = new Promise((resolve, reject) => { resolveA = resolve; rejectA = reject }) + const other = { ...structuredClone(driver), id: '9223372036854775702', driverNo: 'DR-OTHER', details: { ...driver.details, name: '另一司机' } } + vi.mocked(api.getDriver).mockImplementation(id => id === driver.id ? pendingA : Promise.resolve(other)) + const { wrapper, show } = cachedDetails(); await flushPromises() + expect(api.getDriver).toHaveBeenCalledExactlyOnceWith(driver.id) + show(null); await flushPromises(); show(other.id); await flushPromises() + expect(wrapper.text()).toContain('另一司机') + const before = wrapper.text() + if (outcome === 'success') resolveA({ ...structuredClone(driver), details: { ...driver.details, name: '迟到A司机' } }) + else rejectA(new Error('迟到A详情失败')) + await flushPromises() + expect(wrapper.text()).toBe(before) + expect(wrapper.findComponent({ name: 'LoadErrorState' }).exists()).toBe(false) + const fresh = { ...structuredClone(driver), details: { ...driver.details, name: '新请求A司机' } } + vi.mocked(api.getDriver).mockResolvedValueOnce(fresh) + show(null); await flushPromises(); show(driver.id); await flushPromises() + expect(api.getDriver).toHaveBeenCalledTimes(3) + expect(api.getDriver).toHaveBeenLastCalledWith(driver.id) + expect(wrapper.text()).toContain('新请求A司机') + expect(wrapper.text()).not.toContain('迟到A') + expect(wrapper.findComponent({ name: 'LoadErrorState' }).exists()).toBe(false) + }) + + it.each(['success', 'failure'] as const)('keeps B history after cached A history arrives with late %s', async outcome => { + type HistoryPage = Awaited> + let resolveA!: (value: HistoryPage) => void, rejectA!: (reason: Error) => void + const pendingA = new Promise((resolve, reject) => { resolveA = resolve; rejectA = reject }) + const other = { ...structuredClone(driver), id: '9223372036854775702', driverNo: 'DR-OTHER', details: { ...driver.details, name: '另一司机' } } + const history = (snapshot: DriverDetail, total: number): HistoryPage => ({ records: [{ recordVersion: snapshot.recordVersion, action: 'CREATE', reason: null, operatorId: 'test-operator', occurredAt: '2026-10-02T12:00:00', snapshot }], page: 1, size: 20, total, pages: 1 }) + const bHistory = history(other, 15), aHistory = history(driver, 3) + vi.mocked(api.getDriver).mockImplementation(async id => structuredClone(id === driver.id ? driver : other)) + vi.mocked(api.listDriverHistory).mockImplementation(id => id === driver.id ? pendingA : Promise.resolve(bHistory)) + const { wrapper, show } = cachedDetails(); await flushPromises() + await findButton(wrapper, '查看操作历史').trigger('click'); await flushPromises() + expect(api.listDriverHistory).toHaveBeenCalledExactlyOnceWith(driver.id, 1, 20) + show(null); await flushPromises(); show(other.id); await flushPromises() + await findButton(wrapper, '查看操作历史').trigger('click'); await flushPromises() + const before = wrapper.text(), tableB = wrapper.findComponent({ name: 'DataTable' }) + expect(tableB.props('data')).toEqual(bHistory.records); expect(tableB.props('total')).toBe(15); expect(tableB.props('loading')).toBe(false) + if (outcome === 'success') resolveA(aHistory) + else rejectA(new Error('迟到A历史失败')) + await flushPromises() + expect(wrapper.text()).toBe(before) + expect(tableB.props('data')).toEqual(bHistory.records); expect(tableB.props('total')).toBe(15); expect(tableB.props('loading')).toBe(false) + expect(wrapper.findComponent({ name: 'LoadErrorState' }).exists()).toBe(false) + show(null); await flushPromises(); show(driver.id); await flushPromises() + expect(wrapper.findComponent({ name: 'DataTable' }).exists()).toBe(false) + expect(api.listDriverHistory).toHaveBeenCalledTimes(2) + vi.mocked(api.listDriverHistory).mockResolvedValueOnce(aHistory) + await findButton(wrapper, '查看操作历史').trigger('click'); await flushPromises() + expect(api.listDriverHistory).toHaveBeenCalledTimes(3) + expect(api.listDriverHistory).toHaveBeenLastCalledWith(driver.id, 1, 20) + expect(wrapper.findComponent({ name: 'DataTable' }).props('data')).toEqual(aHistory.records) + expect(wrapper.findComponent({ name: 'DataTable' }).props('total')).toBe(3) + expect(wrapper.findComponent({ name: 'LoadErrorState' }).exists()).toBe(false) + }) + it('does not fetch an absent driver on return to list and loads the valid driver on reentry', async () => { const { wrapper, show } = cachedDetails(); await flushPromises() expect(api.getDriver).toHaveBeenCalledExactlyOnceWith(driver.id)