feat(admin): synchronize fleet photos and multi-image loading documents

This commit is contained in:
2026-10-26 17:12:38 +08:00
parent e399f3f76f
commit acb90007de
29 changed files with 556 additions and 95 deletions
@@ -0,0 +1,54 @@
import { flushPromises, mount } from '@vue/test-utils'
import { beforeEach, expect, it, vi } from 'vitest'
import { ApiResponseError } from '@/shared/api/http'
import ResourcePhotoPanel from './ResourcePhotoPanel.vue'
import * as api from '../api/resourcePhotos'
const security = vi.hoisted(() => ({ writable: true }))
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => security.writable }) }))
vi.mock('../api/resourcePhotos', () => ({ getResourcePhoto: vi.fn(), uploadResourcePhoto: vi.fn(), downloadResourcePhoto: vi.fn() }))
const photo = { fileId: '81', fileName: 'driver.png', contentType: 'image/png', sizeBytes: 3 }
const options = { props: { kind: 'DRIVER' as const, resourceId: '7' }, global: { directives: { loading: () => undefined }, stubs: {
ElButton: { props: ['disabled'], template: '<button :disabled="disabled"><slot /></button>' },
AppDialog: { props: ['modelValue'], template: '<div v-if="modelValue"><slot /></div>' },
} } }
beforeEach(() => {
vi.resetAllMocks(); security.writable = true
vi.mocked(api.getResourcePhoto).mockResolvedValue({ recordVersion: '2', photo })
URL.createObjectURL = vi.fn().mockReturnValue('blob:photo'); URL.revokeObjectURL = vi.fn()
})
it('keeps reading independent from upload permission and downloads only on preview', async () => {
security.writable = false
vi.mocked(api.downloadResourcePhoto).mockResolvedValue(new Blob(['png'], { type: 'image/png' }))
const wrapper = mount(ResourcePhotoPanel, options); await flushPromises()
expect(wrapper.text()).not.toContain('上传 / 替换')
expect(api.downloadResourcePhoto).not.toHaveBeenCalled()
await wrapper.get('button').trigger('click'); await flushPromises()
expect(api.downloadResourcePhoto).toHaveBeenCalledWith('DRIVER', '7', '81')
wrapper.unmount(); expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:photo')
})
it('submits the independent photo version and releases busy state after failure', async () => {
vi.mocked(api.uploadResourcePhoto).mockRejectedValueOnce(new ApiResponseError('版本冲突', 'A0409'))
.mockResolvedValueOnce({ recordVersion: '3', photo })
const wrapper = mount(ResourcePhotoPanel, options); await flushPromises()
const input = wrapper.get('input[type="file"]'), file = new File(['png'], 'driver.png', { type: 'image/png' })
Object.defineProperty(input.element, 'files', { configurable: true, value: [file] })
await input.trigger('change'); await flushPromises()
expect(api.uploadResourcePhoto).toHaveBeenCalledWith('DRIVER', '7', '2', file)
expect(wrapper.text()).toContain('版本冲突')
await input.trigger('change'); await flushPromises()
expect(api.uploadResourcePhoto).toHaveBeenCalledTimes(2)
wrapper.unmount()
})
it('does not apply an upload result to a different resource', async () => {
let finish!: (value: api.ResourcePhotoDetail) => void
vi.mocked(api.uploadResourcePhoto).mockImplementation(() => new Promise(resolve => { finish = resolve }))
const wrapper = mount(ResourcePhotoPanel, options); await flushPromises()
const input = wrapper.get('input[type="file"]')
Object.defineProperty(input.element, 'files', { value: [new File(['png'], 'driver.png', { type: 'image/png' })] })
await input.trigger('change')
vi.mocked(api.getResourcePhoto).mockResolvedValue({ recordVersion: '0', photo: null })
await wrapper.setProps({ resourceId: '8' }); await flushPromises()
finish({ recordVersion: '3', photo }); await flushPromises()
expect(wrapper.text()).toContain('尚未上传司机照片')
wrapper.unmount()
})