import { expect, test } from "@playwright/test"; import { mockWorkbenchContext } from "./support/mock-workbench-context"; test("keeps pipes and SCADA visible across fractional zoom changes", async ({ page }, testInfo) => { await mockWorkbenchContext(page); await page.goto("/", { waitUntil: "domcontentloaded" }); await expect.poll(() => page.evaluate(() => Boolean( window.__waterNetworkMap?.getLayer("pipes-flow") && window.__waterNetworkMap?.getLayer("scada-pressure-symbol") )), { timeout: 15_000 }).toBe(true); await page.evaluate(() => { const map = window.__waterNetworkMap!; map.addSource("scale-fixture", { type: "geojson", data: { type: "FeatureCollection", features: [ ...[800, 400, 100, null].map((diameter, index) => ({ type: "Feature" as const, id: `pipe-${index}`, properties: { diameter, fixture_id: `pipe-${index}` }, geometry: { type: "LineString" as const, coordinates: [[121.46, 30.91 + index * 0.001], [121.47, 30.91 + index * 0.001]] } })), ...["pipe_flow", "pressure"].map((deviceType, index) => ({ type: "Feature" as const, id: deviceType, properties: { device_type: deviceType, fixture_id: deviceType }, geometry: { type: "Point" as const, coordinates: [121.462 + index * 0.008, 30.915] } })) ] } }); for (const id of ["pipes-casing", "pipes-flow", "pipes-hit", "scada-flow-symbol", "scada-pressure-symbol"]) { const original = map.getStyle().layers.find((layer) => layer.id === id)!; const copy = { ...original }; if ("source-layer" in copy) delete copy["source-layer"]; map.addLayer({ ...copy, id: `scale-fixture-${id}`, source: "scale-fixture", filter: ["all", ["==", ["geometry-type"], original.type === "line" ? "LineString" : "Point"], "filter" in original ? original.filter ?? true : true] }); } map.jumpTo({ center: [121.465, 30.913], zoom: 11.7, pitch: 0, bearing: 0 }); }); const pipeIds = ["pipe-0", "pipe-1", "pipe-2", "pipe-3"]; for (const [zoom, scadaIds] of [ [11.7, ["pipe_flow", "pressure"]], [12, ["pipe_flow", "pressure"]], [13, ["pipe_flow", "pressure"]], [14, ["pipe_flow", "pressure"]], [16, ["pipe_flow", "pressure"]], [11.7, ["pipe_flow", "pressure"]] ] as const) { await page.evaluate((value) => window.__waterNetworkMap!.jumpTo({ zoom: value }), zoom); await expect.poll(() => page.evaluate(() => { const map = window.__waterNetworkMap!; const ids = (layers: string[]) => [...new Set( map.queryRenderedFeatures({ layers }).map((feature) => String(feature.properties.fixture_id)) )].sort(); return { pipes: ids(["scale-fixture-pipes-flow"]), hits: ids(["scale-fixture-pipes-hit"]), scada: ids(["scale-fixture-scada-flow-symbol", "scale-fixture-scada-pressure-symbol"]) }; })).toEqual({ pipes: [...pipeIds], hits: [...pipeIds], scada: [...scadaIds] }); await page.screenshot({ path: testInfo.outputPath(`zoom-${zoom}.png`) }); } }); for (const width of [1440, 390]) { test(`declutters dense facilities and preserves selection at width ${width}`, async ({ page }, testInfo) => { await page.setViewportSize({ width, height: 900 }); await mockWorkbenchContext(page); await page.goto("/", { waitUntil: "domcontentloaded" }); await expect.poll(() => page.evaluate(() => Boolean( window.__waterNetworkMap?.getLayer("pipes-flow") )), { timeout: 15_000 }).toBe(true); await page.evaluate(async () => { const layerModuleUrl = "/src/features/workbench/map/layers.ts"; const { waterNetworkLayers } = await import(layerModuleUrl); const scadaModuleUrl = "/src/features/workbench/map/scada.ts"; const { scadaLayers } = await import(scadaModuleUrl); const layers = [...waterNetworkLayers, ...scadaLayers]; const map = window.__waterNetworkMap!; map.addSource("density-fixture", { type: "geojson", promoteId: "fixture_id", data: { type: "FeatureCollection", features: [ ...Array.from({ length: 70 }, (_, index) => ({ type: "Feature" as const, properties: { fixture_id: `valve-${index}`, kind: "valve" }, geometry: { type: "Point" as const, coordinates: [121.4635 + index % 10 * 0.00035, 30.912 + Math.floor(index / 10) * 0.00035] } })), { type: "Feature" as const, properties: { fixture_id: "reservoir", kind: "reservoir" }, geometry: { type: "Point" as const, coordinates: [121.465, 30.913] } }, ...[ ["pump", [121.464, 30.9114]], ["tank", [121.466, 30.9114]], ["water-quality", [121.465, 30.9098]], ["pressure", [121.505, 30.913]], ["junction", [121.4648, 30.9138]], ["gis-detail", [121.466, 30.9138]], ["gis-flow", [121.467, 30.9138]] ].map(([kind, coordinates]) => ({ type: "Feature" as const, properties: { fixture_id: kind, kind }, geometry: { type: "Point" as const, coordinates } })) ] } }); const kinds: Record = { "reservoirs-symbol": "reservoir", "pumps-symbol": "pump", "tanks-symbol": "tank", "water_quality_monitoring_points-symbol": "water-quality", "water_quality_monitoring_points-hit": "water-quality", "scada-pressure-symbol": "pressure", junctions: "junction", "junctions-hit": "junction", "fx_20200423_hydrants-point": "gis-detail", "fx_20200423_hydrants-hit": "gis-detail", "fx_20200423_flow_meters-point": "gis-flow", "fx_20200423_flow_meters-hit": "gis-flow" }; for (const id of ["valves-casing", "valves", "valves-symbol", ...Object.keys(kinds), "valves-selected-outer", "valves-selected", "valves-hit"]) { const original = layers.find((layer: { id: string }) => layer.id === id); const copy = { ...original }; delete copy["source-layer"]; map.addLayer({ ...copy, id: `density-${id}`, source: "density-fixture", filter: ["==", ["get", "kind"], kinds[id] ?? "valve"] }); } map.jumpTo({ center: [121.465, 30.913], zoom: 13.1, pitch: 0, bearing: 0 }); }); const renderedCounts = () => page.evaluate(() => { const map = window.__waterNetworkMap!; const count = (id: string) => new Set( map.queryRenderedFeatures({ layers: [id] }).map((feature) => feature.properties.fixture_id) ).size; return { dots: count("density-valves"), symbols: count("density-valves-symbol"), hits: count("density-valves-hit"), reservoir: count("density-reservoirs-symbol"), pump: count("density-pumps-symbol"), tank: count("density-tanks-symbol"), waterQuality: count("density-water_quality_monitoring_points-symbol"), waterQualityHit: count("density-water_quality_monitoring_points-hit"), pressure: count("density-scada-pressure-symbol"), nodes: count("density-junctions"), nodeHits: count("density-junctions-hit"), gisDetails: count("density-fx_20200423_hydrants-point"), gisDetailHits: count("density-fx_20200423_hydrants-hit"), gisFlow: count("density-fx_20200423_flow_meters-point"), gisFlowHits: count("density-fx_20200423_flow_meters-hit") }; }); await expect.poll(renderedCounts).toMatchObject({ dots: 70, reservoir: 1, pump: 1, tank: 1, waterQuality: 1 }); const overview = await renderedCounts(); expect(overview.symbols).toBeLessThan(overview.dots / 3); await page.screenshot({ path: testInfo.outputPath("dense-overview.png") }); await page.evaluate(() => window.__waterNetworkMap!.jumpTo({ zoom: 10.99 })); await expect.poll(renderedCounts).toMatchObject({ waterQuality: 0, waterQualityHit: 0, pressure: 0, gisFlow: 0, gisFlowHits: 0 }); await page.evaluate(() => window.__waterNetworkMap!.jumpTo({ zoom: 11 })); await expect.poll(renderedCounts).toMatchObject({ waterQuality: 1, waterQualityHit: 1, pressure: 1, gisFlow: 1, gisFlowHits: 1 }); await page.evaluate(() => window.__waterNetworkMap!.jumpTo({ zoom: 11.99 })); await expect.poll(renderedCounts).toMatchObject({ nodes: 0, nodeHits: 0, gisDetails: 0, gisDetailHits: 0 }); await page.evaluate(() => window.__waterNetworkMap!.jumpTo({ zoom: 12 })); await expect.poll(renderedCounts).toMatchObject({ nodes: 1, nodeHits: 1, gisDetails: 1, gisDetailHits: 1 }); await page.evaluate(() => window.__waterNetworkMap!.jumpTo({ zoom: 12.9 })); await expect.poll(renderedCounts).toMatchObject({ dots: 0, symbols: 0, hits: 0, waterQuality: 1 }); await page.evaluate(() => window.__waterNetworkMap!.jumpTo({ zoom: 13 })); await expect.poll(renderedCounts).toMatchObject({ dots: 70, hits: 70 }); await page.evaluate(() => window.__waterNetworkMap!.jumpTo({ zoom: 15.5 })); await expect.poll(async () => (await renderedCounts()).symbols).toBeGreaterThan(overview.symbols); await page.screenshot({ path: testInfo.outputPath("dense-detail.png") }); await page.evaluate(() => { const map = window.__waterNetworkMap!; map.setFeatureState({ source: "density-fixture", id: "valve-35" }, { selected: true }); map.jumpTo({ zoom: 13.1 }); }); await expect.poll(() => page.evaluate(() => window.__waterNetworkMap! .getFeatureState({ source: "density-fixture", id: "valve-35" }).selected)).toBe(true); await page.screenshot({ path: testInfo.outputPath("dense-selected.png") }); }); }