175 lines
4.9 KiB
TypeScript
175 lines
4.9 KiB
TypeScript
/**
|
|
* useOnboardingData Hook 单元测试
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { renderHook, waitFor, act } from "@testing-library/react";
|
|
import { SWRConfig } from "swr";
|
|
import type { ReactNode } from "react";
|
|
|
|
vi.mock("@/lib/api/client", () => ({
|
|
fetchWithAuth: vi.fn(),
|
|
}));
|
|
|
|
import { fetchWithAuth } from "@/lib/api/client";
|
|
const mockFetchWithAuth = vi.mocked(fetchWithAuth);
|
|
|
|
vi.mock("next-auth/react", () => ({
|
|
useSession: vi.fn(() => ({
|
|
data: { accessToken: "test-token" },
|
|
status: "authenticated",
|
|
})),
|
|
}));
|
|
|
|
import { useOnboardingData } from "@/lib/hooks/use-onboarding-data";
|
|
|
|
const noRetryOptions = { shouldRetryOnError: false };
|
|
|
|
function createWrapper() {
|
|
return ({ children }: { children: ReactNode }) =>
|
|
SWRConfig({ value: { provider: () => new Map() }, children });
|
|
}
|
|
|
|
describe("useOnboardingData", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("应检查引导状态", async () => {
|
|
mockFetchWithAuth.mockResolvedValue({ completed: false });
|
|
|
|
const { result } = renderHook(
|
|
() => useOnboardingData({ swrOptions: noRetryOptions }),
|
|
{ wrapper: createWrapper() }
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.onboardingStatus).toEqual({ completed: false });
|
|
});
|
|
});
|
|
|
|
it("引导已完成时应标记 isCompleted", async () => {
|
|
mockFetchWithAuth.mockResolvedValue({ completed: true });
|
|
|
|
const { result } = renderHook(
|
|
() => useOnboardingData({ swrOptions: noRetryOptions }),
|
|
{ wrapper: createWrapper() }
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.onboardingStatus).toEqual({ completed: true });
|
|
expect(result.current.isCompleted).toBe(true);
|
|
});
|
|
});
|
|
|
|
it("应正确暴露 isLoading 状态", async () => {
|
|
mockFetchWithAuth.mockResolvedValue({ completed: false });
|
|
|
|
const { result } = renderHook(
|
|
() => useOnboardingData({ swrOptions: noRetryOptions }),
|
|
{ wrapper: createWrapper() }
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isLoading).toBe(false);
|
|
});
|
|
});
|
|
|
|
it("应正确暴露 error 状态", async () => {
|
|
mockFetchWithAuth.mockRejectedValue(new Error("检查引导状态失败"));
|
|
|
|
const { result } = renderHook(
|
|
() => useOnboardingData({ swrOptions: noRetryOptions }),
|
|
{ wrapper: createWrapper() }
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.error).toBeDefined();
|
|
expect(result.current.error?.message).toBe("检查引导状态失败");
|
|
});
|
|
});
|
|
|
|
it("应提供 createBrand mutation 方法", () => {
|
|
mockFetchWithAuth.mockResolvedValue({ completed: false });
|
|
|
|
const { result } = renderHook(
|
|
() => useOnboardingData({ swrOptions: noRetryOptions }),
|
|
{ wrapper: createWrapper() }
|
|
);
|
|
|
|
expect(typeof result.current.createBrand).toBe("function");
|
|
});
|
|
|
|
it("createBrand 成功应返回 brand_id", async () => {
|
|
mockFetchWithAuth.mockImplementation((url: string, options?: RequestInit) => {
|
|
if (options?.method === "POST") return Promise.resolve({ brand_id: "new-brand-1" });
|
|
return Promise.resolve({ completed: false });
|
|
});
|
|
|
|
const { result } = renderHook(
|
|
() => useOnboardingData({ swrOptions: noRetryOptions }),
|
|
{ wrapper: createWrapper() }
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isLoading).toBe(false);
|
|
});
|
|
|
|
let brandId: string | null = null;
|
|
await act(async () => {
|
|
brandId = await result.current.createBrand({
|
|
name: "新品牌",
|
|
competitors: [],
|
|
platforms: ["wenxin"],
|
|
frequency: "weekly",
|
|
});
|
|
});
|
|
|
|
expect(brandId).toBe("new-brand-1");
|
|
});
|
|
|
|
it("createBrand 失败应返回 null 并设置 mutationError", async () => {
|
|
mockFetchWithAuth.mockImplementation((url: string, options?: RequestInit) => {
|
|
if (options?.method === "POST") return Promise.reject(new Error("创建品牌失败"));
|
|
return Promise.resolve({ completed: false });
|
|
});
|
|
|
|
const { result } = renderHook(
|
|
() => useOnboardingData({ swrOptions: noRetryOptions }),
|
|
{ wrapper: createWrapper() }
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isLoading).toBe(false);
|
|
});
|
|
|
|
let brandId: string | null = null;
|
|
await act(async () => {
|
|
brandId = await result.current.createBrand({
|
|
name: "新品牌",
|
|
competitors: [],
|
|
platforms: ["wenxin"],
|
|
frequency: "weekly",
|
|
});
|
|
});
|
|
|
|
expect(brandId).toBeNull();
|
|
expect(result.current.mutationError).toBeInstanceOf(Error);
|
|
});
|
|
|
|
it("应提供 refresh 方法", async () => {
|
|
mockFetchWithAuth.mockResolvedValue({ completed: false });
|
|
|
|
const { result } = renderHook(
|
|
() => useOnboardingData({ swrOptions: noRetryOptions }),
|
|
{ wrapper: createWrapper() }
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isLoading).toBe(false);
|
|
});
|
|
|
|
expect(typeof result.current.refresh).toBe("function");
|
|
});
|
|
});
|