// Rôle : vérifie les états visuels des outils, leurs pickers et les masques d'icônes compilés. import { test, expect } from "@playwright/test"; import { mountStyles, computed, expectPseudo, expectFits } from "./fixtures/compiled-styles.js"; test("table cells distinguish selection, move targets, editing and errors", async ({ page }) => { await mountStyles(page, `
`); await expect(page.locator(".table-module")).toHaveCSS("display", "grid"); const cell = page.locator("input"); await expect(cell).toHaveCSS("caret-color", "rgba(0, 0, 0, 0)"); await cell.evaluate((element) => element.classList.add("is-selected")); await expect(cell).toHaveCSS("box-shadow", /34, 211, 238/); await cell.evaluate((element) => { element.className = "table-cell-input is-move-target"; }); await expect(cell).toHaveCSS("box-shadow", /246, 196, 83/); await expect(cell).toHaveCSS("cursor", "move"); await cell.evaluate((element) => { element.className = "table-cell-input is-editing"; }); await cell.focus(); await expect(cell).not.toHaveCSS("caret-color", "rgba(0, 0, 0, 0)"); await cell.evaluate((element) => { element.blur(); element.className = "table-cell-input has-error"; }); await expect(cell).toHaveCSS("color", "rgb(254, 202, 202)"); }); test("goal nodes and NDS reorder indicators follow orientation and placement", async ({ page }) => { await mountStyles(page, `
1Goal
`); const node = page.locator(".goal-tree-node"); const card = page.locator(".goal-tree-node-card"); const indicator = page.locator(".nds-reorder-indicator"); await expect(card).toHaveCSS("border-top-color", "rgba(246, 196, 83, 0.48)"); await expect(page.locator(".goal-tree-node-content-row")).toHaveCSS("display", "grid"); for (const axis of ["vertical", "horizontal"]) { for (const placement of ["before", "after"]) { await node.evaluate((element, { axis, placement }) => { element.dataset.reorderOrientation = axis; element.classList.toggle("drop-after", placement === "after"); }, { axis, placement }); await indicator.evaluate((element, { axis, placement }) => { element.dataset.axis = axis; element.dataset.placement = placement; }, { axis, placement }); const side = axis === "vertical" ? (placement === "before" ? "top" : "bottom") : (placement === "before" ? "left" : "right"); const thickness = axis === "vertical" ? "height" : "width"; await expectPseudo(card, "::after", side, "-8px"); await expectPseudo(card, "::after", thickness, "3px"); await expectPseudo(indicator, "::before", side, "0px"); await expectPseudo(indicator, "::before", thickness, "3px"); await expectPseudo(card, "::after", "pointer-events", "none"); } } }); test("calendar picker opens on focus and alert controls retain blocked states", async ({ page }) => { await mountStyles(page, `
`); await expect(page.locator("form")).toHaveCSS("display", "grid"); await expect(page.locator("form")).toHaveCSS("background-color", "rgba(0, 0, 0, 0)"); const picker = page.locator(".calendar-month-date-picker"); await expect(picker).toHaveCSS("opacity", "0"); await expect(picker).toHaveCSS("pointer-events", "none"); await page.locator(".calendar-month-date-trigger").focus(); await expect(picker).toHaveCSS("opacity", "1"); await expect(picker).toHaveCSS("pointer-events", "auto"); expect((await computed(picker, "grid-template-columns")).split(" ")).toHaveLength(7); const alert = page.locator(".timer-alert-mode-switch"); await expect(alert).toHaveCSS("align-self", "start"); await alert.evaluate((element) => element.classList.add("is-blocked")); await expect(alert.locator("button")).toHaveCSS("opacity", "0.2"); await expect(alert.locator("button")).toHaveCSS("cursor", "not-allowed"); const scroll = page.locator(".tool-split-scroll-toggle"); await scroll.focus(); await expect(picker).toHaveCSS("opacity", "0"); await expectPseudo(scroll.locator("i"), "::before", "left", "3px"); await scroll.evaluate((element) => element.classList.add("active")); await expectPseudo(scroll.locator("i"), "::before", "left", "17px"); }); test("combo tokens distinguish analog, directional and held inputs", async ({ page }) => { await mountStyles(page, `
Up Up
`); await expect(page.locator(".combo-hold-controls")).toHaveCSS("width", "92px"); await expect(page.locator(".combo-hold-controls button")).toHaveCSS("background-image", /246, 196, 83/); await expect(page.locator("#analog")).toHaveCSS("color", "rgb(165, 243, 252)"); await expect(page.locator("#direction")).toHaveCSS("color", "rgb(248, 217, 130)"); await expect(page.locator("#direction")).toHaveCSS("border-radius", "10px"); await expect(page.locator(".combo-value-grab .ui-icon")).toHaveCSS("width", "24px"); await expect(page.locator(".has-hold")).toHaveCSS("box-shadow", /7px/); }); for (const width of [1280, 390]) { test(`text exchange and image annotation keep usable bounds at ${width}px`, async ({ page }) => { await page.setViewportSize({ width, height: 900 }); await mountStyles(page, `
`); await expectFits(page.locator(".text-import-modal-dialog")); await expect(page.locator(".text-import-actions")).toHaveCSS("flex-wrap", "wrap"); await expect(page.locator("textarea")).toHaveCSS("border-top-color", "rgba(248, 113, 113, 0.75)"); const marker = page.locator(".annotation-marker"); for (const position of ["0%", "50%", "100%"]) { await marker.evaluate((element, position) => { element.style.setProperty("--marker-x", position); element.style.setProperty("--marker-y", position); }, position); const box = await marker.boundingBox(); const frame = await page.locator("#image").boundingBox(); expect(box.width).toBe(30); expect(box.height).toBe(30); expect(box.x).toBeGreaterThanOrEqual(frame.x); expect(box.y).toBeGreaterThanOrEqual(frame.y); expect(box.x + box.width).toBeLessThanOrEqual(frame.x + frame.width); expect(box.y + box.height).toBeLessThanOrEqual(frame.y + frame.height); } }); } test("public icon masks resolve to decodable assets instead of empty shapes", async ({ page }) => { const icons = ["drag", "dropdown", "enter", "calculator", "scrollable", "hide", "eye-open", "eye-closed", "chevron-down", "chevron-up", "flip", "add", "add-row", "add-column", "remove-row", "remove-column", "table", "map", "grab", "shield", "gun"]; const modules = ["abacus", "calculator", "columns", "table", "chest-armor"]; await page.route("**/__style-icons", (route) => route.fulfill({ contentType: "text/html", body: "" })); await page.goto("/__style-icons"); await mountStyles(page, `
${icons.map((name) => ``).join("")}${modules.map((name) => ``).join("")}
`); for (const icon of await page.locator("i").all()) { await expect(icon).toHaveCSS("mask-size", "contain"); const mask = await computed(icon, "mask-image"); expect(mask).toMatch(/^url\(".+"\)$/); const url = JSON.parse(mask.slice(4, -1)); const decoded = await icon.evaluate(async (element, url) => { const image = new element.ownerDocument.defaultView.Image(); image.src = url; await image.decode(); return image.naturalWidth > 0 && image.naturalHeight > 0; }, url); expect(decoded, url).toBe(true); } });