feat: initialize TJWater WebGIS frontend

This commit is contained in:
2026-07-21 18:31:08 +08:00
commit ad5e5d3133
191 changed files with 37963 additions and 0 deletions
@@ -0,0 +1,68 @@
import type { FeatureCollection } from "geojson";
import type { Map as MapLibreMap } from "maplibre-gl";
import { describe, expect, it, vi } from "vitest";
import { WorkbenchMapController } from "./workbench-map-controller";
describe("WorkbenchMapController", () => {
it("locates points with zoom 18 and keeps selected state untouched", async () => {
const map = createMap();
const controller = createController(map, pointCollection);
await controller.locateAndHighlight({ sourceId: "junctions", featureId: "J-1" });
expect(map.easeTo).toHaveBeenCalledWith(expect.objectContaining({ center: [120.7, 28], zoom: 18 }));
expect(map.setFeatureState).toHaveBeenCalledWith(expect.objectContaining({ source: "junctions", id: "J-1" }), { highlighted: true });
controller.clearHighlight();
expect(map.removeFeatureState).toHaveBeenCalledWith(expect.anything(), "highlighted");
expect(map.removeFeatureState).not.toHaveBeenCalledWith(expect.anything(), "selected");
});
it("fits line bounds and uses responsive workbench padding", async () => {
const map = createMap();
const padding = { top: 50, right: 420, bottom: 50, left: 320 };
const controller = createController(map, lineCollection, padding);
await controller.locateAndHighlight({ sourceId: "pipes", featureId: "P-1" });
expect(map.fitBounds).toHaveBeenCalledWith([[120, 28], [121, 29]], expect.objectContaining({ padding, maxZoom: 18 }));
});
it("does not clear the last valid highlight when a target is missing", async () => {
const map = createMap();
let collection = pointCollection;
const controller = new WorkbenchMapController({
getMap: () => map as unknown as MapLibreMap,
isReady: () => true,
getPadding: () => ({ top: 0, right: 0, bottom: 0, left: 0 }),
fetchFeatures: async () => collection
});
await controller.highlight({ sourceId: "junctions", featureId: "J-1" });
collection = { type: "FeatureCollection", features: [] };
await controller.highlight({ sourceId: "junctions", featureId: "missing" });
expect(controller.getSnapshot().target).toEqual({ sourceId: "junctions", featureId: "J-1" });
expect(controller.getSnapshot().errorCode).toBe("FEATURE_NOT_FOUND");
expect(map.removeFeatureState).not.toHaveBeenCalled();
});
it("reports SCADA analysis as unavailable for the water-network data source", async () => {
const map = createMap();
const controller = createController(map, pointCollection);
await expect(controller.renderScadaAnalysis([{ sensor_id: "MP01", level: "high" }])).rejects.toThrow("SCADA_FEATURES_NOT_FOUND");
expect(controller.getSnapshot().errorCode).toBe("SCADA_FEATURES_NOT_FOUND");
});
});
const pointCollection: FeatureCollection = { type: "FeatureCollection", features: [{ type: "Feature", geometry: { type: "Point", coordinates: [120.7, 28] }, properties: { id: "J-1" } }] };
const lineCollection: FeatureCollection = { type: "FeatureCollection", features: [{ type: "Feature", geometry: { type: "LineString", coordinates: [[120, 28], [121, 29]] }, properties: { id: "P-1" } }] };
function createMap() {
return {
easeTo: vi.fn(), fitBounds: vi.fn(), setFeatureState: vi.fn(), removeFeatureState: vi.fn(),
getSource: vi.fn(), getLayer: vi.fn(), addLayer: vi.fn(), setPaintProperty: vi.fn(), setLayoutProperty: vi.fn(), removeControl: vi.fn()
};
}
function createController(map: ReturnType<typeof createMap>, collection: FeatureCollection, padding = { top: 48, right: 48, bottom: 48, left: 48 }) {
return new WorkbenchMapController({
getMap: () => map as unknown as MapLibreMap,
isReady: () => true,
getPadding: () => padding,
fetchFeatures: vi.fn(async () => collection)
});
}