Files
xiaoxia-saas/apps/web/src/test/hooks/useCloneProgress.test.tsx
T
xiaoxia 3862996045
CI/CD Pipeline / Production Browser E2E (push) Failing after 1312h43m40s
CI/CD Pipeline / Deploy Production (push) Failing after 1312h43m42s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1312h48m16s
CI/CD Pipeline / Build Production API Image (push) Failing after 1312h48m20s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1312h49m4s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1312h51m34s
CI/CD Pipeline / PR Build API Image (push) Failing after 1312h49m8s
CI/CD Pipeline / Build Staging API Image (push) Successful in 13m4s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m5s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m56s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 58s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 2m0s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m5s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m12s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 21s
CI/CD Pipeline / Unit Tests (push) Successful in 6m21s
CI/CD Pipeline / Integration Tests (push) Successful in 2m38s
CI/CD Pipeline / Frontend Lint (push) Successful in 46s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 2m39s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1313h21m29s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m40s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1313h20m41s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 35s
refactor(#783): 统一API文件命名风格为kebab-case (#788)
2026-07-23 22:34:56 +08:00

136 lines
4.0 KiB
TypeScript

import { describe, expect, it, vi, beforeEach } from "vitest"
import { renderHook, act } from "@testing-library/react"
import { useCloneProgress } from "@/hooks/useCloneProgress"
// mock API
const mockGetVoiceClones = vi.fn()
vi.mock("@/api/voice-clone", () => ({
getVoiceClones: (...args: unknown[]) => mockGetVoiceClones(...args),
VoiceCloneStatus: { READY: "ready" },
}))
vi.useFakeTimers()
describe("useCloneProgress", () => {
beforeEach(() => {
vi.clearAllMocks()
vi.clearAllTimers()
})
it("should initial load clones", async () => {
const mockClones = [{ id: "1", name: "克隆1", status: "ready" }]
mockGetVoiceClones.mockResolvedValue(mockClones)
const { result } = renderHook(() => useCloneProgress())
// 等待初始加载
await vi.waitFor(() => {
expect(result.current.clones).toEqual(mockClones)
})
expect(result.current.loading).toBe(false)
expect(result.current.hasProcessing).toBe(false)
})
it("should have loading state during fetch", async () => {
mockGetVoiceClones.mockResolvedValue([])
const { result } = renderHook(() => useCloneProgress())
expect(result.current.loading).toBe(true)
await vi.waitFor(() => {
expect(result.current.loading).toBe(false)
})
})
it("should add clone via addClone", async () => {
mockGetVoiceClones.mockResolvedValue([])
const { result } = renderHook(() => useCloneProgress())
await vi.waitFor(() => expect(result.current.loading).toBe(false))
act(() => {
result.current.addClone({ id: "new-1", name: "新克隆", status: "processing" })
})
expect(result.current.clones.length).toBe(1)
expect(result.current.clones[0].id).toBe("new-1")
})
it("should remove clone via removeClone", async () => {
mockGetVoiceClones.mockResolvedValue([
{ id: "1", name: "克隆1", status: "ready" },
{ id: "2", name: "克隆2", status: "ready" },
])
const { result } = renderHook(() => useCloneProgress())
await vi.waitFor(() => expect(result.current.clones.length).toBe(2))
act(() => {
result.current.removeClone("1")
})
expect(result.current.clones.length).toBe(1)
expect(result.current.clones[0].id).toBe("2")
})
it("should update clone via updateClone", async () => {
mockGetVoiceClones.mockResolvedValue([{ id: "1", name: "旧名字", status: "processing" }])
const { result } = renderHook(() => useCloneProgress())
await vi.waitFor(() => expect(result.current.clones.length).toBe(1))
act(() => {
result.current.updateClone({ id: "1", name: "新名字", status: "ready" })
})
expect(result.current.clones[0].name).toBe("新名字")
expect(result.current.clones[0].status).toBe("ready")
})
it("should refresh clones manually", async () => {
let callCount = 0
mockGetVoiceClones.mockImplementation(() => {
callCount++
return Promise.resolve([{ id: `${callCount}`, name: `克隆${callCount}`, status: "ready" }])
})
const { result } = renderHook(() => useCloneProgress())
await vi.waitFor(() => expect(result.current.clones[0]?.id).toBe("1"))
await act(async () => {
await result.current.refresh()
})
expect(mockGetVoiceClones).toHaveBeenCalledTimes(2)
})
it("should not start polling when all clones are ready", async () => {
mockGetVoiceClones.mockResolvedValue([{ id: "1", name: "克隆1", status: "ready" }])
renderHook(() => useCloneProgress())
await vi.waitFor(() => expect(mockGetVoiceClones).toHaveBeenCalledTimes(1))
// 快进 10 秒,ready 状态不应该轮询
vi.advanceTimersByTime(10000)
// 应该只调用了初始的那一次
expect(mockGetVoiceClones).toHaveBeenCalledTimes(1)
})
it("hasProcessing should be true when there are processing clones", async () => {
mockGetVoiceClones.mockResolvedValue([{ id: "1", name: "克隆1", status: "processing" }])
const { result } = renderHook(() => useCloneProgress())
await vi.waitFor(() => {
expect(result.current.hasProcessing).toBe(true)
})
})
})