fix(fleet): mount measured controls after record loads
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
|
||||
import { shallowMount, flushPromises, type VueWrapper } from '@vue/test-utils'
|
||||
import { mount as mountFull, shallowMount, flushPromises, type VueWrapper } from '@vue/test-utils'
|
||||
import { ElForm, ElFormItem, ElInput, ElInputNumber } from 'element-plus'
|
||||
import TripOperationsRecordDialog from './TripOperationsRecordDialog.vue'
|
||||
import * as api from '../api/tripOperations'
|
||||
import { ApiResponseError } from '@/shared/api/http'
|
||||
@@ -9,16 +10,60 @@ const record: api.TripOperationsRecord = { tripNo: 'TR1', recordVersion: '0', de
|
||||
const wrappers: VueWrapper[] = []
|
||||
const slot = { template: '<div><slot/></div>' }
|
||||
const input = { name: 'ElInputNumber', props: ['modelValue'], emits: ['update:modelValue'], template: '<input :value="modelValue" @input="$emit(\'update:modelValue\',Number($event.target.value))" />' }
|
||||
async function mount(readonly = false) {
|
||||
const wrapper = shallowMount(TripOperationsRecordDialog, { props: { modelValue: false, row, readonly }, global: { stubs: {
|
||||
AppDialog: { props: ['modelValue'], template: '<div v-if="modelValue"><slot/><slot name="footer"/></div>' }, ElForm: slot, ElFormItem: slot,
|
||||
ElTabs: { name: 'ElTabs', props: ['modelValue'], emits: ['update:modelValue'], template: '<div><slot/></div>' }, ElTabPane: slot, ElInputNumber: input,
|
||||
ElInput: { ...input, name: 'ElInput' }, ElButton: { props: ['disabled', 'loading'], template: '<button :disabled="disabled || loading"><slot/></button>' },
|
||||
async function mount(readonly = false, realControls = false) {
|
||||
const wrapper = (realControls ? mountFull : shallowMount)(TripOperationsRecordDialog, { props: { modelValue: false, row, readonly }, global: { components: { ElForm, ElFormItem, ElInput, ElInputNumber }, stubs: {
|
||||
AppDialog: { props: ['modelValue'], template: '<div v-if="modelValue"><slot/><slot name="footer"/></div>' }, ElForm: realControls ? false : slot, ElFormItem: realControls ? false : slot,
|
||||
ElTabs: { name: 'ElTabs', props: ['modelValue'], emits: ['update:modelValue'], template: '<div><slot/></div>' }, ElTabPane: slot, ElInputNumber: realControls ? false : input,
|
||||
ElInput: realControls ? false : { ...input, name: 'ElInput' }, ElButton: { props: ['disabled', 'loading'], template: '<button :disabled="disabled || loading"><slot/></button>' },
|
||||
ElAlert: { props: ['title'], template: '<div>{{ title }}<slot/></div>' }, ApiErrorMeta: true, DataTable: true,
|
||||
} } }); wrappers.push(wrapper); await wrapper.setProps({ modelValue: true }); await flushPromises(); return wrapper
|
||||
}
|
||||
beforeEach(() => { vi.clearAllMocks(); vi.mocked(api.getTripOperationsRecord).mockResolvedValue(record); vi.mocked(api.listTripOperationsRecordHistory).mockResolvedValue({ records: [], total: 0, page: 1, size: 20, pages: 0 }) })
|
||||
afterEach(() => wrappers.splice(0).forEach(w => w.unmount()))
|
||||
it.each([[null, 0], [0, null]])('delayed records keep distance %s and fuel %s editable in real number controls', async (distance, fuel) => {
|
||||
let resolve!: (value: api.TripOperationsRecord) => void
|
||||
vi.mocked(api.getTripOperationsRecord).mockReturnValue(new Promise(done => { resolve = done }))
|
||||
const wrapper = await mount(false, true)
|
||||
expect(wrapper.findComponent(ElForm).exists()).toBe(false)
|
||||
expect(wrapper.findAll('button').find(button => button.text() === '保存记录')!.attributes('disabled')).toBeDefined()
|
||||
resolve({ ...record, details: { ...record.details, actualDistanceMiles: distance, actualFuelLitres: fuel } })
|
||||
await flushPromises()
|
||||
const controls = wrapper.findAllComponents(ElInputNumber)
|
||||
expect(controls.map(control => control.props('modelValue'))).toEqual([distance, fuel])
|
||||
const inputs = controls.map(control => control.find('input'))
|
||||
expect(inputs.map(input => (input.element as HTMLInputElement).value)).toEqual([distance === null ? '' : '0.0', fuel === null ? '' : '0.0'])
|
||||
for (const input of inputs) {
|
||||
expect((input.element as HTMLInputElement).disabled).toBe(false)
|
||||
expect((input.element as HTMLInputElement).readOnly).toBe(false)
|
||||
expect(input.attributes('aria-disabled')).toBe('false')
|
||||
}
|
||||
await inputs[0]!.setValue('12.5')
|
||||
await inputs[0]!.trigger('change')
|
||||
expect(controls[0]!.props('modelValue')).toBe(12.5)
|
||||
await inputs[0]!.setValue(distance === null ? '' : '0')
|
||||
await inputs[0]!.trigger('change')
|
||||
expect(controls[0]!.props('modelValue')).toBe(distance)
|
||||
vi.mocked(api.saveTripOperationsRecord).mockResolvedValue(record)
|
||||
await wrapper.findAll('button').find(button => button.text() === '保存记录')!.trigger('click')
|
||||
await flushPromises()
|
||||
expect(vi.mocked(api.saveTripOperationsRecord).mock.calls[0]![1].details).toMatchObject({ actualDistanceMiles: distance, actualFuelLitres: fuel })
|
||||
})
|
||||
it('delayed read-only records keep real number controls disabled', async () => {
|
||||
let resolve!: (value: api.TripOperationsRecord) => void
|
||||
vi.mocked(api.getTripOperationsRecord).mockReturnValue(new Promise(done => { resolve = done }))
|
||||
const wrapper = await mount(true, true)
|
||||
expect(wrapper.findComponent(ElForm).exists()).toBe(false)
|
||||
resolve({ ...record, details: { ...record.details, actualFuelLitres: 0 } })
|
||||
await flushPromises()
|
||||
expect(wrapper.findAllComponents(ElInputNumber).map(control => control.props('modelValue'))).toEqual([null, 0])
|
||||
for (const control of wrapper.findAllComponents(ElInputNumber)) {
|
||||
const input = control.find('input')
|
||||
expect((input.element as HTMLInputElement).disabled).toBe(true)
|
||||
expect(input.attributes('aria-disabled')).toBe('true')
|
||||
}
|
||||
expect(wrapper.text()).not.toContain('保存记录')
|
||||
expect(api.saveTripOperationsRecord).not.toHaveBeenCalled()
|
||||
})
|
||||
it('loads current record on open, history only when selected, and hides writes for readers', async () => {
|
||||
const wrapper = await mount(true)
|
||||
expect(api.getTripOperationsRecord).toHaveBeenCalledExactlyOnceWith('TR1'); expect(api.listTripOperationsRecordHistory).not.toHaveBeenCalled()
|
||||
|
||||
@@ -85,7 +85,7 @@ watch([tab, page, size], () => { clearPreview(); if (tab.value === 'history') vo
|
||||
<p v-if="row">{{ row.tripNo }} · {{ row.vehiclePlate || '—' }} · 实际发车 {{ row.departedAt }} · 货物结案 {{ row.completedAt || '—' }}</p>
|
||||
<el-tabs v-model="tab" class="density-element-tabs density-contained-tabs"><el-tab-pane label="实测记录" name="measured">
|
||||
<p v-if="uncertain">上次保存结果未确认,将按原请求重试。</p>
|
||||
<el-form label-position="top" class="density-form record-form" :disabled="readonly || !record || loading || saving || uploading || uncertain">
|
||||
<el-form v-if="record" label-position="top" class="density-form record-form" :disabled="readonly || !record || loading || saving || uploading || uncertain">
|
||||
<el-form-item label="本趟实际里程(mile)"><el-input-number :model-value="form.actualDistanceMiles" @update:model-value="form.actualDistanceMiles = $event ?? null" :min="0" :precision="1" placeholder="选填,本趟实际英里数" /></el-form-item>
|
||||
<el-form-item label="本趟实际油耗(L)"><el-input-number :model-value="form.actualFuelLitres" @update:model-value="form.actualFuelLitres = $event ?? null" :min="0" :precision="1" placeholder="选填,本趟实际用油升数" /></el-form-item>
|
||||
<el-form-item label="备注" class="full"><el-input :model-value="form.remark || ''" @update:model-value="form.remark = $event || null" type="textarea" maxlength="1000" placeholder="选填,补充实测情况" /></el-form-item>
|
||||
|
||||
Reference in New Issue
Block a user