// Rôle : vérifie les surfaces, feedbacks, overlays et préférences visuelles compilés. import { test, expect } from "@playwright/test"; import { mountStyles, computed, expectPseudo, expectFits } from "./fixtures/compiled-styles.js"; test("panels, tool surfaces and heroes keep layered non-intercepting decoration", async ({ page }) => { await mountStyles(page, `

Hero

Heading

Empty
`); for (const selector of [".nds-panel", ".nds-tool-panel", ".nds-filter-panel", ".nds-page-hero"]) { const surface = page.locator(selector); await expect(surface).toHaveCSS("border-top-width", "1px"); await expect(surface).toHaveCSS("overflow", "hidden"); await expectPseudo(surface, "::before", "pointer-events", "none"); await expectPseudo(surface, "::after", "pointer-events", "none"); expect(await computed(surface, "background-image", "::after")).toContain("gradient"); await expect(surface.locator(":scope > *")).toHaveCSS("z-index", "1"); } await expect(page.locator(".nds-tool-panel")).toHaveCSS("contain", "paint"); await page.locator(".nds-tool-panel").hover(); await expect(page.locator(".nds-tool-panel")).toHaveCSS("box-shadow", /139, 92, 246/); await expect(page.locator(".nds-filter-panel")).toHaveCSS("backdrop-filter", "blur(10px)"); await expect(page.locator(".nds-page-hero")).toHaveCSS("min-height", "180px"); await expect(page.locator(".nds-toolbar")).toHaveCSS("flex-wrap", "wrap"); await expect(page.locator(".nds-section-heading")).toHaveCSS("justify-content", "space-between"); await expect(page.locator(".nds-empty-state")).toHaveCSS("min-height", "132px"); await expect(page.locator(".nds-empty-state")).toHaveCSS("align-content", "center"); }); test("badges, notices, progress and chips expose meaningful visual states", async ({ page }) => { const tones = { success: "rgb(52, 211, 153)", warning: "rgb(251, 191, 36)", danger: "rgb(251, 113, 133)" }; await mountStyles(page, `
${Object.keys(tones).map((tone) => `${tone}
${tone}
`).join("")}
`); for (const [tone, color] of Object.entries(tones)) { await expect(page.locator(`.nds-badge[data-variant="${tone}"]`)).toHaveCSS("color", color); await expect(page.locator(`.nds-inline-notice[data-variant="${tone}"]`)).toHaveCSS("color", color); const progress = page.locator(`.nds-progress[data-variant="${tone}"]`); await expect(progress).toHaveCSS("height", "8px"); await expect(progress).toHaveCSS("overflow", "hidden"); expect(await computed(progress.locator("span"), "background-image")).toContain(color); await expect(progress.locator("span")).toHaveCSS("width", "99px"); await progress.locator("span").evaluate((element) => { element.style.width = "100%"; }); await expect(progress.locator("span")).toHaveCSS("width", "198px"); } const chip = page.locator(".nds-chip"); await expect(chip).toHaveCSS("background-color", "rgba(5, 7, 17, 0.32)"); await chip.locator("input").check(); await page.mouse.move(0, 0); await expect(chip).toHaveCSS("background-color", "rgba(139, 92, 246, 0.14)"); }); for (const width of [1280, 390]) { test(`overlays fit, scroll internally and preserve stacking at ${width}px`, async ({ page }) => { await page.setViewportSize({ width, height: 800 }); await mountStyles(page, `

Confirm

