Improve toolbox checklists, image browsing, and guide integration
All checks were successful
Deploy Sokko G / deploy (push) Successful in 18s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 18s
This commit is contained in:
parent
00e73f0062
commit
386212041f
47 changed files with 744 additions and 112 deletions
|
|
@ -64,6 +64,32 @@ test("counters persist signed values and reset without affecting other counters"
|
|||
await expect(tool.locator(".counter-value span")).toHaveText("Other");
|
||||
});
|
||||
|
||||
test("empty collection tools explain that they have no content", async ({ page }) => {
|
||||
await page.goto("/#/toolboxes");
|
||||
await page.getByText("Importer une toolbox", { exact: true }).locator('input[type="file"]').setInputFiles({
|
||||
name: "empty-tools.json",
|
||||
mimeType: "application/json",
|
||||
buffer: Buffer.from(JSON.stringify({
|
||||
toolbox: {
|
||||
id: "empty-tools",
|
||||
name: "Empty tools",
|
||||
modules: [
|
||||
{ id: "checklist", type: "checklist", title: "Checklist" },
|
||||
{ id: "images", type: "images", title: "Images" },
|
||||
{ id: "links", type: "links", title: "Links" },
|
||||
{ id: "counters", type: "counters", title: "Counters" }
|
||||
]
|
||||
},
|
||||
modules: {}
|
||||
}))
|
||||
});
|
||||
await page.getByRole("link", { name: "Ouvrir Empty tools", exact: true }).first().click();
|
||||
await expect(page.getByText("Aucun élément dans cette checklist.", { exact: true })).toBeVisible();
|
||||
await expect(page.getByText("Aucune image.", { exact: true })).toBeVisible();
|
||||
await expect(page.getByText("Aucun lien.", { exact: true })).toBeVisible();
|
||||
await expect(page.getByText("Aucun compteur.", { exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test("checklist quantities are clamped and completed categories hide without losing data", async ({ page }) => {
|
||||
const tool = await setup(page, "checklist", { sections: [] });
|
||||
await tool.getByPlaceholder("Catégorie", { exact: true }).fill("Craft");
|
||||
|
|
@ -81,6 +107,14 @@ test("checklist quantities are clamped and completed categories hide without los
|
|||
await expect(tool.getByRole("radio", { name: "Cachée quand terminée", exact: true })).toHaveAttribute("aria-checked", "true");
|
||||
await tool.getByRole("radio", { name: "Visible quand terminée", exact: true }).click();
|
||||
await expect(quantity).toHaveValue("3");
|
||||
const hideCompleted = tool.getByRole("button", { name: "Masquer les éléments terminés", exact: true });
|
||||
await hideCompleted.click();
|
||||
await expect(hideCompleted).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(quantity).toHaveCount(0);
|
||||
await page.reload();
|
||||
await expect(hideCompleted).toHaveAttribute("aria-pressed", "true");
|
||||
await hideCompleted.click();
|
||||
await expect(quantity).toHaveValue("3");
|
||||
await tool.getByRole("button", { name: "Retirer une quantité", exact: true }).click();
|
||||
await page.reload();
|
||||
await expect(quantity).toHaveValue("2");
|
||||
|
|
@ -88,3 +122,63 @@ test("checklist quantities are clamped and completed categories hide without los
|
|||
await page.reload();
|
||||
await expect(quantity).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("a simple checklist can hide and restore checked rows", async ({ page }) => {
|
||||
const tool = await setup(page, "checklist", {
|
||||
items: [
|
||||
{ id: "done", label: "Done", qtyCurrent: 1 },
|
||||
{ id: "pending", label: "Pending" }
|
||||
]
|
||||
});
|
||||
const hideCompleted = tool.getByRole("button", { name: "Masquer les éléments terminés", exact: true });
|
||||
await hideCompleted.click();
|
||||
await expect(tool.getByRole("checkbox", { name: "Terminer Done", exact: true })).toHaveCount(0);
|
||||
await expect(tool.getByRole("checkbox", { name: "Terminer Pending", exact: true })).toBeVisible();
|
||||
await hideCompleted.click();
|
||||
await expect(tool.getByRole("checkbox", { name: "Terminer Done", exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test("images open a gallery and navigate the current collection without looping", async ({ page }) => {
|
||||
const makeImage = (color) => `data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" width="640" height="360"><rect width="640" height="360" fill="${color}"/></svg>`)}`;
|
||||
const tool = await setup(page, "images", {
|
||||
images: [
|
||||
{ id: "one", label: "First", dataUrl: makeImage("#8b5cf6") },
|
||||
{ id: "two", label: "Second", dataUrl: makeImage("#22d3ee") },
|
||||
{ id: "three", dataUrl: makeImage("#f6c453") }
|
||||
]
|
||||
});
|
||||
|
||||
await tool.getByRole("button", { name: "Galerie", exact: true }).click();
|
||||
const dialog = page.getByRole("dialog", { name: "Galerie d'images", exact: true });
|
||||
await expect(dialog).toBeVisible();
|
||||
await expect(dialog.locator(".image-viewer-gallery-item")).toHaveCount(3);
|
||||
await dialog.getByRole("button", { name: "Afficher en grand : Second", exact: true }).click();
|
||||
await expect(page.locator(".image-viewer-title")).toHaveText("Second");
|
||||
await expect(page.locator(".image-viewer-position")).toHaveText("2 / 3");
|
||||
|
||||
await page.keyboard.press("ArrowRight");
|
||||
await expect(page.locator(".image-viewer-title")).toHaveText("Image");
|
||||
await expect(page.getByRole("button", { name: "Image suivante", exact: true })).toBeDisabled();
|
||||
await page.keyboard.press("ArrowLeft");
|
||||
await expect(page.locator(".image-viewer-title")).toHaveText("Second");
|
||||
|
||||
await page.getByRole("button", { name: "Revenir à la galerie", exact: true }).click();
|
||||
const secondThumbnail = dialog.getByRole("button", { name: "Afficher en grand : Second", exact: true });
|
||||
await expect(secondThumbnail).toBeFocused();
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(dialog).toHaveCount(0);
|
||||
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await tool.getByRole("button", { name: "Galerie", exact: true }).click();
|
||||
const galleryBox = await dialog.boundingBox();
|
||||
expect(galleryBox.x).toBeGreaterThanOrEqual(0);
|
||||
expect(galleryBox.y).toBeGreaterThanOrEqual(0);
|
||||
expect(galleryBox.x + galleryBox.width).toBeLessThanOrEqual(390);
|
||||
expect(galleryBox.y + galleryBox.height).toBeLessThanOrEqual(844);
|
||||
await dialog.getByRole("button", { name: "Fermer", exact: true }).click();
|
||||
|
||||
await tool.locator(".image-preview").nth(2).click();
|
||||
await expect(page.locator(".image-viewer-position")).toHaveText("3 / 3");
|
||||
await expect(page.getByRole("button", { name: "Image suivante", exact: true })).toBeDisabled();
|
||||
await page.getByRole("button", { name: "Fermer", exact: true }).click();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ test("opening and editing one toolbox only reads its media and reuses it on retu
|
|||
test("the game drawer loads only the linked toolbox media", async ({ page }) => {
|
||||
const seeded = await setup(page);
|
||||
await page.goto("/#/games/mhwilds");
|
||||
await page.getByRole("button", { name: "Ouvrir la toolbox Alpha", exact: true }).click();
|
||||
await page.getByRole("button", { name: "Afficher la toolbox", exact: true }).click();
|
||||
const drawer = page.locator("#toolbox-drawer .drawer-panel");
|
||||
await expect(drawer.locator('.image-preview img')).toHaveAttribute("src", seeded.modules["a:images"].images[0].dataUrl);
|
||||
await expect(drawer.locator(".annotation-marker")).toHaveCount(1);
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ for (const width of [1920, 900]) {
|
|||
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 filters.locator(".nds-filter-search").boundingBox())?.width).toBeLessThanOrEqual(260);
|
||||
expect(await page.evaluate(() => globalThis.document.documentElement.scrollWidth <= globalThis.innerWidth)).toBe(true);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Rôle : vérifie copie, liaison de session et progression des checklists dans les guides.
|
||||
// Rôle : vérifie copie, liaison persistante et progression des checklists dans les guides.
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resetSokkoStorage } from "./helpers/storage.js";
|
||||
|
|
@ -6,7 +6,7 @@ import { importChecklistText } from "../website/src/features/toolboxes/modules/t
|
|||
import { normalizeChecklistData } from "../website/src/features/toolboxes/storage/modules/checklist.js";
|
||||
|
||||
const route = "/#/games/aniimo/aniimos";
|
||||
const sessionKey = "sokkog:guide-checklists";
|
||||
const storageKey = "sokkog:guide-checklists";
|
||||
const controls = (page, id) => page.locator('[data-module-type="checklist"]').filter({ has: page.getByRole("button", { name: `Retirer ${id}`, exact: true }) }).locator(".guide-checklist-controls");
|
||||
const controlsToggle = (page, id) => page.getByRole("button", { name: `Lien avec le guide — ${id}`, exact: true });
|
||||
const linkButton = (page, id) => controls(page, id).locator(".guide-checklist-link");
|
||||
|
|
@ -125,7 +125,7 @@ test("Aniimo copies visible species or forms and round-trips through checklist i
|
|||
});
|
||||
|
||||
for (const width of [1280, 390]) {
|
||||
test(`drawer linking, live completion and session restoration at ${width}px`, async ({ page }) => {
|
||||
test(`drawer linking, page controls and persistent restoration at ${width}px`, async ({ page }) => {
|
||||
test.setTimeout(60000);
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
await seed(page);
|
||||
|
|
@ -164,6 +164,19 @@ 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();
|
||||
const headingActionsBounds = await page.locator(".game-heading-actions").boundingBox();
|
||||
expect(pageControlsBounds.y).toBeGreaterThanOrEqual(pageHeadingBounds.y);
|
||||
expect(pageControlsBounds.y + pageControlsBounds.height).toBeLessThanOrEqual(pageHeadingBounds.y + pageHeadingBounds.height);
|
||||
if (width > 760) {
|
||||
expect(headingActionsBounds.height).toBeCloseTo(pageHeadingBounds.height, 0);
|
||||
expect(pageControlsBounds.y).toBeCloseTo(pageHeadingBounds.y, 0);
|
||||
}
|
||||
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");
|
||||
await expect(card.locator(".aniimo-front .guide-checklist-mark")).toHaveCount(0);
|
||||
await card.getByRole("button", { name: /Afficher les capacités/ }).click();
|
||||
|
|
@ -177,7 +190,7 @@ for (const width of [1280, 390]) {
|
|||
await expect.poll(async () => (await readModule(page, "forms")).items.find((item) => item.label.includes("Forme de base")).qtyCurrent || 0).toBe(0);
|
||||
await card.getByRole("checkbox", { name: "Cocher dans la checklist" }).check();
|
||||
await expect(card).toHaveAttribute("data-checklist-complete", "true");
|
||||
await toggleHidden(page);
|
||||
await pageControls.getByRole("button", { name: "Masquer les cochés", exact: true }).click();
|
||||
await expect(card.getByRole("tab")).toHaveCount(2);
|
||||
await expect(card).toHaveAttribute("data-form", "mountain_woods_form");
|
||||
expect(normalizeChecklistData(await readModule(page, "forms")).items).toEqual(normalizeChecklistData(before).items);
|
||||
|
|
@ -188,6 +201,8 @@ for (const width of [1280, 390]) {
|
|||
await drawer.locator('[data-module-type="checklist"]').filter({ has: page.getByRole("button", { name: "Retirer capture", exact: true }) }).getByRole("checkbox", { name: "Terminer Loufeuteau", exact: true }).check();
|
||||
await closeDrawer(page);
|
||||
await expect(card).toHaveCount(0);
|
||||
await expect.poll(() => page.evaluate((key) => globalThis.localStorage.getItem(key), storageKey)).not.toBeNull();
|
||||
await page.evaluate(() => globalThis.sessionStorage.clear());
|
||||
await page.reload();
|
||||
await assertLink(page, "capture", true);
|
||||
await page.getByRole("searchbox").fill("emberpup");
|
||||
|
|
@ -198,16 +213,15 @@ for (const width of [1280, 390]) {
|
|||
await closeDrawer(page);
|
||||
await expect(card).toHaveAttribute("data-checklist-complete", "true");
|
||||
await page.getByRole("button", { name: "Réinitialiser les filtres", exact: true }).click();
|
||||
await page.locator(".guide-checklist-page-controls").getByRole("button", { name: "Dissocier la checklist", exact: true }).click();
|
||||
await openDrawer(page);
|
||||
await openControls(page, "capture");
|
||||
await expect(linkButton(page, "capture")).toHaveAttribute("aria-pressed", "true");
|
||||
await linkButton(page, "capture").click();
|
||||
await expect(linkButton(page, "capture")).toHaveAttribute("aria-pressed", "false");
|
||||
await expect(activeButtons(page)).toHaveCount(0);
|
||||
await expect(hiddenSwitch(page)).toHaveCount(0);
|
||||
await closeDrawer(page);
|
||||
await expect(page.locator('[data-checklist-complete="true"]')).toHaveCount(0);
|
||||
expect(await page.evaluate((key) => globalThis.sessionStorage.getItem(key), sessionKey)).toBeNull();
|
||||
expect(await page.evaluate((key) => globalThis.localStorage.getItem(key), storageKey)).toBeNull();
|
||||
expect(await page.evaluate(() => globalThis.document.documentElement.scrollWidth <= globalThis.innerWidth)).toBe(true);
|
||||
});
|
||||
}
|
||||
|
|
@ -261,7 +275,7 @@ test("renames retain a zero-match selection; changing toolbox clears the associa
|
|||
await dialog.getByRole("button", { name: "Valider", exact: true }).click();
|
||||
await expect(activeButtons(page)).toHaveCount(0);
|
||||
await expect(page.locator(".guide-checklist-controls")).toHaveCount(1);
|
||||
expect(await page.evaluate((key) => globalThis.sessionStorage.getItem(key), sessionKey)).toBeNull();
|
||||
expect(await page.evaluate((key) => globalThis.localStorage.getItem(key), storageKey)).toBeNull();
|
||||
});
|
||||
|
||||
test("deleting a selected checklist clears its link immediately", async ({ page }) => {
|
||||
|
|
@ -276,20 +290,20 @@ test("deleting a selected checklist clears its link immediately", async ({ page
|
|||
await expect(hiddenSwitch(page)).toHaveCount(0);
|
||||
await page.reload();
|
||||
await assertLink(page, "", false);
|
||||
expect(await page.evaluate((key) => globalThis.sessionStorage.getItem(key), sessionKey)).toBeNull();
|
||||
expect(await page.evaluate((key) => globalThis.localStorage.getItem(key), storageKey)).toBeNull();
|
||||
});
|
||||
|
||||
test("unavailable session storage retains links across navigation; overview has no guide controls", async ({ page }) => {
|
||||
test("unavailable local storage retains links in memory across navigation; overview has no guide controls", async ({ page }) => {
|
||||
await seed(page);
|
||||
await page.addInitScript((key) => {
|
||||
for (const method of ["getItem", "setItem", "removeItem"]) {
|
||||
const original = globalThis.Storage.prototype[method];
|
||||
globalThis.Storage.prototype[method] = function (name, ...args) {
|
||||
if (name === key) throw new Error("Session storage unavailable");
|
||||
if (name === key) throw new Error("Local storage unavailable");
|
||||
return original.call(this, name, ...args);
|
||||
};
|
||||
}
|
||||
}, sessionKey);
|
||||
}, storageKey);
|
||||
await page.reload();
|
||||
await page.goto(route);
|
||||
await choose(page, "perfect");
|
||||
|
|
@ -304,4 +318,8 @@ test("unavailable session storage retains links across navigation; overview has
|
|||
await expect(page.locator(".module-guide-button")).toHaveCount(0);
|
||||
await page.goto(route);
|
||||
await assertLink(page, "perfect", true);
|
||||
await openDrawer(page);
|
||||
await page.goto("/#/toolboxes");
|
||||
await expect(page.locator("#toolbox-drawer .drawer-panel")).toHaveCount(0);
|
||||
await expect(page.locator(".app-shell")).not.toHaveClass(/toolbox-drawer-open/);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -63,3 +63,17 @@ test("cross-column drops and empty column boundaries preserve every module and t
|
|||
await page.getByRole("button", { name: "Afficher en lignes", exact: true }).click();
|
||||
await expect(page.locator(".module-title")).toHaveText(["A", "B", "C", "D"]);
|
||||
});
|
||||
|
||||
test("adding an off-screen tool scrolls it into view", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 900, height: 420 });
|
||||
await setup(page, 1);
|
||||
await page.getByRole("button", { name: "Afficher l'ajout rapide", exact: true }).click();
|
||||
await page.getByRole("button", { name: "Ajouter Compteurs", exact: true }).click();
|
||||
const added = page.locator('article[data-module-type="counters"]').last();
|
||||
await expect(page.locator("article.module")).toHaveCount(5);
|
||||
await expect.poll(async () => added.evaluate((element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return rect.top >= 0 && rect.top < globalThis.window.innerHeight && rect.bottom > 0;
|
||||
})).toBe(true);
|
||||
expect(await page.evaluate(() => globalThis.window.scrollY)).toBeGreaterThan(0);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ test("cancelled and unreadable replacements preserve the cover and allow selecti
|
|||
|
||||
test("game association takes precedence without discarding the custom cover", async ({ page }) => {
|
||||
await page.goto("/#/games/mhwilds");
|
||||
await page.getByRole("button", { name: "Associer une toolbox", exact: true }).click();
|
||||
await page.getByRole("button", { name: "Afficher la toolbox", exact: true }).click();
|
||||
const drawer = page.locator("#toolbox-drawer .drawer-panel");
|
||||
await importToolbox(page, "Linked cover", drawer);
|
||||
await expect(drawer.getByRole("heading", { name: "Linked cover" })).toBeVisible();
|
||||
|
|
@ -111,7 +111,7 @@ test("game association takes precedence without discarding the custom cover", as
|
|||
await expect(page.locator(".toolbox-game-icon img")).toHaveAttribute("src", gameCover);
|
||||
await expect(picker(page)).toHaveCount(0);
|
||||
await page.goto("/#/games/mhwilds");
|
||||
await page.getByRole("button", { name: "Ouvrir la toolbox Linked cover", exact: true }).click();
|
||||
await page.getByRole("button", { name: "Afficher la toolbox", exact: true }).click();
|
||||
await drawer.getByRole("button", { name: "Lier une toolbox", exact: true }).click();
|
||||
const dialog = page.getByRole("dialog", { name: "Lier une toolbox", exact: true });
|
||||
await dialog.getByLabel("Toolbox associée").selectOption("");
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
|
||||
async function openDrawer(page) {
|
||||
await page.locator(".drawer-button").click();
|
||||
const trigger = page.getByRole("button", { name: "Afficher la toolbox", exact: true });
|
||||
await expect(trigger).toBeVisible();
|
||||
await trigger.click();
|
||||
await expect(page.getByRole("button", { name: "Masquer la toolbox", exact: true })).toHaveAttribute("aria-pressed", "true");
|
||||
const drawer = page.locator("#toolbox-drawer");
|
||||
await expect(drawer.locator(".drawer-panel")).toBeVisible();
|
||||
return drawer;
|
||||
|
|
@ -61,6 +64,7 @@ test("drawer reserves 700px while additional width overlays the page", async ({
|
|||
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(page.getByRole("button", { name: "Afficher la toolbox", exact: true })).toHaveAttribute("aria-pressed", "false");
|
||||
await expect(sidebar).not.toHaveAttribute("data-variant");
|
||||
await expect(sidebar).toHaveCSS("width", "248px");
|
||||
const reopenedDrawer = await openDrawer(page);
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ test("loads randomizer tabs after module split", async ({ page }) => {
|
|||
test("creates a game-linked toolbox from the drawer", async ({ page }) => {
|
||||
await page.goto("/#/games/mhwilds");
|
||||
await expect(page.getByRole("heading", { name: /Monster Hunter.*Wilds/ })).toBeVisible();
|
||||
await page.getByRole("button", { name: "Associer une toolbox" }).click();
|
||||
await page.getByRole("button", { name: "Afficher la toolbox", exact: true }).click();
|
||||
|
||||
const drawer = page.locator("#toolbox-drawer .drawer-panel");
|
||||
await expect(drawer).toBeVisible();
|
||||
|
|
@ -216,7 +216,7 @@ test("creates a game-linked toolbox from the drawer", async ({ page }) => {
|
|||
await expect(drawer.getByRole("heading", { name: /Monster Hunter.*Wilds/ })).toBeVisible();
|
||||
await expect(drawer.locator("[data-module-type='memos']")).toBeVisible();
|
||||
await expect(drawer.locator("[data-module-type='links']")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: /Ouvrir la toolbox Monster Hunter.*Wilds/ })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Masquer la toolbox", exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test("imports and exports a toolbox payload", async ({ page }) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue