Files
writer-work-flow/apps/web/lib/foreshadow/useChapterForeshadow.test.ts
Yaojia Wang b810a3fa3c feat(foreshadow): 写作台本章伏笔主动提醒(前端)
- 新增 chapterForeshadow 纯分类:按当前章分 可回收/待回收/已逾期(启用闲置的 isWindowApproaching)
- useChapterForeshadow hook(复用 GET /foreshadow,切章仅重分类不重拉)
- 右栏「本章参考」+ ContextDrawer 伏笔页新增「本章伏笔」实时清单,
  分色展示 + 跳看板链接 + 空态;标注『按已验收进度』
2026-07-12 17:53:00 +02:00

80 lines
2.8 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.

// @vitest-environment jsdom
import { renderHook, waitFor } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { ForeshadowView } from "@/lib/api/types";
import { useChapterForeshadow } from "./useChapterForeshadow";
// 后端客户端是 hook 的外部副作用边界,单测一律 mock。
const get = vi.fn();
vi.mock("@/lib/api/client", () => ({
api: { GET: (...a: unknown[]) => get(...a) },
}));
function makeRow(over: Partial<ForeshadowView>): ForeshadowView {
return { code: "F-001", title: "线索", status: "OPEN", ...over } as ForeshadowView;
}
describe("useChapterForeshadow", () => {
beforeEach(() => get.mockReset());
afterEach(() => vi.clearAllMocks());
it("拉取成功后按当前章号分类", async () => {
get.mockResolvedValue({
data: {
foreshadow: [
makeRow({ code: "R", status: "OPEN", expected_close_from: 40, expected_close_to: 60 }),
makeRow({ code: "P", status: "OPEN", expected_close_from: 80 }),
makeRow({ code: "O", status: "OVERDUE" }),
],
},
error: null,
});
const { result } = renderHook(() => useChapterForeshadow("p1", 50));
await waitFor(() => expect(result.current.status).toBe("ready"));
expect(result.current.groups.recyclable.map((v) => v.code)).toEqual(["R"]);
expect(result.current.groups.pending.map((v) => v.code)).toEqual(["P"]);
expect(result.current.groups.overdue.map((v) => v.code)).toEqual(["O"]);
});
it("拉取失败 → status=error、空分组", async () => {
get.mockResolvedValue({ data: null, error: { error: {} } });
const { result } = renderHook(() => useChapterForeshadow("p1", 3));
await waitFor(() => expect(result.current.status).toBe("error"));
expect(result.current.groups.recyclable).toEqual([]);
expect(result.current.groups.pending).toEqual([]);
expect(result.current.groups.overdue).toEqual([]);
});
it("切章不重新请求,仅重新分类", async () => {
get.mockResolvedValue({
data: {
foreshadow: [
makeRow({ code: "W", status: "OPEN", expected_close_from: 40, expected_close_to: 60 }),
],
},
error: null,
});
const { result, rerender } = renderHook(
({ ch }: { ch: number }) => useChapterForeshadow("p1", ch),
{ initialProps: { ch: 50 } },
);
await waitFor(() => expect(result.current.status).toBe("ready"));
expect(result.current.groups.recyclable.map((v) => v.code)).toEqual(["W"]);
// 切到窗口外的章:仍是同一份数据,重新分类进 pending且未再次 GET。
rerender({ ch: 10 });
await waitFor(() =>
expect(result.current.groups.pending.map((v) => v.code)).toEqual(["W"]),
);
expect(result.current.groups.recyclable).toEqual([]);
expect(get).toHaveBeenCalledTimes(1);
});
});