gongxiangp2/components/home/MaterialCard.test.tsx
chenpei 7326e273e1 fix: harden media upload and release safety
Protect upload cleanup and atomic completion paths, add durable media worker leases and brand asset validation, and introduce verified migration/bootstrap/deployment gates.
2026-08-12 18:04:08 +08:00

184 lines
4.8 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('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天策略隐藏源地址时仍显示视频转码进度', () => {
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')
expect(screen.getByText('15天后可查看详情')).toBeInTheDocument()
})
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')
})
})