import { describe, expect, it } from "vitest"; import type { MarineForecast, RouteResult } from "@watermaps/shared"; import { createRouteWeatherReport, summarizeRouteWeather } from "../src/routeWeatherReport"; describe("route weather report", () => { it("samples start, middle and destination forecasts", async () => { const requested: Array<{ lat: number; lon: number }> = []; const report = await createRouteWeatherReport(routeFixture, async (coordinate) => { requested.push(coordinate); return forecast({ windSpeed: coordinate.lon > 7.1 ? 18 : 10, waveHeightM: coordinate.lon > 7.1 ? 1.2 : 0.4 }); }); expect(requested).toHaveLength(3); expect(report.maxWindSpeedKn).toBe(18); expect(report.maxWaveHeightM).toBe(1.2); expect(report.severity).toBe("caution"); expect(report.summary).toContain("Aufmerksam fahren"); expect(report.bridgeReport).toBeNull(); }); it("projects forecasts along the route timeline and adjusts ETA for along-route current", async () => { const requestedTimes: Array = []; const departureTime = "2026-07-20T06:00:00.000Z"; const timedRoute: RouteResult = { ...routeFixture, geometry: { type: "LineString", coordinates: [[7, 52], [7.2, 52], [7.4, 52]] }, distanceNm: 20, departureTime, durationMinutes: 240, eta: "2026-07-20T10:00:00.000Z" }; const report = await createRouteWeatherReport( timedRoute, { draughtM: 1.4, safetyReserveM: 0.5, cruiseSpeedKn: 5 }, async (_coordinate, at) => { requestedTimes.push(at); return forecast({ oceanCurrentSpeedKn: 1, oceanCurrentDirectionDeg: 90, forecastTime: at }); } ); expect(requestedTimes).toEqual([ "2026-07-20T06:00:00.000Z", "2026-07-20T08:00:00.000Z", "2026-07-20T10:00:00.000Z" ]); expect(report.samples.map((sample) => sample.plannedTime)).toEqual(requestedTimes); expect(report.samples.map((sample) => sample.currentAlongRouteKn)).toEqual([1, 1, 1]); expect(report.departureTime).toBe(departureTime); expect(report.averageAlongRouteCurrentKn).toBe(1); expect(report.currentAdjustmentMinutes).toBe(-40); expect(report.adjustedEta).toBe("2026-07-20T09:20:00.000Z"); }); it("adds a bridge report and marks a route as blocked by a low bridge", async () => { const report = await createRouteWeatherReport( routeFixture, { draughtM: 1.4, safetyReserveM: 0.5, airDraftM: 3 }, async () => forecast({}), async (params) => { expect(params.layers).toEqual(["bridges"]); return { type: "FeatureCollection", features: [ { type: "Feature", id: "bridge-low", properties: { layer: "bridges", name: "Niedrige Brücke", clearance_m: 2.7, clearance_label: "H 2.7 m", label: "Niedrige Brücke H 2.7 m", source: "OSM/Geofabrik" }, geometry: { type: "LineString", coordinates: [ [6.99, 53.5], [7.21, 53.5] ] } }, { type: "Feature", id: "bridge-high", properties: { layer: "bridges", name: "Hohe Brücke", clearance_m: 4.2, clearance_label: "H 4.2 m", label: "Hohe Brücke H 4.2 m", source: "OSM/Geofabrik" }, geometry: { type: "LineString", coordinates: [ [7.18, 53.68], [7.22, 53.68] ] } } ] }; } ); expect(report.bridgeReport?.severity).toBe("critical"); expect(report.bridgeReport?.tooLowCount).toBe(1); expect(report.bridgeReport?.checkedCount).toBe(2); expect(report.bridgeReport?.summary).toContain("Nicht passierbar"); expect(report.bridgeReport?.bridges[0]?.name).toBe("Niedrige Brücke"); }); it("marks critical weather when wind or wave thresholds are exceeded", () => { const report = summarizeRouteWeather([ { label: "Mitte", coordinate: { lat: 53.5, lon: 7.1 }, forecast: forecast({ windSpeed: 28, waveHeightM: 1.1 }) } ]); expect(report.severity).toBe("critical"); expect(report.summary).toContain("Kritische Bedingungen"); }); }); const routeFixture: RouteResult = { geometry: { type: "LineString", coordinates: [ [7, 53.3], [7.1, 53.5], [7.2, 53.7] ] }, distanceNm: 25, eta: null, warnings: [], minKnownDepthM: null, unknownDepthRatio: 1, dataSources: [], routingMode: "fairway" }; function forecast(overrides: Partial): MarineForecast { return { waveHeightM: 0.4, waveDirectionDeg: 280, wavePeriodS: 4, windSpeed: 10, windDirectionDeg: 260, weatherCode: 2, temperatureC: 18, source: "Test", updatedAt: "2026-07-13T12:00:00.000Z", ...overrides }; }