diff --git a/frontend/tests/widgets.spec.ts b/frontend/tests/widgets.spec.ts index 98435c1..e453e36 100644 --- a/frontend/tests/widgets.spec.ts +++ b/frontend/tests/widgets.spec.ts @@ -16,6 +16,9 @@ const flowName = `test_widgets_${Date.now().toString(36)}` const dashboardName = `${flowName}_panel` /** A panel of its own: a bar of three rows needs a tile to itself. */ const stackName = `${flowName}_stack` +/** A read-only dashboard, on its own so the lock guard below has a lamp the + * interaction test above hasn't already flipped. */ +const lockedName = `${flowName}_locked` /** A message of the flow under test, qualified the way the engine names it. */ const w = (name: string) => `${flowName}.${name}` @@ -230,6 +233,30 @@ test.beforeAll(async ({ browser }) => { method: "POST", data: { version: stacked.version }, }) + + const locked = await ( + await api(page, `/dashboards/${lockedName}`, { method: "POST" }) + ).json() + locked.settings = { ...(locked.settings ?? {}), locked: { value: true } } + locked.widgets = [ + { + id: "lamp", + type: "switch", + title: "Lamp", + layout: { lg: { x: 0, y: 0, w: 3, h: 2 } }, + config: { target: w("lamp"), dtype: "bool", style: "button" }, + }, + ] + const lockedDraft = await ( + await api(page, `/dashboards/${lockedName}`, { + method: "PUT", + data: locked, + }) + ).json() + await api(page, `/dashboards/${lockedName}/publish`, { + method: "POST", + data: { version: lockedDraft.version }, + }) await page.close() }) @@ -237,6 +264,7 @@ test.afterAll(async ({ browser }) => { await deleteAll(browser, [ `/dashboards/${dashboardName}`, `/dashboards/${stackName}`, + `/dashboards/${lockedName}`, `/flows/${flowName}`, ]) }) @@ -469,6 +497,29 @@ test("a control reads back what it published", async ({ page }) => { await expect(lamp).toHaveText("Off") }) +/** + * A locked dashboard, read and arranged on the desktop grid. + * + * mobile.spec.ts guards this same setting through the stacked editor, which a + * phone's width routes through `DashboardView` on both the read and the + * arrange side. Desktop takes a second path once editing starts — `?edit=true` + * swaps in `` directly, which never mounts `LockedProvider` at + * all — so this is the same guard through the branch that path never reaches. + */ +test("a locked dashboard locks when read, not while arranged", async ({ + page, +}) => { + const lamp = page.getByRole("button", { name: "Lamp" }) + + await page.goto(`/dashboards/${lockedName}`) + await lamp.waitFor({ timeout: 15000 }) + await expect(lamp, "a locked dashboard being read").toBeDisabled() + + await page.goto(`/dashboards/${lockedName}?edit=true`) + await lamp.waitFor({ timeout: 15000 }) + await expect(lamp, "a locked dashboard being arranged").toBeEnabled() +}) + test("an unbound widget says so and takes nothing down", async ({ page }) => { const crashes: string[] = [] page.on("pageerror", (error) => crashes.push(error.message))