feat(burst): add analysis report and valve binding
Replace the obsolete location result with a printable analysis report, bind valve analysis to the selected scheme, and cache report diameter lookups. Scheme identity is tracked with valve results so reports cannot reuse results from another scheme that shares the same pipe.
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
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 } from "./valveIsolationScope";
|
||||
|
||||
jest.mock("@/utils/mapQueryService", () => ({
|
||||
queryFeaturesByIds: jest.fn(),
|
||||
}));
|
||||
|
||||
const scheme: SchemeRecord = {
|
||||
id: 17,
|
||||
schemeName: "burst-report-demo",
|
||||
type: "burst_analysis",
|
||||
user: "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 === "geo_pipes_mat"
|
||||
? [feature("P-1", 315)]
|
||||
: [feature("P-2", 800)],
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clearAnalysisReportDiameterCache();
|
||||
jest.clearAllMocks();
|
||||
document.body.classList.remove("burst-analysis-report-printing");
|
||||
});
|
||||
|
||||
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-2"],
|
||||
"geo_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("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("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(2);
|
||||
|
||||
firstRender.unmount();
|
||||
render(<AnalysisReport {...props} />);
|
||||
|
||||
expect(
|
||||
within(screen.getByTestId("burst-analysis-report-preview")).getByText(
|
||||
"800 mm",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(queryFeaturesByIds).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user