optimized events
Test and publish container images / test (push) Successful in 2m26s
Test and publish container images / publish (push) Failing after 3s

This commit is contained in:
BuTzZ
2026-07-28 14:36:47 +02:00
parent b98ec8bc6f
commit 593dbd5f85
42 changed files with 2405 additions and 283 deletions
@@ -0,0 +1,199 @@
import { cleanup, renderHook, waitFor } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { RouteResult } from "@watermaps/shared";
import { useRouteEventFeatures } from "../src/hooks/useRouteEventFeatures";
const apiMocks = vi.hoisted(() => ({
getMapFeatures: vi.fn()
}));
vi.mock("../src/api", () => ({
getMapFeatures: apiMocks.getMapFeatures
}));
beforeEach(() => {
apiMocks.getMapFeatures.mockReset();
});
afterEach(cleanup);
describe("useRouteEventFeatures", () => {
it("loads harbours, locks and bridges once and independently", async () => {
apiMocks.getMapFeatures.mockImplementation(
async ({ layers }: { layers: string[] }) =>
featureCollection(layers[0]!)
);
const activeRoute = route("first", 0);
const { result } = renderHook(() =>
useRouteEventFeatures(activeRoute, { airDraftM: 3 })
);
await waitFor(() => expect(result.current.loading).toBe(false));
expect(apiMocks.getMapFeatures).toHaveBeenCalledTimes(3);
expect(
apiMocks.getMapFeatures.mock.calls.map(([request]) => request.layers)
).toEqual(expect.arrayContaining([["harbours"], ["locks"], ["bridges"]]));
expect(result.current.harbours.map((harbour) => harbour.name)).toEqual([
"Testhafen"
]);
expect(result.current.locks.map((lock) => lock.name)).toEqual([
"Testschleuse"
]);
expect(result.current.bridgeReport?.bridges.map((bridge) => bridge.name)).toEqual([
"Testbrücke"
]);
expect(result.current.error).toBeNull();
});
it("keeps working categories when one feature request fails", async () => {
apiMocks.getMapFeatures.mockImplementation(
async ({ layers }: { layers: string[] }) => {
if (layers[0] === "harbours") {
throw new Error("Feature-Datenbank fehlt");
}
return featureCollection(layers[0]!);
}
);
const activeRoute = route("partial", 0);
const { result } = renderHook(() =>
useRouteEventFeatures(activeRoute, { airDraftM: 3 })
);
await waitFor(() => expect(result.current.loading).toBe(false));
expect(result.current.harbours).toEqual([]);
expect(result.current.locks).toHaveLength(1);
expect(result.current.bridgeReport?.bridges).toHaveLength(1);
expect(result.current.errors.harbours).toContain(
"Häfen nicht erreichbar: Feature-Datenbank fehlt"
);
expect(result.current.errors.locks).toBeNull();
expect(result.current.errors.bridges).toBeNull();
});
it("reassesses only bridges when the boat height changes", async () => {
apiMocks.getMapFeatures.mockImplementation(
async ({ layers }: { layers: string[] }) =>
featureCollection(layers[0]!)
);
const activeRoute = route("height", 0);
const { result, rerender } = renderHook(
({ airDraftM }) =>
useRouteEventFeatures(activeRoute, { airDraftM }),
{ initialProps: { airDraftM: 3 } }
);
await waitFor(() => expect(result.current.loading).toBe(false));
apiMocks.getMapFeatures.mockClear();
rerender({ airDraftM: 3.5 });
await waitFor(() => expect(result.current.loading).toBe(false));
expect(apiMocks.getMapFeatures).toHaveBeenCalledTimes(1);
expect(apiMocks.getMapFeatures).toHaveBeenCalledWith(
expect.objectContaining({ layers: ["bridges"] })
);
expect(result.current.harbours).toHaveLength(1);
expect(result.current.locks).toHaveLength(1);
expect(result.current.bridgeReport?.requiredAirDraftM).toBe(3.5);
});
it("aborts stale category requests on a route change and exposes only the new route", async () => {
const staleSignals: AbortSignal[] = [];
const currentSignals: AbortSignal[] = [];
let staleCalls = 0;
apiMocks.getMapFeatures.mockImplementation(
({ layers, signal }: { layers: string[]; signal: AbortSignal }) => {
if (staleCalls < 3) {
staleCalls += 1;
staleSignals.push(signal);
return new Promise(() => undefined);
}
currentSignals.push(signal);
return Promise.resolve(featureCollection(layers[0]!, 0.5));
}
);
const firstRoute = route("first", 0);
const secondRoute = route("second", 0.5);
const { result, rerender, unmount } = renderHook(
({ activeRoute }) =>
useRouteEventFeatures(activeRoute, { airDraftM: 3 }),
{ initialProps: { activeRoute: firstRoute } }
);
await waitFor(() => expect(apiMocks.getMapFeatures).toHaveBeenCalledTimes(3));
rerender({ activeRoute: secondRoute });
await waitFor(() => expect(result.current.loading).toBe(false));
expect(apiMocks.getMapFeatures).toHaveBeenCalledTimes(6);
expect(staleSignals).toHaveLength(3);
expect(staleSignals.every((signal) => signal.aborted)).toBe(true);
expect(result.current.harbours[0]?.coordinate.lon).toBeCloseTo(0.7);
expect(result.current.locks[0]?.coordinate.lon).toBeCloseTo(0.8);
expect(result.current.bridgeReport?.bridges[0]?.coordinate.lon).toBeCloseTo(0.9);
unmount();
expect(currentSignals).toHaveLength(3);
expect(currentSignals.every((signal) => signal.aborted)).toBe(true);
});
});
function featureCollection(layer: string, offset = 0) {
const fixtures = {
harbours: {
type: "Feature" as const,
id: `harbour-${offset}`,
properties: { layer: "harbours", name: "Testhafen" },
geometry: {
type: "Point" as const,
coordinates: [0.2 + offset, 0]
}
},
locks: {
type: "Feature" as const,
id: `lock-${offset}`,
properties: { layer: "locks", name: "Testschleuse" },
geometry: {
type: "Point" as const,
coordinates: [0.3 + offset, 0]
}
},
bridges: {
type: "Feature" as const,
id: `bridge-${offset}`,
properties: {
layer: "bridges",
name: "Testbrücke",
clearance_m: 4
},
geometry: {
type: "Point" as const,
coordinates: [0.4 + offset, 0]
}
}
};
return {
type: "FeatureCollection" as const,
features: [fixtures[layer as keyof typeof fixtures]]
};
}
function route(id: string, offset: number): RouteResult {
return {
id,
geometry: {
type: "LineString",
coordinates: [
[offset, 0],
[offset + 1, 0]
]
},
distanceNm: 60,
eta: null,
warnings: [],
dataSources: ["test"],
routingMode: "fairway"
};
}