Config UI: mobile optimized section navigation (#32810)
This commit is contained in:
parent
070c16cb4f
commit
0e58436ca4
19 changed files with 1190 additions and 474 deletions
152
tests/config-mobile-nav.spec.ts
Normal file
152
tests/config-mobile-nav.spec.ts
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
import { test, expect, devices } from "@playwright/test";
|
||||
import { start, stop, restart, baseUrl } from "./evcc";
|
||||
import { expectModalHidden, expectModalVisible, newLoadpoint, addDemoCharger } from "./utils";
|
||||
|
||||
test.use({ baseURL: baseUrl(), viewport: devices["iPhone 12 Mini"].viewport });
|
||||
test.describe.configure({ mode: "parallel" });
|
||||
|
||||
const desktop = devices["Desktop Chrome"].viewport;
|
||||
|
||||
test.describe("mobile section navigation", async () => {
|
||||
test.beforeAll(async () => {
|
||||
await start();
|
||||
});
|
||||
test.afterAll(async () => {
|
||||
await stop();
|
||||
});
|
||||
test("shows section list instead of long page", async ({ page }) => {
|
||||
await page.goto("/#/config");
|
||||
const nav = page.getByTestId("config-section-nav");
|
||||
await expect(nav).toBeVisible();
|
||||
await expect(nav.getByRole("button", { name: "Vehicles" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Vehicles" })).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("open and close section detail", async ({ page }) => {
|
||||
await page.goto("/#/config");
|
||||
await page.getByRole("button", { name: "Vehicles" }).click();
|
||||
|
||||
const detail = page.getByTestId("section-detail-vehicles");
|
||||
await expect(detail).toBeVisible();
|
||||
await page.waitForURL("**/#/config#vehicles");
|
||||
await expect(detail.getByTestId("add-vehicle")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "Back" }).click();
|
||||
await expect(detail).not.toBeVisible();
|
||||
await page.waitForURL("**/#/config");
|
||||
await expect(page.getByTestId("config-section-nav")).toBeVisible();
|
||||
});
|
||||
|
||||
test("browser back closes section detail", async ({ page }) => {
|
||||
await page.goto("/#/config");
|
||||
await page.getByRole("button", { name: "Grid" }).click();
|
||||
await expect(page.getByTestId("section-detail-grid")).toBeVisible();
|
||||
|
||||
await page.goBack();
|
||||
await expect(page.getByTestId("section-detail-grid")).not.toBeVisible();
|
||||
await expect(page.getByTestId("config-section-nav")).toBeVisible();
|
||||
});
|
||||
|
||||
test("deep link opens section detail directly", async ({ page }) => {
|
||||
await page.goto("/#/config#tariffs");
|
||||
const detail = page.getByTestId("section-detail-tariffs");
|
||||
await expect(detail).toBeVisible();
|
||||
|
||||
// back button must not leave the app
|
||||
await page.getByRole("button", { name: "Back" }).click();
|
||||
await expect(detail).not.toBeVisible();
|
||||
await page.waitForURL("**/#/config");
|
||||
});
|
||||
|
||||
test("modal keeps section detail open", async ({ page }) => {
|
||||
await page.goto("/#/config#vehicles");
|
||||
const detail = page.getByTestId("section-detail-vehicles");
|
||||
await expect(detail).toBeVisible();
|
||||
|
||||
await detail.getByTestId("add-vehicle").click();
|
||||
const vehicleModal = page.getByTestId("vehicle-modal");
|
||||
await expectModalVisible(vehicleModal);
|
||||
expect(page.url()).toContain("vehicle");
|
||||
expect(page.url()).toContain("#vehicles");
|
||||
|
||||
await vehicleModal.getByRole("button", { name: "Cancel" }).click();
|
||||
await expectModalHidden(vehicleModal);
|
||||
await expect(detail).toBeVisible();
|
||||
});
|
||||
|
||||
test("modal deep link with section hash survives reload", async ({ page }) => {
|
||||
await page.goto("/#/config#integrations");
|
||||
const detail = page.getByTestId("section-detail-integrations");
|
||||
await expect(detail).toBeVisible();
|
||||
|
||||
await detail.getByTestId("mqtt").getByRole("button", { name: "edit" }).click();
|
||||
const mqttModal = page.getByTestId("mqtt-modal");
|
||||
await expectModalVisible(mqttModal);
|
||||
|
||||
await page.reload();
|
||||
await expectModalVisible(mqttModal);
|
||||
await mqttModal.getByRole("button", { name: "Cancel" }).click();
|
||||
await expectModalHidden(mqttModal);
|
||||
await expect(detail).toBeVisible();
|
||||
});
|
||||
|
||||
test("desktop keeps long page and scrolls to anchor", async ({ page }) => {
|
||||
await page.setViewportSize(desktop);
|
||||
await page.goto("/#/config#tariffs");
|
||||
|
||||
await expect(page.getByTestId("config-section-nav")).not.toBeVisible();
|
||||
await expect(page.getByTestId("section-detail-tariffs")).not.toBeVisible();
|
||||
const heading = page.getByRole("heading", { name: "Tariffs & forecasts" });
|
||||
await expect(heading).toBeInViewport();
|
||||
});
|
||||
|
||||
test("resize switches between list and long page", async ({ page }) => {
|
||||
await page.goto("/#/config#grid");
|
||||
await expect(page.getByTestId("section-detail-grid")).toBeVisible();
|
||||
|
||||
await page.setViewportSize(desktop);
|
||||
await expect(page.getByTestId("section-detail-grid")).not.toBeVisible();
|
||||
// level 2: the header title briefly shows "Grid" while its swap transition runs
|
||||
await expect(page.getByRole("heading", { name: "Grid", level: 2 })).toBeVisible();
|
||||
|
||||
await page.setViewportSize(devices["iPhone 12 Mini"].viewport);
|
||||
await expect(page.getByTestId("section-detail-grid")).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("section indicators", async () => {
|
||||
test.afterEach(async () => {
|
||||
await stop();
|
||||
});
|
||||
|
||||
test("device counts", async ({ page }) => {
|
||||
await start("config-with-vehicle.evcc.yaml");
|
||||
await page.goto("/#/config");
|
||||
|
||||
const nav = page.getByTestId("config-section-nav");
|
||||
await expect(nav.getByRole("button", { name: "Charging points & heaters" })).toContainText("1");
|
||||
await expect(nav.getByRole("button", { name: "Vehicles" })).toContainText("1");
|
||||
await expect(nav.getByRole("button", { name: "Grid" })).toContainText("1");
|
||||
});
|
||||
|
||||
test("error indicator on fatal device error", async ({ page }) => {
|
||||
await start("config-invalid-references-vehicle.evcc.yaml");
|
||||
await page.goto("/#/config#loadpoints");
|
||||
|
||||
// loadpoint referencing a vehicle that disappears after restart
|
||||
await newLoadpoint(page, "Garage");
|
||||
await addDemoCharger(page);
|
||||
const lpModal = page.getByTestId("loadpoint-modal");
|
||||
await lpModal.getByRole("link", { name: "Advanced configuration" }).click();
|
||||
await lpModal.getByLabel("Default vehicle").selectOption("Legacy Vehicle");
|
||||
await lpModal.getByRole("button", { name: "Save" }).click();
|
||||
await expectModalHidden(lpModal);
|
||||
|
||||
await restart();
|
||||
await page.goto("/#/config");
|
||||
|
||||
await expect(page.getByTestId("fatal-error")).toBeVisible();
|
||||
const row = page.getByRole("button", { name: "Charging points & heaters" });
|
||||
await expect(row.getByTestId("section-error")).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
@ -26,7 +26,7 @@ test.describe("tariffs", async () => {
|
|||
await page.goto("/#/config");
|
||||
|
||||
// New configuration section should show with "Add Tariff" button
|
||||
await expect(page.getByRole("heading", { name: "Tariffs & Forecasts" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Tariffs & forecasts" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Add Tariff" })).toBeVisible();
|
||||
|
||||
// Old tariff card should not be shown
|
||||
|
|
@ -97,7 +97,7 @@ grid:
|
|||
await expect(page.getByTestId("tariffs-legacy")).toBeVisible();
|
||||
await expect(page.getByTestId("tariffs-legacy")).toContainText(
|
||||
[
|
||||
"Tariffs & Forecasts",
|
||||
"Tariffs & forecasts",
|
||||
"Grid price",
|
||||
"30.0 öre",
|
||||
"Feed-in price",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue