Cover remaining Sass families and fix autocomplete hover
All checks were successful
Deploy Sokko G / deploy (push) Successful in 18s

This commit is contained in:
Shinuwa 2026-09-14 13:58:40 +02:00
parent 77f357e056
commit e52d6e66eb
13 changed files with 617 additions and 286 deletions

View file

@ -0,0 +1,135 @@
// 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, `<div class="table-module"><div class="table-grid-wrap"><input type="text" class="table-cell-input" value="12"></div></div>`);
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, `<main>
<div class="goal-tree-node is-drop-target" data-reorder-orientation="vertical"><div class="goal-tree-node-card"><div class="goal-tree-node-content-row"><span>1</span><span>Goal</span></div></div></div>
<div class="nds-reorder-indicator" style="width:200px;height:60px"></div>
</main>`);
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, `<main><form class="calendar-event-form">
<div class="calendar-month-date-dropdown"><button type="button" class="calendar-month-date-trigger">Date</button><div class="calendar-month-date-picker"><button type="button">1</button></div></div>
<div class="timer-alert-mode-switch calendar-alert-mode-switch"><button type="button" class="timer-alert-mode-button">Alert</button></div>
<button type="button" class="tool-split-scroll-toggle"><span class="ui-icon"></span><i></i></button>
</form></main>`);
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, `<main>
<div class="combo-hold-field"><div class="combo-hold-controls"><button class="active">Hold</button><input type="number" value="1"></div></div>
<span id="analog" class="combo-input-token combo-input-direction combo-device-n64 combo-value-stick-up">Up</span>
<span id="direction" class="combo-input-token combo-input-direction combo-device-n64 combo-value-up">Up</span>
<span class="combo-input-token combo-value-grab has-hold"><i class="ui-icon"></i></span>
</main>`);
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, `<main>
<div class="text-import-modal-dialog nds-modal"><form class="text-import-modal-form"><textarea class="has-error"></textarea><footer class="text-import-actions"><button>Import</button><button>Export</button></footer></form></div>
<div id="image" style="position:relative;width:300px;height:200px"><button class="annotation-marker">1</button></div>
</main>`);
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: "<!doctype html><html><body></body></html>" }));
await page.goto("/__style-icons");
await mountStyles(page, `<main>${icons.map((name) => `<i class="ui-icon ui-icon-${name}"></i>`).join("")}${modules.map((name) => `<i class="module-icon-svg module-icon-${name}"></i>`).join("")}</main>`);
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);
}
});