重构为函数式通用 Markdown 修改库

This commit is contained in:
2026-08-26 15:33:37 +08:00
parent 1abf72ccb1
commit bb0507db30
89 changed files with 1589 additions and 14850 deletions
-256
View File
@@ -1,256 +0,0 @@
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { App } from "../src/client/App.js";
import type {
ComponentStageResponse,
DocumentComparisonResponse,
RunSummaryResponse,
} from "../src/shared/api.js";
vi.mock("../src/client/DiffView.js", () => ({
DiffView: ({ beforeLabel, afterLabel }: { beforeLabel: string; afterLabel: string }) => (
<div data-testid="diff-view">
{beforeLabel} / {afterLabel}
</div>
),
}));
const components = [
{
component_position: 0,
component_id: "paper.rule",
version: "1.0.0",
parameters: [],
applicability: "替换测试单词。",
change_count: 1,
},
{
component_position: 1,
component_id: "paper.zero",
version: "1.0.0",
parameters: [],
applicability: "不修改当前测试文档。",
change_count: 0,
},
];
const documentSummary = {
document_id: "paper",
source_label: "inputs/paper.md",
status: "success" as const,
input_sha256: "1".repeat(64),
current_sha256: "2".repeat(64),
change_count: 1,
source_available: true,
output_available: true,
availability_error: null,
};
const runResponse: RunSummaryResponse = {
schema_version: 1,
run: {
run_id: "review-run",
run_date: "2026-08-23",
status: "success",
started_at_utc: "2026-08-23T01:00:00Z",
completed_at_utc: "2026-08-23T01:01:00Z",
retention_until: "2026-09-22T01:01:00Z",
},
components,
documents: [documentSummary],
summary: {
document_count: 1,
success_count: 1,
failed_count: 0,
unstable_count: 0,
change_count: 1,
},
original_run_location_changed: false,
};
const change = {
component_id: "paper.rule",
component_version: "1.0.0",
component_position: 0,
proposal_ref: {
component_position: 0,
snapshot_sha256: "1".repeat(64),
proposal_index: 0,
},
edit_index: 0,
reason: "替换测试单词",
location: { line: 1, column: 3 },
editor_range: { start: 0, end: 3 },
before: "old",
after: "new",
};
const documentResponse: DocumentComparisonResponse = {
schema_version: 1,
document: documentSummary,
components,
original_markdown: "old",
cleaned_markdown: "new",
changes: [change],
errors: [],
residual_proposals: [],
};
const stageResponse: ComponentStageResponse = {
schema_version: 1,
document_id: "paper",
component: components[0]!,
before_sha256: "1".repeat(64),
after_sha256: "2".repeat(64),
before_markdown: "old",
after_markdown: "new",
changes: [change],
};
function response(payload: unknown, status = 200): Response {
return new Response(JSON.stringify(payload), {
status,
headers: { "Content-Type": "application/json" },
});
}
describe("App", () => {
beforeEach(() => {
vi.restoreAllMocks();
});
it("shows the run, document comparison, component order and component stage", async () => {
const fetchMock = vi.fn((input: string | URL | Request) => {
const pathname =
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
if (pathname === "/api/v1/run") {
return Promise.resolve(response(runResponse));
}
if (pathname.endsWith("/components/0")) {
return Promise.resolve(response(stageResponse));
}
return Promise.resolve(response(documentResponse));
});
vi.stubGlobal("fetch", fetchMock);
render(<App />);
expect(await screen.findByRole("heading", { name: "review-run" })).toBeInTheDocument();
expect(await screen.findByTestId("diff-view")).toHaveTextContent("清洗前 / 清洗后");
expect(screen.getByText("paper.rule")).toBeInTheDocument();
expect(screen.getByText("paper.zero")).toBeInTheDocument();
expect(screen.getByText("替换测试单词")).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: /paper\.rule/ }));
await waitFor(() => {
expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining("components/0"), expect.anything());
});
expect(await screen.findByTestId("diff-view")).toHaveTextContent("组件 1 执行前 / 组件 1 执行后");
});
it("shows API failures without rendering document text", async () => {
vi.stubGlobal(
"fetch",
vi.fn(() =>
Promise.resolve(
response({ error: { code: "unsupported_schema", message: "不支持这个产物版本。" } }, 409),
),
),
);
render(<App />);
expect(await screen.findByRole("alert")).toHaveTextContent("不支持这个产物版本");
expect(screen.queryByTestId("diff-view")).not.toBeInTheDocument();
});
it("shows failed audit evidence without inventing a cleaned result", async () => {
const failedSummary = {
...documentSummary,
status: "failed" as const,
current_sha256: documentSummary.input_sha256,
change_count: 0,
output_available: false,
};
const failedRun: RunSummaryResponse = {
...runResponse,
run: { ...runResponse.run, status: "failed" },
documents: [failedSummary],
summary: {
document_count: 1,
success_count: 0,
failed_count: 1,
unstable_count: 0,
change_count: 0,
},
};
const failedDocument: DocumentComparisonResponse = {
schema_version: 1,
document: failedSummary,
components: components.map((component) => ({ ...component, change_count: 0 })),
original_markdown: "原文",
cleaned_markdown: null,
changes: [],
errors: [
{
component_id: "paper.rule",
component_version: "1.0.0",
component_position: 0,
stage: "transform",
error_type: "SyntheticError",
message: "测试组件失败。",
},
],
residual_proposals: [],
};
vi.stubGlobal(
"fetch",
vi.fn((input: string | URL | Request) => {
const pathname =
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
return Promise.resolve(response(pathname === "/api/v1/run" ? failedRun : failedDocument));
}),
);
render(<App />);
expect(await screen.findByRole("heading", { name: "失败文档只展示审计证据" })).toBeInTheDocument();
expect(screen.getByText("测试组件失败。")).toBeInTheDocument();
expect(screen.queryByTestId("diff-view")).not.toBeInTheDocument();
});
it("keeps audit details visible when a successful run has lost its original source", async () => {
const unavailableSummary = {
...documentSummary,
source_available: false,
availability_error: "原文路径已经失效。",
};
const unavailableRun: RunSummaryResponse = {
...runResponse,
documents: [unavailableSummary],
};
const unavailableDocument: DocumentComparisonResponse = {
...documentResponse,
document: unavailableSummary,
original_markdown: null,
changes: [{ ...change, editor_range: null }],
};
vi.stubGlobal(
"fetch",
vi.fn((input: string | URL | Request) => {
const pathname =
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
return Promise.resolve(
response(pathname === "/api/v1/run" ? unavailableRun : unavailableDocument),
);
}),
);
render(<App />);
expect(await screen.findByRole("alert")).toHaveTextContent("原文路径已经失效");
expect(screen.getByText("替换测试单词")).toBeInTheDocument();
expect(screen.queryByTestId("diff-view")).not.toBeInTheDocument();
});
});