Long content
`); const modal = page.locator(".nds-modal"); await expectFits(modal); await expect(modal).toHaveCSS("overflow-y", "auto"); expect((await modal.boundingBox()).height).toBeLessThanOrEqual(752); await modal.evaluate((element) => { element.scrollTop = 300; }); expect(await modal.evaluate((element) => element.scrollTop)).toBeGreaterThan(0); await expect(page.locator(".confirm-modal-root")).toHaveCSS("overscroll-behavior", "contain"); await expect(page.locator(".nds-modal-backdrop")).toHaveCSS("position", "fixed"); await expect(page.locator(".nds-modal-backdrop")).toHaveCSS("backdrop-filter", "blur(6px)"); await page.locator("body").evaluate((element) => element.classList.add("is-modal-open")); await expect(page.locator("body")).toHaveCSS("overflow", "hidden"); await mountStyles(page, `
Drawer
`); const drawer = page.locator(".drawer-panel"); await expectFits(drawer); await expect(drawer).toHaveCSS("top", width <= 760 ? "110px" : "68px"); await expect(drawer).toHaveCSS("position", "fixed"); await expect(drawer).toHaveCSS("overflow-y", "auto"); await expect(drawer).toHaveCSS("backdrop-filter", "blur(10px)"); await expect(page.locator(".drawer-resize-handle")).toHaveCSS("display", width <= 760 ? "none" : "block"); await page.locator(".drawer").evaluate((element) => element.setAttribute("aria-hidden", "true")); await expect(drawer).toBeHidden(); await mountStyles(page, `
Tools
`); await expectFits(page.locator(".tool-add-modal")); await expect(page.locator(".tool-add-modal-root")).toHaveCSS("overscroll-behavior", "contain"); await expect(page.locator(".tool-add-modal-root")).toHaveCSS("z-index", "130"); }); test(`toast positions, popovers and reduced motion render correctly at ${width}px`, async ({ page }) => { await page.setViewportSize({ width, height: 800 }); await mountStyles(page, `
Popover
Notification
`); const stack = page.locator(".nds-toast-stack"); const toast = page.locator(".nds-toast"); await expect(stack).toHaveCSS("position", "fixed"); await expect(stack).toHaveCSS("pointer-events", "none"); await expect(toast).toHaveCSS("pointer-events", "auto"); await expect(toast).toHaveCSS("background-image", /conic-gradient/); await expect.poll(async () => (await toast.boundingBox()).x + (await toast.boundingBox()).width).toBeLessThanOrEqual(width); await expect(page.locator(".nds-popover")).toHaveCSS("overflow", "hidden"); await expect(page.locator(".nds-popover")).toHaveCSS("background-color", "rgba(7, 10, 24, 0.96)"); await stack.evaluate((element) => { element.dataset.position = "left"; element.classList.add("is-left"); }); await expect(stack).toHaveCSS("left", "20px"); await expect(toast).toHaveCSS("animation-name", /notification-toast-enter-left/); await page.emulateMedia({ reducedMotion: "reduce" }); await expect(toast).toHaveCSS("animation-duration", "1e-05s"); await expect(toast).toHaveCSS("animation-iteration-count", "1"); await expectFits(toast); }); } test("event sizes preserve metadata density and light/dark contrast", async ({ page }) => { await mountStyles(page, `
${["md", "sm", "xs"].map((size) => `
`).join("")}
`); for (const [size, height] of [["md", 44], ["sm", 22], ["xs", 10]]) { const event = page.locator(`[data-size="${size}"]`); await expect(event).toHaveCSS("min-height", `${height}px`); await expect(event.locator("span")).toHaveCSS("display", size === "md" ? "block" : "none"); await expect(event.locator("strong")).toHaveCSS("text-overflow", "ellipsis"); const light = await computed(event.locator("button"), "color"); await event.evaluate((element) => { element.dataset.variant = "dark"; element.style.setProperty("--nds-event-color", "#f6c453"); }); await expect.poll(() => computed(event.locator("button"), "color")).not.toBe(light); await event.locator("button").hover(); await expect(event.locator("button")).toHaveCSS("background-color", size === "xs" ? "rgba(0, 0, 0, 0.18)" : "rgba(0, 0, 0, 0.14)"); } }); test("scrollbars and nebula layers compile without blocking content", async ({ page }) => { const classes = ["themed-scrollbar", "legacy-scrollbar", "nds-scrollbox", "module-content", "table-grid-wrap", "notepad-surface", "annotation-marker-list", "goal-tree-view-wrap", "filter-options"]; await mountStyles(page, classes.map((name) => `
Scroll
`).join("")); for (const name of classes) { const box = page.locator(`.${name}`); await expect(box).toHaveCSS("scrollbar-width", "thin"); await expect(box).toHaveCSS("scrollbar-color", "rgb(88, 55, 174) rgba(0, 0, 0, 0)"); await expectPseudo(box, "::-webkit-scrollbar", "width", "6px"); await box.evaluate((element) => { element.scrollTop = 100; }); expect(await box.evaluate((element) => element.scrollTop)).toBeGreaterThan(0); } for (const pseudo of ["::before", "::after"]) { await expectPseudo(page.locator("body"), pseudo, "position", "fixed"); await expectPseudo(page.locator("body"), pseudo, "pointer-events", "none"); expect(await computed(page.locator("body"), "background-image", pseudo)).toContain("radial-gradient"); } });