Compare commits
No commits in common. "f0797ccf14544eedbbac4b5c9a2d7b4d071232e4" and "4eb48169379b617695601d21d5d66761bb24d320" have entirely different histories.
f0797ccf14
...
4eb4816937
6 changed files with 25 additions and 276 deletions
|
|
@ -53,5 +53,3 @@
|
||||||
{"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"}}
|
{"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"}}
|
||||||
{"id":"int-bb9e0e4a748e0fa71f7a88e8d5f1b9c1","kind":"field_change","created_at":"2026-09-17T15:41:40.939310492Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
|
{"id":"int-bb9e0e4a748e0fa71f7a88e8d5f1b9c1","kind":"field_change","created_at":"2026-09-17T15:41:40.939310492Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
|
||||||
{"id":"int-8ce072a6196bda5d63c77776fe0a1c14","kind":"field_change","created_at":"2026-09-17T16:06:03.481316789Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
|
{"id":"int-8ce072a6196bda5d63c77776fe0a1c14","kind":"field_change","created_at":"2026-09-17T16:06:03.481316789Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
|
||||||
{"id":"int-d8c4d7fe12bbb2589779d4fe3d920ba7","kind":"field_change","created_at":"2026-09-17T20:29:55.005289442Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
|
|
||||||
{"id":"int-3504141284535c146e3b392ad6bf43c8","kind":"field_change","created_at":"2026-09-17T21:51:54.241158283Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed agreed risk-based C06 scope on 2026-09-17: four final behavioral scenarios cover persistent Links, Counters, Checklist and planner subtree reorder; removed ten redundant implementation assertions. Coverage and optional extensions documented in docs/TEST_STRATEGY.md. npm run check:full passed: 107 Node tests, 113 development E2E and 1 production E2E. Exhaustive regex conversion and advanced permutations are explicitly outside closure scope."}}
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ Checklist à suivre lors de l'ajout ou de la modification d'un outil, d'une page
|
||||||
- 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.
|
||||||
- Les parcours `e2e/table.spec.js` vérifient les en-têtes éditables sans changer les références des formules, le redimensionnement sans restauration des cellules supprimées, la navigation et la sélection clavier, la copie des valeurs TSV, le collage et l'effacement de blocs, le déplacement à la souris et l'insertion d'une plage dans une formule. Les valeurs et dimensions doivent survivre au rechargement.
|
- Les parcours `e2e/table.spec.js` vérifient les en-têtes éditables sans changer les références des formules, le redimensionnement sans restauration des cellules supprimées, la navigation et la sélection clavier, la copie des valeurs TSV, le collage et l'effacement de blocs, le déplacement à la souris et l'insertion d'une plage dans une formule. Les valeurs et dimensions doivent survivre au rechargement.
|
||||||
- Les parcours `e2e/text-exchange.spec.js` vérifient les modales partagées via Tableau et Calculateur : export désactivé à vide, validation des imports, conservation des données après erreur, brouillon effacé à la réouverture, export en lecture seule et copie exacte. L'import doit persister avec ses formules, libellés et relations ; Annuler, Fermer, Échap et le fond de modale doivent fermer correctement, avec restitution du défilement.
|
- Les parcours `e2e/text-exchange.spec.js` vérifient les modales partagées via Tableau et Calculateur : export désactivé à vide, validation des imports, conservation des données après erreur, brouillon effacé à la réouverture, export en lecture seule et copie exacte. L'import doit persister avec ses formules, libellés et relations ; Annuler, Fermer, Échap et le fond de modale doivent fermer correctement, avec restitution du défilement.
|
||||||
- Les parcours `e2e/task-planner.spec.js` couvrent création, renommage, description multiligne, catégorie, suppression et persistance. Un parent transmet sa catégorie ; un prérequis manquant avertit sans bloquer la validation. Supprimer un parent doit conserver l'enfant sans relation orpheline. Avec une horloge contrôlée, les tâches quotidiennes et hebdomadaires se réinitialisent, les ponctuelles restent cochées ; masquage et nettoyage doivent préserver les autres tâches.
|
|
||||||
- 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.
|
- 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.
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
# Test Strategy
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
The risk-based completion boundary for audit C06 (2026-09-09), agreed on
|
|
||||||
2026-09-17, is regression coverage of important workflows, not elimination of
|
|
||||||
every source-text assertion. Beads task `sokko-g-zsd` tracks acceptance and status.
|
|
||||||
This document is a coverage reference, not a separate task backlog.
|
|
||||||
|
|
||||||
An inventory found 350 `match` / `doesNotMatch` calls across the static app,
|
|
||||||
toolbox and style tests before the final batch. This counts assertion calls,
|
|
||||||
not bugs, missing scenarios or a percentage of work remaining.
|
|
||||||
|
|
||||||
## Coverage Boundary
|
|
||||||
|
|
||||||
| Risk | Behavioral coverage |
|
|
||||||
| --- | --- |
|
|
||||||
| Data loss, startup failure, deferred media | `e2e/persistence.spec.js`, `storage-startup.spec.js`, `deferred-media.spec.js`; Node persistence queue and image action tests |
|
|
||||||
| React lifecycle, shared editing and navigation | `e2e/editor-lifecycle.spec.js`, `inline-edit.spec.js`, `form-reorder.spec.js`, `shell-behavior.spec.js`, `session-state.spec.js` |
|
|
||||||
| Shared reorder policy and module order | `e2e/grouped-reorder.spec.js`, `module-order.spec.js` |
|
|
||||||
| Toolbox creation, import/export, text exchange | `e2e/toolbox.spec.js`, `text-exchange.spec.js`; Node toolbox module tests |
|
|
||||||
| Links: creation, copy, category removal preserving links, deletion and reload | `e2e/basic-tools.spec.js` |
|
|
||||||
| Counters: creation, signed values, reset, deletion and reload | `e2e/basic-tools.spec.js` |
|
|
||||||
| Checklist: quantities, clamping, completed category visibility and persistence | `e2e/basic-tools.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` |
|
|
||||||
|
|
||||||
These suites cover selected risks, not every permutation of every tool.
|
|
||||||
Public formats, product vocabulary, editorial validation, security restrictions
|
|
||||||
and deliberate architecture constraints retain static or pure-function tests.
|
|
||||||
Implementation-name assertions can be replaced when their owning code changes;
|
|
||||||
their mere presence is not a functional defect.
|
|
||||||
|
|
||||||
## Optional Extensions
|
|
||||||
|
|
||||||
Exhaustive advanced tool variants (calendar recurrence operations, goal graph
|
|
||||||
combinations, equipment and combo permutations), every text-exchange UI adapter,
|
|
||||||
fine calculator geometry and wholesale conversion of remaining implementation
|
|
||||||
assertions are outside this closure boundary. They are candidates when related
|
|
||||||
features change or a concrete regression demonstrates missing coverage, not
|
|
||||||
automatic follow-on batches required to finish this audit.
|
|
||||||
|
|
||||||
## Validation
|
|
||||||
|
|
||||||
Run targeted tests while implementing a grouped change, then `npm run check:full`
|
|
||||||
as the final gate: lint, Node tests, build, development E2E and production E2E.
|
|
||||||
Review the diff and report actual failures or limits before closing the task.
|
|
||||||
Do not introduce visual snapshots solely to prove that a page loads.
|
|
||||||
Keep detailed command logs outside tracked source and report concise results.
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
// Rôle : couvre les mutations persistantes des liens, compteurs et checklists.
|
|
||||||
import { expect, test } from "@playwright/test";
|
|
||||||
import { Buffer } from "node:buffer";
|
|
||||||
|
|
||||||
async function setup(page, type, data) {
|
|
||||||
await page.goto("/#/toolboxes");
|
|
||||||
await page.getByText("Importer une toolbox", { exact: true }).locator('input[type="file"]').setInputFiles({
|
|
||||||
name: "basic.json", mimeType: "application/json",
|
|
||||||
buffer: Buffer.from(JSON.stringify({
|
|
||||||
toolbox: { id: "basic", name: "Basic workflows", modules: [{ id: "tool", type, title: "Tool" }] },
|
|
||||||
modules: { tool: data },
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
await page.getByRole("link", { name: "Ouvrir Basic workflows", exact: true }).first().click();
|
|
||||||
const tool = page.locator(`[data-module-type="${type}"]`);
|
|
||||||
await tool.getByRole("button", { name: "Afficher l'ajout de Tool", exact: true }).click();
|
|
||||||
return tool;
|
|
||||||
}
|
|
||||||
|
|
||||||
test("links persist creation and copying; deleting a category preserves its links", async ({ page, context }) => {
|
|
||||||
await context.grantPermissions(["clipboard-read", "clipboard-write"]);
|
|
||||||
const tool = await setup(page, "links", { links: [] });
|
|
||||||
const form = tool.locator(".links-add-form");
|
|
||||||
await form.getByPlaceholder("Nom du lien", { exact: true }).fill("Guide");
|
|
||||||
await form.locator('input[type="url"]').fill("https://example.com/guide");
|
|
||||||
await form.getByPlaceholder("Catégorie", { exact: true }).fill("Reference");
|
|
||||||
await form.getByRole("button", { name: "Ajouter", exact: true }).click();
|
|
||||||
const link = tool.getByRole("link", { name: /Guide/ });
|
|
||||||
await expect(link).toHaveAttribute("href", "https://example.com/guide");
|
|
||||||
await expect(link).toHaveAttribute("target", "_blank");
|
|
||||||
await expect(link).toHaveAttribute("rel", /noreferrer/);
|
|
||||||
await expect(form.locator('input[type="url"]')).toHaveValue("");
|
|
||||||
await tool.getByRole("button", { name: "Copier Guide", exact: true }).click();
|
|
||||||
await expect.poll(() => page.evaluate(() => globalThis.navigator.clipboard.readText())).toBe("https://example.com/guide");
|
|
||||||
await page.reload();
|
|
||||||
await expect(link).toHaveAttribute("href", "https://example.com/guide");
|
|
||||||
await tool.getByRole("button", { name: "Supprimer la catégorie Reference", exact: true }).click();
|
|
||||||
await expect(tool.getByRole("heading", { name: "Reference", exact: true })).toHaveCount(0);
|
|
||||||
await page.reload();
|
|
||||||
await expect(link).toBeVisible();
|
|
||||||
await tool.getByRole("button", { name: "Supprimer Guide", exact: true }).click();
|
|
||||||
await page.reload();
|
|
||||||
await expect(link).toHaveCount(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("counters persist signed values and reset without affecting other counters", async ({ page }) => {
|
|
||||||
const tool = await setup(page, "counters", { counters: [{ id: "other", label: "Other", value: 9 }] });
|
|
||||||
await tool.getByPlaceholder("Nom du compteur", { exact: true }).fill("Attempts");
|
|
||||||
await tool.locator(".counters-add-form").getByRole("button", { name: "Ajouter", exact: true }).click();
|
|
||||||
const item = tool.locator(".counter-item").filter({ has: page.getByRole("button", { name: "Supprimer Attempts", exact: true }) });
|
|
||||||
await tool.getByRole("button", { name: "Incrémenter Attempts", exact: true }).click();
|
|
||||||
await expect(item.locator("strong")).toHaveText("1");
|
|
||||||
await tool.getByRole("button", { name: "Décrémenter Attempts", exact: true }).click({ clickCount: 2 });
|
|
||||||
await expect(item.locator("strong")).toHaveText("-1");
|
|
||||||
await page.reload();
|
|
||||||
await expect(item.locator("strong")).toHaveText("-1");
|
|
||||||
await tool.getByRole("button", { name: "Réinitialiser Attempts", exact: true }).click();
|
|
||||||
await page.reload();
|
|
||||||
await expect(item.locator("strong")).toHaveText("0");
|
|
||||||
await tool.getByRole("button", { name: "Supprimer Attempts", exact: true }).click();
|
|
||||||
await page.reload();
|
|
||||||
await expect(tool.locator(".counter-item")).toHaveCount(1);
|
|
||||||
await expect(tool.locator(".counter-value strong")).toHaveText("9");
|
|
||||||
await expect(tool.locator(".counter-value span")).toHaveText("Other");
|
|
||||||
});
|
|
||||||
|
|
||||||
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");
|
|
||||||
await tool.getByPlaceholder("Nouvel item", { exact: true }).fill("Ore");
|
|
||||||
await tool.getByRole("spinbutton", { name: "Quantité cible", exact: true }).fill("3");
|
|
||||||
await tool.getByRole("button", { name: "Ajouter", exact: true }).click();
|
|
||||||
const quantity = tool.getByRole("spinbutton", { name: "Quantité actuelle Ore", exact: true });
|
|
||||||
await tool.getByRole("button", { name: "Ajouter une quantité", exact: true }).click();
|
|
||||||
await expect(quantity).toHaveValue("1");
|
|
||||||
await quantity.fill("99");
|
|
||||||
await expect(quantity).toHaveValue("3");
|
|
||||||
await tool.getByRole("radio", { name: "Cachée quand terminée", exact: true }).click();
|
|
||||||
await expect(quantity).toHaveCount(0);
|
|
||||||
await page.reload();
|
|
||||||
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");
|
|
||||||
await tool.getByRole("button", { name: "Retirer une quantité", exact: true }).click();
|
|
||||||
await page.reload();
|
|
||||||
await expect(quantity).toHaveValue("2");
|
|
||||||
await tool.getByRole("button", { name: "Supprimer Ore", exact: true }).click();
|
|
||||||
await page.reload();
|
|
||||||
await expect(quantity).toHaveCount(0);
|
|
||||||
});
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
// Rôle : vérifie l'édition, les prérequis et les réinitialisations du planificateur réel.
|
|
||||||
import { expect, test } from "@playwright/test";
|
|
||||||
import { Buffer } from "node:buffer";
|
|
||||||
|
|
||||||
async function setup(page) {
|
|
||||||
await page.goto("/#/toolboxes");
|
|
||||||
await page.getByText("Importer une toolbox", { exact: true }).locator('input[type="file"]').setInputFiles({
|
|
||||||
name: "tasks.json", mimeType: "application/json",
|
|
||||||
buffer: Buffer.from(JSON.stringify({
|
|
||||||
toolbox: { id: "tasks", name: "Task workflows", modules: [{ id: "planner", type: "taskPlanner", title: "Tasks" }] },
|
|
||||||
modules: { planner: { resetTime: "00:00", weeklyResetDay: 1, tasks: [
|
|
||||||
{ id: "parent", title: "Parent", type: "unique", category: "Raid" },
|
|
||||||
{ id: "child", title: "Child", type: "daily" },
|
|
||||||
{ id: "weekly", title: "Weekly", type: "weekly" },
|
|
||||||
] } },
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
await page.getByRole("link", { name: "Ouvrir Task workflows", exact: true }).first().click();
|
|
||||||
const tool = page.locator('[data-module-type="taskPlanner"]');
|
|
||||||
await expect(tool.getByRole("checkbox", { name: "Marquer effectué Parent", exact: true })).toBeVisible();
|
|
||||||
return tool;
|
|
||||||
}
|
|
||||||
|
|
||||||
test("creates and edits a categorized task with a multiline description and persists deletion", async ({ page }) => {
|
|
||||||
const tool = await setup(page);
|
|
||||||
await tool.getByRole("button", { name: "Afficher l'ajout de Tasks", exact: true }).click();
|
|
||||||
const form = tool.locator(".task-planner-add-form");
|
|
||||||
await form.getByRole("button", { name: "Ajouter", exact: true }).click();
|
|
||||||
await expect(tool.locator(".task-planner-title-input")).toHaveCount(3);
|
|
||||||
await form.getByPlaceholder("Nouvelle tâche", { exact: true }).fill("New task");
|
|
||||||
await form.getByPlaceholder("Nom de catégorie", { exact: true }).fill("Crafting");
|
|
||||||
await form.getByRole("combobox", { name: "Type", exact: true }).selectOption("unique");
|
|
||||||
await form.getByRole("button", { name: "Ajouter", exact: true }).click();
|
|
||||||
await expect(form.getByPlaceholder("Nouvelle tâche", { exact: true })).toHaveValue("");
|
|
||||||
await expect(tool.getByRole("textbox", { name: "Titre", exact: true })).toHaveCount(4);
|
|
||||||
const item = tool.locator("li.task-planner-item").filter({ has: page.getByRole("button", { name: "Supprimer New task", exact: true }) });
|
|
||||||
await item.getByRole("textbox", { name: "Titre", exact: true }).fill("Renamed");
|
|
||||||
await item.getByRole("textbox", { name: "Titre", exact: true }).press("Enter");
|
|
||||||
await tool.getByRole("button", { name: "Afficher la description Renamed", exact: true }).click();
|
|
||||||
const description = tool.getByRole("textbox", { name: "Description Renamed", exact: true });
|
|
||||||
await description.fill("First line");
|
|
||||||
await description.press("End");
|
|
||||||
await description.press("Enter");
|
|
||||||
await description.pressSequentially("Second line");
|
|
||||||
await tool.getByRole("textbox", { name: "Catégorie Renamed", exact: true }).click();
|
|
||||||
await page.reload();
|
|
||||||
await tool.getByRole("button", { name: "Afficher la description Renamed", exact: true }).click();
|
|
||||||
await expect(description).toHaveValue("First line\nSecond line");
|
|
||||||
await expect(tool.getByRole("textbox", { name: "Catégorie Renamed", exact: true })).toHaveValue("Crafting");
|
|
||||||
await tool.getByRole("button", { name: "Supprimer Renamed", exact: true }).click();
|
|
||||||
await page.reload();
|
|
||||||
await expect(tool.locator(".task-planner-title-input")).toHaveCount(3);
|
|
||||||
await expect(tool.getByRole("button", { name: "Supprimer Renamed", exact: true })).toHaveCount(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("parent links inherit categories, warn without blocking completion and are removed when the parent is deleted", async ({ page }) => {
|
|
||||||
const tool = await setup(page);
|
|
||||||
await tool.getByRole("button", { name: "Réglages de la tâche Child", exact: true }).click();
|
|
||||||
await tool.getByRole("combobox", { name: "Définir un parent", exact: true }).selectOption({ label: "Parent" });
|
|
||||||
await tool.getByRole("checkbox", { name: "Pré requis Parent", exact: true }).check();
|
|
||||||
await tool.getByRole("button", { name: "Afficher la description Child", exact: true }).click();
|
|
||||||
const category = tool.getByRole("textbox", { name: "Catégorie Child", exact: true });
|
|
||||||
await expect(category).toBeDisabled();
|
|
||||||
await expect(category).toHaveValue("Raid");
|
|
||||||
await tool.getByRole("checkbox", { name: "Marquer effectué Child", exact: true }).check();
|
|
||||||
await expect(tool.getByText("Pré requis parent non effectué", { exact: true })).toBeVisible();
|
|
||||||
await expect(tool.getByRole("checkbox", { name: "Marquer non effectué Child", exact: true })).toBeChecked();
|
|
||||||
await tool.getByRole("checkbox", { name: "Marquer effectué Parent", exact: true }).check();
|
|
||||||
await expect(tool.locator(".task-planner-warning")).toHaveCount(0);
|
|
||||||
await page.reload();
|
|
||||||
await tool.getByRole("button", { name: "Réglages de la tâche Child", exact: true }).click();
|
|
||||||
await expect(tool.getByRole("checkbox", { name: "Pré requis Parent", exact: true })).toBeChecked();
|
|
||||||
await tool.getByRole("button", { name: "Supprimer Parent", exact: true }).click();
|
|
||||||
await expect(tool.getByRole("checkbox", { name: "Pré requis Parent", exact: true })).toHaveCount(0);
|
|
||||||
await expect(tool.getByText("Aucun parent", { exact: true })).toBeVisible();
|
|
||||||
await tool.getByRole("button", { name: "Afficher la description Child", exact: true }).click();
|
|
||||||
await expect(category).toBeEnabled();
|
|
||||||
await page.reload();
|
|
||||||
await expect(tool.getByRole("button", { name: "Supprimer Child", exact: true })).toBeVisible();
|
|
||||||
await expect(tool.getByRole("button", { name: "Supprimer Parent", exact: true })).toHaveCount(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("recurring resets preserve unique completions and cleanup deletes only completed unique tasks", async ({ page }) => {
|
|
||||||
await page.clock.install({ time: new Date("2030-01-07T12:00:00Z") });
|
|
||||||
const tool = await setup(page);
|
|
||||||
for (const title of ["Parent", "Child", "Weekly"]) {
|
|
||||||
await tool.getByRole("checkbox", { name: `Marquer effectué ${title}`, exact: true }).check();
|
|
||||||
}
|
|
||||||
await page.clock.fastForward(8 * 24 * 60 * 60 * 1000);
|
|
||||||
await expect(tool.getByRole("checkbox", { name: "Marquer effectué Child", exact: true })).not.toBeChecked();
|
|
||||||
await expect(tool.getByRole("checkbox", { name: "Marquer effectué Weekly", exact: true })).not.toBeChecked();
|
|
||||||
await expect(tool.getByRole("checkbox", { name: "Marquer non effectué Parent", exact: true })).toBeChecked();
|
|
||||||
const hide = tool.getByRole("button", { name: "Masquer les tâches effectuées", exact: true });
|
|
||||||
await hide.click();
|
|
||||||
await expect(tool.getByRole("button", { name: "Supprimer Parent", exact: true })).toHaveCount(0);
|
|
||||||
await page.reload();
|
|
||||||
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();
|
|
||||||
const cleanup = tool.getByRole("button", { name: "Nettoyer les tâches ponctuelles effectuées", exact: true });
|
|
||||||
await cleanup.click();
|
|
||||||
await expect(cleanup).toBeDisabled();
|
|
||||||
await page.reload();
|
|
||||||
await expect(tool.locator(".task-planner-title-input")).toHaveCount(2);
|
|
||||||
await expect(tool.getByRole("checkbox", { name: "Marquer effectué Child", exact: true })).toBeVisible();
|
|
||||||
await expect(tool.getByRole("checkbox", { name: "Marquer effectué Weekly", exact: true })).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("reordering a parent moves its subtree and rejects detaching a child", async ({ page }) => {
|
|
||||||
const tool = await setup(page);
|
|
||||||
await tool.getByRole("button", { name: "Réglages de la tâche Child", exact: true }).click();
|
|
||||||
await tool.getByRole("combobox", { name: "Définir un parent", exact: true }).selectOption({ label: "Parent" });
|
|
||||||
await tool.getByRole("button", { name: "Réglages de la tâche Child", exact: true }).click();
|
|
||||||
async function drag(source, target) {
|
|
||||||
await tool.getByRole("button", { name: `Déplacer ${source}`, exact: true }).hover();
|
|
||||||
await page.mouse.down();
|
|
||||||
const box = await tool.getByRole("button", { name: `Déplacer ${target}`, exact: true }).boundingBox();
|
|
||||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height * 0.2, { steps: 5 });
|
|
||||||
await page.mouse.up();
|
|
||||||
}
|
|
||||||
const titles = () => tool.locator(".task-planner-title-input").evaluateAll((inputs) => inputs.map((input) => input.value));
|
|
||||||
await drag("Weekly", "Parent");
|
|
||||||
await expect.poll(titles).toEqual(["Weekly", "Parent", "Child"]);
|
|
||||||
await drag("Parent", "Weekly");
|
|
||||||
await expect.poll(titles).toEqual(["Parent", "Child", "Weekly"]);
|
|
||||||
await drag("Child", "Weekly");
|
|
||||||
await expect.poll(titles).toEqual(["Parent", "Child", "Weekly"]);
|
|
||||||
await page.reload();
|
|
||||||
await expect.poll(titles).toEqual(["Parent", "Child", "Weekly"]);
|
|
||||||
await tool.getByRole("button", { name: "Réglages de la tâche Child", exact: true }).click();
|
|
||||||
await expect(tool.getByRole("button", { name: "Retirer Parent", exact: true })).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
@ -135,6 +135,7 @@ test("toolbox module registry and modules expose expected behavior", async () =>
|
||||||
const taskPlannerTopbar = await readFile("website/src/features/toolboxes/modules/TaskPlannerTopbar.jsx", "utf8");
|
const taskPlannerTopbar = await readFile("website/src/features/toolboxes/modules/TaskPlannerTopbar.jsx", "utf8");
|
||||||
const taskPlannerSettings = await readFile("website/src/features/toolboxes/modules/TaskPlannerSettings.jsx", "utf8");
|
const taskPlannerSettings = await readFile("website/src/features/toolboxes/modules/TaskPlannerSettings.jsx", "utf8");
|
||||||
const taskPlannerBundle = `${taskPlannerBranch}\n${taskPlannerItem}\n${taskPlannerItemPanels}\n${taskPlannerReorder}\n${taskPlannerTopbar}\n${taskPlannerSettings}`;
|
const taskPlannerBundle = `${taskPlannerBranch}\n${taskPlannerItem}\n${taskPlannerItemPanels}\n${taskPlannerReorder}\n${taskPlannerTopbar}\n${taskPlannerSettings}`;
|
||||||
|
const taskPlannerUtils = await readFile("website/src/features/toolboxes/modules/taskPlannerUtils.js", "utf8");
|
||||||
const goalTreeModule = await readFile("website/src/features/toolboxes/modules/GoalTreeModule.jsx", "utf8");
|
const goalTreeModule = await readFile("website/src/features/toolboxes/modules/GoalTreeModule.jsx", "utf8");
|
||||||
const goalTreeEditor = await readFile("website/src/features/toolboxes/modules/GoalTreeEditor.jsx", "utf8");
|
const goalTreeEditor = await readFile("website/src/features/toolboxes/modules/GoalTreeEditor.jsx", "utf8");
|
||||||
const goalTreeParts = await readFile("website/src/features/toolboxes/modules/GoalTreeParts.jsx", "utf8");
|
const goalTreeParts = await readFile("website/src/features/toolboxes/modules/GoalTreeParts.jsx", "utf8");
|
||||||
|
|
@ -320,9 +321,23 @@ test("toolbox module registry and modules expose expected behavior", async () =>
|
||||||
assert.match(calendarUtils, /export function getCalendarOccurrences/);
|
assert.match(calendarUtils, /export function getCalendarOccurrences/);
|
||||||
assert.match(calendarUtils, /CALENDAR_VIEW_MODES = \["week", "month"\]/);
|
assert.match(calendarUtils, /CALENDAR_VIEW_MODES = \["week", "month"\]/);
|
||||||
assert.match(calendarUtils, /export function getNextCalendarAlertTime/);
|
assert.match(calendarUtils, /export function getNextCalendarAlertTime/);
|
||||||
|
assert.match(taskPlannerModule, /export function TaskPlannerModule/);
|
||||||
|
assert.match(taskPlannerModule, /normalizeTaskPlannerData/);
|
||||||
|
assert.match(taskPlannerModule, /applyDueResets/);
|
||||||
|
assert.match(taskPlannerModule, /TaskPlannerBranch/);
|
||||||
assert.match(taskPlannerBundle, /useGroupedReorder/);
|
assert.match(taskPlannerBundle, /useGroupedReorder/);
|
||||||
|
assert.match(taskPlannerModule, /useDraftForm/);
|
||||||
assert.match(taskPlannerBundle, /TextExchangeActions/);
|
assert.match(taskPlannerBundle, /TextExchangeActions/);
|
||||||
assert.match(taskPlannerBundle, /reorderFeatures/);
|
assert.match(taskPlannerBundle, /reorderFeatures/);
|
||||||
|
assert.match(taskPlannerBranch, /export function TaskPlannerBranch/);
|
||||||
|
assert.match(taskPlannerItem, /export function TaskPlannerItem/);
|
||||||
|
assert.match(taskPlannerBundle, /task-planner-warning/);
|
||||||
|
assert.match(taskPlannerBundle, /useInlineEdit/);
|
||||||
|
assert.match(taskPlannerBundle, /commitOnEnter: false/);
|
||||||
|
assert.match(taskPlannerBundle, /categoryLabel/);
|
||||||
|
assert.match(taskPlannerBundle, /prerequisite/);
|
||||||
|
assert.match(taskPlannerUtils, /export function getTaskParentMap/);
|
||||||
|
assert.match(taskPlannerUtils, /export function applyDueResets/);
|
||||||
assert.doesNotMatch(taskPlannerModule, /canMoveItem:/);
|
assert.doesNotMatch(taskPlannerModule, /canMoveItem:/);
|
||||||
assert.doesNotMatch(taskPlannerModule, /canMoveGroup:/);
|
assert.doesNotMatch(taskPlannerModule, /canMoveGroup:/);
|
||||||
assert.match(goalTreeModule, /export function GoalTreeModule/);
|
assert.match(goalTreeModule, /export function GoalTreeModule/);
|
||||||
|
|
@ -415,11 +430,21 @@ test("toolbox module registry and modules expose expected behavior", async () =>
|
||||||
assert.match(imageAnnotationModule, /createMarkerId/);
|
assert.match(imageAnnotationModule, /createMarkerId/);
|
||||||
assert.match(imageAnnotationModule, /<Icon name="zoom"/);
|
assert.match(imageAnnotationModule, /<Icon name="zoom"/);
|
||||||
assert.match(imageAnnotationModule, /TextExchangeActions/);
|
assert.match(imageAnnotationModule, /TextExchangeActions/);
|
||||||
|
assert.match(linksModule, /export function LinksModule/);
|
||||||
assert.match(linksModule, /TextExchangeActions/);
|
assert.match(linksModule, /TextExchangeActions/);
|
||||||
|
assert.match(linksModule, /editing &&/);
|
||||||
assert.match(linksModule, /normalizeUrl/);
|
assert.match(linksModule, /normalizeUrl/);
|
||||||
|
assert.match(linksModule, /copyText/);
|
||||||
assert.match(linksModule, /useGroupedReorder/);
|
assert.match(linksModule, /useGroupedReorder/);
|
||||||
|
assert.match(linksModule, /useDraftForm/);
|
||||||
|
assert.match(linksModule, /linkDraft\.getFieldProps/);
|
||||||
assert.match(linksModule, /link-drag-handle/);
|
assert.match(linksModule, /link-drag-handle/);
|
||||||
|
assert.match(countersModule, /export function CountersModule/);
|
||||||
|
assert.match(countersModule, /editing &&/);
|
||||||
|
assert.match(countersModule, /counter-actions/);
|
||||||
assert.match(countersModule, /useGroupedReorder/);
|
assert.match(countersModule, /useGroupedReorder/);
|
||||||
|
assert.match(countersModule, /useDraftForm/);
|
||||||
|
assert.match(countersModule, /counterDraft\.getFieldProps/);
|
||||||
assert.match(countersModule, /orientation: "horizontal"/);
|
assert.match(countersModule, /orientation: "horizontal"/);
|
||||||
assert.match(countersModule, /counter-drag-handle/);
|
assert.match(countersModule, /counter-drag-handle/);
|
||||||
assert.match(countersModule, /TextExchangeActions/);
|
assert.match(countersModule, /TextExchangeActions/);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue