Initial Watermaps import
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import { cleanup, fireEvent, render, screen, within } from "@testing-library/react";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { UpcomingRouteEvent } from "../src/routeEvents";
|
||||
import { UpcomingEventsPanel } from "../src/components/UpcomingEventsPanel";
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe("UpcomingEventsPanel", () => {
|
||||
it("shows the nearest event first, preserves chronological list order and filters by kind", () => {
|
||||
render(<UpcomingEventsPanel events={events} hasRoute />);
|
||||
|
||||
const hero = screen.getByText("Nächstes Ereignis").closest("article");
|
||||
expect(hero).not.toBeNull();
|
||||
expect(within(hero!).getByText("Schleuse Nah")).toBeVisible();
|
||||
expect(within(hero!).getByText("3,0 sm")).toBeVisible();
|
||||
expect(within(hero!).getByText(/ETA/)).toBeVisible();
|
||||
|
||||
const list = screen.getByRole("list");
|
||||
expect(
|
||||
within(list)
|
||||
.getAllByRole("button")
|
||||
.map((button) => button.textContent)
|
||||
).toEqual([
|
||||
expect.stringContaining("Schleuse Nah"),
|
||||
expect.stringContaining("Brücke Mitte"),
|
||||
expect.stringContaining("Hafen Weit")
|
||||
]);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /Brücken/ }));
|
||||
expect(screen.getByRole("button", { name: /Brücken/ })).toHaveAttribute(
|
||||
"aria-pressed",
|
||||
"true"
|
||||
);
|
||||
expect(screen.getAllByText("Brücke Mitte")).toHaveLength(2);
|
||||
expect(screen.queryByText("Schleuse Nah")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Hafen Weit")).not.toBeInTheDocument();
|
||||
expect(within(screen.getByRole("list")).getAllByRole("button")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("opens a lock drilldown with direct contact, VHF, hours and map actions", () => {
|
||||
const onShowOnMap = vi.fn();
|
||||
render(
|
||||
<UpcomingEventsPanel
|
||||
events={events}
|
||||
hasRoute
|
||||
onShowOnMap={onShowOnMap}
|
||||
/>
|
||||
);
|
||||
|
||||
const hero = screen.getByText("Nächstes Ereignis").closest("article");
|
||||
fireEvent.click(within(hero!).getByRole("button", { name: /Schleuse Nah/ }));
|
||||
|
||||
const detail = screen.getByRole("region", { name: "Schleuse Nah" });
|
||||
expect(within(detail).getByText("Mo–Fr 08:00–18:00")).toBeVisible();
|
||||
expect(within(detail).getByText("Kanal 12")).toBeVisible();
|
||||
expect(within(detail).getByRole("link", { name: "Schleuse Nah anrufen" })).toHaveAttribute(
|
||||
"href",
|
||||
"tel:+4949123456"
|
||||
);
|
||||
expect(
|
||||
within(detail).getByRole("link", { name: "Website von Schleuse Nah öffnen" })
|
||||
).toHaveAttribute("href", "https://lock.example/");
|
||||
|
||||
fireEvent.click(within(detail).getByRole("button", { name: "Auf Karte zeigen" }));
|
||||
expect(onShowOnMap).toHaveBeenCalledWith(events[0]);
|
||||
|
||||
fireEvent.click(within(detail).getByRole("button", { name: "Zurück zur Ereignisliste" }));
|
||||
expect(screen.getByText("Nächstes Ereignis")).toBeVisible();
|
||||
});
|
||||
|
||||
it("shows bridge clearance, required height and reserve in its drilldown", () => {
|
||||
render(<UpcomingEventsPanel events={events} hasRoute />);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /Brücken/ }));
|
||||
const hero = screen.getByText("Nächstes Ereignis").closest("article");
|
||||
fireEvent.click(within(hero!).getByRole("button", { name: /Brücke Mitte/ }));
|
||||
|
||||
const detail = screen.getByRole("region", { name: "Brücke Mitte" });
|
||||
expect(within(detail).getByText("H 4,2 m")).toBeVisible();
|
||||
expect(within(detail).getByText("3.8 m")).toBeVisible();
|
||||
const reserve = within(detail).getByText("Reserve").closest("div");
|
||||
expect(reserve).toHaveTextContent("0.4 m Reserve");
|
||||
expect(within(detail).queryByRole("button", { name: "Auf Karte zeigen" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
props: { hasRoute: false, events: [] as UpcomingRouteEvent[] },
|
||||
text: "Noch keine Route",
|
||||
role: undefined
|
||||
},
|
||||
{
|
||||
props: { hasRoute: true, events: [] as UpcomingRouteEvent[], loading: true },
|
||||
text: "Ereignisse werden geladen",
|
||||
role: "status"
|
||||
},
|
||||
{
|
||||
props: {
|
||||
hasRoute: true,
|
||||
events: [] as UpcomingRouteEvent[],
|
||||
error: "Datenquelle antwortet nicht"
|
||||
},
|
||||
text: "Ereignisse nicht erreichbar",
|
||||
role: "alert"
|
||||
},
|
||||
{
|
||||
props: { hasRoute: true, events: [] as UpcomingRouteEvent[] },
|
||||
text: "Keine bevorstehenden Ereignisse",
|
||||
role: undefined
|
||||
}
|
||||
])("renders the state '$text'", ({ props, text, role }) => {
|
||||
render(<UpcomingEventsPanel {...props} />);
|
||||
|
||||
expect(screen.getByText(text)).toBeVisible();
|
||||
if (role) {
|
||||
expect(screen.getByRole(role)).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const events: UpcomingRouteEvent[] = [
|
||||
{
|
||||
kind: "lock",
|
||||
id: "lock-near",
|
||||
name: "Schleuse Nah",
|
||||
coordinate: { lat: 53.1, lon: 7.1 },
|
||||
routeDistanceNm: 8,
|
||||
distanceFromRouteNm: 0.02,
|
||||
remainingNm: 3,
|
||||
eta: eta("2026-07-23T12:30:00.000Z", 30),
|
||||
feature: {
|
||||
id: "lock-near",
|
||||
name: "Schleuse Nah",
|
||||
coordinate: { lat: 53.1, lon: 7.1 },
|
||||
routeDistanceNm: 8,
|
||||
distanceFromRouteNm: 0.02,
|
||||
openingHours: "Mo–Fr 08:00–18:00",
|
||||
phone: "+49 49 123456",
|
||||
vhf: "Kanal 12",
|
||||
website: "lock.example"
|
||||
}
|
||||
},
|
||||
{
|
||||
kind: "harbour",
|
||||
id: "harbour-far",
|
||||
name: "Hafen Weit",
|
||||
coordinate: { lat: 53.3, lon: 7.3 },
|
||||
routeDistanceNm: 17,
|
||||
distanceFromRouteNm: 0.4,
|
||||
remainingNm: 12,
|
||||
eta: eta("2026-07-23T14:00:00.000Z", 120),
|
||||
feature: {
|
||||
id: "harbour-far",
|
||||
name: "Hafen Weit",
|
||||
coordinate: { lat: 53.3, lon: 7.3 },
|
||||
kind: "marina",
|
||||
amenities: { water: "available", electricity: true },
|
||||
phone: null,
|
||||
website: null
|
||||
}
|
||||
},
|
||||
{
|
||||
kind: "bridge",
|
||||
id: "bridge-middle",
|
||||
name: "Brücke Mitte",
|
||||
coordinate: { lat: 53.2, lon: 7.2 },
|
||||
routeDistanceNm: 11,
|
||||
distanceFromRouteNm: 0.01,
|
||||
remainingNm: 6,
|
||||
eta: eta("2026-07-23T13:00:00.000Z", 60),
|
||||
feature: {
|
||||
id: "bridge-middle",
|
||||
name: "Brücke Mitte",
|
||||
label: "Brücke Mitte H 4,2 m",
|
||||
coordinate: { lat: 53.2, lon: 7.2 },
|
||||
distanceNm: 0.01,
|
||||
clearanceM: 4.2,
|
||||
clearanceLabel: "H 4,2 m",
|
||||
requiredAirDraftM: 3.8,
|
||||
marginM: 0.4,
|
||||
status: "tight",
|
||||
source: "Test"
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
function eta(estimatedAt: string, minutesFromProgress: number) {
|
||||
return {
|
||||
estimatedAt,
|
||||
minutesFromProgress,
|
||||
speedKn: 6,
|
||||
speedSource: "vessel-cruise-speed" as const,
|
||||
referenceTime: "2026-07-23T12:00:00.000Z",
|
||||
referenceSource: "current-time" as const
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user