muse-agent-example/web/tests/交互/规划重看基线.test.tsx
zizi 224df00706 W13 渐进探索、分层规划与近期细化:私人探索、明确选定、分层规划与近期细化;含竞态修复新用例。
按 R2 串行阶段整理提交;包内文件为该阶段交付(含后续小增量),状态以工作包清单为准。
2026-09-10 19:25:41 +08:00

90 lines
3.9 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.

/** 冲突后的重看必须等待最新基线,不能靠渲染时序取到旧版本。 */
import { afterEach, expect, it, vi } from "vitest";
import {
cleanup,
fireEvent,
render,
screen,
waitFor,
} from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
afterEach(() => {
cleanup();
vi.unstubAllGlobals();
sessionStorage.clear();
});
const 空模式 = { type: "object", properties: {}, required: [] };
it("NC-plan-rebase-await-fresh:冲突后立即重看并保存,携带最新正式版本", async () => {
const 保存请求: unknown[] = [];
let 表单次数 = 0;
const 延迟 = (毫秒: number) => new Promise((r) => setTimeout(r, 毫秒));
vi.stubGlobal(
"fetch",
vi.fn(async (request: Request) => {
const 地址 = new URL(request.url).pathname;
const 应答 = (体: unknown, 状态 = 200) =>
new Response(JSON.stringify(体), {
status: 状态,
headers: { "Content-Type": "application/json" },
});
if (地址.startsWith("/api/v1/session")) return 应答({});
if (地址.includes("/plan-nodes"))
return 应答({ work_id: "work", revision: 1, nodes: [] });
if (地址.includes("/plans/form")) {
表单次数 += 1;
if (表单次数 === 1) return 应答({ work_id: "work", chapter_id: null, node_id: null, plan_id: "plan", revision: 1, schema_binding: {}, content: {}, visible_schema: 空模式, editable_schema: 空模式 });
// 冲突后的刷新故意延迟,放大“重看取到旧基线”的竞态窗口。
await 延迟(250);
return 应答({ work_id: "work", chapter_id: null, node_id: null, plan_id: "plan", revision: 2, schema_binding: {}, content: {}, visible_schema: 空模式, editable_schema: 空模式 });
}
if (地址.includes("/plans/candidates") && request.method === "GET")
return 应答([]);
if (地址.includes("/plans/candidates")) {
保存请求.push(await request.clone().json());
if (保存请求.length === 1)
return 应答(
{ code: "REVISION_CONFLICT", message: "规划正式版本已变化" },
409,
);
return 应答({ receipt_id: "r2", action: "manual_save", results: [] });
}
if (地址.includes("/explorations/selection"))
return new Response("404", { status: 404 });
return 应答({});
}),
);
const { Ui规划工作区 } = await import("../../src/功能/作品规划/规划工作区");
const 客户端 = new QueryClient({ defaultOptions: { queries: { retry: false } } });
render(
<QueryClientProvider client={客户端}>
<MemoryRouter initialEntries={["/works/work/plans"]}>
<Ui规划工作区 />
</MemoryRouter>
</QueryClientProvider>,
);
fireEvent.click(await screen.findByRole("button", { name: "另拟大纲" }));
fireEvent.click(screen.getByRole("button", { name: "保存规划候选" }));
expect(
await screen.findByText("保存基线已变化。你的输入仍在下方,请先比较当前内容。"),
).toBeTruthy();
// 冲突提示出现时最新表单仍在途;重看按钮等待新数据后才可点击。
const 重看 = screen.getByRole("button", { name: "保留我的改动,基于当前版本重看" });
await waitFor(() => expect((重看 as HTMLButtonElement).disabled).toBe(false));
fireEvent.click(重看);
await waitFor(() =>
expect(
screen.queryByText("保存基线已变化。你的输入仍在下方,请先比较当前内容。"),
).toBeNull(),
);
fireEvent.click(screen.getByRole("button", { name: "保存规划候选" }));
expect(
await screen.findByText("规划候选已保存,请打开审阅。"),
).toBeTruthy();
const 第二次 = 保存请求.at(-1) as { request: { expected_revision: number } };
expect(第二次.request.expected_revision).toBe(2);
});