// Rôle : vérifie les calculs hiérarchiques et les actions du calculateur dans une toolbox réelle. import { expect, test } from "@playwright/test"; async function setup(page) { await page.goto("/#/toolboxes"); await page.getByRole("button", { name: "Nouvelle toolbox" }).click(); const dialog = page.getByRole("dialog", { name: "Nouvelle toolbox" }); await dialog.getByLabel("Nom").fill("Calculator workflows"); await dialog.getByRole("button", { name: "Créer", exact: true }).click(); await page.getByRole("button", { name: "Ajouter un outil", exact: true }).click(); await page.getByRole("dialog", { name: "Ajouter un outil" }).getByRole("button", { name: "Calculateur" }).click(); return page.locator('[data-module-type="calculator"]'); } function entry(tool, label) { return tool.locator(".calculator-entry").filter({ has: tool.page().getByRole("button", { name: `Supprimer ${label}`, exact: true }) }); } async function saveRoot(tool, formula, label) { await tool.getByRole("textbox", { name: "Calcul", exact: true }).fill(formula); await tool.getByLabel("Libellé", { exact: true }).fill(label); await tool.getByRole("button", { name: "Enregistrer", exact: true }).click(); await expect(entry(tool, label)).toBeVisible(); } test("parent edits recalculate descendants and survive reload; deleting a parent removes its subtree", async ({ page }) => { const tool = await setup(page); const expression = tool.getByRole("textbox", { name: "Calcul", exact: true }); await expression.fill("2 +"); await expect(tool.getByRole("button", { name: "Enregistrer", exact: true })).toBeDisabled(); await saveRoot(tool, "2 + 3 * 4", "Parent"); await expect(entry(tool, "Parent").locator("strong")).toHaveAttribute("aria-label", "14"); await tool.getByRole("button", { name: "Ajouter un calcul enfant Parent", exact: true }).click(); await expect(expression).toBeFocused(); await expect(expression).toContainText("Valeur du parent"); await expression.press("End"); await expression.pressSequentially(" * 3"); await tool.getByLabel("Libellé", { exact: true }).fill("Child"); await tool.getByRole("button", { name: "Mettre à jour", exact: true }).click(); await expect(entry(tool, "Child").locator("strong")).toHaveAttribute("aria-label", "42"); await entry(tool, "Parent").getByTitle("Modifier", { exact: true }).click(); await expression.fill("20"); await tool.getByRole("button", { name: "Mettre à jour", exact: true }).click(); await expect(entry(tool, "Child").locator("strong")).toHaveAttribute("aria-label", "60"); await page.reload(); await expect(entry(tool, "Parent").locator("strong")).toHaveAttribute("aria-label", "20"); await expect(entry(tool, "Child").locator("strong")).toHaveAttribute("aria-label", "60"); await entry(tool, "Child").getByTitle("Modifier", { exact: true }).click(); await tool.getByRole("button", { name: "Supprimer Parent", exact: true }).click(); await expect(tool.locator(".calculator-entry")).toHaveCount(0); await expect(expression).toHaveValue(""); await expect(tool.getByRole("button", { name: "Enregistrer", exact: true })).toBeDisabled(); await page.reload(); await expect(tool.getByText("Aucun résultat enregistré.", { exact: true })).toBeVisible(); }); test("current values are subtracted from totals and kept during recalculation", async ({ page }) => { const tool = await setup(page); const expression = tool.getByRole("textbox", { name: "Calcul", exact: true }); const current = tool.getByLabel("Actuel", { exact: true }); const totalLabel = tool.getByText("Total", { exact: true }); await expect(current).toHaveCount(0); await expect(totalLabel).toHaveCount(0); const subtractCurrent = tool.getByRole("button", { name: "Soustraire l'actuel", exact: true }); await expect(subtractCurrent).toHaveAttribute("aria-pressed", "false"); await subtractCurrent.click(); await expect(subtractCurrent).toHaveAttribute("aria-pressed", "true"); await expect(current).toBeFocused(); await expect(totalLabel).toBeVisible(); await expect(tool.locator(".calculator-result-field")).toHaveClass(/has-current/); await expect(tool.locator(".calculator-result").getByText("Résultat", { exact: true })).toHaveCount(0); await expect(tool.locator(".calculator-result-field > span")).toHaveText("Résultat"); const expressionBounds = await expression.boundingBox(); const currentBounds = await current.boundingBox(); expect(currentBounds.x).toBeCloseTo(expressionBounds.x, 0); expect(currentBounds.width).toBeCloseTo(expressionBounds.width, 0); await expression.fill("10"); await current.fill("2"); await tool.getByLabel("Libellé", { exact: true }).fill("Parent"); await expect(tool.locator(".calculator-result strong")).toHaveAttribute("aria-label", "8"); await tool.getByRole("button", { name: "Enregistrer", exact: true }).click(); await tool.getByRole("button", { name: "Ajouter un calcul enfant Parent", exact: true }).click(); await expression.fill("base * 10"); await subtractCurrent.click(); await current.fill("20"); await tool.getByLabel("Libellé", { exact: true }).fill("Child"); await expect(tool.locator(".calculator-result strong")).toHaveAttribute("aria-label", "60"); await tool.getByRole("button", { name: "Mettre à jour", exact: true }).click(); await entry(tool, "Parent").getByTitle("Modifier", { exact: true }).click(); await expect(current).toHaveValue("2"); await expression.fill("20"); await tool.getByRole("button", { name: "Mettre à jour", exact: true }).click(); await expect(entry(tool, "Child").locator("strong")).toHaveAttribute("aria-label", "160"); await page.reload(); await entry(tool, "Child").getByTitle("Modifier", { exact: true }).click(); await expect(current).toHaveValue("20"); await expect(tool.locator(".calculator-result strong")).toHaveAttribute("aria-label", "160"); await subtractCurrent.click(); await expect(current).toHaveCount(0); await expect(tool.locator(".calculator-result-field")).not.toHaveClass(/has-current/); await expect(tool.locator(".calculator-result strong")).toHaveAttribute("aria-label", "180"); }); test("reset clears only the draft, new calculation returns to root, and clipboard and scroll preferences persist", async ({ page, context }) => { await context.grantPermissions(["clipboard-read", "clipboard-write"]); const tool = await setup(page); await expect(tool.getByRole("button", { name: "Copier pour checklist", exact: true })).toBeDisabled(); await saveRoot(tool, "10", "Ore"); await tool.getByRole("button", { name: "Ajouter un calcul enfant Ore", exact: true }).click(); await tool.getByRole("textbox", { name: "Calcul", exact: true }).fill("base * 2"); await tool.getByLabel("Libellé", { exact: true }).fill("Bars"); await tool.getByRole("button", { name: "Mettre à jour", exact: true }).click(); await tool.getByRole("button", { name: "Nouveau calcul", exact: true }).click(); await saveRoot(tool, "7", "Spare"); await tool.getByRole("textbox", { name: "Calcul", exact: true }).fill("99"); await tool.getByLabel("Libellé", { exact: true }).fill("Unsaved"); await tool.getByRole("button", { name: "Réinitialiser", exact: true }).click(); await expect(tool.getByRole("textbox", { name: "Calcul", exact: true })).toHaveValue(""); await expect(tool.getByLabel("Libellé", { exact: true })).toHaveValue(""); await expect(tool.locator(".calculator-entry")).toHaveCount(3); await tool.getByRole("button", { name: "Copier pour checklist", exact: true }).click(); await expect.poll(() => page.evaluate(() => globalThis.navigator.clipboard.readText())).toBe("Ore:10\nBars:20\nSpare:7"); const scroll = tool.getByRole("button", { name: "Liste scrollable", exact: true }); await expect(scroll).toHaveAttribute("aria-pressed", "false"); await scroll.click(); await page.reload(); await expect(scroll).toHaveAttribute("aria-pressed", "true"); await expect(tool.locator(".calculator-tree")).toHaveCSS("overflow-y", "auto"); await scroll.click(); await page.reload(); await expect(scroll).toHaveAttribute("aria-pressed", "false"); await expect(tool.locator(".calculator-entry")).toHaveCount(3); }); test("reordering roots keeps descendants attached and rejects moving a child to another parent", async ({ page }) => { const tool = await setup(page); await saveRoot(tool, "10", "First"); await saveRoot(tool, "20", "Second"); await tool.getByRole("button", { name: "Ajouter un calcul enfant First", exact: true }).click(); await tool.getByLabel("Libellé", { exact: true }).fill("Child"); await tool.getByRole("button", { name: "Mettre à jour", exact: true }).click(); async function drag(label, targetLabel) { await tool.getByRole("button", { name: `Déplacer ${label}`, exact: true }).hover(); await page.mouse.down(); const target = entry(tool, targetLabel); await target.scrollIntoViewIfNeeded(); const box = await target.boundingBox(); await page.mouse.move(box.x + box.width / 2, box.y + box.height * 0.2, { steps: 5 }); await page.mouse.up(); } await drag("Second", "First"); await expect(tool.locator(".calculator-entry em")).toHaveText(["Second", "First", "Child"]); await drag("Child", "Second"); await expect(tool.locator(".calculator-entry em")).toHaveText(["Second", "First", "Child"]); await page.reload(); await expect(tool.locator(".calculator-entry em")).toHaveText(["Second", "First", "Child"]); await tool.getByRole("button", { name: "Supprimer First", exact: true }).click(); await expect(tool.locator(".calculator-entry em")).toHaveText(["Second"]); });