import { describe, expect, it } from "vitest"; import { INTERACTIVE_HIT_LAYER_IDS, waterNetworkLayers } from "./layers"; import { MAP_STYLE_TOKENS } from "./map-colors"; import { DRAINAGE_LAYER_VISUALS, MAP_MAX_ZOOM } from "./map-layer-visuals"; describe("drainage network interaction layers", () => { it("adds one invisible hit layer for every interactive source", () => { expect(INTERACTIVE_HIT_LAYER_IDS).toEqual([ "conduits-hit", "junctions-hit", "orifices-hit", "pumps-hit", "outfalls-hit" ]); const hitLayers = INTERACTIVE_HIT_LAYER_IDS.map((layerId) => { const layer = waterNetworkLayers.find((candidate) => candidate.id === layerId); expect(layer).toBeDefined(); return layer!; }); expect(hitLayers.map((layer) => "source" in layer ? layer.source : null)).toEqual([ "conduits", "junctions", "orifices", "pumps", "outfalls" ]); hitLayers.forEach((layer) => { if (layer.type === "line") { expect(layer.paint?.["line-width"]).toBe(DRAINAGE_LAYER_VISUALS.conduits.hitWidth); expect(layer.paint?.["line-opacity"]).toBe(0); } else if (layer.type === "circle") { expect(layer.paint?.["circle-radius"]).toBe(DRAINAGE_LAYER_VISUALS.junctions.hitRadius); expect(layer.paint?.["circle-opacity"]).toBe(0); } else { throw new Error(`Unexpected hit layer type: ${layer.type}`); } }); }); it("orders smaller facilities above conduits for overlapping hits", () => { const layerIds = waterNetworkLayers.map((layer) => layer.id); const hitLayerIndexes = INTERACTIVE_HIT_LAYER_IDS.map((layerId) => layerIds.indexOf(layerId)); expect(hitLayerIndexes).toEqual([...hitLayerIndexes].sort((first, second) => first - second)); expect(hitLayerIndexes.every((index) => index >= 0)).toBe(true); }); it("provides persistent selected layers for every source", () => { const selectedLayerIds = [ "pipes-selected-outline", "junctions-selected", "orifices-selected-outline", "pumps-selected-outline", "outfalls-selected" ]; selectedLayerIds.forEach((layerId) => { const layer = waterNetworkLayers.find((candidate) => candidate.id === layerId); expect(layer && "filter" in layer ? layer.filter : undefined).toBeUndefined(); const paint = layer?.paint as Record | undefined; expect(JSON.stringify(paint)).toContain("feature-state"); expect(JSON.stringify(paint)).toContain("selected"); }); }); it("renders selected features as opaque hollow outlines", () => { const expectedLineOutlines = [ ["pipes-selected-outline", DRAINAGE_LAYER_VISUALS.conduits], ["orifices-selected-outline", DRAINAGE_LAYER_VISUALS.orifices], ["pumps-selected-outline", DRAINAGE_LAYER_VISUALS.pumps] ] as const; expectedLineOutlines.forEach(([layerId, visuals]) => { const layer = waterNetworkLayers.find((candidate) => candidate.id === layerId); const paint = layer?.paint as Record | undefined; expect(paint?.["line-color"]).toBe(MAP_STYLE_TOKENS.state.selected); expect(paint?.["line-gap-width"]).toBe(visuals.selectedWidth); expect(paint?.["line-width"]).toBe(visuals.selectedBorderWidth); expect(paint?.["line-opacity"]).toEqual(["case", ["boolean", ["feature-state", "selected"], false], 1, 0]); }); const expectedPointOutlines = [ "junctions-selected", "outfalls-selected" ] as const; expectedPointOutlines.forEach((layerId) => { const layer = waterNetworkLayers.find((candidate) => candidate.id === layerId); const paint = layer?.paint as Record | undefined; expect(paint?.["circle-color"]).toBe(MAP_STYLE_TOKENS.canvas.transparent); expect(JSON.stringify(paint?.["circle-opacity"])).toContain("feature-state"); expect(paint?.["circle-stroke-color"]).toBe(MAP_STYLE_TOKENS.state.selected); expect(JSON.stringify(paint?.["circle-stroke-opacity"])).toContain("selected"); }); }); it("keeps source geometry readable through zoom level 24", () => { expect(MAP_MAX_ZOOM).toBe(24); expect(DRAINAGE_LAYER_VISUALS.junctions.minzoom).toBe(12); expect(DRAINAGE_LAYER_VISUALS.junctions.radius).toEqual([ "interpolate", ["linear"], ["zoom"], 12, 0.6, 14, 1, 15, 1.5, 16, 3, 20, 5.5, 24, 8 ]); expect(DRAINAGE_LAYER_VISUALS.outfalls.radius).toEqual([ "interpolate", ["linear"], ["zoom"], 11, 3, 16, 5, 20, 7, 24, 9 ]); expect(DRAINAGE_LAYER_VISUALS.orifices.width).toEqual([ "interpolate", ["linear"], ["zoom"], 11, 1.6, 17, 4.5, 22, 6, 24, 7 ]); expect(DRAINAGE_LAYER_VISUALS.pumps.width).toEqual([ "interpolate", ["linear"], ["zoom"], 11, 2.4, 17, 6.2, 22, 8, 24, 9 ]); expect((DRAINAGE_LAYER_VISUALS.conduits.width as unknown[]).at(-2)).toBe(24); expect((DRAINAGE_LAYER_VISUALS.conduits.hitWidth as unknown[]).at(-1)).toBe(24); expect((DRAINAGE_LAYER_VISUALS.junctions.hitRadius as unknown[]).at(-1)).toBe(14); }); it("prioritizes continuous conduits before full junction detail at low zoom", () => { expect(DRAINAGE_LAYER_VISUALS.conduits.width).toEqual([ "interpolate", ["linear"], ["zoom"], 9, 1.1, 13, 2, 17, ["+", 2.5, ["/", ["coalesce", ["get", "diameter"], 100], 360]], 22, ["+", 3.5, ["/", ["coalesce", ["get", "diameter"], 100], 300]], 24, ["+", 4, ["/", ["coalesce", ["get", "diameter"], 100], 280]] ]); expect(DRAINAGE_LAYER_VISUALS.junctions.haloOpacity).toEqual([ "interpolate", ["linear"], ["zoom"], 12, 0, 14, 0, 15, 1 ]); const junctionHalo = waterNetworkLayers.find((layer) => layer.id === "junctions-halo"); const orifices = waterNetworkLayers.find((layer) => layer.id === "orifices"); const pumps = waterNetworkLayers.find((layer) => layer.id === "pumps"); expect((junctionHalo?.paint as Record)?.["circle-opacity"]).toBe(DRAINAGE_LAYER_VISUALS.junctions.haloOpacity); expect((orifices?.paint as Record)?.["line-dasharray"]).toEqual([4, 2]); expect((pumps?.paint as Record)?.["line-dasharray"]).toEqual([6, 2, 1.5, 2]); }); it("uses the centralized geometry tokens in rendered layers", () => { const expectedGeometry = [ ["pipes-flow", "line-width", DRAINAGE_LAYER_VISUALS.conduits.width], ["pipes-selected-outline", "line-width", DRAINAGE_LAYER_VISUALS.conduits.selectedBorderWidth], ["junctions", "circle-radius", DRAINAGE_LAYER_VISUALS.junctions.radius], ["junctions-selected", "circle-radius", DRAINAGE_LAYER_VISUALS.junctions.selectedRadius], ["orifices", "line-width", DRAINAGE_LAYER_VISUALS.orifices.width], ["orifices-selected-outline", "line-width", DRAINAGE_LAYER_VISUALS.orifices.selectedBorderWidth], ["pumps", "line-width", DRAINAGE_LAYER_VISUALS.pumps.width], ["pumps-selected-outline", "line-width", DRAINAGE_LAYER_VISUALS.pumps.selectedBorderWidth], ["outfalls", "circle-radius", DRAINAGE_LAYER_VISUALS.outfalls.radius], ["outfalls-selected", "circle-radius", DRAINAGE_LAYER_VISUALS.outfalls.selectedRadius] ] as const; expectedGeometry.forEach(([layerId, paintProperty, visualToken]) => { const layer = waterNetworkLayers.find((candidate) => candidate.id === layerId); const paint = layer?.paint as Record | undefined; expect(paint?.[paintProperty]).toBe(visualToken); }); ["junctions-halo", "junctions", "junctions-hover", "junctions-hit"].forEach((layerId) => { const layer = waterNetworkLayers.find((candidate) => candidate.id === layerId); expect(layer && "minzoom" in layer ? layer.minzoom : undefined).toBe(12); }); }); });