Add responsive game filters and hybrid toolbox drawer
All checks were successful
Deploy Sokko G / deploy (push) Successful in 20s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 20s
This commit is contained in:
parent
1ee460b94f
commit
00e73f0062
32 changed files with 1202 additions and 241 deletions
|
|
@ -3,19 +3,51 @@ import { test, expect } from "@playwright/test";
|
|||
|
||||
const route = "/#/games/aniimo/aniimos";
|
||||
const cards = (page) => page.locator(".aniimo-result");
|
||||
const section = (page, name) => page.getByRole("group", { name, exact: true }).filter({ has: page.locator("legend") });
|
||||
const filterTrigger = (page, name) => page.getByRole("button", { name: new RegExp(`^${name}`) });
|
||||
const filterMenu = (page, name) => page.getByRole("dialog", { name: new RegExp(`^${name}`) });
|
||||
async function openFilter(page, name) {
|
||||
const trigger = filterTrigger(page, name);
|
||||
if (await trigger.getAttribute("aria-expanded") !== "true") await trigger.click();
|
||||
await expect(trigger).toHaveAttribute("aria-expanded", "true");
|
||||
return filterMenu(page, name);
|
||||
}
|
||||
|
||||
test("role and element menus use two columns and highlight their condition", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1280, height: 900 });
|
||||
await page.goto(route);
|
||||
const roles = await openFilter(page, "Rôles");
|
||||
await expect(roles).toHaveAttribute("data-columns", "2");
|
||||
expect(await roles.locator(".nds-filter-menu-options").evaluate((element) => globalThis.getComputedStyle(element).gridTemplateColumns.split(" ").length)).toBe(2);
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
const elements = await openFilter(page, "Éléments");
|
||||
await expect(elements).toHaveAttribute("data-columns", "2");
|
||||
expect(await elements.locator(".nds-filter-menu-options").evaluate((element) => globalThis.getComputedStyle(element).gridTemplateColumns.split(" ").length)).toBe(2);
|
||||
expect((await elements.boundingBox()).width).toBeLessThanOrEqual(320);
|
||||
expect((await elements.locator(".nds-filter-menu-option").first().boundingBox()).width).toBeLessThan(160);
|
||||
const condition = elements.locator(".nds-filter-logic");
|
||||
await expect(condition).toHaveCSS("border-top-style", "solid");
|
||||
await expect(condition).not.toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
|
||||
});
|
||||
|
||||
for (const width of [1280, 390]) {
|
||||
test(`Prismana filter combines with search and survives grouping and navigation at ${width}px`, async ({ page }) => {
|
||||
test.setTimeout(45000);
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
await page.goto(route);
|
||||
const prismana = page.getByRole("checkbox", { name: "Prismana uniquement", exact: true });
|
||||
const prismana = page.getByRole("button", { name: "Prismana uniquement", exact: true });
|
||||
await expect(prismana).toBeVisible({ timeout: 30000 });
|
||||
await expect(prismana).not.toBeChecked();
|
||||
await expect(prismana).toHaveAttribute("aria-pressed", "false");
|
||||
await prismana.focus();
|
||||
await prismana.press("Space");
|
||||
await expect(cards(page)).toHaveCount(27);
|
||||
await expect(prismana).toHaveText("Prismana");
|
||||
await expect(prismana).toHaveClass(/nds-filter-option/);
|
||||
const prismanaBadge = page.getByRole("button", { name: "Retirer le filtre Prismana", exact: true });
|
||||
await expect(prismanaBadge).toBeVisible();
|
||||
await prismanaBadge.click();
|
||||
await expect(prismana).toHaveAttribute("aria-pressed", "false");
|
||||
await prismana.click();
|
||||
await expect(page.locator('.aniimo-result:not([data-form="prismana_form"])')).toHaveCount(0);
|
||||
await expect(page.getByRole("tablist")).toHaveCount(0);
|
||||
expect(await page.locator('img[src="/static/icons/prismana.svg"]').evaluate((image) => image.complete && image.naturalWidth > 0)).toBe(true);
|
||||
|
|
@ -23,14 +55,14 @@ for (const width of [1280, 390]) {
|
|||
await expect(cards(page)).toHaveCount(3);
|
||||
await page.locator('.game-breadcrumb a[href="#/games/aniimo"]').click();
|
||||
await page.locator('a[href="#/games/aniimo/aniimos"]').click();
|
||||
await expect(prismana).toBeChecked();
|
||||
await expect(prismana).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(cards(page)).toHaveCount(3);
|
||||
await page.getByRole("button", { name: "Regrouper les variantes", exact: true }).click();
|
||||
await expect(cards(page)).toHaveCount(3);
|
||||
await page.getByRole("searchbox").fill("emberpup");
|
||||
await expect(page.getByRole("heading", { name: "Aucun résultat" })).toBeVisible();
|
||||
await page.getByRole("button", { name: "Réinitialiser les filtres", exact: true }).click();
|
||||
await expect(prismana).not.toBeChecked();
|
||||
await expect(prismana).toHaveAttribute("aria-pressed", "false");
|
||||
await expect(cards(page)).toHaveCount(208);
|
||||
await expect(page.getByRole("button", { name: "Regrouper les variantes", exact: true })).toHaveAttribute("aria-pressed", "false");
|
||||
expect(await page.evaluate(() => globalThis.document.documentElement.scrollWidth <= globalThis.innerWidth)).toBe(true);
|
||||
|
|
@ -64,12 +96,14 @@ test("Aniimo matches MHW card motion, surfaces and filter controls", async ({ pa
|
|||
const indicator = await styles(page.locator(".mhwilds-flip-indicator").first(), indicatorProperties);
|
||||
const filter = await styles(page.locator(".filter-logic"), filterProperties);
|
||||
await page.goto(route);
|
||||
await openFilter(page, "Éléments");
|
||||
const card = cards(page).first();
|
||||
await expect(card).toBeVisible();
|
||||
expect(await styles(card.locator(".aniimo-card-inner"), motionProperties)).toEqual(motion);
|
||||
expect(await styles(card.locator(".aniimo-front"), faceProperties)).toEqual(face);
|
||||
expect(await styles(card.locator(".aniimo-front .aniimo-flip"), indicatorProperties)).toEqual(indicator);
|
||||
expect(await styles(page.locator(".filter-logic").first(), filterProperties)).toEqual(filter);
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(card.locator(".aniimo-face .aniimo-flip")).toHaveCount(2);
|
||||
await expect(card.locator(".aniimo-card > .aniimo-flip")).toHaveCount(0);
|
||||
await card.getByRole("button", { name: /Afficher les capacités/ }).press("Enter");
|
||||
|
|
@ -132,12 +166,13 @@ test("tabs, flip, matching variants and reset remain independent", async ({ page
|
|||
await page.keyboard.press("ArrowRight");
|
||||
await expect(card).toHaveAttribute("data-form", "mountain_woods_form");
|
||||
await expect(card.locator(".aniimo-back")).toHaveAttribute("aria-hidden", "false");
|
||||
await section(page, "Éléments").getByRole("checkbox", { name: "Roche", exact: true }).check();
|
||||
const elements = await openFilter(page, "Éléments");
|
||||
await elements.getByRole("checkbox", { name: "Roche", exact: true }).check();
|
||||
await expect(card.getByRole("tab")).toHaveCount(0);
|
||||
await expect(card).toHaveAttribute("data-form", "highlands");
|
||||
await section(page, "Éléments").getByRole("checkbox", { name: "Eau", exact: true }).check();
|
||||
await elements.getByRole("checkbox", { name: "Eau", exact: true }).check();
|
||||
await expect(page.getByRole("heading", { name: "Aucun résultat" })).toBeVisible();
|
||||
await section(page, "Éléments").getByRole("button", { name: "OU", exact: true }).click();
|
||||
await elements.getByRole("button", { name: "OU", exact: true }).click();
|
||||
await expect(cards(page)).toHaveCount(1);
|
||||
await page.getByRole("button", { name: "Regrouper les variantes", exact: true }).click();
|
||||
await expect(cards(page)).toHaveCount(1);
|
||||
|
|
@ -148,9 +183,21 @@ test("tabs, flip, matching variants and reset remain independent", async ({ page
|
|||
|
||||
test("ability levels use independent native ranges and retain name constraints", async ({ page }) => {
|
||||
await page.goto(route);
|
||||
const abilities = section(page, "Compétences de logis");
|
||||
const abilities = await openFilter(page, "Compétences de logis");
|
||||
const carry = page.locator("#aniimo-minimum-carry");
|
||||
const crafting = page.locator("#aniimo-minimum-crafting");
|
||||
const abilityColumns = abilities.locator(".aniimo-ability-column");
|
||||
await expect(abilityColumns).toHaveCount(2);
|
||||
await expect(abilityColumns.nth(0)).toHaveAttribute("aria-label", "Éléments");
|
||||
await expect(abilityColumns.nth(1)).toHaveAttribute("aria-label", "Actions");
|
||||
await expect(abilities.locator(".aniimo-ability-column-title")).toHaveCount(0);
|
||||
await expect(abilityColumns.nth(0)).toHaveCSS("border-right-style", "solid");
|
||||
expect((await abilityColumns.nth(0).boundingBox()).height).toBeGreaterThan((await abilityColumns.nth(1).boundingBox()).height);
|
||||
await expect(abilityColumns.nth(0).getByRole("slider", { name: /Eau/ })).toBeVisible();
|
||||
await expect(abilityColumns.nth(1).getByRole("slider", { name: /Artisanat/ })).toBeVisible();
|
||||
expect(await abilities.locator(".aniimo-ability-options").evaluate((element) => globalThis.getComputedStyle(element).gridTemplateColumns.split(" ").length)).toBe(2);
|
||||
expect((await abilities.boundingBox()).width).toBeGreaterThanOrEqual(600);
|
||||
await expect(abilities.locator(".aniimo-ability-options")).toHaveCSS("overflow-y", "visible");
|
||||
await expect(abilities.getByRole("checkbox")).toHaveCount(0);
|
||||
await expect(abilities.getByRole("button", { name: "OU", exact: true })).toHaveCount(0);
|
||||
const minimumLevel = abilities.getByRole("button", { name: "Niveau minimum", exact: true });
|
||||
|
|
@ -166,6 +213,7 @@ test("ability levels use independent native ranges and retain name constraints",
|
|||
await carry.press("ArrowRight");
|
||||
await carry.press("ArrowRight");
|
||||
await expect(carry).toHaveValue("3");
|
||||
await expect(page.getByRole("button", { name: "Retirer le filtre Porter ≥ 3", exact: true })).toBeVisible();
|
||||
await expect(crafting).toHaveValue("0");
|
||||
await crafting.focus();
|
||||
await crafting.press("ArrowRight");
|
||||
|
|
@ -176,16 +224,23 @@ test("ability levels use independent native ranges and retain name constraints",
|
|||
await expect(exactLevel).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(minimumLevel).toHaveAttribute("aria-pressed", "false");
|
||||
await expect(carry).toHaveAccessibleName("Porter — Niveau exact");
|
||||
await expect(section(page, "Éléments").getByRole("button", { name: "ET", exact: true })).toHaveAttribute("aria-pressed", "true");
|
||||
await carry.press("Home");
|
||||
await crafting.press("Home");
|
||||
await expect(carry).toHaveAttribute("aria-valuetext", "Sans filtre");
|
||||
await page.keyboard.press("Escape");
|
||||
const elements = await openFilter(page, "Éléments");
|
||||
await expect(elements.getByRole("button", { name: "ET", exact: true })).toHaveAttribute("aria-pressed", "true");
|
||||
await page.keyboard.press("Escape");
|
||||
const reopenedAbilities = await openFilter(page, "Compétences de logis");
|
||||
const reopenedCarry = reopenedAbilities.getByRole("slider", { name: "Porter — Niveau exact" });
|
||||
const reopenedCrafting = reopenedAbilities.getByRole("slider", { name: "Artisanat — Niveau exact" });
|
||||
await reopenedCarry.press("Home");
|
||||
await reopenedCrafting.press("Home");
|
||||
await expect(reopenedCarry).toHaveAttribute("aria-valuetext", "Sans filtre");
|
||||
await expect(cards(page)).toHaveCount(88);
|
||||
await page.getByRole("searchbox").fill("does-not-exist");
|
||||
await expect(page.getByRole("heading", { name: "Aucun résultat" })).toBeVisible();
|
||||
await page.getByRole("button", { name: "Réinitialiser les filtres", exact: true }).click();
|
||||
await expect(minimumLevel).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(exactLevel).toHaveAttribute("aria-pressed", "false");
|
||||
const resetAbilities = await openFilter(page, "Compétences de logis");
|
||||
await expect(resetAbilities.getByRole("button", { name: "Niveau minimum", exact: true })).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(resetAbilities.getByRole("button", { name: "Niveau exact", exact: true })).toHaveAttribute("aria-pressed", "false");
|
||||
});
|
||||
|
||||
test("seven tabs support keyboard navigation and fall back after filtering", async ({ page }) => {
|
||||
|
|
@ -211,14 +266,16 @@ test("seven tabs support keyboard navigation and fall back after filtering", asy
|
|||
await expect(card).toHaveAttribute("data-form", "basic_form");
|
||||
await card.getByRole("tab", { name: "Forme prismana", exact: true }).click();
|
||||
await expect(card).toHaveAttribute("data-form", "prismana_form");
|
||||
await section(page, "Éléments").getByRole("checkbox", { name: "Herbe", exact: true }).check();
|
||||
const elements = await openFilter(page, "Éléments");
|
||||
await elements.getByRole("checkbox", { name: "Herbe", exact: true }).check();
|
||||
await expect(card).toHaveAttribute("data-form", "prismana_form");
|
||||
await section(page, "Éléments").getByRole("checkbox", { name: "Herbe", exact: true }).uncheck();
|
||||
await elements.getByRole("checkbox", { name: "Herbe", exact: true }).uncheck();
|
||||
await page.getByRole("searchbox").fill("emberpup");
|
||||
await expect(cards(page)).toHaveAttribute("data-form", "basic_form");
|
||||
await section(page, "Éléments").getByRole("checkbox", { name: "Roche", exact: true }).check();
|
||||
const reopenedElements = await openFilter(page, "Éléments");
|
||||
await reopenedElements.getByRole("checkbox", { name: "Roche", exact: true }).check();
|
||||
await expect(cards(page)).toHaveAttribute("data-form", "highlands");
|
||||
await section(page, "Éléments").getByRole("checkbox", { name: "Roche", exact: true }).uncheck();
|
||||
await reopenedElements.getByRole("checkbox", { name: "Roche", exact: true }).uncheck();
|
||||
await expect(cards(page)).toHaveAttribute("data-form", "highlands");
|
||||
});
|
||||
|
||||
|
|
@ -325,12 +382,12 @@ test("portrait geometry, stable faces and mobile layout", async ({ page }) => {
|
|||
expect(await page.evaluate(() => globalThis.document.documentElement.scrollWidth <= globalThis.innerWidth)).toBe(true);
|
||||
});
|
||||
|
||||
test("desktop grid shows five columns with the multiple icon and complete stats", async ({ page }) => {
|
||||
test("desktop grid uses the width freed by horizontal filters and shows complete stats", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1920, height: 1080 });
|
||||
await page.goto(route);
|
||||
await expect(cards(page)).toHaveCount(88);
|
||||
const columns = await page.locator(".aniimo-grid").evaluate((element) => globalThis.getComputedStyle(element).gridTemplateColumns.split(" ").length);
|
||||
expect(columns).toBe(5);
|
||||
expect(columns).toBe(6);
|
||||
await expect(page.locator(".ui-icon-multiple")).toHaveCSS("mask-image", /multiples\.svg/);
|
||||
const card = cards(page).first();
|
||||
await card.getByRole("button", { name: /Afficher les capacités/ }).click();
|
||||
|
|
@ -339,48 +396,35 @@ test("desktop grid shows five columns with the multiple icon and complete stats"
|
|||
await page.screenshot({ path: test.info().outputPath("aniimo-stats-desktop.png") });
|
||||
});
|
||||
|
||||
test("filter categories share one scroll and retain their selections when collapsed", async ({ page }) => {
|
||||
test("advanced filter menus retain selections, counts and keyboard closing", async ({ page }) => {
|
||||
await page.goto(route);
|
||||
const elements = section(page, "Éléments");
|
||||
const roles = section(page, "Rôles");
|
||||
const abilities = section(page, "Compétences de logis");
|
||||
await expect(page.locator(".aniimo-filter-options")).toHaveCount(3);
|
||||
for (const grid of await page.locator(".aniimo-chip-options").all()) {
|
||||
expect(await grid.evaluate((element) => globalThis.getComputedStyle(element).gridTemplateColumns.split(" ").length)).toBe(2);
|
||||
}
|
||||
const groups = await page.locator(".aniimo-ability-filter").evaluateAll((rows) => rows.map((row) => row.dataset.abilityGroup));
|
||||
expect(groups).toEqual([...groups].sort());
|
||||
await expect(page.locator(".aniimo-filter-toggle").first()).toHaveText("Rôles");
|
||||
const roles = await openFilter(page, "Rôles");
|
||||
await expect(roles.getByRole("checkbox", { name: "SOIN", exact: true })).toHaveCount(1);
|
||||
await expect(roles.getByRole("checkbox", { name: "SOUTIEN", exact: true })).toHaveCount(1);
|
||||
const elementScroll = elements.getByRole("region", { name: "Éléments", exact: true });
|
||||
const scroll = page.locator(".aniimo-filter-sections");
|
||||
await expect(scroll).toHaveCSS("overflow-y", "auto");
|
||||
for (const options of await page.locator(".aniimo-filter-options").all()) {
|
||||
await expect(options).toHaveCSS("overflow-y", "visible");
|
||||
expect(await options.evaluate((element) => element.scrollHeight <= element.clientHeight)).toBe(true);
|
||||
}
|
||||
await scroll.evaluate((element) => { element.scrollTop = element.scrollHeight; });
|
||||
expect(await scroll.evaluate((element) => element.scrollTop)).toBeGreaterThan(0);
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(filterTrigger(page, "Rôles")).toBeFocused();
|
||||
const elements = await openFilter(page, "Éléments");
|
||||
await elements.getByRole("checkbox", { name: "Roche", exact: true }).check();
|
||||
const results = await page.getByRole("status").textContent();
|
||||
const toggle = elements.getByRole("button", { name: "Éléments", exact: true });
|
||||
await toggle.click();
|
||||
await expect(toggle).toHaveAttribute("aria-expanded", "false");
|
||||
await expect(elementScroll).toBeHidden();
|
||||
await expect(toggle.locator(".aniimo-selected-count")).toHaveText("1");
|
||||
await expect(filterTrigger(page, "Éléments").locator(".nds-filter-count")).toHaveText("1");
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(elements).toBeHidden();
|
||||
await expect(page.getByRole("status")).toHaveText(results);
|
||||
await expect(roles.getByRole("region")).toBeVisible();
|
||||
await expect(abilities.getByRole("region")).toBeVisible();
|
||||
await toggle.press("Enter");
|
||||
await expect(elements.getByRole("checkbox", { name: "Roche", exact: true })).toBeChecked();
|
||||
const reopenedElements = await openFilter(page, "Éléments");
|
||||
await expect(reopenedElements.getByRole("checkbox", { name: "Roche", exact: true })).toBeChecked();
|
||||
await page.keyboard.press("Escape");
|
||||
const abilities = await openFilter(page, "Compétences de logis");
|
||||
const groups = await abilities.locator(".aniimo-ability-filter").evaluateAll((rows) => rows.map((row) => row.dataset.abilityGroup));
|
||||
expect(new Set(groups)).toEqual(new Set(["elements", "actions"]));
|
||||
expect(groups).toEqual([...groups.filter((group) => group === "elements"), ...groups.filter((group) => group === "actions")]);
|
||||
const slider = abilities.getByRole("slider", { name: "Porter — Niveau minimum" });
|
||||
await slider.focus();
|
||||
await slider.press("End");
|
||||
await abilities.getByRole("button", { name: "Compétences de logis", exact: true }).click();
|
||||
await expect(filterTrigger(page, "Compétences de logis").locator(".nds-filter-count")).toHaveText("1");
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(slider).toBeHidden();
|
||||
await abilities.getByRole("button", { name: "Compétences de logis", exact: true }).click();
|
||||
await expect(slider).toHaveValue("4");
|
||||
const reopenedAbilities = await openFilter(page, "Compétences de logis");
|
||||
await expect(reopenedAbilities.getByRole("slider", { name: "Porter — Niveau minimum" })).toHaveValue("4");
|
||||
});
|
||||
|
||||
test("pending and failed data requests show loading and error states", async ({ page }) => {
|
||||
|
|
|
|||
75
e2e/game-filters.spec.js
Normal file
75
e2e/game-filters.spec.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// Rôle : vérifie les variantes horizontales compactes et avancées des filtres de jeux.
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
for (const width of [1920, 900]) {
|
||||
test(`MHW compact filters keep accessible tooltips at ${width}px`, async ({ page }) => {
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
await page.goto("/#/games/mhwilds/monsters");
|
||||
const filters = page.locator('.nds-filter-panel[data-variant="horizontal-compact"]');
|
||||
await expect(filters).toBeVisible();
|
||||
const options = filters.locator(".nds-filter-option");
|
||||
await expect(options).toHaveCount(5);
|
||||
const optionWidths = await options.evaluateAll((elements) => elements.map((element) => element.getBoundingClientRect().width));
|
||||
if (width > 1040) expect(Math.max(...optionWidths) - Math.min(...optionWidths)).toBeGreaterThan(1);
|
||||
else expect(Math.max(...optionWidths) - Math.min(...optionWidths)).toBeLessThan(1);
|
||||
const first = options.first();
|
||||
await expect(first).toHaveAttribute("aria-label", /.+/);
|
||||
await first.hover();
|
||||
await expect(page.getByRole("tooltip")).toBeVisible();
|
||||
const label = first.locator(".nds-filter-option-label");
|
||||
await expect(label).toHaveCSS("position", width <= 1040 ? "absolute" : "static");
|
||||
await first.click();
|
||||
await expect(first).toHaveAttribute("aria-pressed", "true");
|
||||
const activeBadge = page.locator(".nds-active-filter").first();
|
||||
await expect(activeBadge).toBeVisible();
|
||||
const activeBar = page.locator(".nds-active-filters");
|
||||
await expect(activeBar).toHaveCSS("border-top-style", "solid");
|
||||
await expect(activeBar).toHaveCSS("border-top-color", "rgba(246, 196, 83, 0.3)");
|
||||
await expect(activeBar).toHaveCSS("padding-top", "4px");
|
||||
await expect(activeBar).toHaveCSS("margin-bottom", "-6px");
|
||||
await expect(activeBar.locator("xpath=..")).toHaveClass(/nds-filter-panel/);
|
||||
await expect(activeBar.locator("xpath=..")).toHaveCSS("row-gap", "10px");
|
||||
const panelBox = await filters.boundingBox();
|
||||
const activeBarBox = await activeBar.boundingBox();
|
||||
expect(activeBarBox.x).toBeCloseTo(panelBox.x + 1, 0);
|
||||
expect(activeBarBox.x + activeBarBox.width).toBeCloseTo(panelBox.x + panelBox.width - 1, 0);
|
||||
expect((await activeBar.boundingBox()).height).toBeLessThanOrEqual(30);
|
||||
await activeBadge.click();
|
||||
await expect(first).toHaveAttribute("aria-pressed", "false");
|
||||
await expect(page.locator(".game-filters")).toHaveCSS("position", width <= 760 ? "static" : "sticky");
|
||||
await expect(page.locator(".game-filters")).toHaveCSS("top", width <= 760 ? "auto" : "68px");
|
||||
expect((await filters.locator(".nds-filter-search").boundingBox())?.width).toBeLessThanOrEqual(280);
|
||||
expect(await page.evaluate(() => globalThis.document.documentElement.scrollWidth <= globalThis.innerWidth)).toBe(true);
|
||||
});
|
||||
}
|
||||
|
||||
test("Diablo advanced categories keep text visible and retain focus on Escape", async ({ page }) => {
|
||||
await page.goto("/#/games/diablo4/affixes");
|
||||
const filters = page.locator('.nds-filter-panel[data-variant="horizontal-menu"]');
|
||||
const trigger = filters.getByRole("button", { name: /^Catégories/ });
|
||||
await expect(trigger).toHaveAttribute("aria-expanded", "false");
|
||||
expect(await trigger.locator("span").first().evaluate((element) => element.scrollWidth <= element.clientWidth)).toBe(true);
|
||||
await trigger.click();
|
||||
const menu = page.getByRole("dialog", { name: /^Catégories/ });
|
||||
await expect(menu).toBeVisible();
|
||||
await expect(menu.getByText("Correspondance", { exact: true })).toBeVisible();
|
||||
await expect(menu.getByRole("radio", { name: "Et", exact: true })).toHaveAttribute("aria-checked", "true");
|
||||
await expect(filters.locator(":scope > .nds-filter-group > .nds-filter-logic")).toHaveCount(0);
|
||||
await expect(menu.locator(".nds-filter-logic")).toHaveCSS("margin-bottom", "12px");
|
||||
await expect(menu.locator(".nds-filter-menu-options")).toHaveCSS("overflow-y", "visible");
|
||||
await expect(filters).toHaveCSS("overflow", "visible");
|
||||
const filtersBox = await filters.boundingBox();
|
||||
const menuBox = await menu.boundingBox();
|
||||
expect(filtersBox).not.toBeNull();
|
||||
expect(menuBox).not.toBeNull();
|
||||
expect(menuBox.y + menuBox.height).toBeGreaterThan(filtersBox.y + filtersBox.height);
|
||||
await expect(menu.getByRole("checkbox")).toHaveCount(8);
|
||||
await expect(menu.getByText("Offensif", { exact: true })).toBeVisible();
|
||||
await expect(menu.getByText("Mobilité", { exact: true })).toBeVisible();
|
||||
await menu.getByRole("checkbox", { name: "Offensif", exact: true }).check();
|
||||
await expect(trigger.locator(".nds-filter-count")).toHaveText("1");
|
||||
await expect(page.getByRole("button", { name: "Retirer le filtre Catégories : Offensif", exact: true })).toBeVisible();
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(trigger).toHaveAttribute("aria-expanded", "false");
|
||||
await expect(trigger).toBeFocused();
|
||||
});
|
||||
|
|
@ -88,7 +88,7 @@ for (const width of [1280, 390]) {
|
|||
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>
|
||||
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");
|
||||
|
|
@ -97,7 +97,8 @@ for (const width of [1280, 390]) {
|
|||
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 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();
|
||||
|
||||
|
|
|
|||
|
|
@ -19,10 +19,34 @@ for (const width of [1280, 900, 390]) {
|
|||
await expect(page.locator(".home-onboarding-slider img")).toHaveCSS("object-fit", "cover");
|
||||
const side = page.locator(".sidebar");
|
||||
await expect(side).toHaveCSS("display", width <= 760 ? "none" : "flex");
|
||||
await expect(page.locator(".sidebar-mascot")).toHaveCSS("display", width <= 1024 ? "none" : "block");
|
||||
for (const link of await page.locator(".sidebar-about-link").all()) {
|
||||
await expect(link).toHaveCSS("align-items", "center");
|
||||
if (width <= 1024) await expect(link).toHaveCSS("justify-content", "center");
|
||||
if (width > 760) {
|
||||
const responsiveCompact = width <= 1024;
|
||||
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", responsiveCompact ? "none" : "block");
|
||||
await expect(page.locator(".sidebar-storage-actions")).toHaveCSS("flex-direction", "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"));
|
||||
|
|
@ -47,7 +71,7 @@ for (const width of [1280, 390]) {
|
|||
<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"), width <= 760 ? 1 : 2);
|
||||
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");
|
||||
|
|
|
|||
81
e2e/toolbox-drawer-layout.spec.js
Normal file
81
e2e/toolbox-drawer-layout.spec.js
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
// Rôle : vérifie la réservation hybride, le redimensionnement et le mode mobile du drawer toolbox.
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
async function openDrawer(page) {
|
||||
await page.locator(".drawer-button").click();
|
||||
const drawer = page.locator("#toolbox-drawer");
|
||||
await expect(drawer.locator(".drawer-panel")).toBeVisible();
|
||||
return drawer;
|
||||
}
|
||||
|
||||
async function resizeDrawer(page, deltaX) {
|
||||
const handle = page.locator(".drawer-resize-handle");
|
||||
const bounds = await handle.boundingBox();
|
||||
await page.mouse.move(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(bounds.x + bounds.width / 2 + deltaX, bounds.y + bounds.height / 2);
|
||||
await page.mouse.up();
|
||||
}
|
||||
|
||||
test("drawer reserves 700px while additional width overlays the page", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1280, height: 900 });
|
||||
await page.goto("/#/games/mhwilds/monsters");
|
||||
|
||||
const drawer = await openDrawer(page);
|
||||
const panel = drawer.locator(".drawer-panel");
|
||||
const shell = page.locator(".app-shell");
|
||||
const contentShell = page.locator(".content-shell");
|
||||
const main = page.locator(".content-shell > main");
|
||||
const sidebar = page.locator(".sidebar");
|
||||
await expect(shell).toHaveClass(/toolbox-drawer-open/);
|
||||
await expect(sidebar).toHaveAttribute("data-variant", "compact");
|
||||
await expect(sidebar).toHaveCSS("width", "72px");
|
||||
await expect(drawer.locator(".drawer-backdrop")).toHaveCount(0);
|
||||
|
||||
const contentBounds = await contentShell.boundingBox();
|
||||
const initialPanelBounds = await panel.boundingBox();
|
||||
const initialMainBounds = await main.boundingBox();
|
||||
const topbarBounds = await page.locator(".topbar").boundingBox();
|
||||
expect(initialPanelBounds.width).toBeCloseTo(700, 0);
|
||||
expect(initialPanelBounds.x).toBeCloseTo(contentBounds.x + contentBounds.width - 700, 0);
|
||||
expect(topbarBounds.x + topbarBounds.width).toBeCloseTo(1280, 0);
|
||||
|
||||
const search = page.locator(".nds-filter-search input").first();
|
||||
await search.fill("rath");
|
||||
await expect(search).toHaveValue("rath");
|
||||
await expect(panel).toBeVisible();
|
||||
|
||||
await resizeDrawer(page, -100);
|
||||
await expect.poll(async () => (await panel.boundingBox()).width).toBeCloseTo(800, 0);
|
||||
const expandedPanelBounds = await panel.boundingBox();
|
||||
const expandedMainBounds = await main.boundingBox();
|
||||
expect(expandedPanelBounds.x).toBeCloseTo(initialPanelBounds.x - 100, 0);
|
||||
expect(expandedMainBounds.x).toBeCloseTo(initialMainBounds.x, 0);
|
||||
expect(expandedMainBounds.width).toBeCloseTo(initialMainBounds.width, 0);
|
||||
expect(await page.evaluate(() => globalThis.document.documentElement.scrollWidth)).toBeLessThanOrEqual(1280);
|
||||
|
||||
await resizeDrawer(page, 300);
|
||||
await expect.poll(async () => (await panel.boundingBox()).width).toBeCloseTo(700, 0);
|
||||
|
||||
await resizeDrawer(page, -100);
|
||||
await expect.poll(async () => (await panel.boundingBox()).width).toBeCloseTo(800, 0);
|
||||
await drawer.getByRole("button", { name: "Fermer", exact: true }).click();
|
||||
await expect(drawer).toHaveCount(0);
|
||||
await expect(sidebar).not.toHaveAttribute("data-variant");
|
||||
await expect(sidebar).toHaveCSS("width", "248px");
|
||||
const reopenedDrawer = await openDrawer(page);
|
||||
await expect.poll(async () => (await reopenedDrawer.locator(".drawer-panel").boundingBox()).width).toBeCloseTo(700, 0);
|
||||
});
|
||||
|
||||
test("drawer becomes a non-resizable full-width overlay below 700px", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 390, height: 900 });
|
||||
await page.goto("/#/games/mhwilds/monsters");
|
||||
|
||||
const drawer = await openDrawer(page);
|
||||
const panel = drawer.locator(".drawer-panel");
|
||||
const bounds = await panel.boundingBox();
|
||||
expect(bounds.x).toBeCloseTo(0, 0);
|
||||
expect(bounds.width).toBeCloseTo(390, 0);
|
||||
await expect(drawer.locator(".drawer-resize-handle")).toHaveCSS("display", "none");
|
||||
expect(await page.evaluate(() => globalThis.document.documentElement.scrollWidth)).toBeLessThanOrEqual(390);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue