277 lines
6.9 KiB
TypeScript
277 lines
6.9 KiB
TypeScript
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
import SchemeEditor from "./SchemeEditor";
|
|
import { getSensorPlacementCandidate } from "./schemeApi";
|
|
import type { SensorPlacementScheme } from "./types";
|
|
import { handleMapClickSelectFeatures } from "@/utils/mapQueryService";
|
|
|
|
const mockOpen = jest.fn();
|
|
let mockSingleClickHandler: ((event: unknown) => void) | undefined;
|
|
let mockGridColumns: Array<{
|
|
field: string;
|
|
headerName?: string;
|
|
valueFormatter?: (value: unknown) => string;
|
|
}> = [];
|
|
|
|
const mockMap = {
|
|
addLayer: jest.fn(),
|
|
removeLayer: jest.fn(),
|
|
on: jest.fn((eventName: string, handler: (event: unknown) => void) => {
|
|
if (eventName === "singleclick") {
|
|
mockSingleClickHandler = handler;
|
|
}
|
|
}),
|
|
un: jest.fn(),
|
|
forEachFeatureAtPixel: jest.fn(() => undefined),
|
|
getView: jest.fn(() => ({
|
|
animate: jest.fn(),
|
|
getZoom: jest.fn(() => 16),
|
|
getResolution: jest.fn(() => 1),
|
|
})),
|
|
};
|
|
|
|
jest.mock("@refinedev/core", () => ({
|
|
useNotification: () => ({ open: mockOpen }),
|
|
}));
|
|
|
|
jest.mock("@components/olmap/core/MapComponent", () => ({
|
|
useMap: () => mockMap,
|
|
}));
|
|
|
|
jest.mock("@mui/x-data-grid", () => ({
|
|
DataGrid: (props: {
|
|
columns: typeof mockGridColumns;
|
|
density?: string;
|
|
initialState?: { density?: string };
|
|
rowHeight?: number;
|
|
columnHeaderHeight?: number;
|
|
}) => {
|
|
mockGridColumns = props.columns;
|
|
return (
|
|
<div
|
|
data-testid="scheme-grid"
|
|
data-density={props.density ?? "uncontrolled"}
|
|
data-initial-density={props.initialState?.density ?? ""}
|
|
data-row-height={props.rowHeight ?? "automatic"}
|
|
data-column-header-height={props.columnHeaderHeight ?? "automatic"}
|
|
/>
|
|
);
|
|
},
|
|
GridToolbar: () => null,
|
|
}));
|
|
|
|
jest.mock("@mui/x-data-grid/locales", () => ({
|
|
zhCN: {
|
|
components: {
|
|
MuiDataGrid: {
|
|
defaultProps: {
|
|
localeText: {},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
|
|
jest.mock("ol/Feature", () => ({
|
|
__esModule: true,
|
|
default: class {
|
|
private values: Record<string, unknown>;
|
|
|
|
constructor(values: Record<string, unknown>) {
|
|
this.values = values;
|
|
}
|
|
|
|
get(key: string) {
|
|
return this.values[key];
|
|
}
|
|
|
|
setId() {}
|
|
},
|
|
}));
|
|
|
|
jest.mock("ol/geom/Point", () => ({
|
|
__esModule: true,
|
|
default: class {
|
|
constructor(_coordinates: number[]) {}
|
|
},
|
|
}));
|
|
|
|
jest.mock("ol/source/Vector", () => ({
|
|
__esModule: true,
|
|
default: class {
|
|
clear() {}
|
|
addFeature() {}
|
|
},
|
|
}));
|
|
|
|
jest.mock("ol/layer/Vector", () => ({
|
|
__esModule: true,
|
|
default: class {
|
|
private source: { clear: () => void; addFeature: () => void };
|
|
|
|
constructor(options: {
|
|
source: { clear: () => void; addFeature: () => void };
|
|
}) {
|
|
this.source = options.source;
|
|
}
|
|
|
|
getSource() {
|
|
return this.source;
|
|
}
|
|
|
|
setVisible() {}
|
|
},
|
|
}));
|
|
|
|
jest.mock("ol/style", () => ({
|
|
Circle: class {},
|
|
Fill: class {},
|
|
Stroke: class {},
|
|
Style: class {},
|
|
Text: class {},
|
|
}));
|
|
|
|
jest.mock("@/utils/mapQueryService", () => ({
|
|
handleMapClickSelectFeatures: jest.fn(),
|
|
}));
|
|
|
|
jest.mock("./SchemeDrawingDialog", () => ({
|
|
__esModule: true,
|
|
default: () => null,
|
|
}));
|
|
|
|
jest.mock("./schemeApi", () => ({
|
|
exportSensorPlacementExcel: jest.fn(),
|
|
getSensorPlacementCandidate: jest.fn(),
|
|
overwriteSensorPlacementScheme: jest.fn(),
|
|
}));
|
|
|
|
const scheme: SensorPlacementScheme = {
|
|
id: 1,
|
|
scheme_name: "测试方案",
|
|
sensor_number: 1,
|
|
min_diameter: 300,
|
|
username: "operator",
|
|
create_time: "2026-07-30T08:00:00+08:00",
|
|
sensor_location: ["J1"],
|
|
sensor_points: [
|
|
{
|
|
node_id: "J1",
|
|
max_pipe_diameter: 400,
|
|
project_x: 10,
|
|
project_y: 20,
|
|
map_x: 13500010,
|
|
map_y: 3600020,
|
|
longitude: 121,
|
|
latitude: 31,
|
|
elevation: 5,
|
|
},
|
|
],
|
|
can_edit: true,
|
|
};
|
|
|
|
const mockGetSensorPlacementCandidate = jest.mocked(
|
|
getSensorPlacementCandidate,
|
|
);
|
|
const mockHandleMapClickSelectFeatures = jest.mocked(
|
|
handleMapClickSelectFeatures,
|
|
);
|
|
|
|
describe("SchemeEditor notifications", () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
mockSingleClickHandler = undefined;
|
|
mockGridColumns = [];
|
|
});
|
|
|
|
it("uses the same error notification contract as scheme query when replace starts outside an existing sensor", async () => {
|
|
render(<SchemeEditor scheme={scheme} network="fengyang" />);
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: "替换" }));
|
|
await waitFor(() => expect(mockSingleClickHandler).toBeDefined());
|
|
|
|
act(() => {
|
|
mockSingleClickHandler?.({
|
|
pixel: [0, 0],
|
|
coordinate: [13500000, 3600000],
|
|
stopPropagation: jest.fn(),
|
|
});
|
|
});
|
|
|
|
expect(mockOpen).toHaveBeenCalledWith({
|
|
type: "error",
|
|
message: "请先点击要替换的监测点",
|
|
});
|
|
});
|
|
|
|
it("loads authoritative point details when adding a map-selected node", async () => {
|
|
mockHandleMapClickSelectFeatures.mockResolvedValue({
|
|
get: (key: string) => (key === "id" ? "J2" : undefined),
|
|
getId: () => "J2",
|
|
} as never);
|
|
mockGetSensorPlacementCandidate.mockResolvedValue({
|
|
node_id: "J2",
|
|
max_pipe_diameter: 500,
|
|
project_x: 30,
|
|
project_y: 40,
|
|
map_x: 13500030,
|
|
map_y: 3600040,
|
|
longitude: 121.1,
|
|
latitude: 31.1,
|
|
elevation: 6,
|
|
});
|
|
render(<SchemeEditor scheme={scheme} network="fengyang" />);
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: "添加" }));
|
|
await waitFor(() => expect(mockSingleClickHandler).toBeDefined());
|
|
await act(async () => {
|
|
mockSingleClickHandler?.({
|
|
pixel: [0, 0],
|
|
coordinate: [13500030, 3600040],
|
|
stopPropagation: jest.fn(),
|
|
});
|
|
await Promise.resolve();
|
|
});
|
|
|
|
expect(mockGetSensorPlacementCandidate).toHaveBeenCalledWith("J2");
|
|
});
|
|
|
|
it("lets the Data Grid density selector control row sizing", () => {
|
|
render(<SchemeEditor scheme={scheme} network="fengyang" />);
|
|
|
|
expect(screen.getByTestId("scheme-grid")).toHaveAttribute(
|
|
"data-density",
|
|
"uncontrolled",
|
|
);
|
|
expect(screen.getByTestId("scheme-grid")).toHaveAttribute(
|
|
"data-initial-density",
|
|
"compact",
|
|
);
|
|
expect(screen.getByTestId("scheme-grid")).toHaveAttribute(
|
|
"data-row-height",
|
|
"automatic",
|
|
);
|
|
expect(screen.getByTestId("scheme-grid")).toHaveAttribute(
|
|
"data-column-header-height",
|
|
"automatic",
|
|
);
|
|
});
|
|
|
|
it("places actions and status first and displays the selected pipe diameter", () => {
|
|
render(<SchemeEditor scheme={scheme} network="fengyang" />);
|
|
|
|
expect(mockGridColumns.slice(0, 5).map((column) => column.field)).toEqual([
|
|
"actions",
|
|
"adjustment_status",
|
|
"sequence",
|
|
"node_id",
|
|
"max_pipe_diameter",
|
|
]);
|
|
const diameterColumn = mockGridColumns.find(
|
|
(column) => column.field === "max_pipe_diameter",
|
|
);
|
|
expect(diameterColumn?.headerName).toBe("最大管径 (mm)");
|
|
expect(diameterColumn?.valueFormatter?.(400)).toBe("400");
|
|
expect(diameterColumn?.valueFormatter?.(null)).toBe("无");
|
|
});
|
|
});
|