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 { BoatProfile } from "@watermaps/shared"; import { BoatProfilePanel } from "../src/components/BoatProfilePanel"; afterEach(cleanup); describe("BoatProfilePanel", () => { it("shows the current profile, all routing-relevant fields and cookie information", () => { renderPanel(); expect(screen.getByRole("heading", { name: "Aktuelles Boot" })).toBeVisible(); expect(screen.getByLabelText("Name des Bootes")).toHaveValue("Seestern"); expect(screen.getByLabelText("Kategorie")).toHaveValue("sailboat"); expect(screen.getByLabelText("Länge über alles")).toHaveValue(10.4); expect(screen.getByLabelText("Breite")).toHaveValue(3.3); expect( screen.getByLabelText("Durchfahrtshöhe ab Wasserlinie") ).toHaveValue(14.2); expect(screen.getByLabelText("Tiefgang")).toHaveValue(1.8); expect(screen.getByLabelText("Sicherheitsreserve")).toHaveValue(0.5); expect(screen.getByLabelText("Reisegeschwindigkeit")).toHaveValue(6); expect(screen.getByLabelText("Bugrolle über Wasserlinie")).toHaveValue(1.1); expect( screen.getByRole("group", { name: "Ankern" }) ).toHaveTextContent("von der Durchfahrtshöhe getrennt"); expect(screen.getByText(/in einem Cookie dieses Browsers gespeichert/)).toBeVisible(); }); it("validates, normalizes and saves the edited profile", () => { const onSave = vi.fn(() => true); renderPanel({ onSave, hasActiveRoute: true }); expect(screen.getByText(/Beim Speichern wird sie verworfen/)).toBeVisible(); fireEvent.change(screen.getByLabelText("Name des Bootes"), { target: { value: " Kleine Möwe " } }); fireEvent.change(screen.getByLabelText("Kategorie"), { target: { value: "motor-sailer" } }); fireEvent.change(screen.getByLabelText("Breite"), { target: { value: "3.6" } }); fireEvent.change(screen.getByLabelText("Bugrolle über Wasserlinie"), { target: { value: "1.25" } }); fireEvent.click( screen.getByRole("button", { name: "Bootsdaten speichern" }) ); expect(onSave).toHaveBeenCalledWith({ ...PROFILE, name: "Kleine Möwe", category: "motor-sailer", beamM: 3.6, bowRollerHeightM: 1.25 }); expect(screen.getByRole("status")).toHaveTextContent( "Die Bootsdaten wurden in diesem Browser gespeichert." ); }); it("reports invalid values without calling the persistence callback", () => { const onSave = vi.fn(() => true); renderPanel({ onSave }); fireEvent.change(screen.getByLabelText("Reisegeschwindigkeit"), { target: { value: "81" } }); fireEvent.submit( screen.getByRole("button", { name: "Bootsdaten speichern" }).closest("form")! ); expect(onSave).not.toHaveBeenCalled(); expect(screen.getByRole("alert")).toHaveTextContent( "Reisegeschwindigkeit muss zwischen mehr als 0 und 80 liegen." ); }); it("explains when the browser refuses to save the cookie", () => { renderPanel({ onSave: vi.fn(() => false) }); fireEvent.click( screen.getByRole("button", { name: "Bootsdaten speichern" }) ); expect(screen.getByRole("alert")).toHaveTextContent( "nicht im Cookie gespeichert" ); }); it("only offers deletion for stored data and requires inline confirmation", () => { const onDelete = vi.fn(() => true); const { rerender } = renderPanel({ stored: false, onDelete }); expect( screen.queryByRole("button", { name: "Bootsdaten löschen" }) ).not.toBeInTheDocument(); rerender( true)} onDelete={onDelete} /> ); fireEvent.click( screen.getByRole("button", { name: "Bootsdaten löschen" }) ); const confirmation = screen.getByRole("group", { name: "Löschen der Bootsdaten bestätigen" }); expect(onDelete).not.toHaveBeenCalled(); fireEvent.click( within(confirmation).getByRole("button", { name: "Abbrechen" }) ); expect( screen.queryByRole("group", { name: "Löschen der Bootsdaten bestätigen" }) ).not.toBeInTheDocument(); fireEvent.click( screen.getByRole("button", { name: "Bootsdaten löschen" }) ); fireEvent.click( screen.getByRole("button", { name: "Endgültig löschen" }) ); expect(onDelete).toHaveBeenCalledTimes(1); expect(screen.getByRole("status")).toHaveTextContent( "aus diesem Browser gelöscht" ); }); }); const PROFILE: BoatProfile = { name: "Seestern", category: "sailboat", lengthM: 10.4, beamM: 3.3, draughtM: 1.8, airDraftM: 14.2, safetyReserveM: 0.5, cruiseSpeedKn: 6, bowRollerHeightM: 1.1 }; function renderPanel( overrides: Partial[0]> = {} ) { const props: Parameters[0] = { profile: PROFILE, stored: true, onSave: vi.fn(() => true), onDelete: vi.fn(() => true), ...overrides }; return render(); }