fix: probe all non-core GeoServer layers
Extend availability checks beyond pumps and tanks so unpublished valve and reservoir layers cannot leave stale controls.
This commit is contained in:
@@ -40,13 +40,15 @@ describe("GeoServer data-dependent layer availability", () => {
|
|||||||
it("keeps only optional layers that contain features", async () => {
|
it("keeps only optional layers that contain features", async () => {
|
||||||
const fetchMock = vi
|
const fetchMock = vi
|
||||||
.fn()
|
.fn()
|
||||||
|
.mockResolvedValueOnce(new Response('<wfs:FeatureCollection numberMatched="0" />'))
|
||||||
|
.mockResolvedValueOnce(new Response('<wfs:FeatureCollection numberMatched="0" />'))
|
||||||
.mockResolvedValueOnce(new Response('<wfs:FeatureCollection numberMatched="3" />'))
|
.mockResolvedValueOnce(new Response('<wfs:FeatureCollection numberMatched="3" />'))
|
||||||
.mockResolvedValueOnce(new Response('<wfs:FeatureCollection numberMatched="0" />'));
|
.mockResolvedValueOnce(new Response('<wfs:FeatureCollection numberMatched="0" />'));
|
||||||
vi.stubGlobal("fetch", fetchMock);
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
|
||||||
await expect(resolveGeoServerLayerAvailability()).resolves.toEqual({
|
await expect(resolveGeoServerLayerAvailability()).resolves.toEqual({
|
||||||
availableSourceIds: ["pipes", "junctions", "valves", "reservoirs", "pumps"],
|
availableSourceIds: ["pipes", "junctions", "pumps"],
|
||||||
emptySourceIds: ["tanks"],
|
emptySourceIds: ["valves", "reservoirs", "tanks"],
|
||||||
failedSourceIds: []
|
failedSourceIds: []
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -64,13 +66,15 @@ describe("GeoServer data-dependent layer availability", () => {
|
|||||||
const fetchMock = vi
|
const fetchMock = vi
|
||||||
.fn()
|
.fn()
|
||||||
.mockResolvedValueOnce(new Response("missing", { status: 503 }))
|
.mockResolvedValueOnce(new Response("missing", { status: 503 }))
|
||||||
|
.mockResolvedValueOnce(new Response("<ServiceException />"))
|
||||||
|
.mockResolvedValueOnce(new Response("missing", { status: 503 }))
|
||||||
.mockResolvedValueOnce(new Response("<ServiceException />"));
|
.mockResolvedValueOnce(new Response("<ServiceException />"));
|
||||||
vi.stubGlobal("fetch", fetchMock);
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
|
||||||
await expect(resolveGeoServerLayerAvailability()).resolves.toEqual({
|
await expect(resolveGeoServerLayerAvailability()).resolves.toEqual({
|
||||||
availableSourceIds: ["pipes", "junctions", "valves", "reservoirs"],
|
availableSourceIds: ["pipes", "junctions"],
|
||||||
emptySourceIds: [],
|
emptySourceIds: [],
|
||||||
failedSourceIds: ["pumps", "tanks"]
|
failedSourceIds: ["valves", "reservoirs", "pumps", "tanks"]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ describe("createWaterNetworkSources", () => {
|
|||||||
expect(SUPPLY_LAYER_CATALOG.map((layer) => [layer.id, layer.available, layer.availability])).toEqual([
|
expect(SUPPLY_LAYER_CATALOG.map((layer) => [layer.id, layer.available, layer.availability])).toEqual([
|
||||||
["pipes", true, "required"],
|
["pipes", true, "required"],
|
||||||
["junctions", true, "required"],
|
["junctions", true, "required"],
|
||||||
["valves", true, "required"],
|
["valves", true, "probe"],
|
||||||
["reservoirs", true, "required"],
|
["reservoirs", true, "probe"],
|
||||||
["scada", true, "required"],
|
["scada", true, "required"],
|
||||||
["pumps", true, "probe"],
|
["pumps", true, "probe"],
|
||||||
["tanks", true, "probe"]
|
["tanks", true, "probe"]
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export const SUPPLY_LAYER_CATALOG = [
|
|||||||
sourceLayer: SOURCE_LAYERS.valves,
|
sourceLayer: SOURCE_LAYERS.valves,
|
||||||
geometry: "point",
|
geometry: "point",
|
||||||
available: true,
|
available: true,
|
||||||
availability: "required",
|
availability: "probe",
|
||||||
label: "阀门",
|
label: "阀门",
|
||||||
icon: "valve"
|
icon: "valve"
|
||||||
},
|
},
|
||||||
@@ -77,7 +77,7 @@ export const SUPPLY_LAYER_CATALOG = [
|
|||||||
sourceLayer: SOURCE_LAYERS.reservoirs,
|
sourceLayer: SOURCE_LAYERS.reservoirs,
|
||||||
geometry: "point",
|
geometry: "point",
|
||||||
available: true,
|
available: true,
|
||||||
availability: "required",
|
availability: "probe",
|
||||||
label: "水库",
|
label: "水库",
|
||||||
icon: "reservoir"
|
icon: "reservoir"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { expect, test, type Page } from "@playwright/test";
|
|||||||
import { mockScadaApi } from "./support/mock-scada-api";
|
import { mockScadaApi } from "./support/mock-scada-api";
|
||||||
|
|
||||||
test("loads SCADA from the backend API and aligns map features by device_id", async ({ page }) => {
|
test("loads SCADA from the backend API and aligns map features by device_id", async ({ page }) => {
|
||||||
await mockRuntimeAndMap(page, { pumps: 0, tanks: 0 });
|
await mockRuntimeAndMap(page, { valves: 0, reservoirs: 0, pumps: 0, tanks: 0 });
|
||||||
await mockScadaApi(page, [{
|
await mockScadaApi(page, [{
|
||||||
device_id: "SCADA-1",
|
device_id: "SCADA-1",
|
||||||
device_type: "pressure",
|
device_type: "pressure",
|
||||||
@@ -38,13 +38,16 @@ test("loads SCADA from the backend API and aligns map features by device_id", as
|
|||||||
await expect(layerTool).toBeEnabled();
|
await expect(layerTool).toBeEnabled();
|
||||||
await layerTool.click();
|
await layerTool.click();
|
||||||
await expect(page.getByRole("button", { name: /^管线/ })).toBeVisible();
|
await expect(page.getByRole("button", { name: /^管线/ })).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: /^节点/ })).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: /^阀门/ })).toHaveCount(0);
|
||||||
|
await expect(page.getByRole("button", { name: /^水库/ })).toHaveCount(0);
|
||||||
await expect(page.getByRole("button", { name: /^水泵/ })).toHaveCount(0);
|
await expect(page.getByRole("button", { name: /^水泵/ })).toHaveCount(0);
|
||||||
await expect(page.getByRole("button", { name: /^水箱/ })).toHaveCount(0);
|
await expect(page.getByRole("button", { name: /^水箱/ })).toHaveCount(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("shows data-dependent layer controls only when GeoServer reports features", async ({ page }) => {
|
test("shows data-dependent layer controls only when GeoServer reports features", async ({ page }) => {
|
||||||
await page.setViewportSize({ width: 390, height: 844 });
|
await page.setViewportSize({ width: 390, height: 844 });
|
||||||
await mockRuntimeAndMap(page, { pumps: 2, tanks: 1 });
|
await mockRuntimeAndMap(page, { valves: 1, reservoirs: 1, pumps: 2, tanks: 1 });
|
||||||
await mockScadaApi(page);
|
await mockScadaApi(page);
|
||||||
|
|
||||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||||
@@ -52,12 +55,16 @@ test("shows data-dependent layer controls only when GeoServer reports features",
|
|||||||
const layerTool = page.getByRole("button", { name: /图层:管理地图图层/ });
|
const layerTool = page.getByRole("button", { name: /图层:管理地图图层/ });
|
||||||
await expect(layerTool).toBeEnabled();
|
await expect(layerTool).toBeEnabled();
|
||||||
await layerTool.click();
|
await layerTool.click();
|
||||||
|
await expect(page.getByRole("button", { name: /^阀门/ })).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: /^水库/ })).toBeVisible();
|
||||||
await expect(page.getByRole("button", { name: /^水泵/ })).toBeVisible();
|
await expect(page.getByRole("button", { name: /^水泵/ })).toBeVisible();
|
||||||
await expect(page.getByRole("button", { name: /^水箱/ })).toBeVisible();
|
await expect(page.getByRole("button", { name: /^水箱/ })).toBeVisible();
|
||||||
await expect.poll(() => page.evaluate(() => ({
|
await expect.poll(() => page.evaluate(() => ({
|
||||||
|
valve: Boolean(globalThis.__waterNetworkMap?.getLayer("valves-symbol")),
|
||||||
|
reservoir: Boolean(globalThis.__waterNetworkMap?.getLayer("reservoirs-symbol")),
|
||||||
pump: Boolean(globalThis.__waterNetworkMap?.getLayer("pumps-symbol")),
|
pump: Boolean(globalThis.__waterNetworkMap?.getLayer("pumps-symbol")),
|
||||||
tank: Boolean(globalThis.__waterNetworkMap?.getLayer("tanks-symbol"))
|
tank: Boolean(globalThis.__waterNetworkMap?.getLayer("tanks-symbol"))
|
||||||
}))).toEqual({ pump: true, tank: true });
|
}))).toEqual({ valve: true, reservoir: true, pump: true, tank: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
test("loads required map layers while optional layer probes are still pending", async ({ page }) => {
|
test("loads required map layers while optional layer probes are still pending", async ({ page }) => {
|
||||||
@@ -65,7 +72,11 @@ test("loads required map layers while optional layer probes are still pending",
|
|||||||
const probeGate = new Promise<void>((resolve) => {
|
const probeGate = new Promise<void>((resolve) => {
|
||||||
releaseProbe = resolve;
|
releaseProbe = resolve;
|
||||||
});
|
});
|
||||||
await mockRuntimeAndMap(page, { pumps: 1, tanks: 0 }, { probeGate });
|
await mockRuntimeAndMap(
|
||||||
|
page,
|
||||||
|
{ valves: 0, reservoirs: 0, pumps: 1, tanks: 0 },
|
||||||
|
{ probeGate }
|
||||||
|
);
|
||||||
await mockScadaApi(page);
|
await mockScadaApi(page);
|
||||||
|
|
||||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||||
@@ -86,7 +97,7 @@ test("loads required map layers while optional layer probes are still pending",
|
|||||||
|
|
||||||
async function mockRuntimeAndMap(
|
async function mockRuntimeAndMap(
|
||||||
page: Page,
|
page: Page,
|
||||||
featureCounts: { pumps: number; tanks: number },
|
featureCounts: Record<"valves" | "reservoirs" | "pumps" | "tanks", number>,
|
||||||
options: { probeGate?: Promise<void> } = {}
|
options: { probeGate?: Promise<void> } = {}
|
||||||
) {
|
) {
|
||||||
await page.route("**/runtime-config.js", async (route) => {
|
await page.route("**/runtime-config.js", async (route) => {
|
||||||
@@ -110,10 +121,13 @@ async function mockRuntimeAndMap(
|
|||||||
await page.route("https://scada-map.invalid/geoserver/tjwater_next/ows?**", async (route) => {
|
await page.route("https://scada-map.invalid/geoserver/tjwater_next/ows?**", async (route) => {
|
||||||
await options.probeGate;
|
await options.probeGate;
|
||||||
const typeNames = new URL(route.request().url()).searchParams.get("typeNames");
|
const typeNames = new URL(route.request().url()).searchParams.get("typeNames");
|
||||||
const sourceId = typeNames?.endsWith(":pumps") ? "pumps" : "tanks";
|
const sourceId = typeNames?.split(":").at(-1);
|
||||||
|
const featureCount = sourceId && sourceId in featureCounts
|
||||||
|
? featureCounts[sourceId as keyof typeof featureCounts]
|
||||||
|
: 0;
|
||||||
await route.fulfill({
|
await route.fulfill({
|
||||||
contentType: "application/xml",
|
contentType: "application/xml",
|
||||||
body: `<wfs:FeatureCollection numberMatched="${featureCounts[sourceId]}" numberReturned="0" />`
|
body: `<wfs:FeatureCollection numberMatched="${featureCount}" numberReturned="0" />`
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user