sokko-g/e2e/shell-behavior.spec.js
Shinuwa 0fc18cd8b9
All checks were successful
Deploy Sokko G / deploy (push) Successful in 19s
Improve compact desktop usability and action guidance
2026-10-05 21:47:16 +02:00

108 lines
5.4 KiB
JavaScript

// 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, options = {}) {
await page.evaluate((detail) => window.dispatchEvent(new CustomEvent("sokkog:notify", { detail })), { message, ...options });
await expect(page.getByRole("status").filter({ hasText: message })).toBeVisible();
}
test("ordinary alerts last six seconds while explicit and persistent durations are respected", async ({ page }) => {
await setup(page);
await notify(page, "Texte copié");
await notify(page, "Sauvegarde terminée");
await page.clock.runFor(5999);
await expect(page.getByRole("status")).toHaveCount(2);
await page.clock.runFor(1);
await expect(page.getByRole("status")).toHaveCount(0);
await notify(page, "Minimum", { durationMs: 0 });
await notify(page, "Personnalisée", { durationMs: 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);
await notify(page, "Alerte persistante", { persistent: true });
await page.clock.runFor(120000);
await expect(page.getByRole("status")).toHaveText("Alerte persistante");
await page.getByRole("button", { name: "Fermer l'alerte" }).click();
await expect(page.getByRole("status")).toHaveCount(0);
for (let index = 1; index <= 6; index += 1) await notify(page, `Pile ${index}`);
await expect(page.getByRole("status")).toHaveCount(5);
await expect(page.getByRole("status").filter({ hasText: "Pile 1" })).toHaveCount(0);
await expect(page.getByRole("status").filter({ hasText: "Pile 6" })).toBeVisible();
});
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: "default", message: "Par défaut" } }));
await page.clock.runFor(5999);
expect(await page.evaluate(() => window.shellProbe.closed)).toEqual(["new"]);
await page.clock.runFor(1);
expect(await page.evaluate(() => window.shellProbe.closed)).toEqual(["new", "default"]);
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", "default"]);
});
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());
});