test(admin): cover late driver detail and history responses

This commit is contained in:
2026-10-05 18:39:25 +08:00
parent cb5f0d1bec
commit 899978e53d
@@ -29,7 +29,7 @@ const layout = { template: '<div><slot name="header"/><slot/></div>' }
const button = { props: ['disabled', 'loading'], template: '<button :disabled="disabled || loading"><slot/></button>' } const button = { props: ['disabled', 'loading'], template: '<button :disabled="disabled || loading"><slot/></button>' }
const dialog = { name: 'AppDialog', props: ['modelValue'], emits: ['confirm', 'update:modelValue'], template: '<div v-if="modelValue" data-test="dialog"><slot/><button data-test="confirm" @click="$emit(\'confirm\')">保存</button></div>' } const dialog = { name: 'AppDialog', props: ['modelValue'], emits: ['confirm', 'update:modelValue'], template: '<div v-if="modelValue" data-test="dialog"><slot/><button data-test="confirm" @click="$emit(\'confirm\')">保存</button></div>' }
const input = { name: 'ElInput', props: ['modelValue'], emits: ['update:modelValue'], template: '<input :value="modelValue" @input="$emit(\'update:modelValue\', $event.target.value)" />' } const input = { name: 'ElInput', props: ['modelValue'], emits: ['update:modelValue'], template: '<input :value="modelValue" @input="$emit(\'update:modelValue\', $event.target.value)" />' }
const table = { name: 'DataTable', props: ['data', 'page', 'pageSize', 'total'], emits: ['update:page', 'update:pageSize'], template: '<div><slot name="toolbar-actions"/><div v-for="row in data" :key="row.id || row.recordVersion"><span>{{row.driverNo}}</span><slot name="actions" :row="row"/><slot name="file" :row="row"/></div></div>' } const table = { name: 'DataTable', props: ['data', 'page', 'pageSize', 'total', 'loading'], emits: ['update:page', 'update:pageSize'], template: '<div><slot name="toolbar-actions"/><div v-for="row in data" :key="row.id || row.recordVersion"><span>{{row.driverNo}}</span><slot name="actions" :row="row"/><slot name="file" :row="row"/></div></div>' }
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 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[] = [] const wrappers: VueWrapper[] = []
function mount(component: object, props: Record<string, unknown> = {}): VueWrapper { function mount(component: object, props: Record<string, unknown> = {}): VueWrapper {
@@ -63,6 +63,65 @@ beforeEach(() => {
afterEach(() => { wrappers.splice(0).forEach(wrapper => wrapper.unmount()); vi.unstubAllGlobals() }) afterEach(() => { wrappers.splice(0).forEach(wrapper => wrapper.unmount()); vi.unstubAllGlobals() })
describe('driver page workflows', () => { 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<DriverDetail>((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<ReturnType<typeof api.listDriverHistory>>
let resolveA!: (value: HistoryPage) => void, rejectA!: (reason: Error) => void
const pendingA = new Promise<HistoryPage>((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 () => { 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() const { wrapper, show } = cachedDetails(); await flushPromises()
expect(api.getDriver).toHaveBeenCalledExactlyOnceWith(driver.id) expect(api.getDriver).toHaveBeenCalledExactlyOnceWith(driver.id)