fix(drivers): isolate cached detail requests and reject absent route IDs
This commit is contained in:
@@ -11,8 +11,13 @@ import DriverEditorDialog from '../components/DriverEditorDialog.vue'
|
||||
import DriverLicensePreview from '../components/DriverLicensePreview.vue'
|
||||
|
||||
const security = vi.hoisted(() => ({ allowWrite: true }))
|
||||
const routing = vi.hoisted(() => ({ route: null as null | { name: string; params: { driverId?: string | string[] } } }))
|
||||
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: (code: string) => code.endsWith(':list') || security.allowWrite }) }))
|
||||
vi.mock('vue-router', () => ({ useRouter: () => ({ push: vi.fn() }), useRoute: () => ({ params: { driverId: '9223372036854775701' } }) }))
|
||||
vi.mock('vue-router', async () => {
|
||||
const { reactive } = await import('vue')
|
||||
routing.route = reactive({ name: 'overseas-master-data-driver-detail', params: { driverId: '9223372036854775701' } })
|
||||
return { useRouter: () => ({ push: vi.fn() }), useRoute: () => routing.route }
|
||||
})
|
||||
vi.mock('element-plus', () => ({ ElMessage: { success: vi.fn(), warning: vi.fn(), error: vi.fn() }, ElMessageBox: { confirm: vi.fn().mockResolvedValue(undefined), prompt: vi.fn().mockResolvedValue({ value: '暂停' }) } }))
|
||||
vi.mock('../api/drivers', () => ({ listDrivers: vi.fn(), getDriver: vi.fn(), listDriverHistory: vi.fn(), listDriverWarehouses: vi.fn(), createDriver: vi.fn(), updateDriver: vi.fn(), changeDriverStatus: vi.fn(), reviewDriver: vi.fn(), submitDriver: vi.fn(), getDriverQualification: vi.fn(), uploadDriverLicense: vi.fn(), downloadDriverLicense: vi.fn() }))
|
||||
|
||||
@@ -33,9 +38,22 @@ function mount(component: object, props: Record<string, unknown> = {}): VueWrapp
|
||||
return wrapper
|
||||
}
|
||||
function findButton(wrapper: VueWrapper, text: string) { const found = wrapper.findAll('button').find(value => value.text().includes(text)); expect(found).toBeDefined(); return found! }
|
||||
function cachedDetails() {
|
||||
const showing = ref<string | null>(driver.id)
|
||||
const host = defineComponent({ setup: () => () => h(KeepAlive, null, { default: () => showing.value ? h(DriverDetailView, { key: showing.value }) : h('div', '司机列表') }) })
|
||||
const wrapper = shallowMount(host, { global: { stubs: { ...stubs, KeepAlive: false, DriverDetailView: false }, directives: { loading: () => undefined } } })
|
||||
wrappers.push(wrapper)
|
||||
return { wrapper, show(id: string | null) {
|
||||
routing.route!.name = id ? 'overseas-master-data-driver-detail' : 'overseas-master-data-drivers'
|
||||
routing.route!.params = id ? { driverId: id } : {}
|
||||
showing.value = id
|
||||
} }
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks(); security.allowWrite = true
|
||||
routing.route!.name = 'overseas-master-data-driver-detail'
|
||||
routing.route!.params = { driverId: driver.id }
|
||||
vi.mocked(api.listDrivers).mockResolvedValue({ records: [structuredClone(driver)], page: 1, size: 20, total: 24, pages: 2, summary: { total: 24, owned: 20, dispatched: 4, expiringSoon: 3, expired: 1 } })
|
||||
vi.mocked(api.getDriver).mockResolvedValue(structuredClone(driver))
|
||||
vi.mocked(api.listDriverWarehouses).mockResolvedValue([{ id: '92', code: 'WH-US', name: '洛杉矶仓', region: 'OVERSEAS' }])
|
||||
@@ -45,6 +63,61 @@ beforeEach(() => {
|
||||
afterEach(() => { wrappers.splice(0).forEach(wrapper => wrapper.unmount()); vi.unstubAllGlobals() })
|
||||
|
||||
describe('driver page workflows', () => {
|
||||
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)
|
||||
show(null)
|
||||
await flushPromises()
|
||||
expect(api.getDriver).toHaveBeenCalledTimes(1)
|
||||
const next = { ...structuredClone(driver), recordVersion: 3, details: { ...driver.details, name: '当前司机' } }
|
||||
vi.mocked(api.getDriver).mockResolvedValueOnce(next)
|
||||
show(next.id)
|
||||
await flushPromises()
|
||||
expect(api.getDriver).toHaveBeenCalledTimes(2)
|
||||
expect(api.getDriver).toHaveBeenLastCalledWith(next.id)
|
||||
expect(wrapper.text()).toContain('当前司机')
|
||||
})
|
||||
|
||||
it('loads only the active driver instance when two parameter-keyed detail pages are cached', async () => {
|
||||
const other = { ...structuredClone(driver), id: '9223372036854775702', driverNo: 'DR-OTHER', details: { ...driver.details, name: '另一司机' } }
|
||||
vi.mocked(api.getDriver).mockImplementation(async id => structuredClone(id === driver.id ? driver : other))
|
||||
const { wrapper, show } = cachedDetails(); await flushPromises()
|
||||
const first = wrapper.findComponent(DriverDetailView)
|
||||
expect(api.getDriver).toHaveBeenCalledExactlyOnceWith(driver.id)
|
||||
show(other.id); await flushPromises()
|
||||
expect(api.getDriver).toHaveBeenCalledTimes(2)
|
||||
expect(api.getDriver).toHaveBeenLastCalledWith(other.id)
|
||||
expect(first.text()).not.toContain('另一司机')
|
||||
expect(wrapper.text()).toContain('另一司机')
|
||||
show(driver.id); await flushPromises()
|
||||
expect(api.getDriver).toHaveBeenCalledTimes(3)
|
||||
expect(api.getDriver).toHaveBeenLastCalledWith(driver.id)
|
||||
expect(wrapper.text()).toContain('测试司机')
|
||||
show(null); await flushPromises()
|
||||
expect(api.getDriver).toHaveBeenCalledTimes(3)
|
||||
show(other.id); await flushPromises()
|
||||
expect(api.getDriver).toHaveBeenCalledTimes(4)
|
||||
expect(api.getDriver).toHaveBeenLastCalledWith(other.id)
|
||||
})
|
||||
|
||||
it('invalidates a pending detail response when the route leaves even before params are cleared', async () => {
|
||||
let finish!: (value: DriverDetail) => void
|
||||
vi.mocked(api.getDriver).mockReturnValueOnce(new Promise(resolve => { finish = resolve }))
|
||||
const wrapper = mount(DriverDetailView); await flushPromises()
|
||||
routing.route!.name = 'overseas-master-data-drivers'
|
||||
await flushPromises()
|
||||
finish({ ...structuredClone(driver), details: { ...driver.details, name: '离页迟到司机' } })
|
||||
await flushPromises()
|
||||
expect(api.getDriver).toHaveBeenCalledTimes(1)
|
||||
expect(wrapper.text()).not.toContain('离页迟到司机')
|
||||
})
|
||||
|
||||
it.each([{ label: 'missing', id: undefined }, { label: 'empty', id: '' }, { label: 'array', id: [driver.id] }])('does not fabricate a detail ID from $label route params', async ({ id }) => {
|
||||
routing.route!.params = { driverId: id }
|
||||
mount(DriverDetailView); await flushPromises()
|
||||
expect(api.getDriver).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('refreshes a cached list once on return and retains applied filters and pagination', async () => {
|
||||
const showingList = ref(true)
|
||||
const host = defineComponent({ setup: () => () => h(KeepAlive, null, { default: () => showingList.value ? h(DriversView) : h('div', '司机详情') }) })
|
||||
|
||||
Reference in New Issue
Block a user