sokko-g/e2e/nds-patterns.spec.js
Shinuwa e52d6e66eb
All checks were successful
Deploy Sokko G / deploy (push) Successful in 18s
Cover remaining Sass families and fix autocomplete hover
2026-09-14 13:58:40 +02:00

151 lines
10 KiB
JavaScript

// 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, `<main>
<section class="nds-panel"><button>Panel</button></section>
<section class="nds-tool-panel" data-variant="interactive"><button>Tool</button></section>
<section class="nds-filter-panel"><button>Filter</button></section>
<header class="nds-page-hero"><h1>Hero</h1></header>
<div class="nds-toolbar"><button>A</button><button>B</button></div>
<div class="nds-section-heading"><h2>Heading</h2><button>Action</button></div>
<div class="nds-empty-state">Empty</div>
</main>`);
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, `<main>
${Object.keys(tones).map((tone) => `<span class="nds-badge" data-variant="${tone}">${tone}</span><div class="nds-inline-notice" data-variant="${tone}">${tone}</div><div class="nds-progress" data-variant="${tone}" style="width:200px"><span style="width:50%"></span></div>`).join("")}
<label class="nds-chip"><input type="checkbox">Choice</label>
</main>`);
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, `<div class="confirm-modal-root">
<div class="nds-modal-backdrop"></div>
<section class="nds-modal confirm-modal"><h2>Confirm</h2><div style="height:1200px">Long content</div><footer class="nds-modal-footer"><button>Close</button></footer></section>
</div>`);
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, `<div class="drawer" aria-hidden="false"><div class="drawer-backdrop"></div>
<section class="drawer-panel nds-drawer-panel"><div style="height:1400px">Drawer</div></section>
<div class="drawer-resize-handle nds-drawer-resize-handle"></div></div>`);
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, `<div class="tool-add-modal-root"><section class="nds-modal tool-add-modal">Tools</section></div>`);
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, `<div class="nds-popover">Popover</div>
<div class="nds-toast-stack notification-toast-stack" data-position="right"><div class="nds-toast notification-toast"><span>Notification</span><button>OK</button></div></div>`);
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, `<main>${["md", "sm", "xs"].map((size) => `<div class="nds-event-surface" data-size="${size}"><button><strong>Title</strong><span>12:30</span></button></div>`).join("")}</main>`);
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) => `<div class="${name}" style="height:60px;overflow:auto"><div style="height:500px">Scroll</div></div>`).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");
}
});