165 lines
11 KiB
JavaScript
165 lines
11 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");
|
|
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, `<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">
|
|
<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(drawer).toHaveCSS("width", width < 700 ? `${width}px` : "700px");
|
|
await expect(page.locator(".drawer-resize-handle")).toHaveCSS("display", width < 700 ? "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");
|
|
}
|
|
});
|