Merge branch 'release/test' of http://8.163.30.128:10082/html/admin-web into release/test
This commit is contained in:
@@ -60,17 +60,27 @@ describe('dispatch workflow native layout', () => {
|
||||
field(dialog, '实际托位容量').getComponent({ name: 'ElInputNumber' }).vm.$emit('update:modelValue', 22)
|
||||
await field(dialog, '司机姓名').get('input').setValue('新司机')
|
||||
field(dialog, '计划发车时间').getComponent({ name: 'ElDatePicker' }).vm.$emit('update:modelValue', '2026-11-01T10:00:00Z')
|
||||
await dialog.get('textarea[aria-label="本次操作备注"]').setValue('维护备注')
|
||||
expect(dialog.find('textarea[aria-label="本次操作备注"]').exists()).toBe(false)
|
||||
await dialog.get('button[aria-label="展开全屏"]').trigger('click'); await flushPromises()
|
||||
dialog.vm.$emit('confirm'); await flushPromises()
|
||||
const { vehicleStatus: _status, ...vehicle } = resources().vehicle
|
||||
expect(api.updateDispatch).toHaveBeenCalledExactlyOnceWith('PC/1', { recordVersion: 0, idempotencyKey: expect.any(String), vehicle: { ...vehicle, plate: 'CA-NEW', capacity: { ...vehicle.capacity, palletSpaces: 22 } }, driver: { ...resources().driver, name: '新司机' }, plannedDepartureAt: '2026-11-01T10:00:00Z', estimatedArrivalAt: null, remark: '维护备注' })
|
||||
expect(api.updateDispatch).toHaveBeenCalledExactlyOnceWith('PC/1', { recordVersion: 0, idempotencyKey: expect.any(String), vehicle: { ...vehicle, plate: 'CA-NEW', capacity: { ...vehicle.capacity, palletSpaces: 22 } }, driver: { ...resources().driver, name: '新司机' }, plannedDepartureAt: '2026-11-01T10:00:00Z', estimatedArrivalAt: null, remark: '真实备注' })
|
||||
})
|
||||
it('saves all three resource photos before submitting review with the refreshed version', async () => {
|
||||
const { dialog } = await render('EDIT')
|
||||
const fields = dialog.findAllComponents(DispatchEvidenceFiles)
|
||||
fields.forEach((field, index) => field.vm.$emit('update:modelValue', [String(401 + index)]))
|
||||
await flushPromises()
|
||||
await dialog.findAll('button').find(button => button.text() === '提交审核')!.trigger('click')
|
||||
await flushPromises()
|
||||
expect(api.updateDispatch).toHaveBeenCalledExactlyOnceWith('PC/1', expect.objectContaining({ recordVersion: 0, vehicle: expect.objectContaining({ evidenceFileIds: ['401'] }), driver: expect.objectContaining({ evidence: expect.objectContaining({ photoFileIds: ['402'], fileIds: ['403'] }) }) }))
|
||||
expect(api.submitDispatchReview).toHaveBeenCalledExactlyOnceWith('PC/1', expect.objectContaining({ recordVersion: 1, remark: null }))
|
||||
expect(vi.mocked(api.updateDispatch).mock.invocationCallOrder[0]).toBeLessThan(vi.mocked(api.submitDispatchReview).mock.invocationCallOrder[0]!)
|
||||
})
|
||||
it('retains the locally submitted draft after close and reopen and retries exactly the same failed request', async () => {
|
||||
vi.mocked(api.updateDispatch).mockRejectedValueOnce(new ApiResponseError('结果未确认', 'A0503', 503, null, 'workflow-request')).mockImplementationOnce(async (_, body) => command('EDIT', body.idempotencyKey))
|
||||
const { wrapper, dialog } = await render('EDIT')
|
||||
await field(dialog, '车牌号').get('input').setValue('CA-PRESERVED')
|
||||
await dialog.get('textarea[aria-label="本次操作备注"]').setValue('保留本次输入')
|
||||
dialog.vm.$emit('confirm'); await flushPromises()
|
||||
const original = JSON.parse(JSON.stringify(vi.mocked(api.updateDispatch).mock.calls[0]![1])) as UpdateDispatch
|
||||
expect(dialog.text()).toContain('workflow-request')
|
||||
@@ -79,30 +89,62 @@ describe('dispatch workflow native layout', () => {
|
||||
expect(dialog.props('title')).toBe('维护车辆 · PC/1')
|
||||
await wrapper.setProps({ modelValue: false }); await wrapper.setProps({ modelValue: true }); await flushPromises()
|
||||
expect((field(dialog, '车牌号').get('input').element as HTMLInputElement).value).toBe('CA-PRESERVED')
|
||||
expect((dialog.get('textarea[aria-label="本次操作备注"]').element as HTMLTextAreaElement).value).toBe('保留本次输入')
|
||||
expect(original.remark).toBe('真实备注')
|
||||
expect(dialog.find('textarea[aria-label="本次操作备注"]').exists()).toBe(false)
|
||||
dialog.findComponent(LoadErrorState).vm.$emit('retry'); await flushPromises()
|
||||
expect(vi.mocked(api.updateDispatch).mock.calls[1]![1]).toEqual(original)
|
||||
})
|
||||
it.each(['DISPATCH_REVIEW', 'LOADING_REVIEW'] as const)('keeps %s facts read-only and requires a rejection reason', async action => {
|
||||
const { dialog } = await render(action)
|
||||
expect(dialog.get('[aria-label="本车审核依据"]').findAll('input')).toHaveLength(0)
|
||||
expect(dialog.text()).toContain('审核通过不代替后续现场扫码')
|
||||
field(dialog, '审核结果').getComponent({ name: 'ElRadioGroup' }).vm.$emit('update:modelValue', 'REJECTED'); await flushPromises()
|
||||
expect(dialog.get('[aria-label="本车审核依据"]').find('h3').exists()).toBe(false)
|
||||
expect(dialog.text()).not.toContain('请核对本车资源与本趟证据')
|
||||
expect(dialog.findAll('button').some(button => button.text() === '取消')).toBe(false)
|
||||
expect(dialog.findComponent({ name: 'ElRadioGroup' }).exists()).toBe(false)
|
||||
expect(dialog.findAll('button').some(button => button.text() === '确认提交')).toBe(false)
|
||||
const reject = dialog.findAll('button').find(button => button.text() === '驳回')!
|
||||
const review = action === 'DISPATCH_REVIEW' ? api.reviewDispatch : api.reviewDispatchLoading
|
||||
dialog.vm.$emit('confirm'); await flushPromises(); expect(review).not.toHaveBeenCalled()
|
||||
await dialog.get('textarea').setValue(' 证据不完整 '); dialog.vm.$emit('confirm'); await flushPromises()
|
||||
await reject.trigger('click'); await flushPromises(); expect(review).not.toHaveBeenCalled()
|
||||
await vi.waitFor(() => expect(field(dialog, '驳回原因(必填)').text()).toContain('请填写驳回原因'))
|
||||
await dialog.get('textarea').setValue(' '); await reject.trigger('click'); await flushPromises(); expect(review).not.toHaveBeenCalled()
|
||||
await dialog.get('textarea').setValue(' 证据不完整 '); await reject.trigger('click'); await flushPromises()
|
||||
expect(review).toHaveBeenCalledExactlyOnceWith('PC/1', { recordVersion: 0, idempotencyKey: expect.any(String), decision: 'REJECTED', reason: '证据不完整' })
|
||||
expect(dialog.findAll('input[type="file"]')).toHaveLength(3)
|
||||
expect(dialog.findAll('input[type="file"]')).toHaveLength(0)
|
||||
})
|
||||
it.each(['DISPATCH_REVIEW', 'LOADING_REVIEW'] as const)('uploads three audit attachments with only %s permission and retries the same rejected review', async action => {
|
||||
can.mockImplementation(code => code === (action === 'DISPATCH_REVIEW' ? 'platform:fleet:trip:dispatch-review' : 'platform:fleet:trip:loading-review'))
|
||||
it.each(['DISPATCH_REVIEW', 'LOADING_REVIEW'] as const)('approves %s directly and keeps an unknown result on the original request', async action => {
|
||||
const review = action === 'DISPATCH_REVIEW' ? api.reviewDispatch : api.reviewDispatchLoading
|
||||
vi.mocked(review).mockRejectedValueOnce(new Error('unknown review')).mockImplementationOnce(async (_, body) => command(action, body.idempotencyKey))
|
||||
const { wrapper, dialog } = await render(action, undefined, true)
|
||||
vi.mocked(review).mockRejectedValueOnce(new Error('结果未知'))
|
||||
const { dialog } = await render(action)
|
||||
await dialog.findAll('button').find(button => button.text() === '审核通过')!.trigger('click')
|
||||
await flushPromises()
|
||||
expect(review).toHaveBeenCalledExactlyOnceWith('PC/1', { recordVersion: 0, idempotencyKey: expect.any(String), decision: 'APPROVED', reason: null })
|
||||
const original = structuredClone(vi.mocked(review).mock.calls[0]![1])
|
||||
expect(dialog.props('showCancel')).toBe(false)
|
||||
expect(dialog.findAll('button').some(button => button.text() === '取消')).toBe(false)
|
||||
expect(dialog.findAll('button').some(button => button.text() === '驳回')).toBe(false)
|
||||
await dialog.findAll('button').find(button => button.text() === '核对并重试原请求')!.trigger('click')
|
||||
await flushPromises()
|
||||
expect(vi.mocked(review).mock.calls[1]![1]).toEqual(original)
|
||||
})
|
||||
it.each(['DISPATCH_REVIEW', 'LOADING_REVIEW'] as const)('disables both %s decisions when write permission is missing', async action => {
|
||||
can.mockReturnValue(false)
|
||||
const { dialog } = await render(action)
|
||||
for (const label of ['驳回', '审核通过']) {
|
||||
const button = dialog.findAll('button').find(item => item.text() === label)!
|
||||
expect(button.attributes('disabled')).toBeDefined()
|
||||
await button.trigger('click')
|
||||
}
|
||||
expect(api.reviewDispatch).not.toHaveBeenCalled()
|
||||
expect(api.reviewDispatchLoading).not.toHaveBeenCalled()
|
||||
})
|
||||
it('uploads all three photos during resource maintenance and retries the exact original edit', async () => {
|
||||
can.mockImplementation(code => code === 'platform:overseas-warehouse:dispatch:edit')
|
||||
vi.mocked(api.updateDispatch).mockRejectedValueOnce(new Error('unknown edit')).mockImplementationOnce(async (_, body) => command('EDIT', body.idempotencyKey))
|
||||
const { wrapper, dialog } = await render('EDIT', undefined, true)
|
||||
const fields = dialog.findAllComponents(DispatchEvidenceFiles)
|
||||
expect(fields.map(item => item.props('label'))).toEqual(['车辆图片', '司机照片', '驾驶证照片'])
|
||||
expect(fields[0]!.props('referenceIds')).toEqual(['301'])
|
||||
expect(fields[0]!.findAll('button').some(button => button.text() === '替换')).toBe(false)
|
||||
expect(fields[0]!.props('modelValue')).toEqual(['301'])
|
||||
expect(dialog.get('.workflow-photo-grid').findAll('figure')).toHaveLength(3)
|
||||
for (const [index, files] of fields.entries()) {
|
||||
let resolve!: (value: DispatchEvidenceFile) => void
|
||||
vi.mocked(api.uploadDispatchEvidence).mockReturnValueOnce(new Promise(done => { resolve = done }))
|
||||
@@ -110,28 +152,42 @@ describe('dispatch workflow native layout', () => {
|
||||
Object.defineProperty(input.element, 'files', { configurable: true, value: [file] })
|
||||
await input.trigger('change'); await flushPromises()
|
||||
expect(dialog.props('confirmLoading')).toBe(true)
|
||||
dialog.vm.$emit('confirm'); await flushPromises(); expect(review).not.toHaveBeenCalled()
|
||||
dialog.vm.$emit('confirm'); await flushPromises(); expect(api.updateDispatch).not.toHaveBeenCalled()
|
||||
resolve(evidence(String(401 + index))); await flushPromises()
|
||||
}
|
||||
expect(vi.mocked(api.uploadDispatchEvidence).mock.calls.map(call => call[1])).toEqual(['VEHICLE', 'DRIVER_PHOTO', 'DRIVER'])
|
||||
field(dialog, '审核结果').getComponent({ name: 'ElRadioGroup' }).vm.$emit('update:modelValue', 'REJECTED'); await flushPromises()
|
||||
await dialog.get('textarea').setValue('需重新核对车牌')
|
||||
dialog.vm.$emit('confirm'); await flushPromises()
|
||||
const original = structuredClone(vi.mocked(review).mock.calls[0]![1])
|
||||
expect(original).toMatchObject({ decision: 'REJECTED', reason: '需重新核对车牌', attachments: { vehicleFileIds: ['401'], driverPhotoFileIds: ['402'], licenseFileIds: ['403'] } })
|
||||
const original = structuredClone(vi.mocked(api.updateDispatch).mock.calls[0]![1])
|
||||
expect(original).toMatchObject({ vehicle: { evidenceFileIds: ['301', '401'] }, driver: { evidence: { photoFileIds: ['402'], fileIds: expect.arrayContaining(['403']) } } })
|
||||
await wrapper.setProps({ modelValue: false }); await wrapper.setProps({ modelValue: true }); await flushPromises()
|
||||
dialog.vm.$emit('confirm'); await flushPromises()
|
||||
expect(vi.mocked(review).mock.calls[1]![1]).toEqual(original)
|
||||
expect(api.updateDispatch).not.toHaveBeenCalled()
|
||||
expect(vi.mocked(api.updateDispatch).mock.calls[1]![1]).toEqual(original)
|
||||
expect(api.reviewDispatch).not.toHaveBeenCalled()
|
||||
expect(api.reviewDispatchLoading).not.toHaveBeenCalled()
|
||||
})
|
||||
it.each(['DISPATCH_REVIEW', 'LOADING_REVIEW'] as const)('shows saved photos and old audit evidence read-only in %s', async action => {
|
||||
const { wrapper, dialog } = await render(action)
|
||||
const value = detail({ actions })
|
||||
value.vehicleInfo.driver!.evidence!.photoFileIds = ['402']
|
||||
value.reviews.dispatchReview.attachments = { vehicleFileIds: [], driverPhotoFileIds: ['403'], licenseFileIds: [] }
|
||||
vi.mocked(api.getDispatch).mockResolvedValueOnce(value)
|
||||
await wrapper.setProps({ modelValue: false }); await wrapper.setProps({ modelValue: true }); await flushPromises()
|
||||
const fields = dialog.findAllComponents(DispatchEvidenceFiles)
|
||||
expect(fields.map(item => item.props('label'))).toEqual(['车辆图片', '司机照片', '驾驶证照片'])
|
||||
expect(fields.every(item => item.props('readonly') && item.props('photoCard'))).toBe(true)
|
||||
expect(fields[1]!.props('modelValue')).toEqual(['402', '403'])
|
||||
expect(dialog.findAll('input[type="file"]')).toHaveLength(0)
|
||||
expect(api.uploadDispatchEvidence).not.toHaveBeenCalled()
|
||||
})
|
||||
it.each(['CANCEL', 'RETURN_TO_REVIEW', 'SUBMIT_REVIEW'] as const)('preserves the %s confirmation branch and its exact reason or remark', async action => {
|
||||
const { dialog } = await render(action)
|
||||
const needsReason = action === 'CANCEL' || action === 'RETURN_TO_REVIEW'
|
||||
const send = action === 'CANCEL' ? api.cancelDispatch : action === 'RETURN_TO_REVIEW' ? api.returnDispatchToReview : api.submitDispatchReview
|
||||
if (needsReason) { dialog.vm.$emit('confirm'); await flushPromises(); expect(send).not.toHaveBeenCalled() }
|
||||
await dialog.get('textarea').setValue(needsReason ? ' 现场原因 ' : '本次交接')
|
||||
if (needsReason) await dialog.get('textarea').setValue(' 现场原因 ')
|
||||
else expect(dialog.find('textarea').exists()).toBe(false)
|
||||
dialog.vm.$emit('confirm'); await flushPromises()
|
||||
expect(send).toHaveBeenCalledExactlyOnceWith('PC/1', { recordVersion: 0, idempotencyKey: expect.any(String), ...(needsReason ? { reason: '现场原因' } : { remark: '本次交接' }) })
|
||||
expect(send).toHaveBeenCalledExactlyOnceWith('PC/1', { recordVersion: 0, idempotencyKey: expect.any(String), ...(needsReason ? { reason: '现场原因' } : { remark: null }) })
|
||||
})
|
||||
it('reorders only real unload stops and preserves the external stop and zero route version', async () => {
|
||||
const { dialog } = await render('EDIT', 'ROUTE')
|
||||
@@ -170,11 +226,14 @@ describe('dispatch workflow native layout', () => {
|
||||
vi.mocked(api.uploadDispatchEvidence).mockReturnValue(new Promise(done => { resolve = done }))
|
||||
const { dialog } = await render('EDIT', undefined, true)
|
||||
const files = dialog.findAllComponents(DispatchEvidenceFiles)[0]!
|
||||
await files.findAll('button').find(button => button.text() === '预览')!.trigger('click'); await flushPromises()
|
||||
await files.get('button.evidence-photo-preview').trigger('click'); await flushPromises()
|
||||
const image = files.findComponent(AppDialog)
|
||||
expect(image.props('modelValue')).toBe(true)
|
||||
const preview = new DOMWrapper(document.querySelector('[aria-label="本车附件预览信息"]')!.closest('.el-dialog')!)
|
||||
const preview = new DOMWrapper(document.querySelector('.evidence-preview')!.closest('.el-dialog')!)
|
||||
expect(preview.get('img').attributes('src')).toBe('blob:owned-dispatch')
|
||||
Object.defineProperties(preview.get('img').element, { naturalWidth: { value: 1200 }, naturalHeight: { value: 800 } })
|
||||
await preview.get('img').trigger('load'); await flushPromises()
|
||||
expect(preview.get('.evidence-preview').attributes('aria-busy')).toBe('false')
|
||||
await preview.get('button[aria-label="展开全屏"]').trigger('click'); await flushPromises()
|
||||
expect(preview.classes()).toContain('is-fullscreen')
|
||||
await preview.findAll('button').find(button => button.text() === '关闭')!.trigger('click'); await flushPromises()
|
||||
|
||||
Reference in New Issue
Block a user