Fix compact calendar events and stabilize E2E execution
Some checks failed
Deploy Sokko G / deploy (push) Failing after 2s

This commit is contained in:
Shinuwa 2026-09-11 08:52:20 +02:00
parent 748415082b
commit 2bff58f08c
6 changed files with 120 additions and 7 deletions

View file

@ -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."}}

View file

@ -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 ;

View file

@ -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`.

106
e2e/calendar-layout.spec.js Normal file
View file

@ -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([]);
});
}

View file

@ -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: {

View file

@ -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;