Preserve memo drafts and stabilize goal tree selection
Some checks failed
Deploy Sokko G / deploy (push) Failing after 2s
Some checks failed
Deploy Sokko G / deploy (push) Failing after 2s
This commit is contained in:
parent
b9cc77a4d1
commit
4f644ca116
8 changed files with 158 additions and 49 deletions
45
e2e/fixtures/goal-selection-probe.js
Normal file
45
e2e/fixtures/goal-selection-probe.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// Rôle : observe les sélections et onglets réellement validés par le contrôleur Goal Tree.
|
||||
import { createElement, StrictMode, useLayoutEffect, useMemo } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { flushSync } from "react-dom";
|
||||
import { useGoalTreeController } from "../../website/src/features/toolboxes/modules/useGoalTreeController.js";
|
||||
import { normalizeGoalTreeData } from "../../website/src/features/toolboxes/storage/toolboxStorage.js";
|
||||
|
||||
export function mountGoalSelectionProbe(element) {
|
||||
const root = createRoot(element);
|
||||
const snapshots = [];
|
||||
let controller;
|
||||
|
||||
function Probe({ nodes }) {
|
||||
const context = useMemo(() => ({
|
||||
getModuleData: () => ({ nodes }),
|
||||
normalizeGoalTreeData,
|
||||
setModuleData: () => {}
|
||||
}), [nodes]);
|
||||
const state = useGoalTreeController({
|
||||
toolboxId: "probe", moduleId: "goal", context,
|
||||
editing: true, setEditing: () => {}, openEditor: () => {}
|
||||
});
|
||||
useLayoutEffect(() => {
|
||||
controller = state;
|
||||
snapshots.push({
|
||||
selected: state.selectedNode?.id || "",
|
||||
tab: state.activeFormTab,
|
||||
tabs: state.formTabs.map((tab) => tab.id)
|
||||
});
|
||||
});
|
||||
return createElement("output", null, state.selectedNode?.title || "Empty");
|
||||
}
|
||||
|
||||
function update(nodes) {
|
||||
snapshots.length = 0;
|
||||
flushSync(() => root.render(createElement(StrictMode, null, createElement(Probe, { nodes }))));
|
||||
}
|
||||
update([]);
|
||||
return {
|
||||
update,
|
||||
snapshots,
|
||||
edit: (id) => flushSync(() => controller.editNode(id)),
|
||||
unmount: () => root.unmount()
|
||||
};
|
||||
}
|
||||
44
e2e/goal-selection.spec.js
Normal file
44
e2e/goal-selection.spec.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
// Rôle : couvre les remplacements de données et les onglets invalides du Goal Tree sous StrictMode.
|
||||
/* global window, document */
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
|
||||
const probeUrl = `/@fs/${fileURLToPath(new URL("./fixtures/goal-selection-probe.js", import.meta.url))}`;
|
||||
|
||||
test("goal selection remains valid through kind changes and data replacement", async ({ page }) => {
|
||||
const errors = [];
|
||||
page.on("pageerror", (error) => errors.push(error.message));
|
||||
await page.goto("/");
|
||||
await page.evaluate(async (url) => {
|
||||
const { mountGoalSelectionProbe } = await import(url);
|
||||
const element = document.createElement("div");
|
||||
document.body.prepend(element);
|
||||
window.goalProbe = mountGoalSelectionProbe(element);
|
||||
}, probeUrl);
|
||||
const first = { id: "first", title: "First", kind: "checklist" };
|
||||
const second = { id: "second", title: "Second", kind: "checklist" };
|
||||
|
||||
async function updateAndCheck(nodes, selected, tab) {
|
||||
const snapshots = await page.evaluate((nodes) => {
|
||||
window.goalProbe.update(nodes);
|
||||
return window.goalProbe.snapshots;
|
||||
}, nodes);
|
||||
expect(snapshots.length).toBeGreaterThan(0);
|
||||
for (const snapshot of snapshots) {
|
||||
expect(snapshot.selected).toBe(selected);
|
||||
expect(snapshot.tab).toBe(tab);
|
||||
expect(snapshot.tabs).toContain(snapshot.tab);
|
||||
}
|
||||
}
|
||||
|
||||
await updateAndCheck([first, second], "first", "base");
|
||||
await page.evaluate(() => window.goalProbe.edit("second"));
|
||||
expect(await page.evaluate(() => window.goalProbe.snapshots.at(-1).tab)).toBe("content");
|
||||
await updateAndCheck([first, { ...second, kind: "step" }], "second", "base");
|
||||
await updateAndCheck([first], "first", "base");
|
||||
await updateAndCheck([first, second], "first", "base");
|
||||
await updateAndCheck([], "", "base");
|
||||
await updateAndCheck([second], "second", "base");
|
||||
await page.evaluate(() => window.goalProbe.unmount());
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
// Rôle : couvre les parcours navigateur principaux des toolboxes locales.
|
||||
/* global window, CustomEvent */
|
||||
import { expect, test } from "@playwright/test";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
|
@ -62,6 +63,31 @@ test("creates a toolbox and lazy-loads an added tool", async ({ page }) => {
|
|||
await expect(page.locator("[data-module-type='goalTree']")).toBeVisible();
|
||||
});
|
||||
|
||||
test("keeps an uncommitted memo through unrelated app updates and saves multiline text", async ({ page }) => {
|
||||
await createToolbox(page, "Memo draft toolbox");
|
||||
const memos = moduleByType(page, "memos");
|
||||
const editor = memos.getByRole("textbox", { name: "Modifier le mémo" }).first();
|
||||
await editor.fill("Premiere ligne\nDeuxieme ligne");
|
||||
await page.evaluate(() => window.dispatchEvent(new CustomEvent("sokkog:notify", {
|
||||
detail: { message: "Memo draft notification" }
|
||||
})));
|
||||
await expect(page.getByText("Memo draft notification", { exact: true })).toBeVisible();
|
||||
await expect(editor).toHaveValue("Premiere ligne\nDeuxieme ligne");
|
||||
await editor.press("End");
|
||||
await editor.press("Enter");
|
||||
await editor.pressSequentially("Troisieme ligne");
|
||||
await editor.blur();
|
||||
await expect(editor).toHaveValue("Premiere ligne\nDeuxieme ligne\nTroisieme ligne");
|
||||
await page.reload();
|
||||
await expect(editor).toHaveValue("Premiere ligne\nDeuxieme ligne\nTroisieme ligne");
|
||||
await editor.fill(" Premiere ligne\nDeuxieme ligne\nTroisieme ligne ");
|
||||
await editor.blur();
|
||||
await expect(editor).toHaveValue("Premiere ligne\nDeuxieme ligne\nTroisieme ligne");
|
||||
await editor.fill(" ");
|
||||
await editor.blur();
|
||||
await expect(memos.locator(".memo-card")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("creates, edits and deletes a weekly calendar event", async ({ page }) => {
|
||||
await createToolbox(page, "Calendar E2E Toolbox");
|
||||
await addTool(page, "Calendrier");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue