Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions frontend/e2e-local/priwa-field-write-flows.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,20 +94,42 @@ test.describe("PRIWA local field write flows", () => {
).toHaveCount(0);

await expect(
page.getByRole("button", { name: "Zu Karte wechseln" }),
page.getByRole("button", { name: "Kartenebenen öffnen" }),
).toBeVisible();
await expect(
page.getByRole("button", { name: "Baumliste öffnen" }),
).toBeVisible();
await expect(
page.getByRole("button", { name: "Punkt aufnehmen" }),
page.getByRole("button", { name: "Käferbaum aufnehmen" }),
).toBeVisible();
await expect(
page.getByRole("button", { name: "Aktuelle Position aktivieren" }),
).toBeVisible();
await expect(
page.getByRole("button", { name: "Zu Karte wechseln" }),
).toHaveCount(0);
await expect(
page.getByRole("navigation", { name: "PRIWA Feldaktionen" }),
).toHaveCount(0);
await expectPersistedOfflineAreaVisualization(page);

await page.getByRole("button", { name: "Punkt aufnehmen" }).click();
const addPointButton = page.getByRole("button", {
name: "Käferbaum aufnehmen",
});
const locateButton = page.getByRole("button", {
name: "Aktuelle Position aktivieren",
});
const [addPointBox, locateBox] = await Promise.all([
addPointButton.boundingBox(),
locateButton.boundingBox(),
]);
expect(addPointBox).not.toBeNull();
expect(locateBox).not.toBeNull();
expect(addPointBox!.width).toBe(locateBox!.width);
expect(addPointBox!.height).toBe(locateBox!.height);
expect(addPointBox!.y).toBeLessThan(locateBox!.y);

await addPointButton.click();
const captureDrawer = page.locator(
".priwa-point-drawer-root .ant-drawer-content-wrapper",
);
Expand Down Expand Up @@ -379,6 +401,9 @@ async function expectOfflineBasemapControl(page: Page) {
page.getByRole("button", { name: "Bereich herunterladen" }),
).toBeVisible();
await page.getByRole("button", { name: "Abbrechen" }).click();
await expect(offlineMapButton).toHaveAttribute("aria-pressed", "true");
await offlineMapButton.click();
await expect(offlineMapButton).toHaveAttribute("aria-pressed", "false");
}

async function expectPersistedOfflineAreaVisualization(page: Page) {
Expand Down Expand Up @@ -480,6 +505,12 @@ async function expectOfflineSelectionSuppressesPointInteraction(page: Page) {
page.locator('[data-priwa-offline-selection-frame="true"]'),
).toBeVisible();
await page.getByRole("button", { name: "Abbrechen" }).click();
const offlineMapButton = page.getByRole("button", {
name: /Offline-Karte laden/,
});
await expect(offlineMapButton).toHaveAttribute("aria-pressed", "true");
await offlineMapButton.click();
await expect(offlineMapButton).toHaveAttribute("aria-pressed", "false");
}

async function expectResizableDesktopPointTable(page: Page) {
Expand Down
127 changes: 122 additions & 5 deletions frontend/e2e-local/priwa-warnkarte-local.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,19 @@ const polygon = {
};

async function installWarnkarteAdmin(page: Page) {
await installLocalSession(page, {
const session = await installLocalSession(page, {
user: admin,
supabaseUrl: localSupabaseUrl,
refreshToken: "priwa-warnkarte-refresh-token",
});

await page.route(`${localSupabaseUrl}/rest/v1/**`, fulfillSupabaseRequest);
// Keep the mocked journey independent from the worktree's generated
// Supabase endpoint. The application and test runner can legitimately read
// that endpoint from different env files.
await page.route("**/auth/v1/user", (route) =>
route.fulfill({ contentType: "application/json", json: session.user }),
);
await page.route("**/rest/v1/**", fulfillSupabaseRequest);
}

async function fulfillSupabaseRequest(route: Route) {
Expand Down Expand Up @@ -289,7 +295,9 @@ test.describe("PRIWA Warnkarte local UI", () => {
await installWarnkarteApi(page);
await page.goto("/priwa-field");

await expect(page.locator("[data-priwa-review-queue-panel]")).toHaveCount(0);
await expect(page.locator("[data-priwa-review-queue-panel]")).toHaveCount(
0,
);
await expect(
page.getByRole("button", { name: "Warnkarte verwalten" }),
).toHaveCount(0);
Expand All @@ -301,6 +309,7 @@ test.describe("PRIWA Warnkarte local UI", () => {
});
await expect(page.getByText("Käferbaum bearbeiten")).toBeVisible();
await page.getByRole("button", { name: "Close" }).click();
await page.getByRole("button", { name: "Kartenebenen öffnen" }).click();
await page.getByRole("button", { name: "Zur Warnkarte zoomen" }).click();
await page.waitForTimeout(600);
await page.getByTestId("priwa-field-map").click({
Expand Down Expand Up @@ -580,8 +589,17 @@ test.describe("PRIWA Warnkarte local UI", () => {
});
await page.route("**/priwa/warnkarte/versions?*", unavailable);
await page.route("**/priwa/warnkarte/validate", unavailable);
await page.route("**/priwa/warnkarte/active?*", unavailable);
await page.goto("/priwa-field");

const loadError = page.getByText("Warnkarte konnte nicht geladen werden", {
exact: true,
});
await expect(loadError).toHaveCount(0);
await page.getByRole("button", { name: "Warnkarte einblenden" }).click();
await expect(loadError).toBeVisible();
await expect(loadError).toBeHidden({ timeout: 5_000 });

await page.getByRole("button", { name: "Warnkarte verwalten" }).click();
const explanation =
"Die Warnkarten-Funktion ist in dieser Umgebung noch nicht verfügbar. Die Datei wurde nicht validiert.";
Expand Down Expand Up @@ -614,6 +632,40 @@ test.describe("PRIWA Warnkarte local UI", () => {
).toHaveCount(0);
await expect(page.locator(".dt-map-zoom-control")).toBeHidden();
await expect(page.locator(".dt-map-scale-control")).toBeVisible();
await page.getByRole("button", { name: "Kartenebenen öffnen" }).click();
await page.getByRole("button", { name: "Baumliste öffnen" }).click();
await expect(page.getByLabel("Kartenebenen", { exact: true })).toHaveCount(
0,
);
await expect(
page.getByText("Käferbäume (1)", { exact: true }),
).toBeVisible();
await page.keyboard.press("Escape");
await page.getByRole("button", { name: "Kartenebenen öffnen" }).click();
const layerSheet = page.getByLabel("Kartenebenen", { exact: true });
const layerSheetBox = await layerSheet.boundingBox();
expect(layerSheetBox).not.toBeNull();
expect(layerSheetBox!.height / 852).toBeGreaterThan(0.22);
expect(layerSheetBox!.height / 852).toBeLessThan(0.36);
const layerSheetHeaderBox = await layerSheet
.locator("header")
.boundingBox();
expect(layerSheetHeaderBox).not.toBeNull();
await page.mouse.move(
layerSheetHeaderBox!.x + layerSheetHeaderBox!.width / 2,
layerSheetHeaderBox!.y + 12,
);
await page.mouse.down();
await page.mouse.move(
layerSheetHeaderBox!.x + layerSheetHeaderBox!.width / 2,
layerSheetHeaderBox!.y - 280,
{ steps: 4 },
);
await page.mouse.up();
await expect(layerSheet).toHaveAttribute(
"data-mobile-bottom-sheet-snap",
"expanded",
);
await page.getByRole("button", { name: "Zur Warnkarte zoomen" }).click();
await page.waitForTimeout(600);
await page.getByTestId("priwa-field-map").click({
Expand All @@ -622,14 +674,79 @@ test.describe("PRIWA Warnkarte local UI", () => {
await expect(page.locator(".priwa-warnkarte-tooltip")).toContainText(
"Wahrscheinlichkeit: 60 %",
);
await page.getByRole("button", { name: "Warnkarte ausblenden" }).click();
await page.getByRole("button", { name: "Kartenebenen öffnen" }).click();
await page.getByRole("switch", { name: "Warnkarte ausblenden" }).click();
await expect(page.getByTestId("priwa-warnkarte-legend")).toHaveCount(0);
await expect(
page.getByRole("button", { name: "Zur Warnkarte zoomen" }),
).toHaveCount(0);
await expect(
page.getByRole("button", { name: "Warnkarte einblenden" }),
page.getByRole("switch", { name: "Warnkarte einblenden" }),
).toBeVisible();
await expect(page.getByTestId("priwa-field-map")).toBeVisible();
await page.getByRole("button", { name: "Kartenebenen schließen" }).click();
await page.getByRole("button", { name: /Offline-Karten öffnen/ }).click();
await expect(
page.getByLabel("Offline-Karten", { exact: true }),
).toBeVisible();
await page.getByRole("button", { name: "Neuen Bereich auswählen" }).click();
const selectionFrame = page.locator(
'[data-priwa-offline-selection-frame="true"]',
);
const selectionSheet = page.getByLabel("Offline-Bereich auswählen", {
exact: true,
});
const [selectionFrameBox, selectionSheetBox] = await Promise.all([
selectionFrame.boundingBox(),
selectionSheet.boundingBox(),
]);
expect(selectionFrameBox).not.toBeNull();
expect(selectionSheetBox).not.toBeNull();
expect(
Math.abs(selectionFrameBox!.width - selectionFrameBox!.height),
).toBeLessThan(2);
expect(selectionFrameBox!.y + selectionFrameBox!.height).toBeLessThan(
selectionSheetBox!.y,
);
const [selectionTitleBox, selectionCloseBox] = await Promise.all([
page
.getByRole("heading", { name: "Offline-Bereich auswählen" })
.boundingBox(),
page
.getByRole("button", { name: "Bereichsauswahl schließen" })
.boundingBox(),
]);
expect(selectionTitleBox).not.toBeNull();
expect(selectionCloseBox).not.toBeNull();
expect(selectionTitleBox!.x).toBeGreaterThanOrEqual(12);
expect(selectionCloseBox!.x + selectionCloseBox!.width).toBeLessThanOrEqual(
381,
);
});

test("mobile only reports an unavailable Warnkarte after activation", async ({
page,
}) => {
await page.setViewportSize({ width: 393, height: 852 });
await installWarnkarteAdmin(page);
await page.route("**/priwa/warnkarte/active?*", (route) =>
route.fulfill({
status: 503,
contentType: "application/json",
json: { detail: "Offline" },
}),
);
await page.goto("/priwa-field");
await page.getByRole("button", { name: "Accept" }).click();

const errorMessage = page.getByText(
"Warnkarte konnte nicht geladen werden",
{ exact: true },
);
await expect(errorMessage).toHaveCount(0);
await page.getByRole("button", { name: "Kartenebenen öffnen" }).click();
await page.getByRole("switch", { name: "Warnkarte einblenden" }).click();
await expect(errorMessage).toBeVisible();
await expect(errorMessage).toBeHidden({ timeout: 5_000 });
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
LoginOutlined,
} from "@ant-design/icons";

import { mobileMapThumbnails } from "./mobileMapThumbnails";
import { mobileMapThumbnails } from "../../MapControls/mobile/mobileMapThumbnails";

interface MobileAnalysisDrawerProps {
open: boolean;
Expand Down
29 changes: 15 additions & 14 deletions frontend/src/components/DeadwoodMap/mobile/MobileLayerDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { Button, Slider } from "antd";
import { DownloadOutlined } from "@ant-design/icons";

import { mapColors } from "../../../theme/mapColors";
import MobileLayerRow from "./MobileLayerRow";
import MobileLayerTile from "./MobileLayerTile";
import MobileBottomSheet from "./MobileBottomSheet";
import { mobileMapThumbnails } from "./mobileMapThumbnails";
import MobileLayerRow from "../../MapControls/mobile/MobileLayerRow";
import MobileLayerTile from "../../MapControls/mobile/MobileLayerTile";
import MobileBottomSheet from "../../MapControls/mobile/MobileBottomSheet";
import MobileMapSectionHeading from "../../MapControls/mobile/MobileMapSectionHeading";
import { mobileMapThumbnails } from "../../MapControls/mobile/mobileMapThumbnails";

interface MobileLayerDrawerProps {
open: boolean;
Expand Down Expand Up @@ -37,12 +38,6 @@ const baseMapOptions = [
},
];

const SectionHeading = ({ children }: { children: string }) => (
<div className="mb-2.5 text-xs font-semibold uppercase tracking-[0.08em] text-slate-500">
{children}
</div>
);

const MobileLayerDrawer = ({
open,
mapStyle,
Expand All @@ -62,10 +57,16 @@ const MobileLayerDrawer = ({
const modelLayersVisible = showForest || showDeadwood;

return (
<MobileBottomSheet open={open} onClose={onClose} title="Layers">
<MobileBottomSheet
open={open}
onClose={onClose}
title="Layers"
initialSnap="compact"
compactRatio={0.36}
>
<div className="space-y-5">
<section>
<SectionHeading>Map style</SectionHeading>
<MobileMapSectionHeading>Map style</MobileMapSectionHeading>
<div className="grid grid-cols-2 gap-3">
{baseMapOptions.map((option) => (
<MobileLayerTile
Expand All @@ -80,7 +81,7 @@ const MobileLayerDrawer = ({
</section>

<section>
<SectionHeading>Map layers</SectionHeading>
<MobileMapSectionHeading>Map layers</MobileMapSectionHeading>
<div className="space-y-2.5">
<MobileLayerRow
thumb={mobileMapThumbnails.treeCover}
Expand Down Expand Up @@ -134,7 +135,7 @@ const MobileLayerDrawer = ({
</section>

<section>
<SectionHeading>Feedback</SectionHeading>
<MobileMapSectionHeading>Feedback</MobileMapSectionHeading>
<div className="space-y-2.5">
<MobileLayerRow
thumb={mobileMapThumbnails.communityPoints}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
EnvironmentOutlined,
LoadingOutlined,
} from "@ant-design/icons";
import MapLayersIcon from "../../MapControls/mobile/MapLayersIcon";

export type MobileMapPanel = "layers" | "analysis" | "time";

Expand All @@ -21,37 +22,6 @@ interface MobileMapControlsProps {
const controlButtonClass =
"pointer-events-auto !flex !h-12 !w-12 !min-w-12 !items-center !justify-center border-white/80 bg-white/95 shadow-lg shadow-slate-900/15 backdrop-blur-md";

const MapLayersIcon = () => (
<svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
aria-hidden="true"
>
<path
d="M12 3.75 3.75 8.1 12 12.45l8.25-4.35L12 3.75Z"
stroke="currentColor"
strokeWidth="1.8"
strokeLinejoin="round"
/>
<path
d="m5.75 11.15-2 1.05L12 16.55l8.25-4.35-2-1.05"
stroke="currentColor"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="m5.75 15.2-2 1.05L12 20.6l8.25-4.35-2-1.05"
stroke="currentColor"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);

const MobileMapControls = ({
activePanel,
hidden = false,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { WaybackItemWithMetadata } from "../../../hooks/useWaybackItems";
import MobileBottomSheet from "./MobileBottomSheet";
import MobileBottomSheet from "../../MapControls/mobile/MobileBottomSheet";
import MobileTimeCard from "./MobileTimeCard";

interface MobileTimeDrawerProps {
Expand Down
Loading
Loading