Restore React purity checks and streamline inline drafts
Some checks failed
Deploy Sokko G / deploy (push) Failing after 2s

This commit is contained in:
Shinuwa 2026-09-11 09:17:45 +02:00
parent 2bff58f08c
commit b9cc77a4d1
8 changed files with 111 additions and 10 deletions

View file

@ -0,0 +1,34 @@
// Rôle : monte le hook d'édition réel sous StrictMode pour observer ses commits DOM.
import { createElement, StrictMode, useLayoutEffect } from "react";
import { createRoot } from "react-dom/client";
import { flushSync } from "react-dom";
import { useInlineEdit } from "../../website/src/hooks/useInlineEdit.js";
export function mountInlineEditProbe(element, contentEditable) {
const commits = [];
const renders = [];
const root = createRoot(element);
function Probe({ value }) {
const edit = useInlineEdit({
value,
onCommit: (next) => commits.push(next),
blurOnEscape: true
});
useLayoutEffect(() => {
renders.push({ value, draft: edit.draft });
});
return createElement("section", null,
contentEditable
? createElement("div", edit.getContentEditableProps({ role: "textbox", "aria-label": "Edit" }))
: createElement("input", edit.getInputProps({ type: "text", "aria-label": "Edit" })),
createElement("button", { type: "button" }, "Outside")
);
}
function update(value) {
flushSync(() => root.render(createElement(StrictMode, null, createElement(Probe, { value }))));
}
update("Initial");
return { update, commits, renders, unmount: () => root.unmount() };
}

51
e2e/inline-edit.spec.js Normal file
View file

@ -0,0 +1,51 @@
// Rôle : teste la synchronisation, la validation et l'annulation des éditions inline.
/* global window, document */
import { test, expect } from "@playwright/test";
import { fileURLToPath, URL } from "node:url";
const probeUrl = `/@fs/${fileURLToPath(new URL("./fixtures/inline-edit-probe.js", import.meta.url))}`;
for (const contentEditable of [false, true]) {
test(`inline ${contentEditable ? "contentEditable" : "input"} synchronizes and cancels under StrictMode`, async ({ page }) => {
const errors = [];
page.on("pageerror", (error) => errors.push(error.message));
await page.goto("/");
await page.evaluate(async ({ probeUrl, contentEditable }) => {
const { mountInlineEditProbe } = await import(probeUrl);
const element = document.createElement("div");
element.id = "inline-probe";
document.body.prepend(element);
window.inlineProbe = mountInlineEditProbe(element, contentEditable);
}, { probeUrl, contentEditable });
const editor = page.locator("#inline-probe").getByRole("textbox", { name: "Edit" });
const expectValue = (value) => contentEditable ? expect(editor).toHaveText(value) : expect(editor).toHaveValue(value);
await expectValue("Initial");
await editor.fill("Uncommitted");
await page.evaluate(() => window.inlineProbe.update("Initial"));
await expectValue("Uncommitted");
await editor.press("Escape");
await expectValue("Initial");
expect(await page.evaluate(() => window.inlineProbe.commits)).toEqual([]);
await page.evaluate(() => window.inlineProbe.update("Updated"));
await expectValue("Updated");
const sourceRenders = await page.evaluate(() => window.inlineProbe.renders.filter((entry) => entry.value === "Updated"));
expect(sourceRenders.length).toBeGreaterThan(0);
expect(sourceRenders.every((entry) => entry.draft === "Updated")).toBe(true);
await editor.fill(" Saved ");
await editor.press("Enter");
expect(await page.evaluate(() => window.inlineProbe.commits)).toEqual(["Saved"]);
await page.evaluate(() => window.inlineProbe.update("Saved"));
await expectValue("Saved");
await editor.fill("Discard");
await editor.press("Escape");
await expectValue("Saved");
expect(await page.evaluate(() => window.inlineProbe.commits)).toEqual(["Saved"]);
await page.evaluate(() => window.inlineProbe.update("Initial"));
await expectValue("Initial");
await page.evaluate(() => window.inlineProbe.unmount());
expect(errors).toEqual([]);
});
}

View file

@ -160,6 +160,19 @@ test("loads randomizer tabs after module split", async ({ page }) => {
await randomizer.getByRole("tab", { name: "Équipes" }).click();
await expect(randomizer.getByLabel("Liste de noms")).toBeVisible();
await expect(randomizer.getByRole("button", { name: "Créer les équipes" })).toBeVisible();
await randomizer.getByRole("combobox", { name: "Répartition", exact: true }).selectOption("count");
const count = randomizer.getByRole("spinbutton", { name: "Nombre", exact: true });
await count.fill("5");
await count.blur();
await expect(count).toHaveValue("5");
await randomizer.getByRole("spinbutton", { name: "Équipes", exact: true }).fill("6");
await expect(count).toHaveValue("6");
await count.fill("");
await count.blur();
await expect(count).toHaveValue("6");
await randomizer.getByRole("tab", { name: "Dé", exact: true }).click();
await randomizer.getByRole("tab", { name: "Équipes", exact: true }).click();
await expect(count).toHaveValue("6");
});
test("creates a game-linked toolbox from the drawer", async ({ page }) => {