Files
admin-web/src/modules/overseas-warehouse/components/DispatchLoadingDocuments.test.ts
T
2026-10-21 17:49:04 +08:00

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()
})