From e6e08b458cabf061b31b46f7534477a9dfe4c859 Mon Sep 17 00:00:00 2001 From: Shinuwa Date: Sat, 12 Sep 2026 23:35:50 +0200 Subject: [PATCH] Replace source assertions with browser coverage --- .beads/interactions.jsonl | 1 + e2e/onboarding.spec.js | 93 +++++++++++++++++++++++++++++++++++++++ tests/static-app.test.mjs | 14 ------ 3 files changed, 94 insertions(+), 14 deletions(-) create mode 100644 e2e/onboarding.spec.js diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl index d4f5141..146e164 100644 --- a/.beads/interactions.jsonl +++ b/.beads/interactions.jsonl @@ -42,3 +42,4 @@ {"id":"int-5a1d6dccbbcebf69807b6134785175ef","kind":"field_change","created_at":"2026-09-12T18:30:49.351851361Z","actor":"Shinuwa","issue_id":"sokko-g-d5u","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Protected IndexedDB bootstrap: readiness granted only after complete successful reads; all store writes rejected before initialization; create/import/export guards; startup error/loading gate with non-destructive retry; rejected connection promise evicted for reopening; generic accurate write-error title. Editorial strings validated and storage/workflow docs updated. Four new E2E cover repeated open/read failures, preserved data and exports, creation after retry, direct command guards and later write errors. Desktop/mobile screenshots reviewed. npm run check:full passed lint,101 Node tests,build,44 E2E. No schema or format changes."}} {"id":"int-224b7820169f57116ea4fd87a50560f1","kind":"field_change","created_at":"2026-09-12T21:26:21.88315065Z","actor":"Shinuwa","issue_id":"sokko-g-bhy","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Implemented selective images/imageAnnotation hydration with local retry, preserved unread values in atomic snapshots, and serialized complete exports without populating the UI cache. No schema change; timer/calendar remain eager. check:full passed including 52 E2E; four production scenarios profiled alone, five reloads each. 40-media baseline 120.68 to 2.78 MiB JS heap, 610.1 to 445.4 ms median ready. Reports in .local/performance; docs updated. Opened media remain cached until reload. No git commit or push performed."}} {"id":"int-4036248c53312cfaafae4ee37bb145bd","kind":"field_change","created_at":"2026-09-12T21:26:40.724321767Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"open","old_value":"in_progress"}} +{"id":"int-37bf322d7d6ae9d06e033d9f16b7f057","kind":"field_change","created_at":"2026-09-12T21:33:50.250082802Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"open","old_value":"in_progress"}} diff --git a/e2e/onboarding.spec.js b/e2e/onboarding.spec.js new file mode 100644 index 0000000..4b01523 --- /dev/null +++ b/e2e/onboarding.spec.js @@ -0,0 +1,93 @@ +// Rôle : vérifie la prise en main, son carrousel et son intégration à la home et à la page À propos. +import { test, expect } from "@playwright/test"; +import { readFile } from "node:fs/promises"; +import { URL } from "node:url"; + +const site = JSON.parse(await readFile("website/public/data/site.json", "utf8")); +const content = site.home.onboarding; + +async function setup(page, route = "/") { + await page.clock.install({ time: new Date("2026-09-12T12:00:00Z") }); + await page.clock.pauseAt(new Date("2026-09-12T12:01:00Z")); + await page.goto(route); + const section = page.getByRole("region", { name: content.title, exact: true }); + await expect(section).toBeVisible(); + return section; +} + +async function expectSlide(section, index) { + const slide = content.slides[index]; + await expect(section.getByRole("button", { name: `${content.slideButtonLabel} ${index + 1}`, exact: true })).toHaveAttribute("aria-pressed", "true"); + const image = section.getByRole("img", { name: slide.alt, exact: true }); + await expect(image).toHaveAttribute("src", slide.src); + await expect.poll(() => image.evaluate((element) => element.naturalWidth)).toBeGreaterThan(0); + await expect(section.getByText(slide.title, { exact: true })).toBeVisible(); +} + +test("onboarding cycles, pauses on hover and focus, and allows manual selection", async ({ page }) => { + const section = await setup(page); + await expectSlide(section, 0); + await page.clock.runFor(4500); + await expectSlide(section, 1); + await section.getByRole("img", { name: content.slides[1].alt, exact: true }).hover(); + await page.clock.runFor(9000); + await expectSlide(section, 1); + await page.mouse.move(0, 0); + await page.clock.runFor(4500); + await expectSlide(section, 2); + const first = section.getByRole("button", { name: `${content.slideButtonLabel} 1`, exact: true }); + await first.focus(); + await page.clock.runFor(9000); + await expectSlide(section, 2); + await first.press("Enter"); + await expectSlide(section, 0); + await section.getByRole("button", { name: content.primaryAction, exact: true }).focus(); + await page.clock.runFor(4500); + await expectSlide(section, 1); + await page.clock.runFor(4500 * (content.slides.length - 1)); + await expectSlide(section, 0); +}); + +test("reduced motion keeps previews still but manual selection remains available", async ({ page }) => { + await page.emulateMedia({ reducedMotion: "reduce" }); + const section = await setup(page); + await page.clock.runFor(18000); + await expectSlide(section, 0); + await section.getByRole("button", { name: `${content.slideButtonLabel} 3`, exact: true }).click(); + await expectSlide(section, 2); + await page.mouse.move(0, 0); + await section.getByRole("button", { name: content.primaryAction, exact: true }).focus(); + await page.clock.runFor(18000); + await expectSlide(section, 2); +}); + +test("home onboarding creates the initial tools and starts the tutorial", async ({ page }) => { + await page.goto("/"); + await page.getByRole("button", { name: content.primaryAction, exact: true }).click(); + const dialog = page.getByRole("dialog"); + await dialog.getByRole("textbox", { name: "Nom", exact: true }).fill("Prise en main"); + await dialog.getByRole("button", { name: "Créer", exact: true }).click(); + await expect(page).toHaveURL(/#\/toolbox\//); + await expect(page.locator('[data-module-type="memos"]')).toBeVisible(); + await expect(page.locator('[data-module-type="links"]')).toBeVisible(); + await expect(page.locator(".toolbox-tutorial-count")).toHaveText(/^1\//); +}); + +test("about reuses onboarding without creation and exposes tool examples and destinations", async ({ page }) => { + await page.goto("/#/about"); + await expect(page.getByRole("heading", { name: site.about.title, exact: true })).toBeVisible(); + const section = page.getByRole("region", { name: content.title, exact: true }); + await expect(section).toBeVisible(); + await expect(section.getByRole("button", { name: content.primaryAction, exact: true })).toHaveCount(0); + for (const group of site.about.toolGroups) { + await expect(page.getByRole("list", { name: `Exemples pour ${group.title}`, exact: true }).getByRole("listitem")).toHaveText(group.examples); + } + for (const card of content.cards) { + await section.getByRole("link").filter({ hasText: card.title }).click(); + await expect.poll(() => new URL(page.url()).hash).toBe(card.href); + await page.goBack(); + await expect(section).toBeVisible(); + } + await page.getByRole("link", { name: site.about.toolsLibraryLink, exact: true }).click(); + await expect(page).toHaveURL(/#\/library$/); +}); diff --git a/tests/static-app.test.mjs b/tests/static-app.test.mjs index 1e37f3f..a4d5b30 100644 --- a/tests/static-app.test.mjs +++ b/tests/static-app.test.mjs @@ -11,7 +11,6 @@ test("vite entrypoint and app shell are wired", async () => { const overlays = await readFile("website/src/components/AppOverlays.jsx", "utf8"); const imageViewer = await readFile("website/src/components/ImageViewer.jsx", "utf8"); const homePage = await readFile("website/src/pages/HomePage.jsx", "utf8"); - const homeOnboarding = await readFile("website/src/pages/HomeOnboardingSection.jsx", "utf8"); const aboutPage = await readFile("website/src/pages/AboutPage.jsx", "utf8"); const routeContent = await readFile("website/src/router/RouteContent.jsx", "utf8"); @@ -22,7 +21,6 @@ test("vite entrypoint and app shell are wired", async () => { assert.match(source, /hooks\/useGameFilters\.js/); assert.doesNotMatch(source, /DEFAULT_SITE_CONTENT/); assert.match(gameFiltersHook, /export function useGameFilters/); - assert.match(routeContent, /pages\/AboutPage\.jsx/); assert.match(routeContent, /features\/games\/GameRoute\.jsx/); assert.doesNotMatch(shell, /content\.navigation\.library/); assert.match(shell, /sidebar-about-link/); @@ -32,21 +30,9 @@ test("vite entrypoint and app shell are wired", async () => { assert.match(overlays, /uid\("marker"\)/); assert.match(imageViewer, /export function ImageViewer/); assert.match(imageViewer, /function openImageInNewTab/); - assert.match(homePage, /HomeOnboardingSection/); assert.match(homePage, /import\.meta\.env\.DEV \|\| toolboxes\.length === 0/); - assert.match(homePage, /setCreateModal\(\{ tutorial: true \}\)/); assert.match(homePage, /dragon-home\.png/); - assert.match(homeOnboarding, /export function HomeOnboardingSection/); - assert.match(homeOnboarding, /prefers-reduced-motion: reduce/); - assert.match(homeOnboarding, /onCreateToolbox/); - assert.match(homeOnboarding, /content\.primaryAction/); - assert.match(homeOnboarding, /content\.sliderTitle/); - assert.match(homeOnboarding, /home-onboarding-dots/); - assert.match(aboutPage, /export function AboutPage/); - assert.match(aboutPage, /HomeOnboardingSection/); - assert.match(aboutPage, /siteContent\.home\.onboarding/); assert.match(aboutPage, /module-icon-\$\{group\.icon \|\| "notepad"\}/); - assert.match(aboutPage, /about-tool-examples/); }); test("project documentation tracks storage and feature updates", async () => {