// 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, `
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");
await progress.evaluate((element) => { element.dataset.size = "sm"; });
await expect(progress).toHaveCSS("height", "4px");
}
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)");
await chip.evaluate((element) => {
element.dataset.size = "sm";
const image = globalThis.document.createElement("img");
image.src = "/static/icons/multiples.svg";
image.alt = "";
element.append(image);
});
await expect(chip).toHaveCSS("min-height", "30px");
await expect(chip).toHaveCSS("font-size", "12px");
await expect(chip.locator("img")).toHaveCSS("width", "16px");
await expect(chip.locator("img")).toHaveCSS("height", "16px");
});
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, ``);
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, ``);
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, ``);
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
`);
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) => ``).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");
}
});