diff --git a/DESIGN_SYSTEM.md b/DESIGN_SYSTEM.md index 3b753a1..c8c18bd 100644 --- a/DESIGN_SYSTEM.md +++ b/DESIGN_SYSTEM.md @@ -370,7 +370,8 @@ Regles UI : - palette de couleurs compacte reprenant les swatches du Bloc Note ; - la creation de mémo est masquee par defaut et s'ouvre via le bouton `+` du header d'outil ; - réorganisation des cartes par poignée de drag & drop compacte ; -- bande deroulante visible uniquement quand elle est active et qu'au moins un mémo existe ; elle affiche tous les mémos. +- bande deroulante visible uniquement quand elle est active et qu'au moins un mémo existe ; elle affiche tous les mémos dans l'outil et reste fixée en bas des pages complètes de cette toolbox ainsi que des pages du jeu qui lui est associé ; +- la bande fixe laisse le contenu verticalement accessible, suit la largeur disponible et remonte au-dessus de la navigation mobile. ### Calque De Dessin @@ -859,10 +860,13 @@ reste dorée lorsque la liste est associée. Le bouton NDS dissocie la précédente. Le switch « Masquer les cochés » est désactivé sur les listes non associées. Les commandes sont centrées et reviennent à la ligne sur mobile ; le nombre de correspondances figure dessous. Une liste active devenue incompatible -garde ses commandes avec un message explicite. Aucun sélecteur global n'est affiché. -Quand une liste est associée, un encart compact placé en haut à droite du header -du guide expose directement le switch « Masquer les cochés » et l'action -« Dissocier la checklist ». La colonne d'actions occupe la hauteur existante du +garde ses commandes avec un message explicite. +Un encart compact placé en haut à droite du header du guide expose un sélecteur dès +qu'au moins une checklist compatible existe. Sans association, lui seul est affiché. +Une fois une liste choisie, l'encart ajoute le switch « Masquer les cochés » et +l'action « Dissocier la checklist ». Changer la +valeur du sélecteur remplace immédiatement l'association sans perdre le réglage de +masquage. La colonne d'actions occupe la hauteur existante du header afin que cet encart n'ajoute pas de ligne ou d'espace vertical. Cette association est conservée dans `localStorage` entre les sessions ; elle est retirée si la checklist, la toolbox liée ou la liaison jeu/toolbox disparaît. diff --git a/docs/STORAGE_SCHEMA.md b/docs/STORAGE_SCHEMA.md index 2bd3885..af74158 100644 --- a/docs/STORAGE_SCHEMA.md +++ b/docs/STORAGE_SCHEMA.md @@ -237,6 +237,7 @@ Type : `memos` Stockage compact : - `tickerEnabled` vaut `true` par défaut et est omis dans ce cas ; +- lorsqu'il est actif, la bande est aussi affichée en position fixe sur la page complète de la toolbox et sur les pages du jeu lié ; - `updatedAt` est omis si vide ou invalide ; - les mémos sans texte sont ignorés ; - si aucun mémo n'est valide et que la bande est active par défaut, l'entrée de module peut être supprimée. @@ -251,7 +252,9 @@ affiche un toggle de lien à côté du « + » de son en-tête dans le panneau l Il ouvre une section masquée par défaut en haut du contenu : bouton toggle d'association/dissociation et switch de masquage, actif seulement pour la liste associée. Associer une autre liste remplace la précédente et conserve le -réglage de masquage ; dissocier réaffiche tous les éléments. Replier les commandes +réglage de masquage ; le sélecteur affiché dans l'en-tête du guide dès qu'une liste +compatible existe permet le même changement ; sans association, les autres actions +de l'encart restent masquées. Dissocier réaffiche tous les éléments. Replier les commandes ne modifie pas l'association ; cet état d'affichage reste local au composant. Cette liaison ne modifie ni les données de checklist ni les liens persistants jeu/toolbox. diff --git a/docs/TEST_STRATEGY.md b/docs/TEST_STRATEGY.md index ea4ef51..dd417c4 100644 --- a/docs/TEST_STRATEGY.md +++ b/docs/TEST_STRATEGY.md @@ -24,6 +24,7 @@ not bugs, missing scenarios or a percentage of work remaining. | Checklist: quantities, clamping, completed item/category visibility and persistence | `e2e/basic-tools.spec.js` | | Images: gallery, detail navigation, keyboard controls and focus restoration | `e2e/basic-tools.spec.js` | | Guide checklists: persistent association, page controls, drawer scope and live completion | `e2e/guide-checklist.spec.js` | +| Mémos : bande fixe, portée par route et persistance du toggle | `e2e/memos-ticker.spec.js` | | Planner: editing, recurrence, prerequisites, subtree reorder and persistence | `e2e/task-planner.spec.js` | | Representative visual states and responsive layouts | `e2e/style-layouts.spec.js`, `tool-style-states.spec.js`, `card-surfaces.spec.js`, `nds-controls.spec.js`, `nds-patterns.spec.js` | | Production onboarding | `e2e/production/onboarding.spec.js` | diff --git a/e2e/guide-checklist.spec.js b/e2e/guide-checklist.spec.js index 8102a31..3686fd5 100644 --- a/e2e/guide-checklist.spec.js +++ b/e2e/guide-checklist.spec.js @@ -132,6 +132,12 @@ for (const width of [1280, 390]) { await page.goto(route); await page.getByRole("searchbox").fill("emberpup"); const card = page.locator(".aniimo-result"); + const pageControls = page.locator(".guide-checklist-page-controls"); + const unlinkedSelect = pageControls.getByRole("combobox", { name: "Checklist associée", exact: true }); + await expect(pageControls).toBeVisible(); + await expect(unlinkedSelect).toHaveValue(""); + await expect(unlinkedSelect).toContainText("Choisir une checklist"); + await expect(pageControls.getByRole("button")).toHaveCount(0); await openDrawer(page); await expect(activeButtons(page)).toHaveCount(0); await expect(page.locator(".guide-checklist-controls")).toHaveCount(3); @@ -164,7 +170,6 @@ for (const width of [1280, 390]) { expect(actionsBounds.x + actionsBounds.width / 2).toBeCloseTo(bounds.x + bounds.width / 2, 0); await page.screenshot({ path: test.info().outputPath("linked-drawer.png") }); await closeDrawer(page); - const pageControls = page.locator(".guide-checklist-page-controls"); await expect(pageControls).toBeVisible(); const pageControlsBounds = await pageControls.boundingBox(); const pageHeadingBounds = await page.locator(".page-heading").boundingBox(); @@ -175,6 +180,14 @@ for (const width of [1280, 390]) { expect(headingActionsBounds.height).toBeCloseTo(pageHeadingBounds.height, 0); expect(pageControlsBounds.y).toBeCloseTo(pageHeadingBounds.y, 0); } + const checklistSelect = pageControls.getByRole("combobox", { name: "Checklist associée", exact: true }); + await expect(checklistSelect).toHaveValue("forms"); + await expect(checklistSelect.locator("option")).toHaveCount(3); + await expect(checklistSelect.locator('option[value="unrelated"]')).toHaveCount(0); + await checklistSelect.selectOption("capture"); + await expect(checklistSelect).toHaveValue("capture"); + await checklistSelect.selectOption("forms"); + await expect(checklistSelect).toHaveValue("forms"); await expect(pageControls.getByRole("button", { name: "Masquer les cochés", exact: true })).toHaveAttribute("aria-pressed", "false"); await expect(pageControls.getByRole("button", { name: "Dissocier la checklist", exact: true })).toBeVisible(); await expect(card).toHaveAttribute("data-checklist-complete", "true"); diff --git a/e2e/memos-ticker.spec.js b/e2e/memos-ticker.spec.js new file mode 100644 index 0000000..58ae37c --- /dev/null +++ b/e2e/memos-ticker.spec.js @@ -0,0 +1,56 @@ +// Rôle : vérifie la bande de mémos fixe sur les pages de toolbox et de jeu associées. +import { test, expect } from "@playwright/test"; +import { resetSokkoStorage } from "./helpers/storage.js"; + +async function seedMemosToolbox(page) { + await resetSokkoStorage(page); + await page.evaluate(async () => { + await new Promise((resolve, reject) => { + const request = globalThis.indexedDB.open("sokkog", 1); + request.onsuccess = () => { + const db = request.result; + const tx = db.transaction(["kv", "modules"], "readwrite"); + tx.objectStore("kv").put([ + { id: "memo-toolbox", name: "Toolbox mémos", modules: [{ id: "memos", type: "memos", title: "Mémos" }], moduleColumns: 1 } + ], "toolboxes"); + tx.objectStore("kv").put({ mhwilds: "memo-toolbox" }, "links"); + tx.objectStore("modules").put({ + key: "memo-toolbox:memos", + data: { items: [{ id: "memo-1", text: "Préparer les potions", color: "#f6c453" }] } + }); + tx.oncomplete = () => { db.close(); resolve(); }; + tx.onerror = () => reject(tx.error); + }; + request.onerror = () => reject(request.error); + }); + }); + await page.reload(); +} + +test("shows and persists the memo ticker on linked game and full toolbox pages", async ({ page }) => { + await seedMemosToolbox(page); + + await page.goto("/#/games/mhwilds/monsters"); + const globalTicker = page.locator(".global-memos-ticker"); + await expect(globalTicker).toBeVisible(); + await expect(globalTicker).toContainText("Préparer les potions"); + await expect(globalTicker).toHaveCSS("position", "fixed"); + + await page.goto("/#/games/aniimo/aniimos"); + await expect(globalTicker).toHaveCount(0); + + await page.goto("/#/toolbox/memo-toolbox"); + await expect(globalTicker).toBeVisible(); + const toggle = page.locator(".memos-ticker-toggle"); + await expect(toggle).toHaveAccessibleName("Masquer la bande"); + await toggle.click(); + await expect(globalTicker).toHaveCount(0); + await page.reload(); + await expect(globalTicker).toHaveCount(0); + await expect(toggle).toHaveAccessibleName("Afficher la bande"); + await toggle.click(); + await expect(globalTicker).toBeVisible(); + + await page.goto("/#/toolboxes"); + await expect(globalTicker).toHaveCount(0); +}); diff --git a/tests/helpers/data-validation.mjs b/tests/helpers/data-validation.mjs index 4dc22b1..458f9fd 100644 --- a/tests/helpers/data-validation.mjs +++ b/tests/helpers/data-validation.mjs @@ -90,6 +90,8 @@ export function validateSiteContent(site) { "gamesPage.emptyTitle", "gamesPage.emptyText", "guideChecklist.label", + "guideChecklist.selectLabel", + "guideChecklist.selectPlaceholder", "guideChecklist.associate", "guideChecklist.noMatches", "guideChecklist.matches", diff --git a/website/public/data/site.json b/website/public/data/site.json index 6bbef3a..9a97bab 100644 --- a/website/public/data/site.json +++ b/website/public/data/site.json @@ -2771,6 +2771,8 @@ }, "guideChecklist": { "label": "Lien avec le guide", + "selectLabel": "Checklist associée", + "selectPlaceholder": "Choisir une checklist", "associate": "Associer à la page", "noMatches": "Cette checklist ne contient plus de correspondance avec le guide.", "matches": "correspondances", diff --git a/website/src/features/games/GuideChecklistControls.jsx b/website/src/features/games/GuideChecklistControls.jsx index 901b7a2..ec5ac4a 100644 --- a/website/src/features/games/GuideChecklistControls.jsx +++ b/website/src/features/games/GuideChecklistControls.jsx @@ -31,16 +31,27 @@ export function GuideChecklistControls({ checklist, list, id, open }) { } export function GuideChecklistPageControls({ checklist }) { - if (!checklist.selected) return null; + if (!checklist.lists.length) return null; const { content, hideCompleted } = checklist; return (