fix(fleet): ignore stale vehicle list responses
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import { afterEach, expect, it, vi } from 'vitest'
|
||||
import { flushPromises, shallowMount, type VueWrapper } from '@vue/test-utils'
|
||||
import VehiclesView from './VehiclesView.vue'
|
||||
import * as api from '../api/overseasMasterData'
|
||||
|
||||
vi.mock('vue-router', () => ({ useRouter: () => ({ push: vi.fn() }) }))
|
||||
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => true }) }))
|
||||
vi.mock('../api/overseasMasterData', () => ({ listVehicles: vi.fn(), getVehicle: vi.fn(), createVehicle: vi.fn(), updateVehicle: vi.fn(), changeVehicleStatus: vi.fn(), exportVehicles: vi.fn() }))
|
||||
const layout = { template: '<div><slot/></div>' }
|
||||
const table = { name: 'DataTable', props: ['data', 'total', 'loading'], template: '<div><slot name="filters"/><slot name="notices"/><p v-for="row in data">{{ row.plateNumber }}</p></div>' }
|
||||
type Page = Awaited<ReturnType<typeof api.listVehicles>>
|
||||
function deferred() {
|
||||
let resolve!: (page: Page) => void, reject!: (reason: Error) => void
|
||||
const promise = new Promise<Page>((yes, no) => { resolve = yes; reject = no })
|
||||
return { promise, resolve, reject }
|
||||
}
|
||||
function page(plateNumber: string): Page {
|
||||
return { records: [{ id: plateNumber, plateNumber } as Page['records'][number]], total: 1, page: 1, size: 20, pages: 1 }
|
||||
}
|
||||
const wrappers: VueWrapper[] = []
|
||||
function mount() {
|
||||
const wrapper = shallowMount(VehiclesView, { global: { stubs: { AdminPage: layout, ListPageLayout: layout, DataTable: table, QueryToolbar: true, LoadErrorState: true, AppDialog: true } } })
|
||||
wrappers.push(wrapper); return wrapper
|
||||
}
|
||||
afterEach(() => { wrappers.splice(0).forEach(wrapper => wrapper.unmount()); vi.clearAllMocks() })
|
||||
|
||||
it.each(['success', 'failure'] as const)('keeps the selected ownership results after a stale %s', async outcome => {
|
||||
const old = deferred(), current = deferred()
|
||||
vi.mocked(api.listVehicles).mockReturnValueOnce(old.promise).mockReturnValueOnce(current.promise)
|
||||
const wrapper = mount()
|
||||
await wrapper.get('[role="tab"][aria-selected="false"]').trigger('click')
|
||||
expect(api.listVehicles).toHaveBeenLastCalledWith(expect.objectContaining({ ownership: 'DISPATCHED' }))
|
||||
current.resolve(page('EXTERNAL-CURRENT')); await flushPromises()
|
||||
if (outcome === 'success') old.resolve(page('OWNED-STALE'))
|
||||
else old.reject(new Error('old request failed'))
|
||||
await flushPromises()
|
||||
expect(wrapper.text()).toContain('EXTERNAL-CURRENT')
|
||||
expect(wrapper.text()).not.toContain('OWNED-STALE')
|
||||
expect(wrapper.findComponent({ name: 'LoadErrorState' }).exists()).toBe(false)
|
||||
expect(wrapper.findComponent({ name: 'DataTable' }).props('total')).toBe(1)
|
||||
})
|
||||
|
||||
it('keeps loading until the currently selected query finishes', async () => {
|
||||
const old = deferred(), current = deferred()
|
||||
vi.mocked(api.listVehicles).mockReturnValueOnce(old.promise).mockReturnValueOnce(current.promise)
|
||||
const wrapper = mount()
|
||||
await wrapper.get('[role="tab"][aria-selected="false"]').trigger('click')
|
||||
old.resolve(page('OWNED-STALE')); await flushPromises()
|
||||
expect(wrapper.findComponent({ name: 'DataTable' }).props('loading')).toBe(true)
|
||||
expect(wrapper.text()).not.toContain('OWNED-STALE')
|
||||
current.resolve(page('EXTERNAL-CURRENT')); await flushPromises()
|
||||
expect(wrapper.findComponent({ name: 'DataTable' }).props('loading')).toBe(false)
|
||||
expect(api.listVehicles).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import '@/styles/adminDensity.css'
|
||||
import { vehicleStatusLabels } from '../presentation'
|
||||
import { onMounted, reactive, ref, watch } from 'vue'
|
||||
import { onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { Download, Plus } from '@lucide/vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
@@ -15,7 +15,18 @@ const filters=ref<QueryModel>({keyword:'',region:'',status:''});const fields:Que
|
||||
const columns:DataTableColumn[]=[{key:'actions',label:'操作',width:250,fixed:'left',slotName:'actions'},{key:'plateNumber',prop:'plateNumber',label:'车牌',minWidth:130},{key:'model',prop:'model',label:'车型',minWidth:180},{key:'fuelConsumptionPer100Km',prop:'fuelConsumptionPer100Km',label:'油耗(L/100km)',minWidth:130,align:'right'},{key:'mileageKm',prop:'mileageKm',label:'当前公里码(km)',minWidth:140,align:'right'},{key:'driverOrDispatchCompany',prop:'driverOrDispatchCompany',label:'司机 / 外派单位',minWidth:160},{key:'phone',prop:'phone',label:'联系电话',minWidth:145},{key:'maxPalletSpaces',prop:'maxPalletSpaces',label:'最大托位',minWidth:100,align:'right'},{key:'maxLoadKg',prop:'maxLoadKg',label:'最大载重(kg)',minWidth:130,align:'right'},{key:'maxVolumeCbm',prop:'maxVolumeCbm',label:'可用容积(m³)',minWidth:130,align:'right'},{key:'status',label:'状态',width:100,slotName:'status'},{key:'region',label:'区域',width:100,slotName:'region'}]
|
||||
const form=reactive({plateNumber:'',model:'',fuelConsumptionPer100Km:0,mileageKm:0,driverOrDispatchCompany:'',phone:'',region:'OVERSEAS' as VehicleRegion,ownership:'OWNED' as VehicleOwnership,status:'IN_SERVICE' as VehicleStatus,remark:'',recordVersion:0,maxPalletSpaces:null as number|null,maxLoadKg:null as number|null,maxVolumeCbm:null as number|null})
|
||||
function params(){return{page:page.value,size:pageSize.value,keyword:String(filters.value.keyword||'')||undefined,ownership:ownership.value,region:(filters.value.region||undefined) as VehicleRegion|undefined,status:(filters.value.status||undefined) as VehicleStatus|undefined}}
|
||||
async function load(){loading.value=true;error.value=null;try{const r=await listVehicles(params());rows.value=r.records;total.value=r.total}catch(e){rows.value=[];total.value=0;error.value=getApiErrorDetails(e,'车辆加载失败')}finally{loading.value=false}}
|
||||
let querySequence = 0
|
||||
async function load() {
|
||||
const sequence = ++querySequence
|
||||
loading.value = true; error.value = null
|
||||
try {
|
||||
const result = await listVehicles(params())
|
||||
if (sequence === querySequence) { rows.value = result.records; total.value = result.total }
|
||||
} catch (cause) {
|
||||
if (sequence === querySequence) { rows.value = []; total.value = 0; error.value = getApiErrorDetails(cause, '车辆加载失败') }
|
||||
} finally { if (sequence === querySequence) loading.value = false }
|
||||
}
|
||||
onBeforeUnmount(() => { querySequence++ })
|
||||
function search(){if(page.value===1)void load();else page.value=1} function selectOwnership(v:VehicleOwnership){if(ownership.value===v)return;ownership.value=v;search()}
|
||||
function reset(){Object.assign(form,{plateNumber:'',model:'',fuelConsumptionPer100Km:0,mileageKm:0,driverOrDispatchCompany:'',phone:'',region:'OVERSEAS',ownership:ownership.value,status:'IN_SERVICE',remark:'',recordVersion:0,maxPalletSpaces:null,maxLoadKg:null,maxVolumeCbm:null})}
|
||||
function openCreate(){editingId.value='';reset();editorOpen.value=true} async function openEdit(row:VehicleDetail){try{const d=await getVehicle(row.id);editingId.value=d.id;Object.assign(form,{...d,driverOrDispatchCompany:d.driverOrDispatchCompany||'',phone:d.phone||'',remark:d.remark||''});editorOpen.value=true}catch(e){ElMessage.error(masterDataErrorMessage(e,'车辆详情加载失败'))}}
|
||||
|
||||
Reference in New Issue
Block a user