68 lines
4.7 KiB
TypeScript
68 lines
4.7 KiB
TypeScript
import ElementPlus from 'element-plus'
|
|
import { flushPromises, mount } from '@vue/test-utils'
|
|
import { beforeEach, expect, it, vi } from 'vitest'
|
|
import { ApiResponseError } from '@/shared/api/http'
|
|
import DispatchVehicleDocuments from './DispatchVehicleDocuments.vue'
|
|
import { bindVehiclePhoto, getVehicleDocuments, recoverVehiclePhoto, uploadVehiclePhoto, type VehicleDocuments, type VehiclePhoto } from '../api/dispatchVehicleDocuments'
|
|
const can = vi.hoisted(() => vi.fn(() => true))
|
|
vi.mock('@/core/auth/auth', () => ({ useAuthStore: () => ({ user: { id: 'operator' } }) }))
|
|
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can }) }))
|
|
vi.mock('../api/dispatchVehicleDocuments', () => ({ bindVehiclePhoto: vi.fn(), getVehicleDocuments: vi.fn(), recoverVehiclePhoto: vi.fn(), uploadVehiclePhoto: vi.fn(), readVehiclePhoto: vi.fn() }))
|
|
const photo: VehiclePhoto = { slot: 'FUEL_START', fileId: '9007199254740995', fileName: 'fuel.png', contentType: 'image/png', savedAt: null }
|
|
function response(version = '3'): VehicleDocuments { return { dispatchNo: 'PC/1', recordVersion: version, editable: true, photos: [] } }
|
|
function setup() { return mount(DispatchVehicleDocuments, { props: { dispatchNo: 'PC/1' }, global: { plugins: [ElementPlus] } }) }
|
|
async function upload(wrapper: ReturnType<typeof setup>) { const input = wrapper.get('input'); Object.defineProperty(input.element, 'files', { value: [new File(['image'], 'fuel.png', { type: 'image/png' })], configurable: true }); await input.trigger('change'); await flushPromises() }
|
|
beforeEach(() => {
|
|
vi.clearAllMocks(); sessionStorage.clear(); can.mockReturnValue(true)
|
|
vi.mocked(getVehicleDocuments).mockResolvedValue(response())
|
|
vi.mocked(uploadVehiclePhoto).mockResolvedValue(photo)
|
|
vi.mocked(recoverVehiclePhoto).mockResolvedValue(photo)
|
|
vi.mocked(bindVehiclePhoto).mockResolvedValue({ ...response('4'), photos: [{ ...photo, savedAt: '2026-10-21T00:00:00Z' }] })
|
|
})
|
|
it('does not restore failed bindings after reopening and allows a new upload', async () => {
|
|
vi.mocked(bindVehiclePhoto).mockRejectedValueOnce(new Error('network timeout'))
|
|
const first = setup(); await flushPromises(); await upload(first)
|
|
const table = first.get('table[aria-label="油耗图片"]')
|
|
expect(table.findAll('thead th').map(cell => cell.text())).toEqual(['操作', '图片类别', '文件名称', '保存时间'])
|
|
expect(table.findAll('tbody tr').map(row => row.findAll('td')[1]!.text())).toEqual(['起始油耗表盘', '结束油耗表盘'])
|
|
const original = vi.mocked(bindVehiclePhoto).mock.calls[0]![2]
|
|
expect(original).toMatchObject({ recordVersion: '3', fileId: photo.fileId })
|
|
first.unmount()
|
|
const reopened = setup(); await flushPromises()
|
|
await upload(reopened)
|
|
expect(vi.mocked(bindVehiclePhoto).mock.calls[1]![2].idempotencyKey).not.toBe(original.idempotencyKey)
|
|
expect(uploadVehiclePhoto).toHaveBeenCalledTimes(2)
|
|
expect(reopened.text()).toContain('fuel.png')
|
|
expect(reopened.get('table tbody tr').text()).toContain('2026-10-21 00:00:00Z')
|
|
expect(sessionStorage.length).toBe(0)
|
|
reopened.unmount()
|
|
})
|
|
it('unlocks uncertain uploads for a new file selection', async () => {
|
|
vi.mocked(uploadVehiclePhoto).mockRejectedValueOnce(new Error('connection lost'))
|
|
const wrapper = setup(); await flushPromises(); await upload(wrapper)
|
|
const originalKey = vi.mocked(uploadVehiclePhoto).mock.calls[0]![3]
|
|
await upload(wrapper)
|
|
expect(recoverVehiclePhoto).not.toHaveBeenCalled(); expect(vi.mocked(uploadVehiclePhoto).mock.calls[1]![3]).not.toBe(originalKey)
|
|
expect(uploadVehiclePhoto).toHaveBeenCalledTimes(2)
|
|
expect(bindVehiclePhoto).toHaveBeenCalledTimes(1)
|
|
wrapper.unmount()
|
|
})
|
|
it('allows a new photo and current version after a binding conflict', async () => {
|
|
vi.mocked(bindVehiclePhoto).mockRejectedValueOnce(new ApiResponseError('运营记录版本已变化,请刷新后重试', 'A0409', 409))
|
|
const wrapper = setup(); await flushPromises(); await upload(wrapper)
|
|
const originalKey = vi.mocked(bindVehiclePhoto).mock.calls[0]![2].idempotencyKey
|
|
vi.mocked(getVehicleDocuments).mockResolvedValue(response('8'))
|
|
await upload(wrapper)
|
|
expect(bindVehiclePhoto).toHaveBeenLastCalledWith('PC/1', 'FUEL_START', { recordVersion: '8', fileId: photo.fileId, idempotencyKey: expect.any(String) })
|
|
expect(vi.mocked(bindVehiclePhoto).mock.calls[1]![2].idempotencyKey).not.toBe(originalKey)
|
|
expect(uploadVehiclePhoto).toHaveBeenCalledTimes(2)
|
|
wrapper.unmount()
|
|
})
|
|
it('does not offer upload with list permission alone', async () => {
|
|
can.mockReturnValue(false)
|
|
const wrapper = setup(); await flushPromises()
|
|
expect(wrapper.find('input[type="file"]').exists()).toBe(false)
|
|
expect(uploadVehiclePhoto).not.toHaveBeenCalled()
|
|
wrapper.unmount()
|
|
})
|