// useChildHomework Hook 单测 // 依据:02-architecture-design.md §4.2 GraphQL 接入 // 覆盖:无 currentChildId 暂停 / 加载态 / 成功返回 / status 过滤 / 错误态 import { describe, it, expect, beforeAll, afterAll, afterEach, beforeEach, } from "vitest"; import { renderHook, waitFor, cleanup } from "@testing-library/react"; import { Provider as UrqlProvider, Client, cacheExchange, fetchExchange, } from "urql"; import { graphql, HttpResponse } from "msw"; import type { ReactNode } from "react"; import { server } from "@/test/mocks/server"; import { useChildStore } from "@/store/child-store"; import { useChildHomework } from "./useChildHomework"; function createTestClient(): Client { return new Client({ url: "/api/v1/parent/v1/graphql", exchanges: [cacheExchange, fetchExchange], fetchOptions: { headers: { "X-Requested-With": "XMLHttpRequest" } }, }); } function renderUseChildHomework(status?: string) { const client = createTestClient(); const wrapper = ({ children }: { children: ReactNode }) => ( {children} ); return renderHook(() => useChildHomework(status), { wrapper }); } beforeAll(() => server.listen({ onUnhandledRequest: "error" })); afterEach(() => { cleanup(); server.resetHandlers(); }); afterAll(() => server.close()); beforeEach(() => { localStorage.clear(); useChildStore.setState({ children: [], currentChildId: null, isLoading: false, }); }); describe("useChildHomework", () => { it("无 currentChildId 时暂停查询,返回空数组", () => { const { result } = renderUseChildHomework(); expect(result.current.homework).toEqual([]); expect(result.current.loading).toBe(false); expect(result.current.error).toBeUndefined(); }); it("有 currentChildId 时加载成功返回作业列表", async () => { useChildStore.setState({ currentChildId: "student-001" }); const { result } = renderUseChildHomework(); await waitFor(() => { expect(result.current.homework.length).toBeGreaterThan(0); }); expect(result.current.homework).toHaveLength(4); expect(result.current.homework[0]!.title).toBe("数学第三章习题"); expect(result.current.homework[0]!.status).toBe("in_progress"); expect(result.current.loading).toBe(false); expect(result.current.error).toBeUndefined(); }); it("加载中 loading 为 true", () => { useChildStore.setState({ currentChildId: "student-001" }); const { result } = renderUseChildHomework(); expect(result.current.loading).toBe(true); }); it("传入 status 时变量携带 status", async () => { useChildStore.setState({ currentChildId: "student-001" }); let capturedVars: unknown = null; server.resetHandlers( graphql.query("ChildHomework", ({ variables }) => { capturedVars = variables; return HttpResponse.json({ data: { childHomework: [] } }); }), ); const { result } = renderUseChildHomework("graded"); await waitFor(() => { expect(result.current.loading).toBe(false); }); expect(capturedVars).toEqual({ childId: "student-001", status: "graded", }); }); it("未传 status 时变量 status 为 null", async () => { useChildStore.setState({ currentChildId: "student-001" }); let capturedVars: unknown = null; server.resetHandlers( graphql.query("ChildHomework", ({ variables }) => { capturedVars = variables; return HttpResponse.json({ data: { childHomework: [] } }); }), ); const { result } = renderUseChildHomework(); await waitFor(() => { expect(result.current.loading).toBe(false); }); expect(capturedVars).toEqual({ childId: "student-001", status: null, }); }); it("GraphQL 返回错误时透传 error", async () => { useChildStore.setState({ currentChildId: "student-001" }); server.resetHandlers( graphql.query("ChildHomework", () => HttpResponse.json( { errors: [{ message: "作业查询失败" }] }, { status: 200 }, ), ), ); const { result } = renderUseChildHomework(); await waitFor(() => { expect(result.current.error).toBeDefined(); }); expect(result.current.homework).toEqual([]); expect(result.current.error?.message).toContain("作业查询失败"); }); });