/* eslint-disable @next/next/no-img-element, jsx-a11y/alt-text */ import { fireEvent, render, screen, waitFor } from '@testing-library/react' import gsap from 'gsap' import VideoPreviewImageUpload from './VideoPreviewImageUpload' function createPngFile(name: string, width = 1920, height = 1080) { const bytes = new Uint8Array(24) bytes.set([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]) const view = new DataView(bytes.buffer) view.setUint32(16, width) view.setUint32(20, height) return new File([bytes], name, { type: 'image/png' }) } function setReducedMotionPreference(reducedMotion: boolean) { Object.defineProperty(window, 'matchMedia', { writable: true, value: jest.fn().mockImplementation((query: string) => ({ matches: query === '(prefers-reduced-motion: no-preference)' && !reducedMotion, media: query, onchange: null, addListener: jest.fn(), removeListener: jest.fn(), addEventListener: jest.fn(), removeEventListener: jest.fn(), dispatchEvent: jest.fn(), })), }) } beforeEach(() => { setReducedMotionPreference(false) }) jest.mock('next/image', () => ({ __esModule: true, default: (props: any) => { const { fill, unoptimized, sizes, ...rest } = props return }, })) describe('VideoPreviewImageUpload', () => { test('空状态明确说明选填和自动截取规则', () => { render( , ) expect(screen.getByText('视频预览图')).toBeInTheDocument() expect(screen.getByText('(选填)')).toBeInTheDocument() expect(screen.getByText('未填写时自动从视频截取')).toBeInTheDocument() expect(screen.getByText('添加预览图')).toBeInTheDocument() }) test('选择有效图片后交给上传面板处理', async () => { const onFileSelect = jest.fn().mockResolvedValue(undefined) const onValidationError = jest.fn() const onProcessingChange = jest.fn() const { container } = render( , ) const input = container.querySelector('input[type="file"]') as HTMLInputElement const cover = createPngFile('cover.png') fireEvent.change(input, { target: { files: [cover] } }) await waitFor(() => expect(onFileSelect).toHaveBeenCalledWith(cover)) expect(onValidationError).toHaveBeenCalledWith('') await waitFor(() => expect(onProcessingChange.mock.calls).toEqual([[true], [false]])) }) test('处理失败时保留具体原因并解除处理中状态', async () => { const onValidationError = jest.fn() const onProcessingChange = jest.fn() const { container } = render( , ) const input = container.querySelector('input[type="file"]') as HTMLInputElement fireEvent.change(input, { target: { files: [createPngFile('cover.png')] }, }) await waitFor(() => expect(onValidationError).toHaveBeenLastCalledWith('预览图处理失败')) expect(onProcessingChange.mock.calls).toEqual([[true], [false]]) }) test('在交给上传面板前拒绝超大像素图片', async () => { const onFileSelect = jest.fn() const onValidationError = jest.fn() const { container } = render( , ) const input = container.querySelector('input[type="file"]') as HTMLInputElement fireEvent.change(input, { target: { files: [createPngFile('huge.png', 10_000, 7_000)] }, }) await waitFor(() => expect(onValidationError).toHaveBeenLastCalledWith( expect.stringContaining('像素总量超过 60,000,000'), )) expect(onFileSelect).not.toHaveBeenCalled() }) test('拒绝不支持的图片格式并给出恢复方向', () => { const onFileSelect = jest.fn() const onValidationError = jest.fn() const { container } = render( , ) const input = container.querySelector('input[type="file"]') as HTMLInputElement const gif = new File(['gif'], 'cover.gif', { type: 'image/gif' }) fireEvent.change(input, { target: { files: [gif] } }) expect(onFileSelect).not.toHaveBeenCalled() expect(onValidationError).toHaveBeenCalledWith(expect.stringContaining('JPG、JPEG、PNG、WEBP')) }) test('已选择状态支持更换和移除', () => { const onRemove = jest.fn() render( , ) expect(screen.getByAltText('已选择的视频预览图')).toBeInTheDocument() expect(screen.getByText('cover.jpg')).toBeInTheDocument() expect(screen.getByRole('button', { name: '更换' })).toBeInTheDocument() fireEvent.click(screen.getByRole('button', { name: '移除视频预览图' })) expect(onRemove).toHaveBeenCalledTimes(1) }) test('预览图出现时使用短促的 GSAP 状态动画', async () => { const animation = jest.spyOn(gsap, 'fromTo') render( , ) await waitFor(() => expect(animation).toHaveBeenCalledWith( expect.any(HTMLDivElement), expect.objectContaining({ autoAlpha: 0.72, scale: 0.985, y: 4 }), expect.objectContaining({ duration: 0.22, ease: 'power2.out' }), )) animation.mockRestore() }) test('系统要求减弱动态效果时不播放预览图动画', () => { setReducedMotionPreference(true) const animation = jest.spyOn(gsap, 'fromTo') render( , ) expect(animation).not.toHaveBeenCalled() animation.mockRestore() }) })