Fix compact calendar events and stabilize E2E execution
Some checks failed
Deploy Sokko G / deploy (push) Failing after 2s
Some checks failed
Deploy Sokko G / deploy (push) Failing after 2s
This commit is contained in:
parent
748415082b
commit
2bff58f08c
6 changed files with 120 additions and 7 deletions
|
|
@ -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-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-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-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."}}
|
||||||
|
|
|
||||||
|
|
@ -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="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="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 ;
|
- `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 ;
|
- `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 ;
|
- 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 ;
|
- les couleurs de fond des events restent métier et ne sont pas des variants NDS ;
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,13 @@ npm run check
|
||||||
|
|
||||||
Cette commande vérifie la configuration Node/Vite, lance les tests, puis exécute le build React.
|
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
|
## Contenu éditable
|
||||||
|
|
||||||
Les contenus maintenus à la main sont regroupés dans `website/public/data`.
|
Les contenus maintenus à la main sont regroupés dans `website/public/data`.
|
||||||
|
|
|
||||||
106
e2e/calendar-layout.spec.js
Normal file
106
e2e/calendar-layout.spec.js
Normal 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([]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -4,10 +4,12 @@ import { defineConfig, devices } from "@playwright/test";
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
testDir: "./e2e",
|
testDir: "./e2e",
|
||||||
fullyParallel: true,
|
fullyParallel: true,
|
||||||
|
// Le serveur Vite partagé compile les outils lazy à la première visite.
|
||||||
|
workers: 2,
|
||||||
reporter: [["list"], ["html", { open: "never" }]],
|
reporter: [["list"], ["html", { open: "never" }]],
|
||||||
use: {
|
use: {
|
||||||
baseURL: "http://localhost:5173",
|
baseURL: "http://localhost:5173",
|
||||||
trace: "on-first-retry",
|
trace: "retain-on-failure",
|
||||||
screenshot: "only-on-failure"
|
screenshot: "only-on-failure"
|
||||||
},
|
},
|
||||||
webServer: {
|
webServer: {
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,11 @@
|
||||||
|
|
||||||
.nds-event-surface[data-size="sm"] button {
|
.nds-event-surface[data-size="sm"] button {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 4px 7px;
|
padding: 3px 7px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nds-event-surface[data-size="xs"] button {
|
.nds-event-surface[data-size="xs"] button {
|
||||||
position: relative;
|
|
||||||
display: block;
|
display: block;
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
@ -92,10 +91,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.nds-event-surface[data-size="xs"] strong {
|
.nds-event-surface[data-size="xs"] strong {
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 4px;
|
|
||||||
left: 4px;
|
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
font-size: 8px;
|
font-size: 8px;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue