Initial Watermaps import
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import { act, cleanup, fireEvent, render, screen } from "@testing-library/react";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { RouteResult } from "@watermaps/shared";
|
||||
import { VoyageNavigationTools } from "../src/components/VoyageNavigationTools";
|
||||
|
||||
const originalGeolocation = Object.getOwnPropertyDescriptor(navigator, "geolocation");
|
||||
const originalVibrate = Object.getOwnPropertyDescriptor(navigator, "vibrate");
|
||||
|
||||
beforeEach(() => localStorage.clear());
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
localStorage.clear();
|
||||
restoreNavigatorProperty("geolocation", originalGeolocation);
|
||||
restoreNavigatorProperty("vibrate", originalVibrate);
|
||||
});
|
||||
|
||||
describe("VoyageNavigationTools", () => {
|
||||
it("does not request geolocation before the skipper starts the alarm", () => {
|
||||
const watchPosition = vi.fn(() => 17);
|
||||
installGeolocation(watchPosition, vi.fn());
|
||||
|
||||
render(<VoyageNavigationTools route={routeFixture} />);
|
||||
|
||||
expect(watchPosition).not.toHaveBeenCalled();
|
||||
expect(screen.getByText(/GPS wird erst nach/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not open a second GPS watch while the course assistant is active", () => {
|
||||
const watchPosition = vi.fn(() => 17);
|
||||
installGeolocation(watchPosition, vi.fn());
|
||||
|
||||
render(<VoyageNavigationTools route={routeFixture} courseAssistantActive />);
|
||||
|
||||
expect(screen.getByRole("button", { name: "Kursalarm ist im Kursassistenten enthalten" })).toBeDisabled();
|
||||
expect(screen.getByText(/Querabweichung.*Kursassistenten/)).toBeVisible();
|
||||
expect(watchPosition).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("starts explicitly and warns when the vessel leaves the route", () => {
|
||||
let success: PositionCallback | undefined;
|
||||
const watchPosition = vi.fn((next: PositionCallback) => {
|
||||
success = next;
|
||||
return 42;
|
||||
});
|
||||
const clearWatch = vi.fn();
|
||||
const vibrate = vi.fn();
|
||||
installGeolocation(watchPosition, clearWatch);
|
||||
Object.defineProperty(navigator, "vibrate", { configurable: true, value: vibrate });
|
||||
|
||||
render(<VoyageNavigationTools route={routeFixture} />);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Abweichungsalarm starten" }));
|
||||
|
||||
expect(watchPosition).toHaveBeenCalledTimes(1);
|
||||
expect(screen.getByRole("button", { name: "Abweichungsalarm stoppen" })).toBeInTheDocument();
|
||||
|
||||
act(() => success?.(positionAt(53.01, 7.005, 5)));
|
||||
|
||||
expect(screen.getByRole("alert")).toHaveTextContent(/von der Route entfernt/);
|
||||
expect(vibrate).toHaveBeenCalledWith([200, 100, 200]);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Abweichungsalarm stoppen" }));
|
||||
expect(clearWatch).toHaveBeenCalledWith(42);
|
||||
});
|
||||
|
||||
it("saves and restores the route and plan on this device", () => {
|
||||
const onLoad = vi.fn();
|
||||
render(
|
||||
<VoyageNavigationTools
|
||||
route={routeFixture}
|
||||
plan={{
|
||||
vesselProfile: { draughtM: 1.4, safetyReserveM: 0.5, cruiseSpeedKn: 6 },
|
||||
departureAt: "2026-07-20T06:00:00.000Z"
|
||||
}}
|
||||
onLoadOfflineVoyage={onLoad}
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Route offline speichern" }));
|
||||
expect(screen.getByText(/ist auf diesem Gerät offline verfügbar/)).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole("button", { name: "Offline-Route laden" }));
|
||||
|
||||
expect(onLoad).toHaveBeenCalledTimes(1);
|
||||
expect(onLoad.mock.calls[0]?.[0].route.geometry).toEqual(routeFixture.geometry);
|
||||
expect(onLoad.mock.calls[0]?.[0].plan.vesselProfile.cruiseSpeedKn).toBe(6);
|
||||
});
|
||||
});
|
||||
|
||||
function installGeolocation(watchPosition: typeof navigator.geolocation.watchPosition, clearWatch: typeof navigator.geolocation.clearWatch) {
|
||||
Object.defineProperty(navigator, "geolocation", {
|
||||
configurable: true,
|
||||
value: { watchPosition, clearWatch, getCurrentPosition: vi.fn() }
|
||||
});
|
||||
}
|
||||
|
||||
function restoreNavigatorProperty(name: "geolocation" | "vibrate", descriptor: PropertyDescriptor | undefined) {
|
||||
if (descriptor) {
|
||||
Object.defineProperty(navigator, name, descriptor);
|
||||
} else {
|
||||
Reflect.deleteProperty(navigator, name);
|
||||
}
|
||||
}
|
||||
|
||||
function positionAt(lat: number, lon: number, accuracy: number): GeolocationPosition {
|
||||
return {
|
||||
coords: {
|
||||
latitude: lat,
|
||||
longitude: lon,
|
||||
accuracy,
|
||||
altitude: null,
|
||||
altitudeAccuracy: null,
|
||||
heading: null,
|
||||
speed: null,
|
||||
toJSON: () => ({})
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
toJSON: () => ({})
|
||||
};
|
||||
}
|
||||
|
||||
const routeFixture: RouteResult = {
|
||||
id: "test-route",
|
||||
name: "Testfahrt",
|
||||
geometry: { type: "LineString", coordinates: [[7, 53], [7.01, 53]] },
|
||||
distanceNm: 0.4,
|
||||
eta: null,
|
||||
warnings: [],
|
||||
minKnownDepthM: null,
|
||||
unknownDepthRatio: 1,
|
||||
dataSources: ["Test"],
|
||||
routingMode: "fairway"
|
||||
};
|
||||
Reference in New Issue
Block a user