135 lines
9 KiB
JavaScript
135 lines
9 KiB
JavaScript
// 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);
|
|
}
|
|
});
|