Config UI: fix duration template param handling (#32572)

This commit is contained in:
Michael Geers 2026-08-07 21:35:14 +02:00 • committed by GitHub
parent 6f8a6b8c36
commit afac7ab56d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 346 additions and 13 deletions

View file

@ -31,12 +31,12 @@ test("decimal input with german separator", async ({ page }) => {
await modal.getByRole("button", { name: "Show advanced settings" }).click();
// comma accepted as decimal separator
const timeout = modal.getByLabel("Timeout");
const timeout = modal.getByRole("spinbutton", { name: "Timeout" });
await timeout.pressSequentially("0,5");
await expect(timeout).toHaveValue("0.5");
// intermediate zeros survive typing
const delay = modal.getByLabel("Delay");
const delay = modal.getByRole("spinbutton", { name: "Delay" });
await delay.pressSequentially("0.004");
await expect(delay).toHaveValue("0.004");
await delay.blur();

View file

@ -0,0 +1,25 @@
template: duration-demo
group: generic
products:
- description:
generic: Duration Demo Meter
params:
- name: usage
choice: ["grid"]
- name: shortduration
type: duration
default: 30s
example: 10s
description:
generic: Short duration
- name: longduration
type: duration
unit: hour
default: 3h
description:
generic: Long duration
render: |
type: custom
power:
source: const
value: 0

View file

@ -0,0 +1,23 @@
BEGIN;
CREATE TABLE `configs` (
`id` integer PRIMARY KEY AUTOINCREMENT
, `class` integer
, `type` text
, `title` text
, `icon` text
, `product` text
, `value` text
);
CREATE TABLE `settings` (
`key` text
, `value` text
, PRIMARY KEY(`key`)
);
-- longduration as duration string (UI-written format), shortduration as legacy nanosecond number
INSERT INTO configs(id, class, type, title, icon, product, value) VALUES(1, 2, 'template', 'Duration Test', '', 'Duration Demo Meter', '{"template":"duration-demo","usage":"grid","longduration":"12h","shortduration":15000000000}');
INSERT INTO settings("key", value) VALUES('gridMeter', 'db:1');
COMMIT;

View file

@ -0,0 +1,94 @@
import { test, expect, type Page, type Locator } from "@playwright/test";
import { start, stop, restart, baseUrl } from "./evcc";
import { expectModalVisible, expectModalHidden } from "./utils";
const templateFlags = [
"--disable-auth",
"--template-type",
"meter",
"--template",
"tests/config-duration-demo.tpl.yaml",
];
test.use({ baseURL: baseUrl() });
test.afterEach(async () => {
await stop();
});
async function openGridMeterModal(page: Page): Promise<Locator> {
await page.goto("/#/config");
await page.getByTestId("grid").getByRole("button", { name: "edit" }).click();
const modal = page.getByTestId("meter-modal");
await expectModalVisible(modal);
return modal;
}
test.describe("duration fields", async () => {
test("template defaults and roundtrip", async ({ page }) => {
await start(undefined, undefined, templateFlags);
await page.goto("/#/config");
await page.getByRole("button", { name: "Add grid meter" }).click();
const modal = page.getByTestId("meter-modal");
await expectModalVisible(modal);
await modal.getByLabel("Manufacturer").selectOption("Duration Demo Meter");
await expect(modal.getByRole("spinbutton", { name: "Short duration" })).toHaveValue("30");
await expect(modal.getByText("Example: 10 seconds", { exact: true })).toBeVisible();
await expect(modal.getByLabel("Time unit for Short duration")).toHaveValue("second");
await expect(modal.getByRole("spinbutton", { name: "Long duration" })).toHaveValue("3");
await expect(modal.getByLabel("Time unit for Long duration")).toHaveValue("hour");
await modal.getByRole("spinbutton", { name: "Long duration" }).fill("6");
await modal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(modal);
await restart(undefined, templateFlags);
await page.reload();
// edited value and untouched default are both stored as duration strings
const reopened = await openGridMeterModal(page);
await expect(reopened.getByRole("spinbutton", { name: "Long duration" })).toHaveValue("6");
await expect(reopened.getByRole("spinbutton", { name: "Short duration" })).toHaveValue("30");
});
test("existing values as string and nanoseconds", async ({ page }) => {
await start(undefined, "config-duration-values.sql", templateFlags);
const modal = await openGridMeterModal(page);
await expect(modal.getByRole("spinbutton", { name: "Long duration" })).toHaveValue("12");
await expect(modal.getByRole("spinbutton", { name: "Short duration" })).toHaveValue("15");
await modal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(modal);
await restart(undefined, templateFlags);
await page.reload();
const reopened = await openGridMeterModal(page);
await expect(reopened.getByRole("spinbutton", { name: "Long duration" })).toHaveValue("12");
await expect(reopened.getByRole("spinbutton", { name: "Short duration" })).toHaveValue("15");
});
test("unit switching keeps number", async ({ page }) => {
await start(undefined, "config-duration-values.sql", templateFlags);
// stored "12h" drives the unit, ns number falls back to template default
const modal = await openGridMeterModal(page);
await expect(modal.getByLabel("Time unit for Long duration")).toHaveValue("hour");
await expect(modal.getByLabel("Time unit for Short duration")).toHaveValue("second");
await modal.getByLabel("Time unit for Long duration").selectOption("minutes");
await expect(modal.getByRole("spinbutton", { name: "Long duration" })).toHaveValue("12");
await modal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(modal);
await restart(undefined, templateFlags);
await page.reload();
// stored "12m" restores the selected unit
const reopened = await openGridMeterModal(page);
await expect(reopened.getByRole("spinbutton", { name: "Long duration" })).toHaveValue("12");
await expect(reopened.getByLabel("Time unit for Long duration")).toHaveValue("minute");
});
});