Strengthen shell behavior tests and profile toolbox loading
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
a9b68a5712
commit
b53c7c17c8
8 changed files with 323 additions and 61 deletions
21
e2e/fixtures/shell-probe.js
Normal file
21
e2e/fixtures/shell-probe.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// Rôle : monte les vrais overlays et textes éditoriaux sous StrictMode pour tester leur cycle de vie.
|
||||
import { createElement, StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { flushSync } from "react-dom";
|
||||
import { ConfirmModal, CreateToolboxModal, NotificationToast } from "../../website/src/components/ToolboxModals.jsx";
|
||||
import { RichText } from "../../website/src/components/RichText.jsx";
|
||||
|
||||
export function mountShellProbe(element) {
|
||||
const root = createRoot(element);
|
||||
const closed = [];
|
||||
const onClose = (value) => closed.push(value);
|
||||
function render({ confirm = false, create = false, toast = null, text = "" } = {}) {
|
||||
flushSync(() => root.render(createElement(StrictMode, null,
|
||||
confirm && createElement(ConfirmModal, { key: "confirm", title: "Confirmation test", message: "Continuer ?", onClose }),
|
||||
create && createElement(CreateToolboxModal, { key: "create", onClose }),
|
||||
toast && createElement(NotificationToast, { key: "toast", ...toast, onClose }),
|
||||
createElement("div", { id: "editorial-text", key: "text" }, createElement(RichText, { text }))
|
||||
)));
|
||||
}
|
||||
return { render, closed, unmount: () => root.unmount() };
|
||||
}
|
||||
96
e2e/shell-behavior.spec.js
Normal file
96
e2e/shell-behavior.spec.js
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
// Rôle : couvre les durées des alertes, le nettoyage des overlays et le rendu éditorial sans inspecter leur source.
|
||||
/* global window, document, CustomEvent */
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
|
||||
const probeUrl = `/@fs/${fileURLToPath(new URL("./fixtures/shell-probe.js", import.meta.url))}`;
|
||||
|
||||
async function setup(page, probe = false) {
|
||||
await page.goto("/");
|
||||
await expect(page.locator(".app-shell")).toBeVisible();
|
||||
if (probe) {
|
||||
await page.evaluate(async (url) => {
|
||||
const { mountShellProbe } = await import(url);
|
||||
const element = document.createElement("div");
|
||||
element.id = "shell-probe";
|
||||
document.body.append(element);
|
||||
window.shellProbe = mountShellProbe(element);
|
||||
}, probeUrl);
|
||||
}
|
||||
await page.clock.install({ time: new Date("2026-09-12T12:00:00Z") });
|
||||
await page.clock.pauseAt(new Date("2026-09-12T12:00:01Z"));
|
||||
}
|
||||
|
||||
async function notify(page, message, durationMs) {
|
||||
await page.evaluate((detail) => window.dispatchEvent(new CustomEvent("sokkog:notify", { detail })), { message, durationMs });
|
||||
await expect(page.getByRole("status").filter({ hasText: message })).toBeVisible();
|
||||
}
|
||||
|
||||
test("application alerts use short clipboard and default durations and dismiss individually", async ({ page }) => {
|
||||
await setup(page);
|
||||
await notify(page, "Texte copié");
|
||||
await notify(page, "Sauvegarde terminée");
|
||||
await page.clock.runFor(4999);
|
||||
await expect(page.getByRole("status")).toHaveCount(2);
|
||||
await page.clock.runFor(1);
|
||||
await expect(page.getByRole("status")).toHaveText("Sauvegarde terminée");
|
||||
await page.clock.runFor(114999);
|
||||
await expect(page.getByRole("status")).toHaveCount(1);
|
||||
await page.clock.runFor(1);
|
||||
await expect(page.getByRole("status")).toHaveCount(0);
|
||||
|
||||
await notify(page, "Minimum", 0);
|
||||
await notify(page, "Personnalisée", 2000);
|
||||
await page.clock.runFor(999);
|
||||
await expect(page.getByRole("status")).toHaveCount(2);
|
||||
await page.clock.runFor(1);
|
||||
await expect(page.getByRole("status")).toHaveText("Personnalisée");
|
||||
await page.getByRole("button", { name: "Fermer l'alerte" }).dispatchEvent("click");
|
||||
await expect(page.getByRole("status")).toHaveCount(0);
|
||||
await page.clock.runFor(2000);
|
||||
await expect(page.getByRole("status")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("toast replacement and unmount cancel obsolete expiration callbacks under StrictMode", async ({ page }) => {
|
||||
await setup(page, true);
|
||||
await page.evaluate(() => window.shellProbe.render({ toast: { id: "old", message: "Ancienne", durationMs: 1000 } }));
|
||||
await page.clock.runFor(500);
|
||||
await page.evaluate(() => window.shellProbe.render({ toast: { id: "new", message: "Nouvelle", durationMs: 2000 } }));
|
||||
await page.clock.runFor(1999);
|
||||
expect(await page.evaluate(() => window.shellProbe.closed)).toEqual([]);
|
||||
await page.clock.runFor(1);
|
||||
expect(await page.evaluate(() => window.shellProbe.closed)).toEqual(["new"]);
|
||||
await page.evaluate(() => {
|
||||
window.shellProbe.render({ toast: { id: "unmounted", message: "Fermée" } });
|
||||
window.shellProbe.unmount();
|
||||
});
|
||||
await page.clock.runFor(120000);
|
||||
expect(await page.evaluate(() => window.shellProbe.closed)).toEqual(["new"]);
|
||||
});
|
||||
|
||||
test("overlapping modals keep scroll locked until the last unmount under StrictMode", async ({ page }) => {
|
||||
await setup(page, true);
|
||||
await page.evaluate(() => window.shellProbe.render({ confirm: true, create: true }));
|
||||
await expect(page.getByRole("dialog")).toHaveCount(2);
|
||||
await expect(page.locator("body")).toHaveCSS("overflow-y", "hidden");
|
||||
await page.evaluate(() => window.shellProbe.render({ create: true }));
|
||||
await expect(page.getByRole("dialog")).toHaveCount(1);
|
||||
await expect(page.locator("body")).toHaveCSS("overflow-y", "hidden");
|
||||
await page.evaluate(() => window.shellProbe.unmount());
|
||||
await expect(page.getByRole("dialog")).toHaveCount(0);
|
||||
await expect(page.locator("body")).not.toHaveCSS("overflow-y", "hidden");
|
||||
});
|
||||
|
||||
test("editorial text preserves line breaks and italics but never interprets arbitrary HTML", async ({ page }) => {
|
||||
await setup(page, true);
|
||||
await page.evaluate(() => window.shellProbe.render({ text: 'Première\n<i>Seconde</i>\n<img src=x onerror="alert(1)">' }));
|
||||
const text = page.locator("#editorial-text");
|
||||
await expect(text.locator("br")).toHaveCount(2);
|
||||
await expect(text.locator("i")).toHaveText("Seconde");
|
||||
await expect(text.locator("img, script")).toHaveCount(0);
|
||||
await expect(text).toContainText('<img src=x onerror="alert(1)">');
|
||||
await page.evaluate(() => window.shellProbe.render({ text: "Remplacé" }));
|
||||
await expect(text).toHaveText("Remplacé");
|
||||
await expect(text.locator("br, i")).toHaveCount(0);
|
||||
await page.evaluate(() => window.shellProbe.unmount());
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue