From 2bff58f08cfc5636a833116651c3b843d5c43f89 Mon Sep 17 00:00:00 2001 From: Shinuwa Date: Fri, 11 Sep 2026 08:52:20 +0200 Subject: [PATCH] Fix compact calendar events and stabilize E2E execution --- .beads/interactions.jsonl | 2 + DESIGN_SYSTEM.md | 1 + README.md | 7 ++ e2e/calendar-layout.spec.js | 106 ++++++++++++++++++ playwright.config.js | 4 +- .../design-system/attributes/_events.scss | 7 +- 6 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 e2e/calendar-layout.spec.js diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl index f9982b1..b0c7ca0 100644 --- a/.beads/interactions.jsonl +++ b/.beads/interactions.jsonl @@ -26,3 +26,5 @@ {"id":"int-903a25fde459880bbbadc2764da920a2","kind":"field_change","created_at":"2026-09-10T16:58:56.754421196Z","actor":"Shinuwa","issue_id":"sokko-g-hf6","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Unified confirmed-state queue for all writes; imports use current-state updaters; atomic creation/link/deletion and synchronous transaction abort. Removed obsolete checkpoints. Deterministic concurrency/failure tests and real IndexedDB abort E2E pass; full suite 15/15 and npm run check pass."}} {"id":"int-16ba759581961e395e133df1c6019895","kind":"field_change","created_at":"2026-09-10T17:21:13.173981957Z","actor":"Shinuwa","issue_id":"sokko-g-ra9","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Implemented quota coalescing and shared helpers. npm run check and all 15 E2E tests with --workers=2 pass; default concurrency timeouts tracked separately."}} {"id":"int-df984435537f78d51a7a309feda1109c","kind":"field_change","created_at":"2026-09-10T17:21:18.79044889Z","actor":"Shinuwa","issue_id":"sokko-g-ckq","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Implemented quota coalescing and shared helpers. npm run check and all 15 E2E tests with --workers=2 pass; default concurrency timeouts tracked separately."}} +{"id":"int-300128a7d6ed7fa43c5f43c7cb0b9dad","kind":"field_change","created_at":"2026-09-11T06:50:23.200542022Z","actor":"Shinuwa","issue_id":"sokko-g-v02","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Default E2E concurrency capped at two workers with failure traces; compact calendar titles kept in flow and sm padding corrected. Desktop/mobile geometry and editing regressions added. npm run check:full passed; second full E2E run passed all 17 tests without retries."}} +{"id":"int-a54950bb4a7dbb177188a5ec02e9d9fd","kind":"field_change","created_at":"2026-09-11T06:50:28.175047619Z","actor":"Shinuwa","issue_id":"sokko-g-328","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Default E2E concurrency capped at two workers with failure traces; compact calendar titles kept in flow and sm padding corrected. Desktop/mobile geometry and editing regressions added. npm run check:full passed; second full E2E run passed all 17 tests without retries."}} diff --git a/DESIGN_SYSTEM.md b/DESIGN_SYSTEM.md index 9677de2..6508bf5 100644 --- a/DESIGN_SYSTEM.md +++ b/DESIGN_SYSTEM.md @@ -777,6 +777,7 @@ Date picker et menus compacts : - `nds-event-surface[data-size="md"]` vise les surfaces à partir de `44px` et affiche le titre et la metadata secondaire, par exemple l'heure ; - `nds-event-surface[data-size="sm"]` vise les surfaces autour de `22px` et masque la metadata secondaire ; - `nds-event-surface[data-size="xs"]` vise les surfaces ultra-fines autour de `10px` et affiche uniquement le titre en taille réduite ; +- les titres compacts restent dans le flux pour donner une hauteur au bouton, même si la surface ne définit qu'une hauteur minimale ; en `sm`, bordures et padding doivent tenir avec la ligne de titre dans les `22px` ; - `nds-event-surface[data-color="#8b5cf6"]` expose la couleur métier de l'event ; la variable inline `--nds-event-color` applique cette couleur au fond et a la bordure ; - le hover utilise une superposition sombre legere pour rester lisible sur une couleur dynamique inconnue du Sass ; - les couleurs de fond des events restent métier et ne sont pas des variants NDS ; diff --git a/README.md b/README.md index f2cd7ea..77f805c 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,13 @@ npm run check Cette commande vérifie la configuration Node/Vite, lance les tests, puis exécute le build React. +Pour inclure les parcours navigateur, lancer `npm run check:full`. Les deux +validations sont exécutées successivement pour éviter la concurrence avec le build. +La suite E2E utilise deux workers par défaut pour limiter la contention lors du +chargement initial des outils par Vite. Le parallélisme reste ajustable avec +`npm run test:e2e -- --workers=4`. Les traces des tests en échec sont conservées +dans `test-results` et consultables depuis le rapport Playwright. + ## Contenu éditable Les contenus maintenus à la main sont regroupés dans `website/public/data`. diff --git a/e2e/calendar-layout.spec.js b/e2e/calendar-layout.spec.js new file mode 100644 index 0000000..dc45126 --- /dev/null +++ b/e2e/calendar-layout.spec.js @@ -0,0 +1,106 @@ +// Rôle : vérifie la lisibilité et la zone cliquable des événements calendrier denses. +/* global getComputedStyle */ +import { test, expect } from "@playwright/test"; +import { resetSokkoStorage } from "./helpers/storage.js"; + +async function seedCalendars(page) { + await resetSokkoStorage(page); + await page.evaluate(async () => { + const db = await import("/src/utils/indexedDbStorage.js"); + const dateKey = (day) => { + const now = new Date(); + return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`; + }; + await db.setToolboxSnapshot([{ + id: "calendar-layout", + name: "Calendar layout", + moduleColumns: 1, + modules: [ + { id: "week", type: "calendar", title: "Week" }, + { id: "month", type: "calendar", title: "Month" } + ] + }], {}, { + "calendar-layout:week": { + initialized: true, + viewMode: "week", + events: [30, 60, 120].map((duration, index) => ({ + id: `week-${index}`, title: `Raid ${duration}`, dayIndex: index, + startMinutes: 600, endMinutes: 600 + duration, color: "violet" + })) + }, + "calendar-layout:month": { + initialized: true, + viewMode: "month", + events: [0, 1, 2, 3].map((index) => ({ + id: `month-${index}`, title: `Event ${index}`, repeat: "none", + startDate: dateKey(index < 3 ? 10 : 20), endDate: dateKey(index < 3 ? 10 : 20), + color: "gold" + })) + } + }); + }); + await page.goto("/#/toolbox/calendar-layout"); + await page.reload(); +} + +async function expectTitleFits(surface) { + await surface.scrollIntoViewIfNeeded(); + const box = await surface.evaluate((element) => { + const button = element.querySelector("button"); + const title = button.querySelector("strong"); + const outer = element.getBoundingClientRect(); + const inner = button.getBoundingClientRect(); + const text = title.getBoundingClientRect(); + const style = getComputedStyle(element); + return { + height: outer.height, + buttonHeight: inner.height, + titleHeight: text.height, + topSpace: text.top - outer.top - parseFloat(style.borderTopWidth), + bottomSpace: outer.bottom - parseFloat(style.borderBottomWidth) - text.bottom, + buttonTopSpace: text.top - inner.top, + buttonBottomSpace: inner.bottom - text.bottom + }; + }); + expect(box.titleHeight).toBeGreaterThan(0); + expect(box.buttonHeight).toBeGreaterThanOrEqual(box.height - 2); + for (const gap of [box.topSpace, box.bottomSpace, box.buttonTopSpace, box.buttonBottomSpace]) { + expect(gap, JSON.stringify(box)).toBeGreaterThanOrEqual(-0.5); + } +} + +for (const width of [1440, 390]) { + test(`dense calendar titles fit and remain editable at ${width}px`, async ({ page }, testInfo) => { + await page.setViewportSize({ width, height: 900 }); + await page.clock.setFixedTime(new Date("2026-09-01T12:00:00Z")); + const errors = []; + page.on("pageerror", (error) => errors.push(error.message)); + await seedCalendars(page); + const week = page.locator('[data-module-type="calendar"]').first(); + const month = page.locator('[data-module-type="calendar"]').last(); + await expect(week.locator(".calendar-event")).toHaveCount(3); + await expect(month.locator(".calendar-month-bar")).toHaveCount(4); + for (const size of ["xs", "sm", "md"]) { + const surface = week.locator(`.calendar-event[data-size="${size}"]`); + await expect(surface).toHaveCount(1); + await expectTitleFits(surface); + } + for (const size of ["xs", "sm"]) { + const surfaces = month.locator(`.calendar-month-bar[data-size="${size}"]`); + await expect(surfaces).toHaveCount(size === "xs" ? 3 : 1); + for (const surface of await surfaces.all()) await expectTitleFits(surface); + } + await month.screenshot({ path: testInfo.outputPath("month.png") }); + await week.screenshot({ path: testInfo.outputPath("week.png") }); + for (const [module, selector, title] of [ + [week, ".calendar-event", "Raid 30"], + [month, ".calendar-month-bar", "Event 0"] + ]) { + const button = module.locator(`${selector}[data-size="xs"] button`).first(); + await button.click(); + await expect(module.getByLabel("Titre", { exact: true })).toHaveValue(title); + await module.getByRole("button", { name: "Annuler", exact: true }).click(); + } + expect(errors).toEqual([]); + }); +} diff --git a/playwright.config.js b/playwright.config.js index 2dafc12..180225e 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -4,10 +4,12 @@ import { defineConfig, devices } from "@playwright/test"; export default defineConfig({ testDir: "./e2e", fullyParallel: true, + // Le serveur Vite partagé compile les outils lazy à la première visite. + workers: 2, reporter: [["list"], ["html", { open: "never" }]], use: { baseURL: "http://localhost:5173", - trace: "on-first-retry", + trace: "retain-on-failure", screenshot: "only-on-failure" }, webServer: { diff --git a/website/src/styles/design-system/attributes/_events.scss b/website/src/styles/design-system/attributes/_events.scss index 04d35ce..86ef577 100644 --- a/website/src/styles/design-system/attributes/_events.scss +++ b/website/src/styles/design-system/attributes/_events.scss @@ -44,12 +44,11 @@ .nds-event-surface[data-size="sm"] button { display: block; - padding: 4px 7px; + padding: 3px 7px; text-align: center; } .nds-event-surface[data-size="xs"] button { - position: relative; display: block; padding: 0 4px; text-align: center; @@ -92,10 +91,6 @@ } .nds-event-surface[data-size="xs"] strong { - position: absolute; - top: 0; - right: 4px; - left: 4px; max-width: 100%; height: 10px; font-size: 8px;