sokko-g/e2e/style-layouts.spec.js
Shinuwa 0ac1a0974e
All checks were successful
Deploy Sokko G / deploy (push) Successful in 19s
Refocus responsive layouts on compact desktop use
2026-10-05 15:47:46 +02:00

192 lines
13 KiB
JavaScript

// Rôle : vérifie les layouts responsive réels et les contraintes de placement compilées.
import { test, expect } from "@playwright/test";
import { mountStyles, computed, expectFits } from "./fixtures/compiled-styles.js";
async function columns(locator, count) {
await expect(locator).toHaveCSS("display", "grid");
await expect.poll(async () => (await computed(locator, "grid-template-columns")).split(" ").length).toBe(count);
}
test("calculator layout follows the tool width rather than its location", async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 900 });
await mountStyles(page, `<article class="module" data-module-type="calculator" style="width: 900px"><div class="calculator-module"><form class="calculator-form"><div class="calculator-card"><div class="calculator-field">Formula</div><div class="calculator-current-field">Current</div><div class="calculator-result-field"><span>Result</span><div class="calculator-result"><strong>12</strong></div></div><label class="calculator-label-field">Label</label><button class="calculator-save-button">Save</button></div></form><div>Entries</div></div></article>`);
const tool = page.locator('[data-module-type="calculator"]');
const calculator = tool.locator(".calculator-module");
const card = tool.locator(".calculator-card");
await expect(tool).toHaveCSS("container-type", "inline-size");
await columns(calculator, 2);
await columns(card, 1);
await tool.evaluate((element) => { element.style.width = "700px"; });
await columns(calculator, 1);
await columns(card, 2);
await expect(tool.locator(".calculator-field")).toHaveCSS("grid-column-start", "1");
await expect(tool.locator(".calculator-result-field")).toHaveCSS("grid-column-start", "2");
await expect(tool.locator(".calculator-result")).toHaveCSS("height", "36px");
await expect(tool.locator(".calculator-result")).toHaveCSS("min-height", "36px");
const resultValue = tool.locator(".calculator-result strong");
const compactResultFontSize = Number.parseFloat(await computed(resultValue, "font-size"));
await expect(tool.locator(".calculator-result")).toHaveCSS("padding-top", "7px");
await tool.locator(".calculator-result-field").evaluate((element) => { element.classList.add("has-current"); });
await expect.poll(async () => Number.parseFloat(await computed(tool.locator(".calculator-result"), "height"))).toBeGreaterThan(36);
await expect.poll(async () => Number.parseFloat(await computed(resultValue, "font-size"))).toBeGreaterThan(compactResultFontSize);
await expect(tool.locator(".calculator-result")).toHaveCSS("padding-top", "12px");
await tool.evaluate((element) => { element.style.width = "480px"; });
await columns(card, 1);
await expect(tool.locator(".calculator-result")).toHaveCSS("min-height", "72px");
});
test("toolbox columns follow the toolbox content width", async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 720 });
await mountStyles(page, `<section class="toolbox-page" style="width: 920px">
<div class="modules-toolbar"><div class="layout-switch">Layout</div></div>
<div class="modules" data-layout-cols="2"><div class="module-column"><article class="module">A</article></div><div class="module-column"><article class="module">B</article></div></div>
</section>`);
const toolbox = page.locator(".toolbox-page");
const modules = toolbox.locator(".modules");
await columns(modules, 2);
await expect(toolbox.locator(".modules-toolbar")).toBeVisible();
await toolbox.evaluate((element) => { element.style.width = "899px"; });
await columns(modules, 1);
await expect(toolbox.locator(".modules-toolbar")).toBeHidden();
await expect(toolbox.locator(".module-column").first()).toHaveCSS("display", "contents");
});
for (const width of [1920, 1280, 1025, 1024, 960, 768]) {
test(`home, about and library retain their responsive layout at ${width}px`, async ({ page }) => {
await page.setViewportSize({ width, height: 900 });
await page.goto("/");
const onboarding = page.locator(".home-onboarding");
await expectFits(onboarding);
const hero = page.locator(".hero");
const heroContent = hero.locator(":scope > div").first();
const heroStats = hero.locator(".hero-stats");
await columns(hero, width <= 1024 ? 1 : 2);
const [heroContentBounds, heroStatsBounds] = await Promise.all([heroContent.boundingBox(), heroStats.boundingBox()]);
if (width <= 1024) {
expect(heroStatsBounds.y).toBeGreaterThanOrEqual(heroContentBounds.y + heroContentBounds.height);
expect(heroStatsBounds.x).toBeCloseTo(heroContentBounds.x, 0);
} else {
expect(heroStatsBounds.x).toBeGreaterThan(heroContentBounds.x + heroContentBounds.width);
}
await columns(page.locator(".home-onboarding-layout"), width <= 1024 ? 1 : 2);
await expect(page.locator(".home-onboarding-slider figcaption")).toHaveCSS("position", "absolute");
await expect(page.locator(".home-onboarding-dots")).toHaveCSS("display", "flex");
await expect(page.locator(".home-onboarding-slider img")).toHaveCSS("object-fit", "cover");
await expect(page.locator(".home-visual")).toHaveCSS("display", width <= 1024 ? "none" : "grid");
await expect(page.locator(".origin-copy")).toHaveCSS("border-right-width", width <= 1024 ? "0px" : "2px");
await columns(page.locator(".origin-section"), width <= 1024 ? 1 : 2);
const side = page.locator(".sidebar");
await expect(side).toHaveCSS("display", "flex");
{
const responsiveCompact = width <= 1024;
if (responsiveCompact) await expect(side).toHaveAttribute("data-variant", "compact");
else await expect(side).not.toHaveAttribute("data-variant");
await expect(side).toHaveCSS("width", responsiveCompact ? "72px" : "248px");
await expect(page.locator(".sidebar-mascot")).toHaveCSS("display", responsiveCompact ? "none" : "block");
const secondaryLinks = page.locator(".sidebar-secondary-links");
await expect(secondaryLinks).toHaveCSS("display", "grid");
await expect(page.locator(".sidebar-about-link")).toHaveCount(3);
await expect(page.locator(".sidebar-about-link strong").first()).toHaveCSS("display", responsiveCompact ? "none" : "block");
await expect(page.locator(".sidebar-note")).toHaveCSS("display", "block");
await expect(page.locator(".sidebar-storage-actions")).toHaveCSS("flex-direction", responsiveCompact ? "column" : "row");
await expect(page.locator(".sidebar-action-button")).toHaveCount(2);
await expect(page.locator(".nav-item")).toHaveCount(3);
await expect(page.locator(".nav-item strong").first()).toHaveCSS("display", responsiveCompact ? "none" : "block");
const brand = page.locator(".brand");
const brandMark = page.locator(".brand-mark");
await expect(brandMark).toHaveCSS("animation-name", "none");
await expect(brandMark).toHaveCSS("transform", "none");
await brand.hover();
await expect.poll(async () => brandMark.evaluate((element) => globalThis.getComputedStyle(element).transform)).not.toBe("none");
if (!responsiveCompact) {
const storageActions = await page.locator(".sidebar-action-button").evaluateAll((elements) => elements.map((element) => element.getBoundingClientRect()));
expect(storageActions[1].x).toBeGreaterThan(storageActions[0].x);
expect(storageActions[1].y).toBeCloseTo(storageActions[0].y, 0);
const secondaryBox = await secondaryLinks.boundingBox();
const storageBox = await page.locator(".sidebar-note").boundingBox();
expect(secondaryBox.y + secondaryBox.height).toBeLessThan(storageBox.y);
}
}
await page.goto("/#/about");
await expectFits(page.locator(".about-page"));
await expect(page.locator(".about-story-section")).toHaveCSS("display", "grid");
await columns(page.locator(".about-tools-list"), width <= 760 ? 1 : 2);
await expect(page.locator(".about-tool-item .module-icon").first()).toHaveCSS("width", "34px");
await page.goto("/#/library");
await expectFits(page.locator(".library-page"));
await columns(page.locator(".library-doc-layout"), width <= 1024 ? 1 : 2);
await columns(page.locator(".library-example-layout.has-legend").first(), width <= 1024 ? 1 : 2);
expect(await page.locator("body").evaluate((element) => element.scrollWidth)).toBeLessThanOrEqual(width);
});
}
for (const width of [640, 390]) {
test(`below the validated width the compact sidebar remains available without phone navigation at ${width}px`, async ({ page }) => {
await page.setViewportSize({ width, height: 720 });
await page.goto("/#/games/aniimo/catalog");
await expect(page.locator(".sidebar")).toBeVisible();
await expect(page.locator(".sidebar")).toHaveCSS("width", "72px");
await expect(page.locator(".mobile-nav")).toHaveCount(0);
await expect(page.locator(".route-content-view")).toBeVisible();
await expect(page.locator(".compact-game-view-switch")).toBeVisible();
expect(await page.evaluate(() => globalThis.document.documentElement.scrollWidth)).toBeLessThanOrEqual(width);
});
}
for (const width of [1280, 768]) {
test(`game grids and toolbox columns adapt without horizontal overflow at ${width}px`, async ({ page }) => {
await page.setViewportSize({ width, height: 900 });
await mountStyles(page, `<main>
<div class="game-layout"><aside class="game-filters"><div class="filter-panel nds-filter-panel">Filter</div></aside><div class="game-grid"><article>Game</article><article>Game</article></div></div>
<div class="modules" data-layout-cols="2"><div class="module-column"><article class="module">A</article></div><div class="module-column"><article class="module">B</article></div></div>
<article class="module layout-calculator" data-module-type="calculator"><div class="calculator-module"><div>Formula</div><div class="calculator-result"><strong>12</strong></div></div></article>
<div class="task-planner-module"><div class="task-planner-settings"><label>Reset</label><label>Time</label></div></div>
<div class="equipment-planner-summary"><div class="equipment-planner-summary-source-grid"><div>Equipment</div><div>Materials</div></div></div>
<div class="toolbox-actions-row"><div>Actions</div><div>Preferences</div></div>
</main>`);
await columns(page.locator(".game-layout"), 1);
await expect(page.locator(".game-grid")).toHaveCSS("display", "grid");
await columns(page.locator(".modules"), width <= 760 ? 1 : 2);
await expect(page.locator(".module-column").first()).toHaveCSS("display", width <= 760 ? "contents" : "grid");
await columns(page.locator(".layout-calculator .calculator-module"), width <= 820 ? 1 : 2);
await expect(page.locator(".calculator-result")).toHaveCSS("min-height", "72px");
await expect(page.locator(".task-planner-module")).toHaveCSS("display", "grid");
await columns(page.locator(".task-planner-settings"), width <= 760 ? 1 : 2);
await expect(page.locator(".equipment-planner-summary")).toHaveCSS("padding", "12px");
await columns(page.locator(".equipment-planner-summary-source-grid"), width <= 760 ? 1 : 2);
await page.locator(".equipment-planner-summary-source-grid").evaluate((element) => element.classList.add("columns-1"));
await columns(page.locator(".equipment-planner-summary-source-grid"), 1);
await expect(page.locator(".toolbox-actions-row")).toHaveCSS("flex-direction", width <= 760 ? "column" : "row");
await page.locator(".modules").evaluate((element) => { element.dataset.layoutCols = "1"; });
await columns(page.locator(".modules"), 1);
await expectFits(page.locator("main"));
});
test(`cover crop and icon picker respect image geometry at ${width}px`, async ({ page }) => {
await page.setViewportSize({ width, height: 900 });
await mountStyles(page, `<main>
<section class="toolbox-cover-editor"><div class="toolbox-cover-crop-frame"></div></section>
<div class="toolbox-icon-picker"><button class="toolbox-icon-picker-button"><img alt="Icon"></button>
<div class="toolbox-icon-picker-menu"><button>A</button><button class="toolbox-cover-menu-action nds-button">Cover</button></div></div>
<div class="toolbox-game-icon"><img alt="Game"></div><a class="toolbox-hero-link">Game page</a>
<p class="dialogue-line"><i>Aside</i></p>
</main>`);
const frame = page.locator(".toolbox-cover-crop-frame");
await expectFits(frame);
const bounds = await frame.boundingBox();
expect(bounds.width / bounds.height).toBeCloseTo(620 / 248, 2);
await expect(frame).toHaveCSS("touch-action", "none");
await expect(frame).toHaveCSS("overflow", "hidden");
const icon = page.locator(".toolbox-icon-picker-button");
await expect(icon.locator("img")).toHaveCSS("object-fit", "contain");
await expect(icon.locator("img")).toHaveCSS("width", "72px");
expect(await computed(icon.locator("img"), "border-radius")).toBe(await computed(icon, "border-radius"));
await icon.evaluate((element) => element.classList.add("has-custom-cover"));
await expect(icon.locator("img")).toHaveCSS("object-fit", "cover");
await expect(page.locator(".toolbox-game-icon img")).toHaveCSS("object-fit", "cover");
await expect(page.locator(".toolbox-cover-menu-action")).toHaveCSS("grid-column", "1 / -1");
await expect(page.locator(".toolbox-icon-picker-menu")).toHaveCSS("position", "absolute");
await expect(page.locator(".toolbox-hero-link")).toHaveCSS("display", "flex");
await expect(page.locator(".dialogue-line i")).toHaveCSS("font-style", "italic");
});
}