194 lines
7.1 KiB
TypeScript
194 lines
7.1 KiB
TypeScript
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||
import userEvent from '@testing-library/user-event'
|
||
|
||
import UploadModal from '@/components/UploadModal'
|
||
import {
|
||
submitFormData,
|
||
uploadSingleFile,
|
||
} from '@/components/upload/file-upload-handlers'
|
||
|
||
jest.mock('@/components/upload/file-upload-handlers', () => ({
|
||
...jest.requireActual('@/components/upload/file-upload-handlers'),
|
||
uploadSingleFile: jest.fn(),
|
||
submitFormData: jest.fn(),
|
||
}))
|
||
|
||
jest.mock('@/components/upload/image-input-handlers', () => ({
|
||
...jest.requireActual('@/components/upload/image-input-handlers'),
|
||
handlePreviewImageInput: jest.fn(async (
|
||
files: File[],
|
||
onSuccess: (processedFiles: File[], urls: string[]) => void,
|
||
) => onSuccess(files, files.map((file) => `blob:test-${file.name}`))),
|
||
}))
|
||
|
||
jest.mock('next/image', () => ({
|
||
__esModule: true,
|
||
default: (props: any) => {
|
||
const { priority, fill, unoptimized, sizes, alt = '', ...rest } = props
|
||
// eslint-disable-next-line @next/next/no-img-element
|
||
return <img alt={alt} {...rest} />
|
||
},
|
||
}))
|
||
|
||
jest.mock('@/hooks/useCategoryData', () => ({
|
||
useCategoryData: () => ({
|
||
data: {
|
||
categories: [],
|
||
scenes: [],
|
||
platforms: [],
|
||
productCategories: [],
|
||
},
|
||
refreshCategories: jest.fn(),
|
||
refreshScenes: jest.fn(),
|
||
}),
|
||
}))
|
||
|
||
jest.mock('@/components/upload/ProductPackageUploader', () => ({
|
||
__esModule: true,
|
||
default: () => <div data-testid="product-package-uploader" />,
|
||
}))
|
||
|
||
jest.mock('@/components/upload/VideoScreenshotUploader', () => ({
|
||
__esModule: true,
|
||
default: () => <div data-testid="video-screenshot-uploader" />,
|
||
}))
|
||
|
||
jest.mock('@/components/upload/TaskPageUploader', () => ({
|
||
__esModule: true,
|
||
default: () => <div data-testid="task-page-uploader" />,
|
||
}))
|
||
|
||
describe('UploadModal - 任务-其他', () => {
|
||
beforeEach(() => {
|
||
jest.clearAllMocks()
|
||
|
||
;(uploadSingleFile as jest.Mock).mockResolvedValue(null)
|
||
;(submitFormData as jest.Mock).mockResolvedValue(undefined)
|
||
|
||
global.fetch = jest.fn().mockResolvedValue({
|
||
ok: true,
|
||
json: async () => ({
|
||
materialTypes: ['3D场景', '任务-其他'],
|
||
}),
|
||
}) as any
|
||
})
|
||
|
||
test('任务-其他应使用公共上传面板形式,任务号与钉钉审批编号并列显示', async () => {
|
||
const user = userEvent.setup()
|
||
|
||
render(<UploadModal onClose={jest.fn()} onSuccess={jest.fn()} />)
|
||
|
||
const materialTypeSelect = (await screen.findAllByRole('combobox'))[0]
|
||
await user.selectOptions(materialTypeSelect, '任务-其他')
|
||
|
||
await waitFor(() => {
|
||
expect(screen.getByLabelText('任务号 *')).toBeInTheDocument()
|
||
})
|
||
|
||
expect(screen.getByLabelText('钉钉审批编号')).toBeInTheDocument()
|
||
expect(screen.getByText('预览图片 *')).toBeInTheDocument()
|
||
expect(screen.getByText('素材文件 *')).toBeInTheDocument()
|
||
expect(screen.getByText('支持格式:压缩包(.zip / .rar / .7z)')).toBeInTheDocument()
|
||
|
||
expect(screen.queryByLabelText('SPU型号 *')).not.toBeInTheDocument()
|
||
expect(screen.queryByText('压缩包源文件 *')).not.toBeInTheDocument()
|
||
expect(screen.queryByText('预览图 *')).not.toBeInTheDocument()
|
||
})
|
||
|
||
test('主素材区混合拖入有效与非法文件时应保留有效文件并提示被忽略项', async () => {
|
||
const user = userEvent.setup()
|
||
|
||
render(<UploadModal onClose={jest.fn()} onSuccess={jest.fn()} />)
|
||
|
||
const materialTypeSelect = (await screen.findAllByRole('combobox'))[0]
|
||
await user.selectOptions(materialTypeSelect, '任务-其他')
|
||
|
||
const selectButton = await screen.findByRole('button', { name: '选择素材文件' })
|
||
const dropZone = selectButton.closest('[aria-disabled]') as HTMLDivElement
|
||
const zipFile = new File(['zip'], 'task-source.zip', { type: 'application/zip' })
|
||
const invalidFile = new File(['text'], 'readme.txt', { type: 'text/plain' })
|
||
|
||
fireEvent.drop(dropZone, {
|
||
dataTransfer: {
|
||
files: [zipFile, invalidFile],
|
||
types: ['Files'],
|
||
dropEffect: 'copy',
|
||
},
|
||
})
|
||
|
||
expect(await screen.findByText('task-source.zip')).toBeInTheDocument()
|
||
expect(screen.getByText(/readme\.txt/)).toBeInTheDocument()
|
||
})
|
||
|
||
async function prepareLargeTaskOtherUpload(container: HTMLElement) {
|
||
const user = userEvent.setup()
|
||
const materialTypeSelect = (await screen.findAllByRole('combobox'))[0]
|
||
await user.selectOptions(materialTypeSelect, '任务-其他')
|
||
await user.type(screen.getByLabelText('任务号 *'), 'TASK202601030184')
|
||
|
||
const previewInput = container.querySelector<HTMLInputElement>('input[accept="image/*"]')
|
||
expect(previewInput).not.toBeNull()
|
||
fireEvent.change(previewInput!, {
|
||
target: {
|
||
files: [new File(['preview'], 'preview.jpg', { type: 'image/jpeg' })],
|
||
},
|
||
})
|
||
await screen.findByAltText('预览图 1')
|
||
|
||
const sourceInput = container.querySelector<HTMLInputElement>('input[accept=".zip,.rar,.7z"]')
|
||
expect(sourceInput).not.toBeNull()
|
||
const sourceFile = new File(
|
||
[new Uint8Array(5 * 1024 * 1024 + 1)],
|
||
'task-source.zip',
|
||
{ type: 'application/zip' },
|
||
)
|
||
fireEvent.change(sourceInput!, { target: { files: [sourceFile] } })
|
||
await screen.findByText('task-source.zip')
|
||
|
||
return { user, sourceFile }
|
||
}
|
||
|
||
test('分片上传临时失败时不调用服务端清理,保留断点续传数据', async () => {
|
||
;(uploadSingleFile as jest.Mock).mockRejectedValue(
|
||
new Error('上传会话服务暂时不可用,已保留上传进度,请稍后重试'),
|
||
)
|
||
const { container } = render(<UploadModal onClose={jest.fn()} onSuccess={jest.fn()} />)
|
||
const { user } = await prepareLargeTaskOtherUpload(container)
|
||
|
||
await user.click(screen.getByRole('button', { name: '上传' }))
|
||
|
||
expect(await screen.findByText(/已保留上传进度/)).toBeInTheDocument()
|
||
expect(uploadSingleFile).toHaveBeenCalledTimes(1)
|
||
expect(submitFormData).not.toHaveBeenCalled()
|
||
expect(global.fetch).not.toHaveBeenCalledWith(
|
||
'/api/materials/upload/cleanup',
|
||
expect.anything(),
|
||
)
|
||
})
|
||
|
||
test('分片合并完成但表单提交失败时只清理一次已合并的服务端文件', async () => {
|
||
;(uploadSingleFile as jest.Mock).mockResolvedValue({
|
||
uploadId: 'upload-form-failed',
|
||
existingFilePath: '/uploads/temp/upload-form-failed/merged-task-source.zip',
|
||
})
|
||
;(submitFormData as jest.Mock).mockRejectedValue(new Error('保存素材记录失败'))
|
||
const { container } = render(<UploadModal onClose={jest.fn()} onSuccess={jest.fn()} />)
|
||
const { user } = await prepareLargeTaskOtherUpload(container)
|
||
|
||
await user.click(screen.getByRole('button', { name: '上传' }))
|
||
|
||
expect(await screen.findByText('保存素材记录失败')).toBeInTheDocument()
|
||
const cleanupCalls = (global.fetch as jest.Mock).mock.calls.filter(
|
||
([url]) => url === '/api/materials/upload/cleanup',
|
||
)
|
||
expect(cleanupCalls).toHaveLength(1)
|
||
expect(cleanupCalls[0][1]).toEqual(expect.objectContaining({
|
||
method: 'POST',
|
||
body: JSON.stringify({
|
||
uploadId: 'upload-form-failed',
|
||
existingFilePath: '/uploads/temp/upload-form-failed/merged-task-source.zip',
|
||
}),
|
||
}))
|
||
})
|
||
})
|