geo/frontend/__tests__/hooks/use-compare-data.test.ts

193 lines
5.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* useCompareData 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 { useCompareData } from "@/lib/hooks/use-compare-data";
import type { BrandListResponse, CompareResponse } from "@/types/brand";
const noRetryOptions = { shouldRetryOnError: false };
/** 使用独立 SWR 缓存的 wrapper避免测试间缓存冲突 */
function createWrapper() {
return ({ children }: { children: ReactNode }) =>
SWRConfig({
value: { provider: () => new Map() },
children,
});
}
describe("useCompareData", () => {
beforeEach(() => {
vi.clearAllMocks();
});
const mockBrandsResponse: BrandListResponse = {
items: [
{
id: "brand-1",
name: "测试品牌",
aliases: [],
platforms: ["wenxin"],
frequency: "weekly",
status: "active",
score: 80,
last_queried_at: null,
next_query_at: null,
created_at: "2024-01-01",
},
],
total: 1,
};
const mockCompareResponse: CompareResponse = {
brand_id: "brand-1",
brand_name: "测试品牌",
items: [
{
entity_id: "brand-1",
entity_name: "测试品牌",
entity_type: "brand",
mention_rate_score: 70,
sov_score: 65,
quality_score: 80,
overall_score: 75,
citation_count: 100,
dimensions: [],
overall_trend: "up",
overall_trend_value: 5,
},
],
radar_data: [],
};
it("应返回品牌列表数据", async () => {
mockFetchWithAuth.mockImplementation((url: string) => {
if (url.includes("/api/v1/brands")) return Promise.resolve(mockBrandsResponse);
return Promise.resolve({});
});
const { result } = renderHook(
() => useCompareData({ swrOptions: noRetryOptions }),
{ wrapper: createWrapper() }
);
await waitFor(() => {
expect(result.current.brands).toEqual(mockBrandsResponse.items);
});
});
it("selectedBrandId 为空时应暂停对比数据请求", () => {
mockFetchWithAuth.mockResolvedValue(mockBrandsResponse);
const { result } = renderHook(
() => useCompareData({ swrOptions: noRetryOptions }),
{ wrapper: createWrapper() }
);
expect(result.current.compareData).toBeUndefined();
});
it("selectedBrandId 存在时应获取对比数据", async () => {
mockFetchWithAuth.mockImplementation((url: string) => {
if (url.includes("/compare")) return Promise.resolve(mockCompareResponse);
return Promise.resolve(mockBrandsResponse);
});
const { result } = renderHook(
() => useCompareData({ initialBrandId: "brand-1", swrOptions: noRetryOptions }),
{ wrapper: createWrapper() }
);
await waitFor(() => {
expect(result.current.compareData).toEqual(mockCompareResponse);
});
});
it("应正确暴露 loading 状态", async () => {
mockFetchWithAuth.mockImplementation((url: string) => {
if (url.includes("/compare")) return Promise.resolve(mockCompareResponse);
return Promise.resolve(mockBrandsResponse);
});
const { result } = renderHook(
() => useCompareData({ initialBrandId: "brand-1", swrOptions: noRetryOptions }),
{ wrapper: createWrapper() }
);
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
});
it("应正确暴露 error 状态", async () => {
mockFetchWithAuth.mockRejectedValue(new Error("网络错误"));
const { result } = renderHook(
() => useCompareData({ swrOptions: noRetryOptions }),
{ wrapper: createWrapper() }
);
await waitFor(() => {
expect(result.current.error).toBeDefined();
expect(result.current.error?.message).toBe("网络错误");
});
});
it("setSelectedBrandId 应更新选中品牌", async () => {
mockFetchWithAuth.mockImplementation((url: string) => {
if (url.includes("/compare")) return Promise.resolve(mockCompareResponse);
return Promise.resolve(mockBrandsResponse);
});
const { result } = renderHook(
() => useCompareData({ swrOptions: noRetryOptions }),
{ wrapper: createWrapper() }
);
await waitFor(() => {
expect(result.current.brands.length).toBeGreaterThan(0);
});
act(() => {
result.current.setSelectedBrandId("brand-1");
});
expect(result.current.selectedBrandId).toBe("brand-1");
});
it("应提供 refresh 方法刷新数据", async () => {
mockFetchWithAuth.mockResolvedValue(mockBrandsResponse);
const { result } = renderHook(
() => useCompareData({ swrOptions: noRetryOptions }),
{ wrapper: createWrapper() }
);
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
expect(typeof result.current.refreshBrands).toBe("function");
expect(typeof result.current.refreshCompare).toBe("function");
});
});