// 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\nSeconde\n' })); 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(''); 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()); });