From 37289a224728ce4ce4591d5ec373e274fe7637eb Mon Sep 17 00:00:00 2001 From: ajnart Date: Fri, 14 Aug 2026 15:14:36 +0200 Subject: [PATCH] test: remove obsolete advanced interactions flow --- e2e/board-advanced-interactions.spec.ts | 509 ------------------------ 1 file changed, 509 deletions(-) delete mode 100644 e2e/board-advanced-interactions.spec.ts diff --git a/e2e/board-advanced-interactions.spec.ts b/e2e/board-advanced-interactions.spec.ts deleted file mode 100644 index dad398796..000000000 --- a/e2e/board-advanced-interactions.spec.ts +++ /dev/null @@ -1,509 +0,0 @@ -import type { Browser, BrowserContext, Page } from "@playwright/test"; -import { chromium, expect } from "@playwright/test"; -import { createId } from "@paralleldrive/cuid2"; -import { eq } from "drizzle-orm"; -import { stringify } from "superjson"; -import { afterAll, beforeAll, describe, test } from "vitest"; - -import { hashPasswordAsync } from "../packages/auth/security"; -import { - apps, - boardUserPermissions, - boards, - itemLayouts, - items, - layouts, - sections, - users, -} from "../packages/db/schema/sqlite"; -import type { SqliteDatabase } from "./shared/e2e-db"; -import { createSqliteDbFileAsync } from "./shared/e2e-db"; -import { createHomarrContainer } from "./shared/create-homarr-container"; -import { loginAsync } from "./shared/login"; -import { seedAdminUserAsync } from "./shared/seed-admin-user"; - -const ownerCredentials = { username: "owner", password: "Comp(exP4sswOrd" }; -const disabledModifierCredentials = { username: "modifier", password: "Comp(exP4sswOrd" }; -const viewerCredentials = { username: "viewer", password: "Comp(exP4sswOrd" }; -const boardName = "advanced-interactions-e2e"; - -describe("Board advanced interactions", () => { - let browser: Browser; - let baseUrl: string; - let stopContainerAsync: (() => Promise) | undefined; - - beforeAll(async () => { - const { db, localMountPath } = await createSqliteDbFileAsync(); - const { userId: ownerId } = await seedAdminUserAsync(db, ownerCredentials); - await seedInteractionBoardAsync(db, ownerId); - await seedCredentialsUserAsync(db, { - ...disabledModifierCredentials, - enableRightClickOnWidgets: false, - boardPermission: "modify", - }); - await seedCredentialsUserAsync(db, { - ...viewerCredentials, - enableRightClickOnWidgets: true, - boardPermission: "view", - }); - - const homarrContainer = await createHomarrContainer({ - environment: { AUTH_PROVIDERS: "credentials" }, - mounts: { "/appdata": localMountPath }, - }).start(); - stopContainerAsync = async () => { - await homarrContainer.stop(); - }; - baseUrl = `http://${homarrContainer.getHost()}:${homarrContainer.getMappedPort(7575)}`; - browser = await chromium.launch(); - }, 120_000); - - afterAll(async () => { - await browser?.close(); - await stopContainerAsync?.(); - }); - - test("opens advanced view from Shift-hover, keyboard, and the widget context menu", async () => { - const { context, page } = await openBoardAsync(browser, baseUrl, ownerCredentials); - - try { - const widget = page.locator("[data-grid-item-id][data-kind='clock'] [data-advanced-focus-source]").first(); - const advancedViewTrigger = widget.getByRole("button", { name: "Open advanced view" }); - const compactSurface = page.locator(".clock-wrapper").first(); - const previewSurface = page.getByRole("region", { name: "Date and time advanced view" }); - const manualSurface = page.getByRole("dialog", { name: "Date and time advanced view" }); - const bookmarksPreviewSurface = page.getByRole("region", { name: "Bookmarks advanced view" }); - const dimmingOverlay = page.locator("[data-advanced-focus-overlay]"); - const otherWidget = page - .locator("[data-grid-item-id][data-kind='bookmarks'] [data-advanced-focus-source]") - .first(); - const header = page.locator("header[data-advanced-focus-background]"); - const boardMain = page.locator("main[data-advanced-focus-background]"); - - await compactSurface.evaluate((element) => { - element.setAttribute("data-lifecycle-probe", "same-instance"); - }); - await widget.hover(); - await advancedViewTrigger.focus(); - await page.keyboard.down("Shift"); - await expect(previewSurface).toBeVisible({ timeout: 2_000 }); - await expect(previewSurface).toHaveCSS("animation-name", "none"); - const compactBounds = await widget.boundingBox(); - const advancedBounds = await previewSurface.boundingBox(); - if (!compactBounds || !advancedBounds) throw new Error("Advanced widget has no bounds"); - expect(advancedBounds?.width).toBeGreaterThan(compactBounds?.width ?? 0); - await expect(previewSurface).toHaveAttribute("data-lifecycle-probe", "same-instance"); - await expect(dimmingOverlay).toBeVisible(); - await expect(dimmingOverlay).toHaveCSS("pointer-events", "none"); - await expect(otherWidget).toBeVisible(); - await expect(advancedViewTrigger).toBeFocused(); - await expect(header).not.toHaveAttribute("inert", ""); - await expect(boardMain).not.toHaveAttribute("inert", ""); - - await previewSurface.click({ button: "right", position: { x: 80, y: 80 } }); - const portalledMenuItem = page.getByRole("menuitem", { name: "Open advanced view" }); - await expect(portalledMenuItem).toBeVisible(); - await portalledMenuItem.hover(); - await expect(previewSurface).toBeVisible(); - await page.keyboard.press("Escape"); - await expect(portalledMenuItem).toBeHidden(); - await expect(previewSurface).toBeVisible(); - - const otherBounds = await otherWidget.boundingBox(); - expect(otherBounds).not.toBeNull(); - if (!otherBounds) throw new Error("Bookmarks widget has no bounds"); - await previewSurface.evaluate( - (element, position) => { - const probe = document.createElement("button"); - probe.dataset.interactivePreviewProbe = "true"; - probe.style.cssText = `position:absolute;z-index:20;width:40px;height:40px;left:${position.left}px;top:${position.top}px`; - element.append(probe); - }, - { - left: otherBounds.x + otherBounds.width / 2 - advancedBounds.x - 20, - top: otherBounds.y + otherBounds.height / 2 - advancedBounds.y - 20, - }, - ); - const interactivePreviewProbe = previewSurface.locator("[data-interactive-preview-probe='true']"); - await interactivePreviewProbe.hover(); - await expect(previewSurface).toBeVisible(); - await expect(bookmarksPreviewSurface).toBeHidden(); - await interactivePreviewProbe.evaluate((element) => element.remove()); - await page.mouse.move(compactBounds.x + 4, compactBounds.y + 4); - await page.mouse.move(otherBounds.x + otherBounds.width / 2, otherBounds.y + otherBounds.height / 2); - await expect(previewSurface).toBeVisible(); - await expect(bookmarksPreviewSurface).toBeHidden(); - await page.mouse.move(4, 4); - await expect(previewSurface).toBeHidden(); - await otherWidget.hover({ position: { x: 4, y: 4 } }); - await expect(bookmarksPreviewSurface).toBeVisible({ timeout: 2_000 }); - await page.keyboard.up("Shift"); - await expect(bookmarksPreviewSurface).toBeHidden(); - await expect(dimmingOverlay).toBeHidden(); - await expect(compactSurface).toHaveAttribute("data-lifecycle-probe", "same-instance"); - await advancedViewTrigger.focus(); - await page.keyboard.press("Shift+Enter"); - await expect(manualSurface).toBeVisible(); - await expect(manualSurface).toHaveAttribute("aria-modal", "true"); - await expect(header).toHaveAttribute("inert", ""); - await expect(boardMain).toHaveAttribute("inert", ""); - expect(await manualSurface.evaluate((element) => element.closest("[data-grid-item-id]"))).toBeNull(); - await expect(manualSurface.locator(".clock-wrapper")).toHaveAttribute("data-lifecycle-probe", "same-instance"); - await expect(dimmingOverlay).toHaveCSS("pointer-events", "auto"); - expect(await page.evaluate(() => getComputedStyle(document.body).overflow)).toBe("hidden"); - await expect(page.getByRole("button", { name: "Close advanced view" })).toBeFocused(); - await page.keyboard.press("Tab"); - await expect(page.getByRole("button", { name: "Close advanced view" })).toBeFocused(); - await page.keyboard.press("Shift+Tab"); - await expect(page.getByRole("button", { name: "Close advanced view" })).toBeFocused(); - await expect(page.getByRole("button", { name: /advanced view is pinned|keep advanced view open/i })).toHaveCount( - 0, - ); - await manualSurface.evaluate((element) => { - const mainViewport = document.createElement("div"); - mainViewport.dataset.scrollbars = "y"; - mainViewport.style.cssText = "height:80px;overflow-y:auto"; - - const probe = document.createElement("div"); - probe.dataset.shiftScrollProbe = "true"; - probe.style.cssText = "height:80px;overflow-y:auto"; - probe.innerHTML = '
'; - probe.addEventListener("wheel", (event) => event.stopPropagation()); - mainViewport.append(probe); - mainViewport.insertAdjacentHTML("beforeend", '
'); - element.append(mainViewport); - - const unrelated = document.createElement("div"); - unrelated.dataset.shiftScrollProbe = "unrelated"; - unrelated.style.cssText = "height:80px;overflow-y:hidden"; - unrelated.innerHTML = '
'; - element.append(unrelated); - }); - const scrollProbe = manualSurface.locator("[data-shift-scroll-probe='true']"); - await page.keyboard.down("Shift"); - const pixelEventCancelled = await scrollProbe.evaluate( - (element) => - !element.dispatchEvent( - new WheelEvent("wheel", { bubbles: true, cancelable: true, shiftKey: true, deltaX: 120 }), - ), - ); - expect(pixelEventCancelled).toBe(true); - await scrollProbe.evaluate((element) => { - element.scrollTop = 0; - }); - const lineEventCancelled = await scrollProbe - .locator("[data-shift-scroll-target='true']") - .evaluate( - (element) => - !element.dispatchEvent( - new WheelEvent("wheel", { bubbles: true, cancelable: true, shiftKey: true, deltaX: 3, deltaMode: 1 }), - ), - ); - await page.keyboard.up("Shift"); - expect(lineEventCancelled).toBe(true); - expect(await scrollProbe.evaluate((element) => element.scrollTop)).toBeGreaterThanOrEqual(48); - await scrollProbe.evaluate((element) => { - element.scrollTop = element.scrollHeight; - element.dispatchEvent( - new WheelEvent("wheel", { bubbles: true, cancelable: true, shiftKey: true, deltaX: 120 }), - ); - }); - expect(await manualSurface.locator("[data-scrollbars='y']").evaluate((element) => element.scrollTop)).toBe(120); - expect( - await manualSurface.locator("[data-shift-scroll-probe='unrelated']").evaluate((element) => element.scrollTop), - ).toBe(0); - - await manualSurface.click({ button: "right", position: { x: 80, y: 80 } }); - await expect(portalledMenuItem).toBeVisible(); - await page.keyboard.press("Escape"); - await expect(portalledMenuItem).toBeHidden(); - await expect(manualSurface).toBeVisible(); - await page.keyboard.press("Escape"); - await expect(manualSurface).toBeHidden(); - await expect(advancedViewTrigger).toBeFocused(); - await expect(header).not.toHaveAttribute("inert", ""); - await expect(boardMain).not.toHaveAttribute("inert", ""); - expect(await page.evaluate(() => getComputedStyle(document.body).overflow)).not.toBe("hidden"); - - await widget.click({ button: "right" }); - await page.getByRole("menuitem", { name: "Open advanced view" }).click(); - await expect(manualSurface).toBeVisible(); - const viewport = page.viewportSize(); - if (!viewport) throw new Error("Board page has no viewport"); - await page.mouse.click(4, viewport.height - 4); - await expect(manualSurface).toBeHidden(); - await expect(advancedViewTrigger).toBeFocused(); - - for (const compactOnlyKind of ["app", "iframe"]) { - const compactOnlyWidget = page - .locator(`[data-grid-item-id][data-kind='${compactOnlyKind}'] [data-advanced-focus-source]`) - .first(); - await expect(compactOnlyWidget.locator("[data-advanced-focus-trigger]")).toHaveCount(0); - await compactOnlyWidget.dispatchEvent("contextmenu", { button: 2 }); - await expect(page.getByRole("menuitem", { name: "Open advanced view" })).toHaveCount(0); - await page.keyboard.press("Escape"); - await page.keyboard.down("Shift"); - await compactOnlyWidget.hover({ position: { x: 4, y: 4 } }); - await expect(previewSurface).toBeHidden(); - await expect(dimmingOverlay).toBeHidden(); - await page.keyboard.up("Shift"); - } - } finally { - await context.close(); - } - }, 60_000); - - test("keeps compact-only iframe state and dismisses retained focus when a responsive layout remounts", async () => { - const { context, page } = await openBoardAsync(browser, baseUrl, ownerCredentials); - - try { - const iframeSlot = page.locator("[data-grid-item-id][data-kind='iframe'] [data-advanced-focus-source]").first(); - const iframe = iframeSlot.locator("iframe"); - await iframe.evaluate((element) => { - (element as HTMLIFrameElement).srcdoc = ''; - (element as HTMLIFrameElement).dataset.advancedFocusProbe = "true"; - }); - const frame = page.frameLocator("iframe[data-advanced-focus-probe='true']"); - await frame.locator("#state").fill("changed"); - - await expect(iframeSlot.locator("[data-advanced-focus-trigger]")).toHaveCount(0); - await iframeSlot.dispatchEvent("contextmenu", { button: 2 }); - await expect(page.getByRole("menuitem", { name: "Open advanced view" })).toHaveCount(0); - await page.keyboard.press("Escape"); - await page.keyboard.down("Shift"); - await iframeSlot.hover({ position: { x: 4, y: 4 } }); - await expect(page.getByRole("region", { name: "iFrame advanced view" })).toHaveCount(0); - await page.keyboard.up("Shift"); - await expect(frame.locator("#state")).toHaveValue("changed"); - - const bookmarksSlot = page - .locator("[data-grid-item-id][data-kind='bookmarks'] [data-advanced-focus-source]") - .first(); - await bookmarksSlot.getByRole("button", { name: "Open advanced view" }).focus(); - await page.keyboard.press("Shift+Enter"); - await expect(page.getByRole("dialog", { name: "Bookmarks advanced view" })).toBeVisible(); - await page.setViewportSize({ width: 800, height: 900 }); - await expect(page.getByRole("dialog", { name: "Bookmarks advanced view" })).toBeHidden(); - await expect(page.locator("[data-grid-item-id][data-kind='iframe'] iframe")).toBeVisible(); - } finally { - await context.close(); - } - }, 60_000); - - test("keeps advanced controls below the fixed header on short screens", async () => { - const { context, page } = await openBoardAsync(browser, baseUrl, ownerCredentials, { width: 1366, height: 768 }); - - try { - const widget = page.locator("[data-grid-item-id][data-kind='clock'] [data-advanced-focus-source]").first(); - await widget.getByRole("button", { name: "Open advanced view" }).focus(); - await page.keyboard.press("Shift+Enter"); - const closeButton = page.getByRole("button", { name: "Close advanced view" }); - const bounds = await closeButton.boundingBox(); - expect(bounds).not.toBeNull(); - if (!bounds) return; - expect(bounds.x).toBeGreaterThanOrEqual(0); - expect(bounds.y).toBeGreaterThanOrEqual(60); - expect(bounds.width).toBeGreaterThanOrEqual(44); - expect(bounds.height).toBeGreaterThanOrEqual(44); - expect(bounds.x + bounds.width).toBeLessThanOrEqual(1366); - expect(bounds.y + bounds.height).toBeLessThanOrEqual(768); - const surfaceBounds = await page.getByRole("dialog", { name: "Date and time advanced view" }).boundingBox(); - const contentBounds = await page.locator(".clock-widget-container").boundingBox(); - expect(surfaceBounds).not.toBeNull(); - expect(contentBounds).not.toBeNull(); - if (surfaceBounds && contentBounds) { - expect(contentBounds.y - surfaceBounds.y).toBeLessThan(32); - expect(bounds.x >= surfaceBounds.x + surfaceBounds.width || bounds.x + bounds.width <= surfaceBounds.x).toBe( - true, - ); - } - await closeButton.click(); - await expect(page.getByRole("dialog", { name: "Date and time advanced view" })).toBeHidden(); - } finally { - await context.close(); - } - }, 60_000); - - test("honors the user setting gate even when the user can modify the board", async () => { - const { context, page } = await openBoardAsync(browser, baseUrl, disabledModifierCredentials); - - try { - const widget = page.locator("[data-grid-item-id][data-kind='clock'] [data-advanced-focus-source]").first(); - - await widget.click({ button: "right" }); - await expect(page.getByRole("menuitem", { name: "Open advanced view" })).toHaveCount(0); - } finally { - await context.close(); - } - }, 60_000); - - test("keeps read-only advanced viewing", async () => { - const { context, page } = await openBoardAsync(browser, baseUrl, viewerCredentials); - - try { - const widget = page.locator("[data-grid-item-id][data-kind='clock'] [data-advanced-focus-source]").first(); - - await widget.click({ button: "right" }); - await page.getByRole("menuitem", { name: "Open advanced view" }).click(); - await expect(page.getByRole("dialog", { name: "Date and time advanced view" })).toBeVisible(); - await page.keyboard.press("Escape"); - } finally { - await context.close(); - } - }, 60_000); -}); - -const openBoardAsync = async ( - browser: Browser, - baseUrl: string, - credentials: { username: string; password: string }, - viewport = { width: 1280, height: 900 }, -): Promise<{ context: BrowserContext; page: Page }> => { - const context = await browser.newContext({ reducedMotion: "reduce", viewport }); - const page = await context.newPage(); - - await loginAsync({ page, baseUrl, credentials, destination: `/boards/${boardName}` }); - await expect(page.locator(".clock-wrapper").first()).toBeVisible({ - timeout: 15_000, - }); - - return { context, page }; -}; - -const seedInteractionBoardAsync = async (db: SqliteDatabase, ownerId: string) => { - const boardId = createId(); - const baseLayoutId = createId(); - const desktopLayoutId = createId(); - const sectionId = createId(); - const clockItemId = createId(); - const bookmarksItemId = createId(); - const iframeItemId = createId(); - const appItemId = createId(); - const appId = createId(); - - await db.insert(boards).values({ id: boardId, name: boardName, creatorId: ownerId, isPublic: false }); - await db.insert(apps).values({ - id: appId, - name: "Example app", - iconUrl: "/favicon.ico", - href: "https://example.com", - }); - await db.insert(layouts).values([ - { id: baseLayoutId, name: "Base", columnCount: 12, breakpoint: 0, boardId }, - { id: desktopLayoutId, name: "Desktop", columnCount: 12, breakpoint: 1000, boardId }, - ]); - await db.insert(sections).values({ id: sectionId, kind: "empty", xOffset: 0, yOffset: 0, boardId }); - await db.insert(items).values([ - { - id: clockItemId, - kind: "clock", - boardId, - options: stringify({ is24HourFormat: true, showSeconds: false, showDate: true }), - advancedOptions: stringify({ title: null, customCssClasses: [], borderColor: "" }), - }, - { - id: bookmarksItemId, - kind: "bookmarks", - boardId, - options: stringify({ - title: "", - layout: "column", - hideTitle: false, - hideIcon: false, - hideHostname: false, - openNewTab: true, - withBorder: false, - items: [appId], - }), - advancedOptions: stringify({ title: null, customCssClasses: [], borderColor: "" }), - }, - { - id: iframeItemId, - kind: "iframe", - boardId, - options: stringify({ embedUrl: "http://127.0.0.1:9", allowScrolling: true }), - advancedOptions: stringify({ title: null, customCssClasses: [], borderColor: "" }), - }, - { - id: appItemId, - kind: "app", - boardId, - options: stringify({ appId, openInNewTab: true, showTitle: true, pingEnabled: false }), - advancedOptions: stringify({ title: null, customCssClasses: [], borderColor: "" }), - }, - ]); - await db.insert(itemLayouts).values( - [baseLayoutId, desktopLayoutId].flatMap((layoutId) => [ - { - itemId: clockItemId, - sectionId, - layoutId, - xOffset: 0, - yOffset: 0, - width: 4, - height: 3, - }, - { - itemId: bookmarksItemId, - sectionId, - layoutId, - xOffset: 0, - yOffset: 3, - width: 4, - height: 3, - }, - { - itemId: iframeItemId, - sectionId, - layoutId, - xOffset: 8, - yOffset: 0, - width: 4, - height: 3, - }, - { - itemId: appItemId, - sectionId, - layoutId, - xOffset: 4, - yOffset: 0, - width: 3, - height: 3, - }, - ]), - ); - await db - .update(users) - .set({ homeBoardId: boardId, completedBoardTour: true, completedManageTour: true }) - .where(eq(users.id, ownerId)); -}; - -const seedCredentialsUserAsync = async ( - db: SqliteDatabase, - input: { - username: string; - password: string; - enableRightClickOnWidgets: boolean; - boardPermission: "view" | "modify"; - }, -) => { - const userId = createId(); - const board = await db.query.boards.findFirst({ where: eq(boards.name, boardName) }); - if (!board) throw new Error("Interaction board was not seeded"); - - await db.insert(users).values({ - id: userId, - name: input.username, - provider: "credentials", - password: await hashPasswordAsync(input.password), - enableRightClickOnWidgets: input.enableRightClickOnWidgets, - homeBoardId: board.id, - completedBoardTour: true, - completedManageTour: true, - }); - await db.insert(boardUserPermissions).values({ - boardId: board.id, - userId, - permission: input.boardPermission, - }); -};