168 lines
5.4 KiB
TypeScript
168 lines
5.4 KiB
TypeScript
import React from 'react'
|
|
import { renderToStaticMarkup } from 'react-dom/server'
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
import VideoPanelCardBody from '@/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video/panel-card/VideoPanelCardBody'
|
|
import type { VideoPanelRuntime } from '@/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video/panel-card/hooks/useVideoPanelActions'
|
|
|
|
vi.mock('@/components/task/TaskStatusInline', () => ({
|
|
default: () => React.createElement('span', null, 'task-status'),
|
|
}))
|
|
|
|
vi.mock('@/components/ui/config-modals/ModelCapabilityDropdown', () => ({
|
|
ModelCapabilityDropdown: () => React.createElement('div', null, 'model-dropdown'),
|
|
}))
|
|
|
|
vi.mock('@/components/ui/icons', () => ({
|
|
AppIcon: ({ name }: { name: string }) => React.createElement('span', null, name),
|
|
}))
|
|
|
|
function createRuntime(overrides: Partial<VideoPanelRuntime> = {}): VideoPanelRuntime {
|
|
const translate = (key: string, values?: Record<string, unknown>) => {
|
|
if (key === 'firstLastFrame.asLastFrameFor') {
|
|
return `作为镜头 ${String(values?.number ?? '')} 的尾帧`
|
|
}
|
|
if (key === 'firstLastFrame.asFirstFrameFor') {
|
|
return `作为镜头 ${String(values?.number ?? '')} 的首帧`
|
|
}
|
|
if (key === 'firstLastFrame.generate') return '生成首尾帧视频'
|
|
if (key === 'firstLastFrame.generated') return '首尾帧视频已生成'
|
|
if (key === 'promptModal.promptLabel') return '视频提示词'
|
|
if (key === 'promptModal.placeholder') return '输入首尾帧视频提示词...'
|
|
if (key === 'panelCard.clickToEditPrompt') return '点击编辑提示词...'
|
|
if (key === 'panelCard.selectModel') return '选择模型'
|
|
if (key === 'panelCard.generateVideo') return '生成视频'
|
|
if (key === 'panelCard.unknownShotType') return '未知镜头'
|
|
if (key === 'stage.hasSynced') return '已生成'
|
|
if (key === 'promptModal.duration') return '秒'
|
|
return key
|
|
}
|
|
|
|
const runtime = {
|
|
t: translate,
|
|
tCommon: (key: string) => key,
|
|
panel: {
|
|
storyboardId: 'sb-1',
|
|
panelIndex: 2,
|
|
panelId: 'panel-2',
|
|
imageUrl: 'https://example.com/frame-2.jpg',
|
|
videoUrl: null,
|
|
videoGenerationMode: null,
|
|
lipSyncVideoUrl: null,
|
|
textPanel: {
|
|
shot_type: '平视中景',
|
|
description: '谢俞站在宴席中央',
|
|
duration: 3,
|
|
},
|
|
},
|
|
panelIndex: 2,
|
|
panelKey: 'sb-1-2',
|
|
media: {
|
|
showLipSyncVideo: true,
|
|
onToggleLipSyncVideo: () => undefined,
|
|
onPreviewImage: () => undefined,
|
|
baseVideoUrl: undefined,
|
|
currentVideoUrl: undefined,
|
|
},
|
|
taskStatus: {
|
|
isVideoTaskRunning: false,
|
|
isLipSyncTaskRunning: false,
|
|
taskRunningVideoLabel: '生成中',
|
|
lipSyncInlineState: null,
|
|
},
|
|
videoModel: {
|
|
selectedModel: 'veo-3.1',
|
|
setSelectedModel: () => undefined,
|
|
capabilityFields: [],
|
|
generationOptions: {},
|
|
setCapabilityValue: () => undefined,
|
|
missingCapabilityFields: [],
|
|
videoModelOptions: [],
|
|
},
|
|
player: {
|
|
isPlaying: false,
|
|
},
|
|
promptEditor: {
|
|
isEditing: false,
|
|
editingPrompt: '',
|
|
setEditingPrompt: () => undefined,
|
|
handleStartEdit: () => undefined,
|
|
handleSave: () => undefined,
|
|
handleCancelEdit: () => undefined,
|
|
isSavingPrompt: false,
|
|
localPrompt: '人物从席间回身,接到下一镜头',
|
|
},
|
|
voiceManager: {
|
|
hasMatchedAudio: false,
|
|
hasMatchedVoiceLines: false,
|
|
audioGenerateError: null,
|
|
localVoiceLines: [],
|
|
isVoiceLineTaskRunning: () => false,
|
|
handlePlayVoiceLine: () => undefined,
|
|
handleGenerateAudio: async () => undefined,
|
|
playingVoiceLineId: null,
|
|
},
|
|
lipSync: {
|
|
handleStartLipSync: () => undefined,
|
|
executingLipSync: false,
|
|
},
|
|
layout: {
|
|
isLinked: true,
|
|
isLastFrame: true,
|
|
nextPanel: {
|
|
storyboardId: 'sb-1',
|
|
panelIndex: 3,
|
|
imageUrl: 'https://example.com/frame-3.jpg',
|
|
},
|
|
prevPanel: {
|
|
storyboardId: 'sb-1',
|
|
panelIndex: 1,
|
|
imageUrl: 'https://example.com/frame-1.jpg',
|
|
},
|
|
hasNext: true,
|
|
flModel: 'veo-3.1',
|
|
flModelOptions: [],
|
|
flGenerationOptions: {},
|
|
flCapabilityFields: [],
|
|
flMissingCapabilityFields: [],
|
|
flCustomPrompt: '',
|
|
defaultFlPrompt: '',
|
|
videoRatio: '9:16',
|
|
},
|
|
actions: {
|
|
onGenerateVideo: () => undefined,
|
|
onUpdatePanelVideoModel: () => undefined,
|
|
onToggleLink: () => undefined,
|
|
onFlModelChange: () => undefined,
|
|
onFlCapabilityChange: () => undefined,
|
|
onFlCustomPromptChange: () => undefined,
|
|
onResetFlPrompt: () => undefined,
|
|
onGenerateFirstLastFrame: () => undefined,
|
|
},
|
|
computed: {
|
|
showLipSyncSection: false,
|
|
canLipSync: false,
|
|
hasVisibleBaseVideo: false,
|
|
},
|
|
}
|
|
|
|
return {
|
|
...runtime,
|
|
...overrides,
|
|
} as unknown as VideoPanelRuntime
|
|
}
|
|
|
|
describe('VideoPanelCardBody', () => {
|
|
it('renders incoming and outgoing first-last-frame UI for chained panel', () => {
|
|
const markup = renderToStaticMarkup(
|
|
React.createElement(VideoPanelCardBody, {
|
|
runtime: createRuntime(),
|
|
}),
|
|
)
|
|
|
|
expect(markup).toContain('作为镜头 2 的尾帧')
|
|
expect(markup).toContain('作为镜头 4 的首帧')
|
|
expect(markup).toContain('视频提示词')
|
|
expect(markup).toContain('生成首尾帧视频')
|
|
})
|
|
})
|