From 9f7b552885038611b680937eeca7768ea8faaf6f Mon Sep 17 00:00:00 2001 From: stroblme Date: Wed, 14 Oct 2026 23:59:24 +0200 Subject: [PATCH] Colour tile tests: the disc on the shared widgets panel for every look's screenshot, and a key's release sending rgb and hex Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WgBfkm4rCmtd1T4vHWjEPx --- frontend/tests/color-drag.spec.ts | 79 +++++++++++++++++++++++++++---- frontend/tests/widgets.spec.ts | 12 ++++- 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/frontend/tests/color-drag.spec.ts b/frontend/tests/color-drag.spec.ts index 6edf754..29cc784 100644 --- a/frontend/tests/color-drag.spec.ts +++ b/frontend/tests/color-drag.spec.ts @@ -12,7 +12,8 @@ import { api, apiPage, deleteAll } from "./utils/api" * * The arithmetic behind the wheel is checked in `color.check.ts`, which needs * no browser. What needs one is the gesture: how many messages a drag sends, - * and whether the one it does send is where the pointer was let go. + * and whether the one it does send is where the pointer was let go. And the + * wiring: a tile set to rgb or hex reads and sends that form, not the wheel's. */ const flowName = `test_color_${Date.now().toString(36)}` const dashboard = `${flowName}_d` @@ -36,7 +37,11 @@ test.beforeAll(async ({ browser }) => { { id: "lamp", type: "python", - provides: [{ name: "tint", dtype: "list", item: "float" }], + provides: [ + { name: "tint", dtype: "list", item: "float" }, + { name: "rgb", dtype: "list", item: "int" }, + { name: "hex", dtype: "str" }, + ], }, ], }, @@ -48,6 +53,16 @@ test.beforeAll(async ({ browser }) => { method: "POST", data: { version: flow.definition.version }, }) + // The same amber in either form, as a flow speaking it would set it. + for (const [name, value] of [ + ["rgb", [204, 150, 57]], + ["hex", "#cc9639"], + ] as const) { + await api(page, `/messages/${flowName}.${name}`, { + method: "POST", + data: { value }, + }) + } // A leftover from a run that failed before its teardown would answer the // create with a version conflict. @@ -66,6 +81,20 @@ test.beforeAll(async ({ browser }) => { layout: { lg: { x: 0, y: 0, w: 6, h: 4 } }, config: { target, dtype: "list", format: "hsv" }, }, + { + id: "rgb", + type: "color", + title: "RGB", + layout: { lg: { x: 6, y: 0, w: 3, h: 4 } }, + config: { target: `${flowName}.rgb`, dtype: "list", format: "rgb" }, + }, + { + id: "hex", + type: "color", + title: "Hex", + layout: { lg: { x: 9, y: 0, w: 3, h: 4 } }, + config: { target: `${flowName}.hex`, dtype: "str", format: "hex" }, + }, ], }, }) @@ -87,10 +116,13 @@ test.afterAll(async ({ browser }) => { await deleteAll(browser, [`/dashboards/${dashboard}`, `/flows/${flowName}`]) }) -/** Open the panel, settled, and collect every publish the widget makes. */ -async function openBoard(page: Page) { +/** Open the panel, settled, and collect every publish made to `name`. */ +async function openBoard( + page: Page, + name = target, +) { await page.goto(`/view/${dashboard}`) - await page.getByTestId("color-wheel").waitFor({ timeout: 20000 }) + await page.getByTestId("color-wheel").first().waitFor({ timeout: 20000 }) // A tile fades and scales in, so where the disc is drawn is only settled // once that has finished — and the gestures below are aimed in pixels. await page.waitForFunction( @@ -104,11 +136,11 @@ async function openBoard(page: Page) { // Every publish this panel makes, as the browser sends it. The setup above // talks to the API out of band, so nothing but the widget is counted here. - const sent: [number, number, number][] = [] + const sent: Value[] = [] page.on("request", (request) => { if ( request.method() === "POST" && - request.url().includes(`/messages/${target}`) + request.url().includes(`/messages/${name}`) ) sent.push(request.postDataJSON()?.value) }) @@ -117,7 +149,7 @@ async function openBoard(page: Page) { test("a dragged disc publishes once, where it was let go", async ({ page }) => { const sent = await openBoard(page) - const disc = page.getByTestId("color-wheel") + const disc = page.getByRole("slider", { name: "Tint hue and saturation" }) const box = (await disc.boundingBox())! const middle = { x: box.x + box.width / 2, y: box.y + box.height / 2 } @@ -211,3 +243,34 @@ test("a dragged brightness column publishes where it was let go", async ({ .toBe(1) expect(sent[0][2], "the release sent another brightness").toBe(letGo) }) + +for (const { title, sends } of [ + { title: "RGB", sends: [204, 162, 57] }, + { title: "Hex", sends: "#cca239" }, +]) { + test(`a key's release publishes the colour as ${title}`, async ({ page }) => { + const sent = await openBoard( + page, + `${flowName}.${title.toLowerCase()}`, + ) + const disc = page.getByRole("slider", { + name: `${title} hue and saturation`, + }) + // The amber the setup published, read back from the tile's own form. + await expect(disc).toHaveAttribute( + "aria-valuetext", + "38 degrees, 72% saturated", + ) + + // A key moves the hue five degrees on the way down and commits on the + // way up, so one press is one message. + await disc.press("ArrowRight") + await expect + .poll(() => sent.length, { + timeout: 10000, + message: "letting go of the key published nothing", + }) + .toBe(1) + expect(sent[0], "the key sent another colour").toEqual(sends) + }) +} diff --git a/frontend/tests/widgets.spec.ts b/frontend/tests/widgets.spec.ts index c89f10f..0f23dde 100644 --- a/frontend/tests/widgets.spec.ts +++ b/frontend/tests/widgets.spec.ts @@ -24,7 +24,7 @@ const lockedName = `${flowName}_locked` const w = (name: string) => `${flowName}.${name}` /** Every tile the dashboard carries, including the deliberately unbound one. */ -const TILES = 11 +const TILES = 12 test.use({ storageState: "playwright/.auth/user.json" }) @@ -59,6 +59,7 @@ test.beforeAll(async ({ browser }) => { { name: "lamp", dtype: "bool" }, { name: "setpoint", dtype: "float" }, { name: "idle", dtype: "float" }, + { name: "tint", dtype: "list", item: "float" }, ], }, ], @@ -78,6 +79,8 @@ test.beforeAll(async ({ browser }) => { await publish(page, w("condition"), "sun") await publish(page, w("setpoint"), 21.5) await publish(page, w("idle"), 0) + // Amber, so the disc's handle and shade have something to show. + await publish(page, w("tint"), [38, 72, 80]) await publish(page, w("days"), [ { label: "Mon", icon: "sun", value: "21°" }, { label: "Tue", icon: "cloudy", value: "18°" }, @@ -214,6 +217,13 @@ test.beforeAll(async ({ browser }) => { layout: { lg: { x: 6, y: 4, w: 6, h: 3 } }, config: { series: [{ message: w("level"), dtype: "float" }] }, }, + { + id: "tint", + type: "color", + title: "Tint", + layout: { lg: { x: 6, y: 7, w: 3, h: 3 } }, + config: { target: w("tint"), dtype: "list", format: "hsv" }, + }, ] const draft = await ( await api(page, `/dashboards/${dashboardName}`, {