Add planner-aware memos and unified tool configuration panels
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
acd9b2fe11
commit
808610dcf2
53 changed files with 842 additions and 329 deletions
|
|
@ -8,7 +8,7 @@ import { normalizeChecklistData } from "../website/src/features/toolboxes/storag
|
|||
const route = "/#/games/aniimo/aniimos";
|
||||
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 controlsToggle = (page, id) => page.locator('[data-module-type="checklist"]').filter({ has: page.getByRole("button", { name: `Retirer ${id}`, exact: true }) }).getByRole("button", { name: "Configuration", exact: true });
|
||||
const linkButton = (page, id) => controls(page, id).locator(".guide-checklist-link");
|
||||
const activeButtons = (page) => page.locator('.guide-checklist-link[aria-pressed="true"]');
|
||||
const hiddenSwitch = (page) => page.locator('.guide-checklist-controls[data-linked="true"]').getByRole("button", { name: "Masquer les cochés", exact: true });
|
||||
|
|
@ -97,7 +97,7 @@ async function closeDrawer(page) {
|
|||
await page.locator("#toolbox-drawer").getByRole("button", { name: "Fermer", exact: true }).click();
|
||||
}
|
||||
async function openControls(page, id) {
|
||||
const toggle = id ? controlsToggle(page, id) : page.locator('.module-guide-button[data-linked="true"]');
|
||||
const toggle = id ? controlsToggle(page, id) : page.locator('.module-configuration-button[data-guide-linked="true"]');
|
||||
await expect(toggle).toBeVisible();
|
||||
if (await toggle.getAttribute("aria-expanded") === "false") await toggle.click();
|
||||
}
|
||||
|
|
@ -117,8 +117,8 @@ async function toggleHidden(page) {
|
|||
}
|
||||
async function assertLink(page, id, hidden) {
|
||||
await openDrawer(page);
|
||||
await expect(activeButtons(page)).toHaveCount(id ? 1 : 0);
|
||||
if (id) await openControls(page, id);
|
||||
await expect(activeButtons(page)).toHaveCount(id ? 1 : 0);
|
||||
if (id) await expect(linkButton(page, id)).toHaveAttribute("aria-pressed", "true");
|
||||
if (id) await expect(hiddenSwitch(page)).toHaveAttribute("aria-pressed", String(hidden));
|
||||
else await expect(hiddenSwitch(page)).toHaveCount(0);
|
||||
|
|
@ -206,11 +206,11 @@ for (const width of [1280, 390]) {
|
|||
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);
|
||||
await expect(page.locator(".guide-checklist-controls")).toHaveCount(0);
|
||||
await expect(page.locator(".guide-checklist-controls select")).toHaveCount(0);
|
||||
await expect(controls(page, "unrelated")).toHaveCount(0);
|
||||
await expect(controlsToggle(page, "unrelated")).toHaveCount(0);
|
||||
await expect(controls(page, "capture")).toBeHidden();
|
||||
await expect(controlsToggle(page, "unrelated")).toHaveCount(1);
|
||||
await expect(controls(page, "capture")).toHaveCount(0);
|
||||
await openControls(page, "capture");
|
||||
await expect(controls(page, "capture").getByRole("button", { name: "Masquer les cochés" })).toBeDisabled();
|
||||
const before = await readModule(page, "forms");
|
||||
|
|
@ -218,15 +218,14 @@ for (const width of [1280, 390]) {
|
|||
await expect(heading).toHaveAttribute("aria-expanded", "false");
|
||||
expect(await heading.evaluate((button) => button.previousElementSibling.classList.contains("module-edit-button"))).toBe(true);
|
||||
await heading.press("Enter");
|
||||
await expect(heading).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(controls(page, "forms")).toHaveAttribute("id", await heading.getAttribute("aria-controls"));
|
||||
await expect(heading).toHaveAttribute("aria-expanded", "true");
|
||||
await expect(page.locator(`[id="${await heading.getAttribute("aria-controls")}"]`)).toHaveClass(/module-configuration-panel/);
|
||||
await linkButton(page, "forms").press("Enter");
|
||||
await expect(linkButton(page, "forms")).toHaveAttribute("aria-pressed", "true");
|
||||
const panel = controls(page, "forms");
|
||||
await heading.press("Space");
|
||||
await expect(heading).toHaveAttribute("aria-expanded", "false");
|
||||
await expect(heading).toHaveAttribute("aria-pressed", "false");
|
||||
await expect(heading).toHaveAttribute("data-linked", "true");
|
||||
await expect(heading).toHaveAttribute("data-guide-linked", "true");
|
||||
await expect(linkButton(page, "forms")).toBeHidden();
|
||||
await expect(card).toHaveAttribute("data-checklist-complete", "true");
|
||||
await heading.press("Enter");
|
||||
|
|
@ -357,7 +356,7 @@ test("renames retain a zero-match selection; changing toolbox clears the associa
|
|||
await dialog.getByRole("combobox").selectOption("other");
|
||||
await dialog.getByRole("button", { name: "Valider", exact: true }).click();
|
||||
await expect(activeButtons(page)).toHaveCount(0);
|
||||
await expect(page.locator(".guide-checklist-controls")).toHaveCount(1);
|
||||
await expect(page.locator(".guide-checklist-controls")).toHaveCount(0);
|
||||
expect(await page.evaluate((key) => globalThis.localStorage.getItem(key), storageKey)).toBeNull();
|
||||
});
|
||||
|
||||
|
|
@ -398,7 +397,7 @@ test("unavailable local storage retains links in memory across navigation; overv
|
|||
await page.goto("/#/toolbox/guide");
|
||||
await expect(page.locator('[data-module-type="checklist"]')).toHaveCount(4);
|
||||
await expect(page.locator(".guide-checklist-controls")).toHaveCount(0);
|
||||
await expect(page.locator(".module-guide-button")).toHaveCount(0);
|
||||
await expect(page.locator('.module-configuration-button[data-guide-linked="true"]')).toHaveCount(0);
|
||||
await page.goto(route);
|
||||
await assertLink(page, "perfect", true);
|
||||
await openDrawer(page);
|
||||
|
|
|
|||
|
|
@ -10,14 +10,41 @@ async function seedMemosToolbox(page) {
|
|||
request.onsuccess = () => {
|
||||
const db = request.result;
|
||||
const tx = db.transaction(["kv", "modules"], "readwrite");
|
||||
const soonReset = new Date(Date.now() + 60 * 60 * 1000);
|
||||
const resetTime = `${String(soonReset.getHours()).padStart(2, "0")}:${String(soonReset.getMinutes()).padStart(2, "0")}`;
|
||||
tx.objectStore("kv").put([
|
||||
{ id: "memo-toolbox", name: "Toolbox mémos", modules: [{ id: "memos", type: "memos", title: "Mémos" }], moduleColumns: 1 }
|
||||
{
|
||||
id: "memo-toolbox",
|
||||
name: "Toolbox mémos",
|
||||
modules: [
|
||||
{ id: "memos", type: "memos", title: "Mémos" },
|
||||
{ id: "planner-one", type: "taskPlanner", title: "Urgent planner" },
|
||||
{ id: "planner-two", type: "taskPlanner", title: "Other planner" }
|
||||
],
|
||||
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.objectStore("modules").put({
|
||||
key: "memo-toolbox:planner-one",
|
||||
data: {
|
||||
resetTime,
|
||||
tasks: [
|
||||
{ id: "category-parent", title: "Raid principal", type: "unique", category: "Raid" },
|
||||
{ id: "daily-task", title: "Faire le donjon", type: "daily" },
|
||||
{ id: "done-task", title: "Déjà terminée", type: "daily", checked: true, checkedAt: Date.now() }
|
||||
],
|
||||
relations: [{ id: "relation", fromTaskId: "daily-task", toTaskId: "category-parent" }]
|
||||
}
|
||||
});
|
||||
tx.objectStore("modules").put({
|
||||
key: "memo-toolbox:planner-two",
|
||||
data: { tasks: [{ id: "unique-task", title: "Ponctuelle", type: "unique" }] }
|
||||
});
|
||||
tx.oncomplete = () => { db.close(); resolve(); };
|
||||
tx.onerror = () => reject(tx.error);
|
||||
};
|
||||
|
|
@ -54,3 +81,34 @@ test("shows and persists the memo ticker on linked game and full toolbox pages",
|
|||
await page.goto("/#/toolboxes");
|
||||
await expect(globalTicker).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("links a planner, shows its reset warning and clears the link when it is removed", async ({ page }) => {
|
||||
await seedMemosToolbox(page);
|
||||
await page.goto("/#/toolbox/memo-toolbox");
|
||||
|
||||
const memos = page.locator('[data-module-id="memos"]');
|
||||
await memos.getByRole("button", { name: "Configuration", exact: true }).click();
|
||||
const dataSectionBox = await memos.locator(".module-configuration-data").boundingBox();
|
||||
const settingsSectionBox = await memos.locator(".module-configuration-specific").boundingBox();
|
||||
expect(settingsSectionBox.y).toBeGreaterThan(dataSectionBox.y + dataSectionBox.height);
|
||||
expect(settingsSectionBox.width).toBeCloseTo(dataSectionBox.width, 0);
|
||||
const plannerSelect = memos.getByRole("combobox", { name: "Planificateur lié", exact: true });
|
||||
await expect(plannerSelect).toHaveValue("");
|
||||
await plannerSelect.selectOption("planner-one");
|
||||
|
||||
const globalTicker = page.locator(".global-memos-ticker");
|
||||
await expect(globalTicker).toContainText("Tâche : Raid Faire le donjon expire bientôt");
|
||||
await expect(globalTicker).not.toContainText("Déjà terminée");
|
||||
await expect(globalTicker.locator(".memos-ticker-item.is-task-warning .ui-icon-warning")).toHaveCount(2);
|
||||
|
||||
const planner = page.locator('[data-module-id="planner-one"]');
|
||||
await planner.getByRole("button", { name: "Retirer Urgent planner", exact: true }).click();
|
||||
await page.getByRole("dialog").getByRole("button", { name: "Retirer", exact: true }).click();
|
||||
await expect(planner).toHaveCount(0);
|
||||
await expect(plannerSelect).toHaveValue("");
|
||||
await expect(globalTicker).not.toContainText("Faire le donjon");
|
||||
|
||||
await page.reload();
|
||||
await memos.getByRole("button", { name: "Configuration", exact: true }).click();
|
||||
await expect(plannerSelect).toHaveValue("");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ test("recurring resets preserve unique completions and cleanup deletes only comp
|
|||
await expect(hide).toHaveAttribute("aria-pressed", "true");
|
||||
await hide.click();
|
||||
await expect(tool.getByRole("checkbox", { name: "Marquer non effectué Parent", exact: true })).toBeChecked();
|
||||
await tool.getByRole("button", { name: "Réglages du planificateur", exact: true }).click();
|
||||
await tool.getByRole("button", { name: "Configuration", exact: true }).click();
|
||||
const cleanup = tool.getByRole("button", { name: "Nettoyer les tâches ponctuelles effectuées", exact: true });
|
||||
await cleanup.click();
|
||||
await expect(cleanup).toBeDisabled();
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ test("table text import/export preserves formulas and labels, copies read-only t
|
|||
const table = page.locator('[data-module-type="table"]');
|
||||
const importButton = table.getByRole("button", { name: "Importer TSV", exact: true });
|
||||
await expect(importButton).toBeHidden();
|
||||
await table.getByRole("button", { name: "Afficher l'import/export de Tableau", exact: true }).click();
|
||||
await table.getByRole("button", { name: "Configuration", exact: true }).click();
|
||||
await expect(table.getByRole("button", { name: "Exporter TSV", exact: true })).toBeDisabled();
|
||||
const originalOverflow = await page.locator("body").evaluate((body) => body.style.overflow);
|
||||
await importButton.click();
|
||||
|
|
@ -49,7 +49,7 @@ test("table text import/export preserves formulas and labels, copies read-only t
|
|||
await expect(table.getByRole("textbox", { name: "Intitulé de colonne A", exact: true })).toHaveValue("Quantity");
|
||||
await expect(table.getByRole("textbox", { name: "Intitulé de ligne 1", exact: true })).toHaveValue("Ore");
|
||||
await expect(table.getByRole("textbox", { name: "Cellule B1", exact: true })).toHaveValue("6");
|
||||
await table.getByRole("button", { name: "Afficher l'import/export de Tableau", exact: true }).click();
|
||||
await table.getByRole("button", { name: "Configuration", exact: true }).click();
|
||||
await table.getByRole("button", { name: "Exporter TSV", exact: true }).click();
|
||||
const exported = page.getByRole("dialog", { name: "Exporter un tableau TSV", exact: true });
|
||||
await expect(exported.getByRole("textbox")).toHaveAttribute("readonly", "");
|
||||
|
|
@ -60,14 +60,15 @@ test("table text import/export preserves formulas and labels, copies read-only t
|
|||
await exported.getByRole("textbox").press("Escape");
|
||||
await expect(exported).toBeHidden();
|
||||
await expect.poll(() => page.locator("body").evaluate((body) => body.style.overflow)).toBe(originalOverflow);
|
||||
await table.getByRole("button", { name: "Masquer l'import/export de Tableau", exact: true }).click();
|
||||
await table.getByRole("button", { name: "Configuration", exact: true }).click();
|
||||
await expect(importButton).toBeHidden();
|
||||
await expect(table.getByRole("button", { name: "Configuration", exact: true })).toHaveAttribute("aria-expanded", "false");
|
||||
});
|
||||
|
||||
test("invalid calculator imports preserve existing results and a corrected import replaces them with a persistent hierarchy", async ({ page }) => {
|
||||
await setup(page);
|
||||
const calc = page.locator('[data-module-type="calculator"]');
|
||||
await calc.getByRole("button", { name: "Afficher l'import/export de Calculateur", exact: true }).click();
|
||||
await calc.getByRole("button", { name: "Configuration", exact: true }).click();
|
||||
const openImport = calc.getByRole("button", { name: "Importer du texte", exact: true });
|
||||
await openImport.click();
|
||||
const dialog = page.getByRole("dialog", { name: "Importer des résultats", exact: true });
|
||||
|
|
@ -78,7 +79,7 @@ test("invalid calculator imports preserve existing results and a corrected impor
|
|||
await dialog.getByRole("button", { name: "Fermer", exact: true }).click();
|
||||
await page.reload();
|
||||
await expect(calc.locator(".calculator-entry em")).toHaveText(["Original"]);
|
||||
await calc.getByRole("button", { name: "Afficher l'import/export de Calculateur", exact: true }).click();
|
||||
await calc.getByRole("button", { name: "Configuration", exact: true }).click();
|
||||
await openImport.click();
|
||||
await expect(dialog.getByRole("textbox")).toHaveValue("");
|
||||
await expect(dialog.getByRole("alert")).toHaveCount(0);
|
||||
|
|
@ -97,7 +98,7 @@ test("invalid calculator imports preserve existing results and a corrected impor
|
|||
await page.reload();
|
||||
await expect(calc.locator(".calculator-entry em")).toHaveText(["Ore", "Bars"]);
|
||||
await expect(calc.getByRole("button", { name: "Liste scrollable", exact: true })).toHaveAttribute("aria-pressed", "true");
|
||||
await calc.getByRole("button", { name: "Afficher l'import/export de Calculateur", exact: true }).click();
|
||||
await calc.getByRole("button", { name: "Configuration", exact: true }).click();
|
||||
await calc.getByRole("button", { name: "Exporter en format texte", exact: true }).click();
|
||||
const exported = page.getByRole("dialog", { name: "Exporter des résultats", exact: true });
|
||||
await expect(exported.getByRole("textbox")).toHaveValue(text);
|
||||
|
|
|
|||
|
|
@ -52,7 +52,18 @@ test("creates at most five spaces and moves tools with the accessible selector",
|
|||
await expect(page.getByRole("tab", { name: "Divers" })).toHaveAttribute("aria-selected", "true");
|
||||
|
||||
await page.getByRole("tab", { name: "Défaut" }).click();
|
||||
await page.getByRole("combobox", { name: "Déplacer vers… — Alpha" }).selectOption({ label: "Divers" });
|
||||
const alpha = page.locator("[data-module-id='a']");
|
||||
await alpha.getByRole("button", { name: "Configuration", exact: true }).click();
|
||||
const organization = alpha.locator(".module-configuration-organization");
|
||||
const data = alpha.locator(".module-configuration-data");
|
||||
await expect(organization).toBeVisible();
|
||||
await expect(data).toBeVisible();
|
||||
const organizationBox = await organization.boundingBox();
|
||||
const dataBox = await data.boundingBox();
|
||||
expect(dataBox.y).toBeCloseTo(organizationBox.y, 0);
|
||||
expect(dataBox.x).toBeGreaterThan(organizationBox.x + organizationBox.width);
|
||||
expect(dataBox.width).toBeCloseTo(organizationBox.width, 0);
|
||||
await alpha.getByRole("combobox", { name: "Déplacer vers un autre espace", exact: true }).selectOption({ label: "Divers" });
|
||||
await expect(page.locator("[data-module-id='a']")).toHaveCount(0);
|
||||
await page.getByRole("tab", { name: "Divers" }).click();
|
||||
await expect(page.locator("[data-module-id='a']")).toBeVisible();
|
||||
|
|
@ -66,6 +77,52 @@ test("creates at most five spaces and moves tools with the accessible selector",
|
|||
await expect(page.getByRole("tab", { name: "Défaut" })).toHaveAttribute("aria-selected", "true");
|
||||
});
|
||||
|
||||
test("keeps the compact header order and isolates configuration from add and collapsed content", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 390, height: 900 });
|
||||
await seedToolbox(page, {
|
||||
id: "module-configuration",
|
||||
name: "Module configuration",
|
||||
modules: [{ id: "a", type: "counters", title: "Alpha" }],
|
||||
moduleOrder: { one: ["a"], two: [["a"], []] }
|
||||
});
|
||||
|
||||
const tool = page.locator("[data-module-id='a']");
|
||||
await expect(tool.locator(".module-header-actions > button")).toHaveCount(5);
|
||||
expect(await tool.locator(".module-header-actions > button").evaluateAll((buttons) => buttons.map((button) => button.className.split(" ")[0]))).toEqual([
|
||||
"module-scroll-button",
|
||||
"module-edit-button",
|
||||
"module-configuration-button",
|
||||
"module-delete-button",
|
||||
"module-collapse-button"
|
||||
]);
|
||||
|
||||
const add = tool.getByRole("button", { name: "Afficher l'ajout de Alpha", exact: true });
|
||||
const configuration = tool.getByRole("button", { name: "Configuration", exact: true });
|
||||
await add.click();
|
||||
await expect(tool.locator(".module-add-panel")).toBeVisible();
|
||||
await configuration.click();
|
||||
await expect(add).toHaveAttribute("aria-pressed", "false");
|
||||
await expect(tool.locator(".module-add-panel")).toHaveCount(0);
|
||||
const panel = tool.getByRole("region", { name: "Configuration de Alpha", exact: true });
|
||||
await expect(panel).toBeVisible();
|
||||
await expect(panel.getByRole("button", { name: "Importer du texte", exact: true })).toBeVisible();
|
||||
const toolBox = await tool.boundingBox();
|
||||
const panelBox = await panel.boundingBox();
|
||||
expect(panelBox.width).toBeGreaterThan(toolBox.width - 24);
|
||||
expect(panelBox.width).toBeLessThan(toolBox.width);
|
||||
|
||||
await tool.getByRole("button", { name: "Masquer Alpha", exact: true }).click();
|
||||
await expect(panel).toHaveCount(0);
|
||||
await expect(tool.locator(".module-content")).toHaveCount(0);
|
||||
await configuration.click();
|
||||
await expect(panel).toBeVisible();
|
||||
await expect(tool.locator(".module-content")).toBeHidden();
|
||||
await expect(panel.getByRole("button", { name: "Importer du texte", exact: true })).toBeVisible();
|
||||
await configuration.press("Escape");
|
||||
await expect(panel).toHaveCount(0);
|
||||
await expect(configuration).toHaveAttribute("aria-expanded", "false");
|
||||
});
|
||||
|
||||
test("shows the tab insertion indicator on the correct side while reordering", async ({ page }) => {
|
||||
await seedToolbox(page, {
|
||||
id: "reorder-spaces",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue