Files
writer-work-flow/apps/web/lib/workbench/useContinue.test.ts
Yaojia Wang 59f2f60a7e refactor(frontend): 四生成 hook 结束后额外返回产物(AC-3 留痕前置)
useRefine.refine/recommunicate→RefineVersion|null、useContinue.generate→string|null、
useChapterRewrite.send→RewriteVersion|null、useGenerator.generate→GeneratorArtifact|null。
向后兼容(既有调用忽略返回值不受影响);各 hook 成功用例补返回值断言。
面板据此 if(artifact) 才 append,空候选/错误路径不留痕。
2026-07-09 17:15:49 +02:00

110 lines
4.0 KiB
TypeScript
Raw 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 { act, renderHook } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { useContinue } from "./useContinue";
const post = vi.fn();
const toast = vi.fn();
vi.mock("@/lib/api/client", () => ({
api: { POST: (...a: unknown[]) => post(...a) },
}));
vi.mock("@/components/Toast", () => ({ useToast: () => toast }));
const GEN_PATH = "/projects/{project_id}/skills/{tool_key}/generate";
describe("useContinue", () => {
beforeEach(() => {
post.mockReset();
toast.mockReset();
});
afterEach(() => vi.clearAllMocks());
it("初始为 idle无候选", () => {
const { result } = renderHook(() => useContinue());
expect(result.current.status).toBe("idle");
expect(result.current.candidates).toEqual([]);
});
it("续写:调 continue 生成器读该章前文产出一条候选status=ready", async () => {
post.mockResolvedValue({
data: { output_kind: "ContinuationResult", preview: { text: "续写正文一" } },
error: null,
});
const { result } = renderHook(() => useContinue());
let returned: unknown;
await act(async () => {
returned = await result.current.generate("p1", 3);
});
expect(post).toHaveBeenCalledWith(GEN_PATH, {
params: { path: { project_id: "p1", tool_key: "continue" } },
body: { brief: "", chapter_no: 3 },
});
expect(result.current.status).toBe("ready");
expect(result.current.candidates).toEqual(["续写正文一"]);
// 追加断言generate 返回本条候选文本(供 ContinuePanel 留痕)。
expect(returned).toBe("续写正文一");
});
it("多候选:再次生成累加候选,不覆盖前一条", async () => {
post
.mockResolvedValueOnce({ data: { preview: { text: "候选一" } }, error: null })
.mockResolvedValueOnce({ data: { preview: { text: "候选二" } }, error: null });
const { result } = renderHook(() => useContinue());
await act(async () => {
await result.current.generate("p1", 1);
});
await act(async () => {
await result.current.generate("p1", 1);
});
expect(result.current.candidates).toEqual(["候选一", "候选二"]);
});
it("产出为空不加候选、提示重试、status=ready", async () => {
post.mockResolvedValue({ data: { preview: { text: "" } }, error: null });
const { result } = renderHook(() => useContinue());
await act(async () => {
await result.current.generate("p1", 1);
});
expect(result.current.candidates).toEqual([]);
expect(result.current.status).toBe("ready");
expect(toast).toHaveBeenCalledWith(expect.any(String), "info");
});
it("后端返回 errorstatus=error、弹错误 toast、候选不变", async () => {
post.mockResolvedValue({ data: null, error: { detail: "配额不足" } });
const { result } = renderHook(() => useContinue());
await act(async () => {
await result.current.generate("p1", 1);
});
expect(result.current.status).toBe("error");
expect(result.current.candidates).toEqual([]);
expect(toast).toHaveBeenCalledWith(expect.any(String), "error");
});
it("请求抛异常status=error、弹网络异常 toast", async () => {
post.mockRejectedValue(new Error("network down"));
const { result } = renderHook(() => useContinue());
await act(async () => {
await result.current.generate("p1", 1);
});
expect(result.current.status).toBe("error");
expect(toast).toHaveBeenCalledWith("续写请求异常,请检查网络。", "error");
});
it("reset 清回 idle 与空候选", async () => {
post.mockResolvedValue({ data: { preview: { text: "候选" } }, error: null });
const { result } = renderHook(() => useContinue());
await act(async () => {
await result.current.generate("p1", 1);
});
act(() => result.current.reset());
expect(result.current.status).toBe("idle");
expect(result.current.candidates).toEqual([]);
});
});