Files
xiaoxia-saas/apps/web/src/test/pages/VoiceMaterialLibrary.test.tsx
T
xiaoxia 91ef044e9c
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (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 Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging API Image (push) Successful in 8m2s
CI Build & Deploy Pipeline / Build Staging Web Image (push) Failing after 8m14s
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Successful in 11m2s
CI Build & Deploy Pipeline / Build Production API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (push) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (push) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (push) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Has been skipped
test: 前端单测覆盖率Phase4,行覆盖率51%→62% (#589)
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-07-19 17:17:11 +08:00

226 lines
8.3 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"
// Hoisted mock icons factory - must be before all vi.mock calls
const hoistedIcons = vi.hoisted(() => {
const iconNames = [
"AudioOutlined",
"PlayCircleOutlined",
"PauseCircleOutlined",
"SearchOutlined",
"PlusOutlined",
"EditOutlined",
"DeleteOutlined",
"UploadOutlined",
"UnorderedListOutlined",
"AppstoreOutlined",
"CloseOutlined",
"SoundOutlined",
"UserOutlined",
"ManOutlined",
"WomanOutlined",
"CheckOutlined",
"TagsOutlined",
"MutedOutlined",
"RobotOutlined",
"LoadingOutlined",
"FolderOutlined",
"FolderAddOutlined",
"MoreOutlined",
"ExperimentOutlined",
"ExclamationCircleOutlined",
"DownloadOutlined",
"InboxOutlined",
"VideoCameraOutlined",
"PictureOutlined",
"SaveOutlined",
"UndoOutlined",
"RedoOutlined",
"SettingOutlined",
"HistoryOutlined",
"FilterOutlined",
"FontColorsOutlined",
"BgColorsOutlined",
"MusicOutlined",
"ScissorOutlined",
"ThunderboltOutlined",
"BulbOutlined",
"FundOutlined",
"LayoutOutlined",
"ColumnHeightOutlined",
"SwapOutlined",
"LeftOutlined",
"RightOutlined",
"UpOutlined",
"DownOutlined",
"CopyOutlined",
]
const icons: Record<string, React.FC> = {}
iconNames.forEach((name) => {
icons[name] = () => React.createElement("span", null, name.charAt(0))
})
return icons
})
// === 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(),
})),
useInfiniteQuery: vi.fn(() => ({
data: { pages: [] },
isLoading: false,
fetchNextPage: vi.fn(),
hasNextPage: false,
})),
}))
// === Ant Design Icons mock ===
vi.mock("@ant-design/icons", () => hoistedIcons)
// === 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"),
Popover: ({ children }: any) => React.createElement("span", null, children),
Popconfirm: ({ children }: any) => React.createElement("span", null, children),
Tooltip: ({ children }: any) => React.createElement("span", null, children),
Tabs: () => React.createElement("div"),
Drawer: ({ open, children }: any) =>
open ? React.createElement("div", { role: "dialog" }, children) : null,
Upload: ({ children }: any) => React.createElement("div", null, children),
Slider: () => React.createElement("div"),
Switch: () => React.createElement("input", { type: "checkbox" }),
Segmented: () => React.createElement("div"),
Spin: () => React.createElement("div", null, "Loading"),
Empty: () => React.createElement("div", null, "Empty"),
Divider: () => React.createElement("hr"),
Space: ({ children }: any) => React.createElement("div", null, children),
Dropdown: ({ children }: any) => React.createElement("span", null, children),
Menu: () => React.createElement("div"),
Badge: ({ children }: any) => React.createElement("span", null, children),
Radio: ({ children }: any) => React.createElement("span", null, children),
RadioGroup: ({ children }: any) => React.createElement("div", null, children),
Checkbox: ({ children }: any) => React.createElement("span", null, children),
InputNumber: () => React.createElement("input", { type: "number" }),
Form: ({ children }: any) => React.createElement("form", null, children),
FormItem: ({ children }: any) => React.createElement("div", null, children),
Result: ({ status, title }: any) => React.createElement("div", { "data-status": status }, title),
List: () => React.createElement("div"),
Table: () => React.createElement("div"),
Pagination: () => React.createElement("div"),
Card: ({ children }: any) => React.createElement("div", null, children),
Avatar: ({ children }: any) => React.createElement("span", null, children),
Collapse: ({ children }: any) => React.createElement("div", null, children),
CollapsePanel: ({ children }: any) => React.createElement("div", null, children),
Tag: ({ children }: any) => React.createElement("span", null, children),
Button: ({ children, onClick }: any) => React.createElement("button", { onClick }, children),
Input: ({ placeholder }: any) => React.createElement("input", { placeholder }),
Select: ({ children }: any) => React.createElement("select", null, children),
Option: ({ children }: any) => React.createElement("option", null, children),
ConfigProvider: ({ children }: any) => React.createElement(React.Fragment, null, children),
TextArea: ({ placeholder }: any) => React.createElement("textarea", { placeholder }),
}))
// === 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),
Slider: () => React.createElement("div"),
Switch: () => React.createElement("input", { type: "checkbox" }),
}))
// === API mocks ===
vi.mock("@/api/assets", () => ({
getAssetLibraries: vi.fn().mockResolvedValue([]),
createAssetLibrary: vi.fn().mockResolvedValue({}),
deleteAssetLibrary: vi.fn().mockResolvedValue({}),
getAssetsByKind: vi.fn().mockResolvedValue({ items: [], total: 0 }),
getAssets: vi.fn().mockResolvedValue({ items: [], total: 0 }),
createAsset: vi.fn().mockResolvedValue({}),
updateAsset: vi.fn().mockResolvedValue({}),
deleteAsset: vi.fn().mockResolvedValue({}),
uploadAssetDirect: vi.fn().mockResolvedValue({}),
batchDeleteAssets: vi.fn().mockResolvedValue({}),
batchTagAssets: vi.fn().mockResolvedValue({}),
AssetType: { VIDEO: "video", IMAGE: "image", AUDIO: "audio", VOICE: "voice" },
}))
vi.mock("@/api/tags", () => ({
getTags: vi.fn().mockResolvedValue({ items: [] }),
createTag: vi.fn().mockResolvedValue({}),
tagAsset: vi.fn().mockResolvedValue({}),
untagAsset: vi.fn().mockResolvedValue({}),
}))
vi.mock("@/api/tts", () => ({
synthesizeSpeech: vi.fn().mockResolvedValue({ job_id: "test-job" }),
getTTSJobStatus: vi.fn().mockResolvedValue({ status: "completed", audio_url: "" }),
saveTtsToLibrary: vi.fn().mockResolvedValue({}),
}))
vi.mock("@/api/voices", () => ({
fetchPresetVoices: vi.fn().mockResolvedValue({ items: [] }),
}))
// === PageHead mock ===
vi.mock("@/components/layout/PageHead", () => ({
default: ({ title }: { title: string }) =>
React.createElement("div", { "data-testid": "page-head" }, title),
}))
import VoiceMaterialLibrary from "@/pages/voice-materials/VoiceMaterialLibrary"
describe("VoiceMaterialLibrary", () => {
beforeEach(() => {
vi.useFakeTimers()
})
afterEach(() => {
vi.useRealTimers()
})
it("renders without crashing", () => {
const { container } = render(
React.createElement(MemoryRouter, null, React.createElement(VoiceMaterialLibrary)),
)
expect(container).toBeTruthy()
})
it("advances timers without errors", () => {
render(React.createElement(MemoryRouter, null, React.createElement(VoiceMaterialLibrary)))
act(() => {
vi.advanceTimersByTime(30000)
})
expect(true).toBe(true)
})
})