gongxiangp2/components/UploadModal.video-preview.test.tsx
2026-08-07 23:28:55 +08:00

223 lines
9.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* eslint-disable @next/next/no-img-element, jsx-a11y/alt-text */
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import UploadModal from '@/components/UploadModal'
import { compressVideoPreviewImage } from '@/components/upload/image-processor'
jest.mock('next/image', () => ({
__esModule: true,
default: (props: any) => {
const { priority, fill, unoptimized, sizes, ...rest } = props
return <img {...rest} />
},
}))
jest.mock('@/components/upload/image-processor', () => ({
assertVideoPreviewImageIsSafeToDecode: jest.fn().mockResolvedValue(undefined),
compressPreviewImage: jest.fn(),
compressVideoPreviewImage: jest.fn(),
}))
jest.mock('@/hooks/useCategoryData', () => ({
useCategoryData: () => ({
data: {
categories: [{ id: 'category-1', name: '通用' }],
scenes: [{ id: 'scene-1', name: '通用' }],
platforms: [],
productCategories: [],
},
refreshCategories: jest.fn(),
refreshScenes: jest.fn(),
}),
}))
jest.mock('@/components/upload/ProductPackageUploader', () => ({
__esModule: true,
default: () => <div />,
}))
jest.mock('@/components/upload/VideoScreenshotUploader', () => ({
__esModule: true,
default: () => <div />,
}))
jest.mock('@/components/upload/TaskPageUploader', () => ({
__esModule: true,
default: () => <div />,
}))
function createDeferred<T>() {
let resolve!: (value: T) => void
const promise = new Promise<T>((resolvePromise) => {
resolve = resolvePromise
})
return { promise, resolve }
}
describe('UploadModal - 视频预览图处理状态', () => {
const originalCreateObjectUrl = Object.getOwnPropertyDescriptor(URL, 'createObjectURL')
const originalRevokeObjectUrl = Object.getOwnPropertyDescriptor(URL, 'revokeObjectURL')
beforeEach(() => {
jest.clearAllMocks()
Object.defineProperty(URL, 'createObjectURL', {
configurable: true,
value: jest.fn(() => 'blob:video-preview'),
})
Object.defineProperty(URL, 'revokeObjectURL', {
configurable: true,
value: jest.fn(),
})
global.fetch = jest.fn().mockResolvedValue({
ok: true,
json: async () => ({ materialTypes: ['3D场景', '淘系-主图视频'] }),
}) as any
})
afterAll(() => {
if (originalCreateObjectUrl) Object.defineProperty(URL, 'createObjectURL', originalCreateObjectUrl)
else delete (URL as { createObjectURL?: unknown }).createObjectURL
if (originalRevokeObjectUrl) Object.defineProperty(URL, 'revokeObjectURL', originalRevokeObjectUrl)
else delete (URL as { revokeObjectURL?: unknown }).revokeObjectURL
})
test('压缩完成前阻止提交,并在关闭时释放预览 URL', async () => {
const user = userEvent.setup()
const deferredCompression = createDeferred<File>()
;(compressVideoPreviewImage as jest.Mock).mockReturnValue(deferredCompression.promise)
const view = render(<UploadModal onClose={jest.fn()} onSuccess={jest.fn()} />)
const materialTypeSelect = (await screen.findAllByRole('combobox'))[0]
await user.selectOptions(materialTypeSelect, '淘系-主图视频')
const previewInput = screen.getByLabelText(/视频预览图/) as HTMLInputElement
fireEvent.change(previewInput, {
target: { files: [new File(['cover'], 'cover.png', { type: 'image/png' })] },
})
await waitFor(() => expect(compressVideoPreviewImage).toHaveBeenCalledTimes(1))
expect(screen.getByRole('button', { name: '正在处理预览图...' })).toBeDisabled()
expect(materialTypeSelect).toBeDisabled()
fireEvent.submit(screen.getByRole('button', { name: '正在处理预览图...' }).closest('form')!)
expect(await screen.findByRole('alert')).toHaveTextContent('视频预览图仍在处理中')
const compressedFile = new File(['compressed'], 'cover.jpg', { type: 'image/jpeg' })
deferredCompression.resolve(compressedFile)
await waitFor(() => expect(screen.getByAltText('已选择的视频预览图')).toBeInTheDocument())
expect(screen.getByRole('button', { name: '上传' })).toBeEnabled()
view.unmount()
expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:video-preview')
})
test('处理中忽略同一控件的重复选择', async () => {
const deferredCompression = createDeferred<File>()
;(compressVideoPreviewImage as jest.Mock).mockReturnValue(deferredCompression.promise)
render(<UploadModal onClose={jest.fn()} onSuccess={jest.fn()} />)
const materialTypeSelect = (await screen.findAllByRole('combobox'))[0]
fireEvent.change(materialTypeSelect, { target: { value: '淘系-主图视频' } })
const previewInput = screen.getByLabelText(/视频预览图/) as HTMLInputElement
fireEvent.change(previewInput, {
target: { files: [new File(['cover-1'], 'cover-1.png', { type: 'image/png' })] },
})
fireEvent.change(previewInput, {
target: { files: [new File(['cover-2'], 'cover-2.png', { type: 'image/png' })] },
})
await waitFor(() => expect(compressVideoPreviewImage).toHaveBeenCalledTimes(1))
deferredCompression.resolve(new File(['compressed'], 'cover.jpg', { type: 'image/jpeg' }))
await waitFor(() => expect(screen.getByAltText('已选择的视频预览图')).toBeInTheDocument())
})
test('处理中关闭后丢弃异步结果且不创建 Blob URL', async () => {
const onClose = jest.fn()
const deferredCompression = createDeferred<File>()
;(compressVideoPreviewImage as jest.Mock).mockReturnValue(deferredCompression.promise)
const view = render(<UploadModal onClose={onClose} onSuccess={jest.fn()} />)
const materialTypeSelect = (await screen.findAllByRole('combobox'))[0]
fireEvent.change(materialTypeSelect, { target: { value: '淘系-主图视频' } })
const previewInput = screen.getByLabelText(/视频预览图/) as HTMLInputElement
fireEvent.change(previewInput, {
target: { files: [new File(['cover'], 'cover.png', { type: 'image/png' })] },
})
await waitFor(() => expect(compressVideoPreviewImage).toHaveBeenCalledTimes(1))
fireEvent.click(screen.getByRole('button', { name: '关闭上传素材窗口' }))
expect(onClose).toHaveBeenCalledTimes(1)
view.unmount()
deferredCompression.resolve(new File(['compressed'], 'cover.jpg', { type: 'image/jpeg' }))
await Promise.resolve()
await Promise.resolve()
expect(URL.createObjectURL).not.toHaveBeenCalled()
})
test('通用批量视频的预览图处理失败时保留文件并允许重试', async () => {
const user = userEvent.setup()
const onClose = jest.fn()
const onSuccess = jest.fn()
;(compressVideoPreviewImage as jest.Mock).mockImplementation(async (file: File) => file)
render(<UploadModal onClose={onClose} onSuccess={onSuccess} />)
const materialTypeSelect = (await screen.findAllByRole('combobox'))[0]
await user.selectOptions(materialTypeSelect, '淘系-主图视频')
const mainDropZone = screen.getByRole('button', { name: '选择素材文件' }).closest('div.border-dashed')
expect(mainDropZone).not.toBeNull()
fireEvent.drop(mainDropZone!, {
dataTransfer: {
files: [
new File(['video-1'], 'VIDEO-001.mp4', { type: 'video/mp4' }),
new File(['video-2'], 'VIDEO-002.mp4', { type: 'video/mp4' }),
],
},
})
expect(await screen.findByText('批量上传模式(2 个文件)')).toBeInTheDocument()
const firstMetadataSelects = screen.getAllByRole('combobox')
await user.selectOptions(firstMetadataSelects[1], '通用')
await user.selectOptions(firstMetadataSelects[2], '通用')
const firstPreviewInput = screen.getByLabelText(/^视频预览图/) as HTMLInputElement
await waitFor(() => expect(firstPreviewInput).not.toBeDisabled())
fireEvent.change(firstPreviewInput, {
target: {
files: [new File(['cover-0'], 'cover-0.png', { type: 'image/png' })],
},
})
await waitFor(() => expect(screen.getAllByAltText('已选择的视频预览图')).toHaveLength(1))
await user.click(screen.getByText('VIDEO-002.mp4'))
const previewInputs = await screen.findAllByLabelText(/^视频预览图/)
expect(previewInputs).toHaveLength(2)
const secondPreviewInput = previewInputs[1] as HTMLInputElement
const secondMetadataSelects = screen.getAllByRole('combobox')
await user.selectOptions(secondMetadataSelects[3], '通用')
await user.selectOptions(secondMetadataSelects[4], '通用')
await waitFor(() => expect(secondPreviewInput).not.toBeDisabled())
fireEvent.change(secondPreviewInput, {
target: {
files: [new File(['cover-1'], 'cover-1.png', { type: 'image/png' })],
},
})
await waitFor(() => expect(screen.getAllByAltText('已选择的视频预览图')).toHaveLength(2))
;(compressVideoPreviewImage as jest.Mock).mockRejectedValue(new Error('预览图无法解码'))
fireEvent.click(screen.getByRole('button', { name: '上传' }))
expect(await screen.findByRole('alert')).toHaveTextContent('2 个文件上传失败,请检查后重试')
expect(screen.getByText('批量上传模式(2 个文件)')).toBeInTheDocument()
expect(onSuccess).not.toHaveBeenCalled()
expect(onClose).not.toHaveBeenCalled()
})
})