287 lines
9.2 KiB
TypeScript
287 lines
9.2 KiB
TypeScript
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.each(['任务-AI原图', '直播切片', '音乐', '证书-检测报告'])('%s 的类型和店铺应独立于截断的元信息展示', (materialType) => {
|
||
render(<MaterialCard {...defaultProps} material={{ ...baseMaterial, materialType }} />)
|
||
|
||
const typeBadge = screen.getByLabelText(`素材类型:${materialType}`)
|
||
const shopBadge = screen.getByLabelText('使用店铺:旗舰店')
|
||
expect(typeBadge.closest('[data-material-link]')).not.toBeNull()
|
||
expect(shopBadge.closest('[aria-label="素材分类信息"]')).not.toBeNull()
|
||
expect(typeBadge.closest('.truncate')).toBeNull()
|
||
expect(shopBadge.closest('.truncate')).toBeNull()
|
||
})
|
||
|
||
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).toContainElement(notice)
|
||
expect(notice).toHaveAttribute('data-video-preview-notice', 'true')
|
||
expect(notice.parentElement).not.toHaveClass('bg-[#fafafa]/55')
|
||
})
|
||
|
||
test.each([
|
||
['成品检测报告', '成品检测报告'],
|
||
[null, 'SPU-001'],
|
||
])('检测报告卡片优先使用报告名称,缺失时保留旧 SPU (%s)', (reportTitle, expectedTitle) => {
|
||
render(<MaterialCard {...defaultProps} material={{ ...baseMaterial, materialType: '证书-检测报告', reportKind: '成品', reportTitle }} />)
|
||
expect(screen.getByRole('heading', { level: 3 })).toHaveTextContent(expectedTitle)
|
||
})
|
||
|
||
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('刚上传的直播切片卡片可查看详情和下载,不显示等待期', () => {
|
||
const liveMaterial = {
|
||
...baseMaterial,
|
||
materialType: '直播切片',
|
||
createdAt: new Date().toISOString(),
|
||
videoCoverUrl: 'https://example.com/live-cover.jpg',
|
||
videoPreviewClipUrl: 'https://example.com/live-clip.mp4',
|
||
}
|
||
const { container } = render(<MaterialCard {...defaultProps} material={liveMaterial} />)
|
||
|
||
expect(container.querySelector('[data-video-preview-notice]')).toBeNull()
|
||
fireEvent.click(container.querySelector('[data-material-link="true"]')!)
|
||
expect(defaultProps.onClick).toHaveBeenCalledWith(liveMaterial)
|
||
fireEvent.click(screen.getByRole('button', { name: '下载' }))
|
||
expect(defaultProps.onDownload).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')
|
||
})
|
||
|
||
})
|