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 (
); }, 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; constructor(values: Record) { 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(); 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(); 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(); 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(); 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("无"); }); });