281 lines
8.0 KiB
TypeScript
281 lines
8.0 KiB
TypeScript
import {
|
|
fireEvent,
|
|
render,
|
|
screen,
|
|
waitFor,
|
|
within,
|
|
} from "@testing-library/react";
|
|
import { queryFeaturesByIds } from "@/utils/mapQueryService";
|
|
import AnalysisReport, {
|
|
clearAnalysisReportDiameterCache,
|
|
matchesValveAnalysis,
|
|
} from "./AnalysisReport";
|
|
import { SchemeRecord, ValveIsolationResult } from "./types";
|
|
import {
|
|
isAllowedAccidentPipe,
|
|
normalizeValveIsolationResult,
|
|
} from "./valveIsolationScope";
|
|
|
|
jest.mock("@/utils/mapQueryService", () => ({
|
|
queryFeaturesByIds: jest.fn(),
|
|
}));
|
|
|
|
const scheme: SchemeRecord = {
|
|
id: "17",
|
|
schemeName: "burst-report-demo",
|
|
type: "burst_analysis",
|
|
username: "operator",
|
|
create_time: "2026-07-30T08:00:00+08:00",
|
|
startTime: "2026-07-30T09:00:00+08:00",
|
|
schemeDetail: {
|
|
burst_ID: ["P-1", "P-2"],
|
|
burst_size: [120, 80],
|
|
modify_total_duration: 5400,
|
|
modify_fixed_pump_pattern: null,
|
|
modify_valve_opening: null,
|
|
modify_variable_pump_pattern: null,
|
|
},
|
|
};
|
|
|
|
const valveResult: ValveIsolationResult = {
|
|
accident_elements: ["P-1"],
|
|
affected_nodes: ["J-1", "J-2"],
|
|
must_close_valves: ["V-1"],
|
|
optional_valves: ["V-2"],
|
|
isolatable: true,
|
|
};
|
|
|
|
const feature = (id: string, diameter: number) => ({
|
|
getProperties: () => ({ id, diameter }),
|
|
});
|
|
|
|
describe("AnalysisReport", () => {
|
|
beforeEach(() => {
|
|
(queryFeaturesByIds as jest.Mock).mockImplementation(
|
|
async (ids: string[], layerName: string) =>
|
|
layerName === "pipes"
|
|
? ids.map((id) =>
|
|
id === "P-1" ? feature(id, 315) : feature(id, 800),
|
|
)
|
|
: [],
|
|
);
|
|
});
|
|
|
|
afterEach(() => {
|
|
clearAnalysisReportDiameterCache();
|
|
jest.clearAllMocks();
|
|
document.body.classList.remove("burst-analysis-report-printing");
|
|
});
|
|
|
|
it("guides the user to choose a scheme when no report is active", () => {
|
|
render(
|
|
<AnalysisReport
|
|
scheme={null}
|
|
valveResult={null}
|
|
disabledValves={[]}
|
|
generatedAt={null}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByText("尚未选择分析方案")).toBeInTheDocument();
|
|
expect(
|
|
screen.getByText(
|
|
"请在“方案查询”中打开一个方案,再查看分析报告。",
|
|
),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders the selected scheme, pipe data, and matching valve result", async () => {
|
|
render(
|
|
<AnalysisReport
|
|
scheme={scheme}
|
|
valveResult={valveResult}
|
|
disabledValves={["V-3"]}
|
|
generatedAt={new Date("2026-07-30T10:00:00+08:00")}
|
|
/>,
|
|
);
|
|
|
|
const preview = within(
|
|
screen.getByTestId("burst-analysis-report-preview"),
|
|
);
|
|
expect(
|
|
preview.getByRole("heading", { name: "爆管分析报告" }),
|
|
).toBeInTheDocument();
|
|
expect(preview.getByText("burst-report-demo")).toBeInTheDocument();
|
|
expect(preview.getByText("可隔离")).toBeInTheDocument();
|
|
expect(preview.getByText("V-1")).toBeInTheDocument();
|
|
expect(preview.getByText("V-3")).toBeInTheDocument();
|
|
|
|
await waitFor(() => {
|
|
expect(preview.getByText("315 mm")).toBeInTheDocument();
|
|
expect(preview.getByText("800 mm")).toBeInTheDocument();
|
|
});
|
|
expect(queryFeaturesByIds).toHaveBeenCalledWith(
|
|
["P-1", "P-2"],
|
|
"pipes",
|
|
);
|
|
});
|
|
|
|
it("does not mix an unrelated valve analysis into the report", async () => {
|
|
render(
|
|
<AnalysisReport
|
|
scheme={scheme}
|
|
valveResult={{ ...valveResult, accident_elements: ["P-99"] }}
|
|
disabledValves={[]}
|
|
generatedAt={new Date("2026-07-30T10:00:00+08:00")}
|
|
/>,
|
|
);
|
|
|
|
const preview = within(
|
|
screen.getByTestId("burst-analysis-report-preview"),
|
|
);
|
|
expect(
|
|
preview.getByText(/尚未对本方案执行匹配的关阀分析/),
|
|
).toBeInTheDocument();
|
|
expect(preview.queryByText("V-1")).not.toBeInTheDocument();
|
|
expect(
|
|
matchesValveAnalysis(scheme, {
|
|
...valveResult,
|
|
accident_elements: ["P-99"],
|
|
}),
|
|
).toBe(false);
|
|
await waitFor(() =>
|
|
expect(preview.getByText("315 mm")).toBeInTheDocument(),
|
|
);
|
|
});
|
|
|
|
it("shows only the affected count for a non-isolatable result", async () => {
|
|
const legacyAffectedNodes = Array.from(
|
|
{ length: 100 },
|
|
(_, index) => `legacy-node-${index}`,
|
|
);
|
|
const nonIsolatableResult = {
|
|
...valveResult,
|
|
affected_nodes: legacyAffectedNodes,
|
|
affected_node_count: 85747,
|
|
must_close_valves: [],
|
|
isolatable: false,
|
|
};
|
|
|
|
render(
|
|
<AnalysisReport
|
|
scheme={scheme}
|
|
valveResult={nonIsolatableResult}
|
|
disabledValves={[]}
|
|
generatedAt={new Date("2026-07-30T10:00:00+08:00")}
|
|
/>,
|
|
);
|
|
|
|
const preview = within(
|
|
screen.getByTestId("burst-analysis-report-preview"),
|
|
);
|
|
expect(preview.getByText("85747 个")).toBeInTheDocument();
|
|
expect(
|
|
preview.getByText("不可隔离,未生成受影响节点清单。"),
|
|
).toBeInTheDocument();
|
|
expect(preview.queryByText("legacy-node-0")).not.toBeInTheDocument();
|
|
expect(preview.queryByText("legacy-node-99")).not.toBeInTheDocument();
|
|
await waitFor(() =>
|
|
expect(preview.getByText("315 mm")).toBeInTheDocument(),
|
|
);
|
|
});
|
|
|
|
it("limits scheme-bound valve analysis to the scheme accident pipes", () => {
|
|
expect(isAllowedAccidentPipe("P-1", ["P-1", "P-2"])).toBe(true);
|
|
expect(isAllowedAccidentPipe("P-99", ["P-1", "P-2"])).toBe(false);
|
|
expect(isAllowedAccidentPipe("P-99", undefined)).toBe(true);
|
|
});
|
|
|
|
it("normalizes legacy valve results without changing isolatable lists", () => {
|
|
expect(
|
|
normalizeValveIsolationResult({
|
|
...valveResult,
|
|
affected_nodes: ["J-1", "J-2", "J-3"],
|
|
must_close_valves: [],
|
|
isolatable: false,
|
|
}),
|
|
).toMatchObject({
|
|
affected_nodes: [],
|
|
affected_node_count: 3,
|
|
isolatable: false,
|
|
});
|
|
|
|
expect(normalizeValveIsolationResult(valveResult)).toMatchObject({
|
|
affected_nodes: ["J-1", "J-2"],
|
|
affected_node_count: 2,
|
|
isolatable: true,
|
|
});
|
|
});
|
|
|
|
it("prints only after assigning the report print state", async () => {
|
|
const originalTitle = document.title;
|
|
const print = jest
|
|
.spyOn(window, "print")
|
|
.mockImplementation(() => undefined);
|
|
|
|
render(
|
|
<AnalysisReport
|
|
scheme={scheme}
|
|
valveResult={null}
|
|
disabledValves={[]}
|
|
generatedAt={new Date("2026-07-30T10:00:00+08:00")}
|
|
/>,
|
|
);
|
|
|
|
const preview = within(
|
|
screen.getByTestId("burst-analysis-report-preview"),
|
|
);
|
|
await waitFor(() =>
|
|
expect(preview.getByText("315 mm")).toBeInTheDocument(),
|
|
);
|
|
expect(
|
|
document.querySelector(".burst-analysis-report-print-root"),
|
|
).not.toBeInTheDocument();
|
|
fireEvent.click(
|
|
screen.getByRole("button", { name: "打印/保存 PDF" }),
|
|
);
|
|
expect(print).toHaveBeenCalledTimes(1);
|
|
expect(
|
|
document.querySelector(".burst-analysis-report-print-root"),
|
|
).toBeInTheDocument();
|
|
expect(document.body).toHaveClass("burst-analysis-report-printing");
|
|
expect(document.title).toBe("爆管分析报告-burst-report-demo");
|
|
|
|
fireEvent(window, new Event("afterprint"));
|
|
expect(document.body).not.toHaveClass(
|
|
"burst-analysis-report-printing",
|
|
);
|
|
expect(document.title).toBe(originalTitle);
|
|
print.mockRestore();
|
|
});
|
|
|
|
it("reuses pipe diameters after the report is remounted", async () => {
|
|
const props = {
|
|
scheme,
|
|
valveResult: null,
|
|
disabledValves: [],
|
|
generatedAt: new Date("2026-07-30T10:00:00+08:00"),
|
|
};
|
|
const firstRender = render(<AnalysisReport {...props} />);
|
|
|
|
await waitFor(() =>
|
|
expect(
|
|
within(screen.getByTestId("burst-analysis-report-preview")).getByText(
|
|
"800 mm",
|
|
),
|
|
).toBeInTheDocument(),
|
|
);
|
|
expect(queryFeaturesByIds).toHaveBeenCalledTimes(1);
|
|
|
|
firstRender.unmount();
|
|
render(<AnalysisReport {...props} />);
|
|
|
|
expect(
|
|
within(screen.getByTestId("burst-analysis-report-preview")).getByText(
|
|
"800 mm",
|
|
),
|
|
).toBeInTheDocument();
|
|
expect(queryFeaturesByIds).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|