210 lines
6.6 KiB
TypeScript
210 lines
6.6 KiB
TypeScript
import { afterEach, expect, it, vi } from "vitest";
|
||
import {
|
||
cleanup,
|
||
fireEvent,
|
||
render,
|
||
screen,
|
||
waitFor,
|
||
} from "@testing-library/react";
|
||
import { Ui决策面板 } from "../../src/功能/正式变更/决策面板";
|
||
import type { 候选审阅 } from "../../src/功能/正式变更/数据";
|
||
import { MemoryRouter, Route, Routes } from "react-router-dom";
|
||
import { Ui依赖装配 } from "../../src/应用/依赖装配";
|
||
import { Ui候选审阅页 } from "../../src/功能/正文写作/候选审阅";
|
||
|
||
afterEach(() => {
|
||
cleanup();
|
||
vi.unstubAllGlobals();
|
||
sessionStorage.clear();
|
||
});
|
||
|
||
const document = {
|
||
format_version: 1,
|
||
paragraphs: [
|
||
{
|
||
paragraph_id: "p1",
|
||
content: [{ type: "text" as const, text: "合成原文", marks: [] }],
|
||
},
|
||
],
|
||
};
|
||
const review: 候选审阅 = {
|
||
review_id: "review-original",
|
||
review_hash: "review-hash",
|
||
snapshot: {},
|
||
before: {
|
||
work_id: "work",
|
||
chapter_id: "chapter",
|
||
branch_id: "main",
|
||
document_id: "document",
|
||
revision: 3,
|
||
document,
|
||
document_hash: "base-hash",
|
||
visible_text_hash: "text-hash",
|
||
visible_text: "合成原文",
|
||
state: "saved",
|
||
},
|
||
candidate: {
|
||
candidate_id: "candidate",
|
||
revision: 2,
|
||
candidate_hash: "candidate-hash",
|
||
document_id: "document",
|
||
chapter_id: "chapter",
|
||
branch_id: "main",
|
||
origin: "author",
|
||
base_revision: 3,
|
||
current_document_revision: 3,
|
||
document,
|
||
visible_text: "合成改稿",
|
||
checks: { format: "passed", format_version: 1, quality: "not_applicable" },
|
||
decision: "undecided",
|
||
stale: false,
|
||
changes: [],
|
||
},
|
||
};
|
||
|
||
it("NC-review-fixed-request-retry:响应未知后保留原审阅与命令,刷新后仍重放同一决定", async () => {
|
||
const requests: unknown[] = [];
|
||
vi.stubGlobal(
|
||
"fetch",
|
||
vi.fn(async (request: Request) => {
|
||
requests.push(await request.json());
|
||
if (requests.length === 1) throw new Error("synthetic lost response");
|
||
return new Response(
|
||
JSON.stringify({
|
||
receipt_id: "receipt-one",
|
||
action: "adopt",
|
||
results: [],
|
||
}),
|
||
{ headers: { "Content-Type": "application/json" } },
|
||
);
|
||
}),
|
||
);
|
||
const view = render(
|
||
<Ui决策面板 review={review} author="author" onReopen={() => {}} />,
|
||
);
|
||
fireEvent.click(screen.getByRole("button", { name: "采纳为正文新版本" }));
|
||
await screen.findByRole("alert");
|
||
const retry = screen.getByRole("button", { name: "重试这次决定" });
|
||
expect(retry).toBeTruthy();
|
||
expect(screen.queryByRole("button", { name: "拒绝候选" })).toBeNull();
|
||
view.unmount();
|
||
render(
|
||
<Ui决策面板
|
||
review={{ ...review, review_id: "new-view-only" }}
|
||
author="author"
|
||
onReopen={() => {}}
|
||
/>,
|
||
);
|
||
fireEvent.click(screen.getByRole("button", { name: "重试这次决定" }));
|
||
await screen.findByText("已采纳");
|
||
expect(requests).toHaveLength(2);
|
||
expect(requests[1]).toEqual(requests[0]);
|
||
expect(requests[0]).toMatchObject({
|
||
candidate_revision: 2,
|
||
candidate_hash: "candidate-hash",
|
||
expected_revision: 3,
|
||
author_review_id: "review-original",
|
||
approved_changes: ["document"],
|
||
});
|
||
expect(sessionStorage.length).toBe(0);
|
||
});
|
||
|
||
it("NC-review-conflict-reopen:明确冲突停止决定并重新展示,不以当前版本悄悄替换基线", async () => {
|
||
const calls = vi.fn(
|
||
async () =>
|
||
new Response(
|
||
JSON.stringify({ code: "REVISION_CONFLICT", message: "候选已经更新" }),
|
||
{ status: 409, headers: { "Content-Type": "application/json" } },
|
||
),
|
||
);
|
||
vi.stubGlobal("fetch", calls);
|
||
const reopen = vi.fn();
|
||
render(<Ui决策面板 review={review} author="author" onReopen={reopen} />);
|
||
fireEvent.click(screen.getByRole("button", { name: "采纳为正文新版本" }));
|
||
await screen.findByText("候选已经更新");
|
||
expect(screen.queryByRole("button", { name: "采纳为正文新版本" })).toBeNull();
|
||
fireEvent.click(screen.getByRole("button", { name: "重新打开审阅" }));
|
||
await waitFor(() => expect(reopen).toHaveBeenCalledOnce());
|
||
expect(calls).toHaveBeenCalledOnce();
|
||
expect(sessionStorage.length).toBe(0);
|
||
});
|
||
|
||
it("NC-candidate-stale-buffer-choice:明确放弃旧缓冲后能基于当前正文另拟改稿", async () => {
|
||
const key = "muse:candidate-draft:author:work:chapter:new";
|
||
const current = { ...review.before, revision: 2 };
|
||
const draft = {
|
||
...document,
|
||
paragraphs: [
|
||
{
|
||
paragraph_id: "p1",
|
||
content: [{ type: "text", text: "尚未提交的旧改稿", marks: [] }],
|
||
},
|
||
],
|
||
};
|
||
sessionStorage.setItem(
|
||
key,
|
||
JSON.stringify({
|
||
document: draft,
|
||
base_revision: 1,
|
||
mode: "edit",
|
||
selected: [],
|
||
}),
|
||
);
|
||
const sent: unknown[] = [];
|
||
vi.stubGlobal(
|
||
"fetch",
|
||
vi.fn(async (request: Request) => {
|
||
const path = new URL(request.url).pathname;
|
||
if (request.method === "POST") sent.push(await request.json());
|
||
const data =
|
||
request.method === "POST"
|
||
? { receipt_id: "candidate-saved", results: [] }
|
||
: path.endsWith("session")
|
||
? { author_id: "author" }
|
||
: path.endsWith("writing-candidates")
|
||
? []
|
||
: current;
|
||
return new Response(JSON.stringify(data), {
|
||
headers: { "Content-Type": "application/json" },
|
||
});
|
||
}),
|
||
);
|
||
render(
|
||
<Ui依赖装配>
|
||
<MemoryRouter initialEntries={["/works/work/candidates/chapter"]}>
|
||
<Routes>
|
||
<Route
|
||
path="/works/:work_id/candidates/:chapter_id"
|
||
element={<Ui候选审阅页 />}
|
||
/>
|
||
</Routes>
|
||
</MemoryRouter>
|
||
</Ui依赖装配>,
|
||
);
|
||
fireEvent.click(await screen.findByRole("button", { name: "另拟改稿" }));
|
||
expect(
|
||
screen.getByRole("textbox", { name: "候选改稿" }).textContent,
|
||
).toContain("尚未提交的旧改稿");
|
||
expect(
|
||
(screen.getByRole("button", { name: "保存候选版本" }) as HTMLButtonElement)
|
||
.disabled,
|
||
).toBe(true);
|
||
fireEvent.click(
|
||
screen.getByRole("button", { name: "放弃旧改稿,返回当前版本" }),
|
||
);
|
||
expect(sessionStorage.getItem(key)).toBeNull();
|
||
expect(sent).toHaveLength(0);
|
||
fireEvent.click(screen.getByRole("button", { name: "另拟改稿" }));
|
||
expect(
|
||
screen.getByRole("textbox", { name: "候选改稿" }).textContent,
|
||
).toContain("合成原文");
|
||
const save = screen.getByRole("button", { name: "保存候选版本" });
|
||
expect((save as HTMLButtonElement).disabled).toBe(false);
|
||
fireEvent.click(save);
|
||
await waitFor(() => expect(sent).toHaveLength(1));
|
||
expect(sent[0]).toMatchObject({
|
||
expected_revision: 2,
|
||
document: current.document,
|
||
});
|
||
});
|