85 lines
5.8 KiB
TypeScript
85 lines
5.8 KiB
TypeScript
import { flushPromises, mount } from '@vue/test-utils'
|
|
import ElementPlus from 'element-plus'
|
|
import { beforeEach, expect, it, vi } from 'vitest'
|
|
import { ApiResponseError } from '@/shared/api/http'
|
|
import DispatchLoadingDocuments from './DispatchLoadingDocuments.vue'
|
|
import * as api from '../api/dispatchLoadingDocuments'
|
|
|
|
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/dispatchLoadingDocuments', async importOriginal => ({ ...await importOriginal<typeof api>(), getLoadingDocuments: vi.fn(), uploadLoadingPhoto: vi.fn(), recoverLoadingPhoto: vi.fn(), bindLoadingPhoto: vi.fn(), readLoadingPhoto: vi.fn() }))
|
|
const photo: api.LoadingPhoto = { slot: 'LOADING_SCENE', fileId: '101', fileName: 'loading.png', contentType: 'image/png', savedAt: null }
|
|
const data = (extra: Partial<api.LoadingDocuments> = {}): api.LoadingDocuments => ({ dispatchNo: 'PC/1', recordVersion: '0', dispatchRecordVersion: '3', stage: 'AFTER_LOADING', editable: true, complete: false, photos: [], ...extra })
|
|
function setup() { return mount(DispatchLoadingDocuments, { props: { dispatchNo: 'PC/1' }, global: { plugins: [ElementPlus] } }) }
|
|
async function upload(wrapper: ReturnType<typeof setup>) {
|
|
const input = wrapper.get('input[aria-label="装车照片 1"]')
|
|
Object.defineProperty(input.element, 'files', { configurable: true, value: [new File(['image'], 'loading.png', { type: 'image/png' })] })
|
|
await input.trigger('change'); await flushPromises()
|
|
}
|
|
beforeEach(() => {
|
|
vi.clearAllMocks(); sessionStorage.clear(); can.mockReturnValue(true)
|
|
vi.mocked(api.getLoadingDocuments).mockResolvedValue(data())
|
|
vi.mocked(api.uploadLoadingPhoto).mockResolvedValue(photo)
|
|
vi.mocked(api.bindLoadingPhoto).mockResolvedValue(data({ recordVersion: '1', photos: [photo] }))
|
|
vi.mocked(api.readLoadingPhoto).mockRejectedValue(new Error('preview unavailable'))
|
|
})
|
|
it('offers five whole-dispatch slots and only reports saved after binding succeeds', async () => {
|
|
const wrapper = setup(); await flushPromises()
|
|
expect(wrapper.findAll('input[type="file"]')).toHaveLength(5)
|
|
await upload(wrapper)
|
|
expect(api.uploadLoadingPhoto).toHaveBeenCalledWith('PC/1', 'LOADING_SCENE', expect.any(File), expect.any(String))
|
|
expect(api.bindLoadingPhoto).toHaveBeenCalledWith('PC/1', 'LOADING_SCENE', expect.objectContaining({ recordVersion: '0', fileId: '101' }))
|
|
expect(wrapper.emitted('saved')).toHaveLength(1)
|
|
expect(wrapper.text()).toContain('图片读取失败')
|
|
wrapper.unmount()
|
|
})
|
|
it('keeps the original binding and file after an unknown result and never uploads again', async () => {
|
|
vi.mocked(api.bindLoadingPhoto).mockRejectedValueOnce(new Error('network unknown'))
|
|
const wrapper = setup(); await flushPromises(); await upload(wrapper)
|
|
expect(wrapper.emitted('saved')).toBeUndefined()
|
|
const original = vi.mocked(api.bindLoadingPhoto).mock.calls[0]![2]
|
|
await wrapper.findAll('button').find(item => item.text() === '核对原请求并继续保存')!.trigger('click'); await flushPromises()
|
|
expect(api.bindLoadingPhoto).toHaveBeenLastCalledWith('PC/1', 'LOADING_SCENE', original)
|
|
expect(api.uploadLoadingPhoto).toHaveBeenCalledTimes(1)
|
|
expect(wrapper.emitted('saved')).toHaveLength(1); wrapper.unmount()
|
|
})
|
|
it('refreshes a definitive version conflict while retaining the uploaded file', async () => {
|
|
vi.mocked(api.bindLoadingPhoto).mockRejectedValueOnce(new ApiResponseError('version conflict', 'A0409'))
|
|
const wrapper = setup(); await flushPromises(); await upload(wrapper)
|
|
const original = vi.mocked(api.bindLoadingPhoto).mock.calls[0]![2]
|
|
vi.mocked(api.getLoadingDocuments).mockResolvedValue(data({ recordVersion: '4' }))
|
|
vi.mocked(api.bindLoadingPhoto).mockResolvedValue(data({ stage: 'DELIVERING', complete: true, recordVersion: '5' }))
|
|
await wrapper.findAll('button').find(item => item.text() === '刷新并重试保存')!.trigger('click'); await flushPromises()
|
|
expect(api.bindLoadingPhoto).toHaveBeenLastCalledWith('PC/1', 'LOADING_SCENE', expect.objectContaining({ recordVersion: '4', fileId: '101' }))
|
|
expect(vi.mocked(api.bindLoadingPhoto).mock.calls[1]![2].idempotencyKey).not.toBe(original.idempotencyKey)
|
|
expect(api.uploadLoadingPhoto).toHaveBeenCalledTimes(1); expect(wrapper.emitted('saved')).toHaveLength(1); wrapper.unmount()
|
|
})
|
|
it('shows existing photos without exposing upload for a read-only account', async () => {
|
|
can.mockReturnValue(false)
|
|
const wrapper = setup(); await flushPromises()
|
|
expect(wrapper.find('input[type="file"]').exists()).toBe(false)
|
|
expect(api.uploadLoadingPhoto).not.toHaveBeenCalled(); wrapper.unmount()
|
|
})
|
|
|
|
it('allows choosing another image after the server definitively rejects the file', async () => {
|
|
vi.mocked(api.uploadLoadingPhoto).mockRejectedValueOnce(new ApiResponseError('图片内容无效', 'A0400'))
|
|
const wrapper = setup(); await flushPromises(); await upload(wrapper)
|
|
expect(wrapper.text()).toContain('图片内容无效')
|
|
expect(wrapper.find('[role="status"]').exists()).toBe(false)
|
|
expect(api.bindLoadingPhoto).not.toHaveBeenCalled()
|
|
await upload(wrapper)
|
|
expect(api.uploadLoadingPhoto).toHaveBeenCalledTimes(2)
|
|
expect(wrapper.emitted('saved')).toHaveLength(1); wrapper.unmount()
|
|
})
|
|
|
|
it('allows saving photos while loading and keeps the loading stage', async () => {
|
|
vi.mocked(api.getLoadingDocuments).mockResolvedValue(data({ stage: 'LOADING' }))
|
|
vi.mocked(api.bindLoadingPhoto).mockResolvedValue(data({ stage: 'LOADING', recordVersion: '1', photos: [photo] }))
|
|
const wrapper = setup(); await flushPromises()
|
|
expect(wrapper.text()).toContain('可边装车边上传')
|
|
await upload(wrapper)
|
|
expect(wrapper.emitted('saved')).toHaveLength(1)
|
|
expect(wrapper.text()).toContain('可边装车边上传'); wrapper.unmount()
|
|
})
|