// Rôle : vérifie les parcours Aniimo, les variantes, les cartes et les seuils au clavier. import { test, expect } from "@playwright/test"; const route = "/#/games/aniimo/aniimos"; const cards = (page) => page.locator(".aniimo-result"); 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("button", { name: "Prismana uniquement", exact: true }); await expect(prismana).toBeVisible({ timeout: 30000 }); 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); await page.getByRole("searchbox").fill("iris"); 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).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).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); }); } test("Aniimo matches MHW card motion, surfaces and filter controls", async ({ page }) => { test.setTimeout(45000); async function styles(locator, properties) { return locator.evaluate((element, keys) => { const css = globalThis.getComputedStyle(element); return Object.fromEntries(keys.map((key) => [key, css.getPropertyValue(key)])); }, properties); } const motionProperties = ["transition-duration", "transition-timing-function", "transform-style"]; const faceProperties = ["background-image", "border-radius", "backface-visibility"]; const indicatorProperties = ["width", "height", "background-color", "opacity"]; const filterProperties = ["background-color", "border-radius", "min-height", "padding"]; await page.goto("/#/games/mhwilds"); await expect(page.locator(".game-home-card").first()).toBeVisible(); const homeCard = await page.locator(".game-home-card").first().boundingBox(); await page.goto("/#/games/aniimo"); await expect(page.locator(".aniimo-home-card")).toBeVisible(); const aniimoHomeCard = await page.locator(".aniimo-home-card").boundingBox(); expect(aniimoHomeCard.width).toBeCloseTo(homeCard.width, 1); expect(aniimoHomeCard.height).toBeCloseTo(homeCard.height, 1); await page.goto("/#/games/mhwilds/monsters"); await expect(page.locator(".monster-card").first()).toBeVisible({ timeout: 15000 }); const motion = await styles(page.locator(".mhwilds-card-inner").first(), motionProperties); const face = await styles(page.locator(".mhwilds-card-front").first(), faceProperties); 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"); await expect(card.getByRole("button", { name: /Afficher le portrait/ })).toBeFocused(); await expect(card.locator(".aniimo-front")).toHaveAttribute("inert", ""); await card.getByRole("button", { name: /Afficher le portrait/ }).press("Enter"); await expect(card.getByRole("button", { name: /Afficher les capacités/ })).toBeFocused(); }); test("catalogue, overview and deferred Aniimo loading preserve filters across navigation", async ({ page }) => { const requests = []; page.on("request", (request) => { if (request.url().includes("/data/aniimo/")) requests.push(request.url()); }); await page.goto("/#/games"); await expect(page.getByRole("heading", { name: "Jeux disponibles" })).toBeVisible(); expect(requests).toHaveLength(0); await page.locator('a.game-card-cover[href="#/games/aniimo"]').click(); await expect(page.getByRole("heading", { name: "Aniimo", exact: true })).toBeVisible(); const cover = page.locator('.aniimo-home-card img'); await expect(cover).toHaveAttribute("src", "/static/img/aniimo/emberpup__basic_form.png"); await expect(cover).toHaveCSS("object-fit", "cover"); await expect(cover).toHaveCSS("object-position", "50% 38%"); await expect(cover).toHaveCSS("height", "120px"); await page.locator('a[href="#/games/aniimo/aniimos"]').click(); await expect(cards(page)).toHaveCount(88); await expect(page.getByRole("status")).toHaveText("88 / 88 Aniimos · 208 / 208 variantes"); const switchLabel = page.getByRole("button", { name: "Regrouper les variantes", exact: true }).locator("span:not(.ui-icon)"); expect(await switchLabel.evaluate((element) => element.scrollWidth <= element.clientWidth)).toBe(true); await page.getByRole("searchbox", { name: "Nom" }).fill("EMBERPUP"); await expect(cards(page)).toHaveCount(1); await page.getByRole("button", { name: "Regrouper les variantes", exact: true }).click(); 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(cards(page)).toHaveCount(3); await expect(page.getByRole("searchbox")).toHaveValue("EMBERPUP"); expect(requests).toHaveLength(5); }); test("Aniimo exposes its regions list", async ({ page }) => { await page.goto("/#/games/aniimo/lists"); await expect(page.getByRole("heading", { name: "Listes", exact: true })).toBeVisible(); await expect(page.locator("h2", { hasText: "Regions" })).toBeVisible(); await expect(page.getByText("14 éléments", { exact: true })).toBeVisible(); await expect(page.getByText("Champs de Cumulaine", { exact: true })).toBeVisible(); }); test("tabs, flip, matching variants and reset remain independent", async ({ page }) => { await page.goto(route); await page.getByRole("searchbox").fill("emberpup"); const card = cards(page).first(); await expect(card.getByRole("tab")).toHaveCount(3); await card.getByRole("button", { name: /Afficher les capacités/ }).click(); await expect(card.locator(".aniimo-back")).toHaveAttribute("aria-hidden", "false"); await card.getByRole("tab", { name: "Hautes-plaines", exact: true }).click(); await expect(card).toHaveAttribute("data-form", "highlands"); await expect(card.getByRole("button", { name: /Afficher le portrait/ })).toHaveAttribute("aria-pressed", "true"); await expect(card.locator(".aniimo-back")).toHaveAttribute("aria-hidden", "false"); await expect(card.locator(".aniimo-back .aniimo-attributes").first()).toContainText("Roche"); await card.getByRole("tab", { name: "Forme de base", exact: true }).focus(); await page.keyboard.press("ArrowRight"); await expect(card).toHaveAttribute("data-form", "mountain_woods_form"); await expect(card.locator(".aniimo-back")).toHaveAttribute("aria-hidden", "false"); 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 elements.getByRole("checkbox", { name: "Eau", exact: true }).check(); await expect(page.getByRole("heading", { name: "Aucun résultat" })).toBeVisible(); 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); await page.getByRole("button", { name: "Réinitialiser les filtres", exact: true }).click(); await expect(cards(page)).toHaveCount(208); await expect(page.getByRole("button", { name: "Regrouper les variantes", exact: true })).toHaveAttribute("aria-pressed", "false"); }); test("ability levels use independent native ranges and retain name constraints", async ({ page }) => { await page.goto(route); 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 }); const exactLevel = abilities.getByRole("button", { name: "Niveau exact", exact: true }); await expect(minimumLevel).toHaveAttribute("aria-pressed", "true"); await expect(exactLevel).toHaveAttribute("aria-pressed", "false"); await expect(carry).toHaveAccessibleName("Porter — Niveau minimum"); await expect(crafting).toHaveAccessibleName("Artisanat — Niveau minimum"); await expect(carry).toHaveValue("0"); await expect(carry).toHaveAttribute("min", "0"); await carry.focus(); await carry.press("ArrowRight"); 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"); await crafting.press("ArrowRight"); await expect(crafting).toHaveValue("2"); await expect(crafting).toHaveCSS("outline-style", "solid"); await exactLevel.click(); await expect(exactLevel).toHaveAttribute("aria-pressed", "true"); await expect(minimumLevel).toHaveAttribute("aria-pressed", "false"); await expect(carry).toHaveAccessibleName("Porter — Niveau exact"); 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(); 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 }) => { await page.goto(route); await page.getByRole("searchbox").fill("iris"); const card = cards(page).filter({ has: page.getByRole("heading", { name: "Iris", exact: true }) }); await expect(card.getByRole("tab")).toHaveCount(7); const plateau = card.getByRole("tab", { name: "Forme des plateaux", exact: true }); await expect(plateau).toHaveText("PLA"); await plateau.hover(); await expect(page.getByRole("tooltip", { name: "Forme des plateaux", exact: true })).toBeVisible(); await page.mouse.move(0, 0); await plateau.focus(); await expect(page.getByRole("tooltip", { name: "Forme des plateaux", exact: true })).toBeVisible(); const firstTab = await card.getByRole("tab").first().boundingBox(); const secondTab = await card.getByRole("tab").nth(1).boundingBox(); expect(secondTab.x).toBeGreaterThanOrEqual(firstTab.x + firstTab.width); expect(await card.getByRole("tab").nth(1).evaluate((element) => element.scrollWidth <= element.clientWidth)).toBe(true); await card.getByRole("tab").first().focus(); await page.keyboard.press("End"); await expect(card).toHaveAttribute("data-form", "prismana_form"); await page.keyboard.press("Home"); 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"); 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 elements.getByRole("checkbox", { name: "Herbe", exact: true }).uncheck(); await page.getByRole("searchbox").fill("emberpup"); await expect(cards(page)).toHaveAttribute("data-form", "basic_form"); 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 reopenedElements.getByRole("checkbox", { name: "Roche", exact: true }).uncheck(); await expect(cards(page)).toHaveAttribute("data-form", "highlands"); }); for (const width of [1280, 390]) { test(`uniform cards, visible variant tabs and scrollable backs at ${width}px`, async ({ page }) => { await page.setViewportSize({ width, height: 844 }); await page.emulateMedia({ reducedMotion: "reduce" }); await page.goto(route); await expect(cards(page)).toHaveCount(88); const sizes = await page.locator(".aniimo-card").evaluateAll((elements) => elements.map((element) => { const { width, height } = element.getBoundingClientRect(); return { width, height }; })); expect(Math.max(...sizes.map((size) => size.width)) - Math.min(...sizes.map((size) => size.width))).toBeLessThan(1); expect(Math.max(...sizes.map((size) => size.height)) - Math.min(...sizes.map((size) => size.height))).toBeLessThan(1); await expect(page.locator(".aniimo-front .aniimo-element-icons").first()).toHaveText(""); await expect(page.locator(".aniimo-front .aniimo-element-icons img").first()).toHaveAttribute("alt", "Feu"); await page.getByRole("searchbox").fill("emberpup"); const longFormCard = cards(page).first(); const baseHeight = (await longFormCard.boundingBox()).height; await longFormCard.getByRole("tab", { name: "Forme des montagnes boisées", exact: true }).click(); const form = longFormCard.locator(".aniimo-front .aniimo-form"); await expect(form).toHaveCSS("white-space", "normal"); expect(await form.evaluate((element) => element.scrollHeight <= element.clientHeight)).toBe(true); expect((await longFormCard.boundingBox()).height).toBeCloseTo(baseHeight, 1); await longFormCard.getByRole("tab", { name: "Forme de base", exact: true }).click(); await page.getByRole("searchbox").fill(""); const icons = page.locator(".aniimo-front .aniimo-element-icons").first(); const rowBox = await icons.boundingBox(); const iconBox = await icons.locator("img").boundingBox(); expect(iconBox.x + iconBox.width / 2).toBeCloseTo(rowBox.x + rowBox.width / 2, 1); await page.getByRole("searchbox").fill("iris"); for (const name of ["Iris", "Valsiris"]) { const card = cards(page).filter({ has: page.getByRole("heading", { name, exact: true }) }); const tabs = card.getByRole("tablist"); await expect(card.getByRole("tab")).toHaveCount(7); await expect(card.getByRole("tab").last()).toHaveAccessibleName("Forme prismana"); expect(await tabs.evaluate((element) => { const box = element.getBoundingClientRect(); return element.scrollWidth <= element.clientWidth && [...element.children].every((tab) => { const bounds = tab.getBoundingClientRect(); return bounds.left >= box.left && bounds.right <= box.right && tab.scrollWidth <= tab.clientWidth; }); })).toBe(true); await expect(card.getByRole("tab").last()).toHaveCSS("background-image", /linear-gradient/); } await page.getByRole("searchbox").fill("fenmane"); const card = cards(page).first(); const before = await card.locator(".aniimo-card").boundingBox(); await card.getByRole("button", { name: /Afficher les capacités/ }).click(); await card.getByRole("tab", { name: "Forme prismana", exact: true }).click(); await expect(card.locator(".aniimo-back")).toHaveAttribute("aria-hidden", "false"); const after = await card.locator(".aniimo-card").boundingBox(); expect(after.height).toBeCloseTo(before.height, 1); expect(after.width).toBeCloseTo(before.width, 1); const details = card.locator(".aniimo-back-details"); await expect(details).toHaveCSS("overflow-y", "auto"); await expect(details.locator(".aniimo-abilities li")).toHaveCount(4); if (width === 1280) { expect(await details.evaluate((element) => element.scrollHeight > element.clientHeight)).toBe(true); await details.focus(); await details.press("End"); await expect.poll(() => details.evaluate((element) => element.scrollTop)).toBeGreaterThan(0); } await card.getByRole("button", { name: /Afficher le portrait/ }).click(); expect((await card.locator(".aniimo-card").boundingBox()).height).toBeCloseTo(before.height, 1); }); } test("portrait geometry, stable faces and mobile layout", async ({ page }) => { await page.emulateMedia({ reducedMotion: "reduce" }); await page.setViewportSize({ width: 390, height: 844 }); await page.goto(route); await page.getByRole("searchbox").fill("emberpup"); const card = cards(page).first(); await card.scrollIntoViewIfNeeded(); const portrait = card.locator(".aniimo-portrait img"); await expect(portrait).toHaveCSS("object-fit", "cover"); await expect(portrait).toHaveCSS("object-position", "50% 50%"); await expect(portrait).toHaveJSProperty("naturalWidth", 512); const size = await portrait.boundingBox(); expect(size.height).toBeLessThanOrEqual(250); expect(size.height).toBeGreaterThan(240); const before = await card.boundingBox(); expect(before.height).toBeLessThan(520); expect(before.width).toBeLessThanOrEqual(280); await card.getByRole("button", { name: /Afficher les capacités/ }).press("Enter"); await expect(card.locator(".aniimo-back")).toHaveAttribute("aria-hidden", "false"); expect((await card.boundingBox()).height).toBeCloseTo(before.height, 1); const summary = card.locator(".aniimo-back-summary > div"); const elementBox = await summary.nth(0).boundingBox(); const roleBox = await summary.nth(1).boundingBox(); expect(elementBox.y).toBeCloseTo(roleBox.y, 1); expect(elementBox.x + elementBox.width).toBeLessThan(roleBox.x); await expect(card.getByRole("progressbar")).toHaveCount(6); await expect(card.getByRole("progressbar", { name: "PV", exact: true })).toHaveAttribute("aria-valuenow", "67"); await expect(card.getByRole("progressbar", { name: "PV", exact: true })).toHaveAttribute("aria-valuemax", "130"); await expect(card.getByRole("progressbar", { name: "PV", exact: true })).toHaveCSS("height", "4px"); await expect(card.locator(".aniimo-stat-value")).toHaveText(["67", "90", "60", "57", "40", "68"]); await expect(card.locator(".aniimo-stat-total")).toHaveText("Total382"); expect(await card.locator(".aniimo-card-inner").evaluate((element) => Number.parseFloat(globalThis.getComputedStyle(element).transitionDuration))).toBeLessThan(0.001); expect(await page.evaluate(() => globalThis.document.documentElement.scrollWidth <= globalThis.innerWidth)).toBe(true); }); 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(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(); await card.locator(".aniimo-back-details").evaluate((element) => { element.scrollTop = element.scrollHeight; }); await expect(card.getByRole("progressbar")).toHaveCount(6); await page.screenshot({ path: test.info().outputPath("aniimo-stats-desktop.png") }); }); test("advanced filter menus retain selections, counts and keyboard closing", async ({ page }) => { await page.goto(route); 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); 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(); 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); 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 expect(filterTrigger(page, "Compétences de logis").locator(".nds-filter-count")).toHaveText("1"); await page.keyboard.press("Escape"); await expect(slider).toBeHidden(); 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 }) => { let releaseRequests; const pending = new Promise((resolve) => { releaseRequests = resolve; }); await page.route("**/data/aniimo/aniimos.json", async (route) => { await pending; await route.fulfill({ status: 503, body: "unavailable" }); }); await page.goto(route); await expect(page.getByRole("heading", { name: "Chargement des Aniimos…" })).toBeVisible(); releaseRequests(); await expect(page.getByRole("heading", { name: "Impossible de charger Aniimo" })).toBeVisible(); });