febb1bcfce
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m2s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
前端: - 删除 GenerationProgressModal/GenerationHistoryModal 组件及目录 - 删除 generateEditPlan/getGenerationStatus/getEditPlanGenerations 三个死API函数 - 删除对应类型 GenerateResponse/EditPlanGeneration/GenerationStatusResponse - 清理测试文件中的 mock 和引用 - 用户可见文案 '剪辑编辑器' 统一改为 '剪辑模板' - 不改变量名/组件名/路由路径/API路径 后端: - 删除 templates_editor/generation.py 死接口文件 - 清理 _fallback.py、dependencies.py、schemas.py 中的死代码 - 删除对应无用测试文件
312 lines
13 KiB
TypeScript
312 lines
13 KiB
TypeScript
import React from "react"
|
|
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"
|
|
import { render, act } from "@testing-library/react"
|
|
import { MemoryRouter } from "react-router-dom"
|
|
|
|
// === React Query mock ===
|
|
vi.mock("@tanstack/react-query", () => ({
|
|
useQuery: vi.fn(() => ({
|
|
data: undefined,
|
|
isLoading: false,
|
|
isError: false,
|
|
refetch: vi.fn(),
|
|
})),
|
|
useMutation: vi.fn(() => ({
|
|
mutate: vi.fn(),
|
|
mutateAsync: vi.fn(),
|
|
isLoading: false,
|
|
})),
|
|
useQueryClient: vi.fn(() => ({
|
|
invalidateQueries: vi.fn(),
|
|
setQueryData: vi.fn(),
|
|
getQueryData: vi.fn(),
|
|
})),
|
|
}))
|
|
|
|
// === Ant Design Icons mock ===
|
|
vi.mock("@ant-design/icons", () => ({
|
|
VideoCameraOutlined: () => React.createElement("span", null, "V"),
|
|
PictureOutlined: () => React.createElement("span", null, "P"),
|
|
SoundOutlined: () => React.createElement("span", null, "S"),
|
|
PlusOutlined: () => React.createElement("span", null, "+"),
|
|
DeleteOutlined: () => React.createElement("span", null, "D"),
|
|
EditOutlined: () => React.createElement("span", null, "E"),
|
|
CopyOutlined: () => React.createElement("span", null, "C"),
|
|
DownloadOutlined: () => React.createElement("span", null, "D"),
|
|
PlayCircleOutlined: () => React.createElement("span", null, ">"),
|
|
PauseCircleOutlined: () => React.createElement("span", null, "||"),
|
|
LeftOutlined: () => React.createElement("span", null, "<"),
|
|
RightOutlined: () => React.createElement("span", null, ">"),
|
|
UpOutlined: () => React.createElement("span", null, "^"),
|
|
DownOutlined: () => React.createElement("span", null, "v"),
|
|
SaveOutlined: () => React.createElement("span", null, "S"),
|
|
UndoOutlined: () => React.createElement("span", null, "U"),
|
|
RedoOutlined: () => React.createElement("span", null, "R"),
|
|
CloseOutlined: () => React.createElement("span", null, "X"),
|
|
CheckOutlined: () => React.createElement("span", null, "v"),
|
|
SettingOutlined: () => React.createElement("span", null, "S"),
|
|
AppstoreOutlined: () => React.createElement("span", null, "#"),
|
|
UnorderedListOutlined: () => React.createElement("span", null, "="),
|
|
HistoryOutlined: () => React.createElement("span", null, "H"),
|
|
UploadOutlined: () => React.createElement("span", null, "U"),
|
|
SearchOutlined: () => React.createElement("span", null, "S"),
|
|
FilterOutlined: () => React.createElement("span", null, "F"),
|
|
FontColorsOutlined: () => React.createElement("span", null, "A"),
|
|
BgColorsOutlined: () => React.createElement("span", null, "B"),
|
|
AudioOutlined: () => React.createElement("span", null, "A"),
|
|
MusicOutlined: () => React.createElement("span", null, "M"),
|
|
ScissorOutlined: () => React.createElement("span", null, "X"),
|
|
ThunderboltOutlined: () => React.createElement("span", null, "T"),
|
|
ExperimentOutlined: () => React.createElement("span", null, "E"),
|
|
BulbOutlined: () => React.createElement("span", null, "B"),
|
|
FundOutlined: () => React.createElement("span", null, "F"),
|
|
LayoutOutlined: () => React.createElement("span", null, "L"),
|
|
ColumnHeightOutlined: () => React.createElement("span", null, "C"),
|
|
SwapOutlined: () => React.createElement("span", null, "S"),
|
|
}))
|
|
|
|
// === Ant Design mock ===
|
|
vi.mock("antd", () => ({
|
|
message: { success: vi.fn(), error: vi.fn(), warning: vi.fn(), info: vi.fn() },
|
|
Modal: ({ open, children, title }: any) =>
|
|
open ? React.createElement("div", { role: "dialog", "data-title": title }, children) : null,
|
|
Progress: () => React.createElement("div"),
|
|
Button: ({ children, onClick }: any) => React.createElement("button", { onClick }, children),
|
|
Tabs: ({ items }: any) =>
|
|
React.createElement(
|
|
"div",
|
|
null,
|
|
items?.map?.(() => React.createElement("div")),
|
|
),
|
|
TabPane: () => React.createElement("div"),
|
|
Drawer: ({ open, children, title }: any) =>
|
|
open ? React.createElement("div", { role: "dialog", "data-title": title }, children) : null,
|
|
Select: ({ children }: any) => React.createElement("select", null, children),
|
|
Option: ({ children }: any) => React.createElement("option", null, children),
|
|
Input: ({ placeholder }: any) => React.createElement("input", { placeholder }),
|
|
InputNumber: () => React.createElement("input", { type: "number" }),
|
|
Switch: () => React.createElement("input", { type: "checkbox" }),
|
|
Slider: () => React.createElement("div"),
|
|
ColorPicker: () => React.createElement("div"),
|
|
Upload: ({ children }: any) => React.createElement("div", null, children),
|
|
Space: ({ children }: any) => React.createElement("div", null, children),
|
|
Row: ({ children }: any) => React.createElement("div", null, children),
|
|
Col: ({ children }: any) => React.createElement("div", null, children),
|
|
Card: ({ children }: any) => React.createElement("div", null, children),
|
|
Tag: ({ children }: any) => React.createElement("span", null, children),
|
|
Tooltip: ({ children }: any) => React.createElement("span", null, children),
|
|
Popover: ({ children }: any) => React.createElement("span", null, children),
|
|
Dropdown: ({ children }: any) => React.createElement("span", null, children),
|
|
Menu: () => React.createElement("div"),
|
|
Divider: () => React.createElement("hr"),
|
|
Empty: () => React.createElement("div", null, "Empty"),
|
|
Spin: () => React.createElement("div", null, "Loading"),
|
|
Badge: ({ children }: any) => React.createElement("span", null, children),
|
|
Avatar: ({ children }: any) => React.createElement("span", null, children),
|
|
Checkbox: ({ children }: any) => React.createElement("span", null, children),
|
|
Radio: ({ children }: any) => React.createElement("span", null, children),
|
|
RadioGroup: ({ children }: any) => React.createElement("div", null, children),
|
|
Segmented: () => React.createElement("div"),
|
|
Collapse: ({ children }: any) => React.createElement("div", null, children),
|
|
CollapsePanel: ({ children }: any) => React.createElement("div", null, children),
|
|
Form: ({ children }: any) => React.createElement("form", null, children),
|
|
FormItem: ({ children }: any) => React.createElement("div", null, children),
|
|
List: () => React.createElement("div"),
|
|
Table: () => React.createElement("div"),
|
|
Pagination: () => React.createElement("div"),
|
|
Popconfirm: ({ children }: any) => React.createElement("span", null, children),
|
|
Result: ({ status, title }: any) => React.createElement("div", { "data-status": status }, title),
|
|
ConfigProvider: ({ children }: any) => React.createElement(React.Fragment, null, children),
|
|
}))
|
|
|
|
// === UI Components mock ===
|
|
vi.mock("@/components/ui", () => ({
|
|
Button: ({ children, onClick }: any) => React.createElement("button", { onClick }, children),
|
|
Input: ({ placeholder }: any) => React.createElement("input", { placeholder }),
|
|
Modal: ({ open, children }: any) =>
|
|
open ? React.createElement("div", { role: "dialog" }, children) : null,
|
|
Drawer: ({ open, children }: any) =>
|
|
open ? React.createElement("div", { role: "dialog" }, children) : null,
|
|
Empty: () => React.createElement("div", null, "Empty"),
|
|
Card: ({ children }: any) => React.createElement("div", null, children),
|
|
Tag: ({ children }: any) => React.createElement("span", null, children),
|
|
Tooltip: ({ children }: any) => React.createElement("span", null, children),
|
|
Select: ({ children }: any) => React.createElement("select", null, children),
|
|
Progress: () => React.createElement("div"),
|
|
Upload: ({ children }: any) => React.createElement("div", null, children),
|
|
}))
|
|
|
|
// === API mocks ===
|
|
vi.mock("@/api/editing-planner", () => ({
|
|
getEditingTemplates: vi.fn().mockResolvedValue({ items: [], total: 0 }),
|
|
getEditingTemplate: vi.fn().mockResolvedValue({}),
|
|
createEditingTemplate: vi.fn().mockResolvedValue({}),
|
|
updateEditingTemplate: vi.fn().mockResolvedValue({}),
|
|
getTemplateCategories: vi.fn().mockResolvedValue({ items: [] }),
|
|
MODE_LABELS: { pip: "画中画", intro_outro: "片头片尾", watermark: "水印" },
|
|
}))
|
|
|
|
vi.mock("@/api/template-editor", () => ({
|
|
getMediaAssets: vi.fn().mockResolvedValue({ items: [] }),
|
|
getEditPlan: vi.fn().mockResolvedValue({}),
|
|
updateEditPlan: vi.fn().mockResolvedValue({}),
|
|
getGenerationTaskResults: vi.fn().mockResolvedValue({ items: [] }),
|
|
getEditPlanClips: vi.fn().mockResolvedValue({ items: [] }),
|
|
createEditPlanClip: vi.fn().mockResolvedValue({}),
|
|
batchDeleteEditPlanClips: vi.fn().mockResolvedValue({}),
|
|
}))
|
|
|
|
vi.mock("@/api/assets", () => ({
|
|
ensureDefaultLibrary: vi.fn().mockResolvedValue({ id: "default-lib" }),
|
|
getAssetsByKind: vi.fn().mockResolvedValue({ items: [] }),
|
|
}))
|
|
|
|
vi.mock("@/api/projects", () => ({
|
|
getOrCreateDefaultProject: vi.fn().mockResolvedValue({ id: "default-project" }),
|
|
}))
|
|
|
|
vi.mock("@/api/bgm", () => ({
|
|
DEFAULT_BGM_MIX_CONFIG: { volume: 1, fade_in: 0, fade_out: 0 },
|
|
}))
|
|
|
|
vi.mock("@/pages/editing-planner/components/MediaPanel", () => ({
|
|
default: () => React.createElement("div", { "data-testid": "MediaPanel" }),
|
|
}))
|
|
vi.mock("@/pages/editing-planner/components/PreviewPlayer", () => ({
|
|
default: () => React.createElement("div", { "data-testid": "PreviewPlayer" }),
|
|
}))
|
|
vi.mock("@/pages/editing-planner/components/TimelinePanel", () => ({
|
|
default: () => React.createElement("div", { "data-testid": "TimelinePanel" }),
|
|
}))
|
|
vi.mock("@/pages/editing-planner/components/ClipPropertiesPanel", () => ({
|
|
default: () => React.createElement("div", { "data-testid": "ClipPropertiesPanel" }),
|
|
}))
|
|
vi.mock("@/pages/editing-planner/components/EditorClipList", () => ({
|
|
default: () => React.createElement("div", { "data-testid": "EditorClipList" }),
|
|
}))
|
|
vi.mock("@/pages/editing-planner/components/BgmSelector", () => ({
|
|
default: () => React.createElement("div", { "data-testid": "BgmSelector" }),
|
|
}))
|
|
vi.mock("@/pages/editing-planner/components/SubtitleStylePanel", () => ({
|
|
default: () => React.createElement("div", { "data-testid": "SubtitleStylePanel" }),
|
|
}))
|
|
vi.mock("@/pages/editing-planner/components/TransitionSelector", () => ({
|
|
default: () => React.createElement("div", { "data-testid": "TransitionSelector" }),
|
|
}))
|
|
vi.mock("@/pages/editing-planner/components/SpeedPanel", () => ({
|
|
default: () => React.createElement("div", { "data-testid": "SpeedPanel" }),
|
|
}))
|
|
vi.mock("@/pages/editing-planner/components/TtsPanel", () => ({
|
|
default: () => React.createElement("div", { "data-testid": "TtsPanel" }),
|
|
}))
|
|
vi.mock("@/pages/editing-planner/components/WatermarkPanel", () => ({
|
|
default: () => React.createElement("div", { "data-testid": "WatermarkPanel" }),
|
|
}))
|
|
vi.mock("@/pages/editing-planner/components/IntroOutroPanel", () => ({
|
|
default: () => React.createElement("div", { "data-testid": "IntroOutroPanel" }),
|
|
}))
|
|
vi.mock("@/pages/editing-planner/components/PipConfigPanel", () => ({
|
|
default: () => React.createElement("div", { "data-testid": "PipConfigPanel" }),
|
|
}))
|
|
vi.mock("@/pages/editing-planner/components/FilterPanel", () => ({
|
|
default: () => React.createElement("div", { "data-testid": "FilterPanel" }),
|
|
}))
|
|
vi.mock("@/pages/editing-planner/components/GreenScreenPanel", () => ({
|
|
default: () => React.createElement("div", { "data-testid": "GreenScreenPanel" }),
|
|
}))
|
|
vi.mock("@/pages/editing-planner/components/StickerPanel", () => ({
|
|
default: () => React.createElement("div", { "data-testid": "StickerPanel" }),
|
|
}))
|
|
vi.mock("@/pages/editing-planner/components/SaveModal", () => ({
|
|
default: () => React.createElement("div", { "data-testid": "SaveModal" }),
|
|
}))
|
|
|
|
// === useUndoRedo hook mock ===
|
|
vi.mock("@/pages/editing-planner/hooks/useUndoRedo", () => ({
|
|
useUndoRedo: vi.fn((initial: any) => ({
|
|
state: initial,
|
|
setState: vi.fn(),
|
|
undo: vi.fn(),
|
|
redo: vi.fn(),
|
|
canUndo: false,
|
|
canRedo: false,
|
|
reset: vi.fn(),
|
|
})),
|
|
}))
|
|
|
|
// === Types mock ===
|
|
vi.mock("@/pages/editing-planner/types", () => ({
|
|
DEFAULT_TRANSITION: { type: "fade", duration: 0.5 },
|
|
DEFAULT_SPEED: { rate: 1 },
|
|
DEFAULT_TTS_CONFIG: { enabled: false },
|
|
DEFAULT_WATERMARK: { enabled: false },
|
|
DEFAULT_INTRO_OUTRO: { enabled: false },
|
|
DEFAULT_PIP_CONFIG: { enabled: false },
|
|
DEFAULT_FILTER_CONFIG: { enabled: false },
|
|
DEFAULT_CHROMA_KEY_CONFIG: { enabled: false },
|
|
DEFAULT_STICKER_CONFIG: { enabled: false },
|
|
}))
|
|
|
|
vi.mock("@/pages/editing-planner/types/subtitle", () => ({
|
|
DEFAULT_SUBTITLE_STYLE: {
|
|
font_size: 24,
|
|
font_color: "#ffffff",
|
|
background_color: "#000000",
|
|
},
|
|
}))
|
|
|
|
// === PageHead mock ===
|
|
vi.mock("@/components/layout/PageHead", () => ({
|
|
default: ({ title }: { title: string }) =>
|
|
React.createElement("div", { "data-testid": "page-head" }, title),
|
|
}))
|
|
|
|
import EditingPlanner from "@/pages/editing-planner/EditingPlanner"
|
|
|
|
describe("EditingPlanner", () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers()
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers()
|
|
})
|
|
|
|
it("renders without crashing", () => {
|
|
const { container } = render(
|
|
React.createElement(MemoryRouter, null, React.createElement(EditingPlanner)),
|
|
)
|
|
expect(container).toBeTruthy()
|
|
})
|
|
|
|
it("renders with templateId query param", () => {
|
|
const { container } = render(
|
|
React.createElement(
|
|
MemoryRouter,
|
|
{ initialEntries: ["?templateId=tpl-123"] },
|
|
React.createElement(EditingPlanner),
|
|
),
|
|
)
|
|
expect(container).toBeTruthy()
|
|
})
|
|
|
|
it("renders with planId query param", () => {
|
|
const { container } = render(
|
|
React.createElement(
|
|
MemoryRouter,
|
|
{ initialEntries: ["?planId=plan-456"] },
|
|
React.createElement(EditingPlanner),
|
|
),
|
|
)
|
|
expect(container).toBeTruthy()
|
|
})
|
|
|
|
it("advances timers without errors", () => {
|
|
render(React.createElement(MemoryRouter, null, React.createElement(EditingPlanner)))
|
|
act(() => {
|
|
vi.advanceTimersByTime(10000)
|
|
})
|
|
expect(true).toBe(true)
|
|
})
|
|
})
|