Initialize session state by page and module identity
Some checks failed
Deploy Sokko G / deploy (push) Failing after 3s
Some checks failed
Deploy Sokko G / deploy (push) Failing after 3s
This commit is contained in:
parent
4f644ca116
commit
7c0b72f914
8 changed files with 177 additions and 19 deletions
38
e2e/fixtures/keyed-state-probe.js
Normal file
38
e2e/fixtures/keyed-state-probe.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// Rôle : observe les valeurs validées et les setters différés d'un état lié à une clé.
|
||||
/* global sessionStorage */
|
||||
import { createElement, StrictMode, useLayoutEffect } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { flushSync } from "react-dom";
|
||||
import { useKeyedState } from "../../website/src/hooks/useKeyedState.js";
|
||||
|
||||
export function mountKeyedStateProbe(element) {
|
||||
const root = createRoot(element);
|
||||
const snapshots = [];
|
||||
const setters = new Map();
|
||||
let reads = 0;
|
||||
|
||||
function Probe({ identity }) {
|
||||
const [value, setValue] = useKeyedState(identity, (key) => {
|
||||
reads++;
|
||||
return JSON.parse(sessionStorage.getItem(`keyed-probe:${key}`) || "[]");
|
||||
});
|
||||
useLayoutEffect(() => {
|
||||
snapshots.push({ key: identity, value });
|
||||
setters.set(identity, setValue);
|
||||
});
|
||||
return createElement("output", null, JSON.stringify(value));
|
||||
}
|
||||
|
||||
function update(identity) {
|
||||
snapshots.length = 0;
|
||||
flushSync(() => root.render(createElement(StrictMode, null, createElement(Probe, { identity }))));
|
||||
}
|
||||
update("a");
|
||||
return {
|
||||
update,
|
||||
snapshots,
|
||||
get reads() { return reads; },
|
||||
append: (key, value) => flushSync(() => setters.get(key)((current) => [...current, value])),
|
||||
unmount: () => root.unmount()
|
||||
};
|
||||
}
|
||||
92
e2e/session-state.spec.js
Normal file
92
e2e/session-state.spec.js
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
// Rôle : vérifie l'isolation de l'état de session et des dessins entre pages du Bloc Note.
|
||||
/* global window, document, sessionStorage */
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
import { resetSokkoStorage } from "./helpers/storage.js";
|
||||
|
||||
const probeUrl = `/@fs/${fileURLToPath(new URL("./fixtures/keyed-state-probe.js", import.meta.url))}`;
|
||||
|
||||
test("keyed state never commits another key's value under StrictMode", async ({ page }) => {
|
||||
const errors = [];
|
||||
page.on("pageerror", (error) => errors.push(error.message));
|
||||
await page.goto("/");
|
||||
await page.evaluate(async (url) => {
|
||||
sessionStorage.setItem("keyed-probe:a", '["A"]');
|
||||
sessionStorage.setItem("keyed-probe:b", '["B"]');
|
||||
const { mountKeyedStateProbe } = await import(url);
|
||||
const element = document.createElement("div");
|
||||
document.body.prepend(element);
|
||||
window.keyedProbe = mountKeyedStateProbe(element);
|
||||
}, probeUrl);
|
||||
await page.evaluate(() => window.keyedProbe.append("a", "edit"));
|
||||
const reads = await page.evaluate(() => window.keyedProbe.reads);
|
||||
await page.evaluate(() => window.keyedProbe.update("a"));
|
||||
expect(await page.evaluate(() => window.keyedProbe.reads)).toBe(reads);
|
||||
expect(await page.evaluate(() => window.keyedProbe.snapshots.at(-1).value)).toEqual(["A", "edit"]);
|
||||
|
||||
await page.evaluate(() => window.keyedProbe.update("b"));
|
||||
const snapshots = await page.evaluate(() => window.keyedProbe.snapshots);
|
||||
expect(snapshots.length).toBeGreaterThan(0);
|
||||
for (const snapshot of snapshots) expect(snapshot).toEqual({ key: "b", value: ["B"] });
|
||||
await page.evaluate(() => window.keyedProbe.append("a", "stale"));
|
||||
await page.evaluate(() => window.keyedProbe.append("b", "current"));
|
||||
expect(await page.evaluate(() => window.keyedProbe.snapshots.at(-1).value)).toEqual(["B", "current"]);
|
||||
await page.evaluate(() => window.keyedProbe.update("a"));
|
||||
expect(await page.evaluate(() => window.keyedProbe.snapshots.at(-1).value)).toEqual(["A"]);
|
||||
await page.evaluate(() => window.keyedProbe.unmount());
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
test("notepad temporary drawings stay on their own pages and survive reload", async ({ page }) => {
|
||||
const errors = [];
|
||||
page.on("pageerror", (error) => errors.push(error.message));
|
||||
await resetSokkoStorage(page);
|
||||
await page.evaluate(async () => {
|
||||
const db = await import("/src/utils/indexedDbStorage.js");
|
||||
await db.setToolboxSnapshot([{
|
||||
id: "session-notes", name: "Session notes", modules: [{ id: "note", type: "notepad" }]
|
||||
}], {}, {
|
||||
"session-notes:note": {
|
||||
activePageId: "a",
|
||||
pages: ["a", "b", "c"].map((id) => ({
|
||||
id, title: `Page ${id}`, html: `<p>Content ${id}</p>`, drawingMode: "temporary"
|
||||
}))
|
||||
}
|
||||
});
|
||||
for (const [id, y, color] of [["a", 20, "#f6c453"], ["b", 40, "#22d3ee"]]) {
|
||||
sessionStorage.setItem(`sokkog:notepad-drawings:session-notes:note:${id}`, JSON.stringify([
|
||||
{ id, color, width: 4, points: [{ x: 20, y }, { x: 100, y }] }
|
||||
]));
|
||||
}
|
||||
sessionStorage.setItem("sokkog:notepad-drawings:session-notes:note:c", "invalid json");
|
||||
});
|
||||
await page.goto("/#/toolbox/session-notes");
|
||||
await page.reload();
|
||||
const notepad = page.locator('[data-module-type="notepad"]');
|
||||
const canvas = notepad.locator("canvas");
|
||||
const alphaAt = (y) => canvas.evaluate((element, y) => {
|
||||
const ratio = element.width / element.clientWidth;
|
||||
return element.getContext("2d").getImageData(Math.round(50 * ratio), Math.round(y * ratio), 1, 1).data[3];
|
||||
}, y);
|
||||
async function selectPage(id) {
|
||||
await notepad.getByRole("button", { name: "Afficher les pages du Bloc Note", exact: true }).click();
|
||||
await page.getByRole("button", { name: `Page ${id}`, exact: true }).click();
|
||||
await expect(notepad.locator(".notepad-editor")).toHaveText(`Content ${id}`);
|
||||
}
|
||||
await expect(canvas).toBeVisible();
|
||||
await expect.poll(() => alphaAt(20)).toBeGreaterThan(0);
|
||||
await selectPage("b");
|
||||
await expect.poll(() => alphaAt(40)).toBeGreaterThan(0);
|
||||
await expect.poll(() => alphaAt(20)).toBe(0);
|
||||
await notepad.getByRole("button", { name: "Dessiner", exact: true }).click();
|
||||
await notepad.getByRole("button", { name: "Effacer les dessins", exact: true }).click();
|
||||
await expect.poll(() => page.evaluate(() => JSON.parse(sessionStorage.getItem("sokkog:notepad-drawings:session-notes:note:b")))).toEqual([]);
|
||||
await selectPage("a");
|
||||
await expect.poll(() => alphaAt(20)).toBeGreaterThan(0);
|
||||
await selectPage("c");
|
||||
await expect.poll(() => alphaAt(20)).toBe(0);
|
||||
await selectPage("a");
|
||||
await page.reload();
|
||||
await expect.poll(() => alphaAt(20)).toBeGreaterThan(0);
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue