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: '' }, AppDialog: { props: ['modelValue'], template: '
' }, } } } 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() })