feat: initial release v0.3.0
This commit is contained in:
167
tests/unit/novel-promotion/video-panel-card-body.test.ts
Normal file
167
tests/unit/novel-promotion/video-panel-card-body.test.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
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('生成首尾帧视频')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user