Cover calculator hierarchy and result workflows
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
7b160bac9c
commit
a4268dac54
4 changed files with 114 additions and 26 deletions
|
|
@ -50,3 +50,4 @@
|
||||||
{"id":"int-e3a2bea901894d43a31eb628b588648a","kind":"field_change","created_at":"2026-09-14T07:08:33.027716428Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
|
{"id":"int-e3a2bea901894d43a31eb628b588648a","kind":"field_change","created_at":"2026-09-14T07:08:33.027716428Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
|
||||||
{"id":"int-20d1e0d862e6155eadf2c36eced314db","kind":"field_change","created_at":"2026-09-14T11:56:36.962825878Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
|
{"id":"int-20d1e0d862e6155eadf2c36eced314db","kind":"field_change","created_at":"2026-09-14T11:56:36.962825878Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
|
||||||
{"id":"int-cc67f2645165e0eac9f57d71e8a34e57","kind":"field_change","created_at":"2026-09-14T12:11:10.207360007Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
|
{"id":"int-cc67f2645165e0eac9f57d71e8a34e57","kind":"field_change","created_at":"2026-09-14T12:11:10.207360007Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
|
||||||
|
{"id":"int-f9245b926aa0f581b75f6b5904778e4e","kind":"field_change","created_at":"2026-09-14T13:08:47.57641261Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ Checklist à suivre lors de l'ajout ou de la modification d'un outil, d'une page
|
||||||
- Pour l'éditeur de couverture, vérifier les décodages concurrents, les erreurs après fermeture et la libération des bitmaps/URLs ; une image en chargement ne doit pas permettre de valider la couverture précédente.
|
- Pour l'éditeur de couverture, vérifier les décodages concurrents, les erreurs après fermeture et la libération des bitmaps/URLs ; une image en chargement ne doit pas permettre de valider la couverture précédente.
|
||||||
- Les parcours `e2e/toolbox-cover.spec.js` vérifient le choix d'icône, l'enregistrement d'une couverture 620 × 248, sa persistance après rechargement et import/export, l'annulation et les fichiers illisibles. L'association à un jeu doit afficher sa couverture sans perdre l'image personnalisée, restaurée après dissociation ; choisir une icône doit supprimer la couverture personnalisée.
|
- Les parcours `e2e/toolbox-cover.spec.js` vérifient le choix d'icône, l'enregistrement d'une couverture 620 × 248, sa persistance après rechargement et import/export, l'annulation et les fichiers illisibles. L'association à un jeu doit afficher sa couverture sans perdre l'image personnalisée, restaurée après dissociation ; choisir une icône doit supprimer la couverture personnalisée.
|
||||||
- Pour Mémos, vérifier l'édition courte, les couleurs, la réorganisation et la bande déroulante.
|
- Pour Mémos, vérifier l'édition courte, les couleurs, la réorganisation et la bande déroulante.
|
||||||
|
- Les parcours `e2e/calculator.spec.js` vérifient la validation des formules, l'édition parent/enfant avec recalcul et persistance, la suppression d'une branche active, le retour à un calcul racine, la réinitialisation du brouillon sans effacer les résultats, la copie pour checklist et la persistance du défilement. La réorganisation des racines doit conserver leurs descendants et refuser de changer le parent d'un enfant.
|
||||||
- Vérifier qu'une notification ou une mise à jour sans rapport ne remplace pas un brouillon Mémos avant sa validation au blur.
|
- Vérifier qu'une notification ou une mise à jour sans rapport ne remplace pas un brouillon Mémos avant sa validation au blur.
|
||||||
- Pour l'arbre d'objectifs, vérifier la sélection après suppression/import et le retour à Base quand le nœud sélectionné devient une étape sans onglet Contenu.
|
- Pour l'arbre d'objectifs, vérifier la sélection après suppression/import et le retour à Base quand le nœud sélectionné devient une étape sans onglet Contenu.
|
||||||
- Vérifier le mode une colonne et deux colonnes.
|
- Vérifier le mode une colonne et deux colonnes.
|
||||||
|
|
|
||||||
112
e2e/calculator.spec.js
Normal file
112
e2e/calculator.spec.js
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
// 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", exact: true }).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("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"]);
|
||||||
|
});
|
||||||
|
|
@ -106,9 +106,6 @@ test("toolbox module registry and modules expose expected behavior", async () =>
|
||||||
const combosBundle = `${combosModule}\n${combosEditor}\n${combosParts}\n${combosList}\n${combosEditorState}\n${combosReorder}`;
|
const combosBundle = `${combosModule}\n${combosEditor}\n${combosParts}\n${combosList}\n${combosEditorState}\n${combosReorder}`;
|
||||||
const combosUtils = await readFile("website/src/features/toolboxes/modules/combosUtils.js", "utf8");
|
const combosUtils = await readFile("website/src/features/toolboxes/modules/combosUtils.js", "utf8");
|
||||||
const calculatorModule = await readFile("website/src/features/toolboxes/modules/CalculatorModule.jsx", "utf8");
|
const calculatorModule = await readFile("website/src/features/toolboxes/modules/CalculatorModule.jsx", "utf8");
|
||||||
const calculatorEntries = await readFile("website/src/features/toolboxes/modules/CalculatorEntries.jsx", "utf8");
|
|
||||||
const calculatorUtils = await readFile("website/src/features/toolboxes/modules/calculatorUtils.js", "utf8");
|
|
||||||
const calculatorBundle = `${calculatorModule}\n${calculatorEntries}\n${calculatorUtils}`;
|
|
||||||
const tableModule = await readFile("website/src/features/toolboxes/modules/TableModule.jsx", "utf8");
|
const tableModule = await readFile("website/src/features/toolboxes/modules/TableModule.jsx", "utf8");
|
||||||
const tableGrid = await readFile("website/src/features/toolboxes/modules/TableGrid.jsx", "utf8");
|
const tableGrid = await readFile("website/src/features/toolboxes/modules/TableGrid.jsx", "utf8");
|
||||||
const tableGridParts = await readFile("website/src/features/toolboxes/modules/TableGridParts.jsx", "utf8");
|
const tableGridParts = await readFile("website/src/features/toolboxes/modules/TableGridParts.jsx", "utf8");
|
||||||
|
|
@ -276,12 +273,6 @@ test("toolbox module registry and modules expose expected behavior", async () =>
|
||||||
assert.match(combosBundle, /setItemGroup: setComboItemCategory/);
|
assert.match(combosBundle, /setItemGroup: setComboItemCategory/);
|
||||||
assert.doesNotMatch(combosModule, /canMoveItem:/);
|
assert.doesNotMatch(combosModule, /canMoveItem:/);
|
||||||
assert.doesNotMatch(combosModule, /canMoveGroup:/);
|
assert.doesNotMatch(combosModule, /canMoveGroup:/);
|
||||||
assert.match(calculatorModule, /export function CalculatorModule/);
|
|
||||||
assert.match(calculatorUtils, /export function calculateExpression/);
|
|
||||||
assert.match(calculatorModule, /activeParentId/);
|
|
||||||
assert.match(calculatorModule, /parentId/);
|
|
||||||
assert.match(calculatorModule, /MixTagsInput/);
|
|
||||||
assert.match(calculatorModule, /useGroupedReorder/);
|
|
||||||
assert.match(moduleMetadataRegistry, /label: "Mémos"/);
|
assert.match(moduleMetadataRegistry, /label: "Mémos"/);
|
||||||
assert.match(moduleMetadataRegistry, /defaultTitle: "Mémos"/);
|
assert.match(moduleMetadataRegistry, /defaultTitle: "Mémos"/);
|
||||||
assert.match(moduleMetadataRegistry, /label: "Bloc Note"/);
|
assert.match(moduleMetadataRegistry, /label: "Bloc Note"/);
|
||||||
|
|
@ -295,25 +286,8 @@ test("toolbox module registry and modules expose expected behavior", async () =>
|
||||||
assert.match(randomizerShared, /export function RandomizerHistory/);
|
assert.match(randomizerShared, /export function RandomizerHistory/);
|
||||||
assert.match(randomizerUiUtils, /export function hasReducedMotion/);
|
assert.match(randomizerUiUtils, /export function hasReducedMotion/);
|
||||||
assert.match(randomizerBundle, /type="randomizer"/);
|
assert.match(randomizerBundle, /type="randomizer"/);
|
||||||
assert.match(calculatorEntries, /calculator-drag-handle/);
|
|
||||||
assert.match(calculatorModule, /scrollResults/);
|
|
||||||
assert.match(calculatorModule, /tool-split-scroll-toggle/);
|
|
||||||
assert.match(calculatorModule, /copyChecklistImport/);
|
|
||||||
assert.match(calculatorModule, /resetCalculator/);
|
|
||||||
assert.match(calculatorModule, /returnToRoot/);
|
|
||||||
assert.match(calculatorModule, /createChildEntry/);
|
|
||||||
assert.match(calculatorModule, /const childId = context\.uid\("calc"\)/);
|
|
||||||
assert.match(calculatorModule, /parentId: entry\.id/);
|
|
||||||
assert.match(calculatorModule, /formula: BASE_TOKEN/);
|
|
||||||
assert.match(calculatorModule, /pendingExpressionFocusRef/);
|
|
||||||
assert.match(calculatorModule, /setExpression\(BASE_TOKEN\)/);
|
|
||||||
assert.match(calculatorModule, /editingEntryId/);
|
|
||||||
assert.match(calculatorModule, /updateButton/);
|
|
||||||
assert.match(calculatorEntries, /onAddChild/);
|
|
||||||
assert.match(calculatorEntries, /entry\.label \|\| formatResult\(entry\.value\)/);
|
|
||||||
assert.match(calculatorModule, /ResizeObserver/);
|
assert.match(calculatorModule, /ResizeObserver/);
|
||||||
assert.match(calculatorModule, /--calculator-scroll-height/);
|
assert.match(calculatorModule, /--calculator-scroll-height/);
|
||||||
assert.match(calculatorBundle, /CalculatorEntries/);
|
|
||||||
assert.match(tableModule, /export function TableModule/);
|
assert.match(tableModule, /export function TableModule/);
|
||||||
assert.match(tableBundle, /Icon name="add-row"/);
|
assert.match(tableBundle, /Icon name="add-row"/);
|
||||||
assert.match(tableBundle, /Icon name="add-column"/);
|
assert.match(tableBundle, /Icon name="add-column"/);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue