gongxiangp2/components/home/MaterialCard.test.tsx
2026-08-21 13:41:49 +08:00

249 lines
7.2 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.

import { createRef } from 'react'
import { fireEvent, render, screen } from '@testing-library/react'
import MaterialCard, { type Material } from '@/components/home/MaterialCard'
jest.mock('next/image', () => ({
__esModule: true,
default: (props: any) => {
const { priority, fill, unoptimized, sizes, ...rest } = props
return <img {...rest} />
},
}))
jest.mock('@/components/AudioVisualizer', () => ({
__esModule: true,
default: () => <div data-testid="audio-visualizer" />,
}))
describe('MaterialCard', () => {
const baseMaterial: Material = {
id: 'material-1',
productModel: 'SPU-001',
categoryName: '旗舰店',
sceneName: '客厅',
platformName: '天猫',
productCategoryName: '沙发',
materialType: '任务-AI原图',
renderer: '',
authorName: '张三',
previewUrl: 'https://example.com/preview.jpg',
zipFileName: 'sample.zip',
zipFileSize: 1024,
downloadCount: 0,
likeCount: 0,
isLiked: false,
isFavorited: false,
isAiGenerated: true,
userId: 'user-1',
createdAt: '2026-03-25T08:00:00.000Z',
}
const defaultProps = {
material: baseMaterial,
session: null,
playingAudioId: null,
audioElement: null,
audioProgress: 0,
audioCurrentTime: 0,
audioDuration: 0,
audioRef: createRef<HTMLAudioElement>(),
downloadingId: null,
downloadProgress: 0,
themeColor: '#111827',
onAudioPlay: jest.fn(),
onDownload: jest.fn(),
onLike: jest.fn(),
onFavorite: jest.fn(),
onShare: jest.fn(),
onEdit: jest.fn(),
onDelete: jest.fn(),
onClick: jest.fn(),
onRetryTranscode: jest.fn(),
showToast: jest.fn(),
}
beforeEach(() => {
jest.clearAllMocks()
})
test('AI 导入素材应显示 AI生成 标签', () => {
render(<MaterialCard {...defaultProps} />)
expect(screen.getByText('AI生成')).toBeInTheDocument()
})
test('非 AI 导入素材不显示 AI生成 标签', () => {
render(
<MaterialCard
{...defaultProps}
material={{
...baseMaterial,
isAiGenerated: false,
}}
/>,
)
expect(screen.queryByText('AI生成')).not.toBeInTheDocument()
})
test('任务-详情应优先显示出图方式标签', () => {
render(
<MaterialCard
{...defaultProps}
material={{
...baseMaterial,
materialType: '任务-详情',
taskDetailSourceType: '实拍',
}}
/>,
)
expect(screen.getByText('实拍')).toBeInTheDocument()
expect(screen.queryByText('AI生成')).not.toBeInTheDocument()
})
test('任务-详情未设置出图方式时应显示未设置', () => {
render(
<MaterialCard
{...defaultProps}
material={{
...baseMaterial,
materialType: '任务-详情',
isAiGenerated: false,
taskDetailSourceType: null,
}}
/>,
)
expect(screen.getByText('未设置')).toBeInTheDocument()
})
test('15天策略隐藏源地址时仍显示视频转码进度,提示不遮挡预览', () => {
const { container } = render(
<MaterialCard
{...defaultProps}
material={{
...baseMaterial,
materialType: '淘系-逛逛视频',
previewUrl: '',
videoSourceUrl: undefined,
videoProcessingStatus: 'pending',
videoPreviewBlocked: true,
videoPreviewRemainingDays: 15,
}}
/>,
)
expect(screen.getByRole('status')).toHaveTextContent('等待中')
expect(screen.getByRole('progressbar')).toHaveAttribute('aria-valuenow', '5')
const previewArea = container.querySelector('[data-material-link="true"]')
const notice = screen.getByText('15天后可查看详情')
expect(previewArea).not.toBeNull()
expect(previewArea).not.toContainElement(notice)
expect(notice).toHaveAttribute('data-video-preview-notice', 'true')
})
test('视频卡片初始只加载封面,悬停时才挂载视频预览', () => {
const { container } = render(
<MaterialCard
{...defaultProps}
material={{
...baseMaterial,
materialType: '淘系-逛逛视频',
videoCoverUrl: 'https://example.com/video-cover.jpg',
videoPreviewClipUrl: 'https://example.com/video-preview.mp4',
videoProcessingStatus: 'done',
}}
/>,
)
const previewArea = container.querySelector('[data-material-link="true"]')
expect(previewArea).not.toBeNull()
expect(screen.getByAltText('SPU-001 视频封面')).toHaveAttribute(
'src',
'https://example.com/video-cover.jpg',
)
expect(screen.queryByLabelText('SPU-001 视频预览')).not.toBeInTheDocument()
fireEvent.mouseEnter(previewArea!)
expect(screen.getByLabelText('SPU-001 视频预览')).toHaveAttribute(
'src',
'https://example.com/video-preview.mp4',
)
fireEvent.mouseLeave(previewArea!)
expect(screen.queryByLabelText('SPU-001 视频预览')).not.toBeInTheDocument()
expect(screen.getByAltText('SPU-001 视频封面')).toBeInTheDocument()
})
test('普通素材恢复 400px 高预览区并铺满容器', () => {
const { container } = render(<MaterialCard {...defaultProps} />)
const previewArea = container.querySelector('[data-material-link="true"]')
expect(previewArea).toHaveClass('h-[400px]')
expect(previewArea).not.toHaveClass('aspect-[4/3]')
expect(screen.getByAltText('客厅')).toHaveClass('object-cover')
})
test('15天保护期内仍可复制分享链接', () => {
render(
<MaterialCard
{...defaultProps}
material={{
...baseMaterial,
materialType: '淘系-逛逛视频',
videoPreviewBlocked: true,
videoPreviewRemainingDays: 12,
}}
/>,
)
fireEvent.click(screen.getByRole('button', { name: '分享' }))
expect(defaultProps.onShare).toHaveBeenCalledWith('material-1')
expect(defaultProps.showToast).not.toHaveBeenCalled()
})
test('接口未返回并发上限时按当前单任务策略展示', () => {
render(
<MaterialCard
{...defaultProps}
material={{
...baseMaterial,
materialType: '淘系-逛逛视频',
previewUrl: '',
videoProcessingStatus: 'processing',
transcodeQueueProcessingIds: ['material-1'],
transcodeQueueWaitingTotal: 1,
}}
/>,
)
expect(screen.getByRole('status')).toHaveTextContent('1/1并发')
})
test('15天遮罩下的转码失败仍可见且可以重试', () => {
render(
<MaterialCard
{...defaultProps}
material={{
...baseMaterial,
materialType: '抖音-千川素材',
previewUrl: '',
videoSourceUrl: undefined,
videoProcessingStatus: 'failed',
transcodeLastError: '转码失败:源文件暂时不可读',
videoPreviewBlocked: true,
videoPreviewRemainingDays: 15,
}}
/>,
)
expect(screen.getByRole('status')).toHaveTextContent('转码失败,请稍后重试')
fireEvent.click(screen.getByRole('button', { name: '重试转码' }))
expect(defaultProps.onRetryTranscode).toHaveBeenCalledWith('material-1')
})
})