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 }) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue