"Œuf"/"Œufs" (ingrédient, sous-catégorie, allergène) et "Jaune/Blanc
d'œuf" (ajoutés par #60) s'écrivaient avec le œ ligaturé — remplacé par
"oe" (deux lettres) partout où le mot apparaît. Ne touche pas "bœuf"
(mot différent, non concerné).
Le scénario Cucumber recipe-form.feature qui sélectionne l'ingrédient
par son libellé affiché est mis à jour en conséquence.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Remplace l'unité texte libre de RecipeIngredient (max 20 caractères,
"g"/"grammes"/"G"... jamais fiable à additionner) par une référence
vers un nouveau catalogue Unit (id/key/type/toBaseFactor), même
traitement que Diet/Allergy/Ingredient : GET /reference/units, seedé
par reference-seed-data.ts (14 unités : gram/kilogram/milliliter/
centiliter/liter/tablespoon/teaspoon/piece/pinch/slice/clove/bunch/
sachet/sprig), sélectionnable uniquement via un <select> dans le
formulaire recette (plus de saisie libre).
`toBaseFactor` (combien d'unités de base — gramme pour MASS,
millilitre pour VOLUME — vaut une unité) pose les bases d'une future
fonctionnalité de conversion (ex. liste de courses additionnant
"500g" + "0.5kg") sans construire cette fonctionnalité elle-même —
les unités COUNT restent à toBaseFactor=1, non convertibles entre
elles (une "pincée" n'est pas une fraction fixe d'une "gousse").
Migration : recipe_ingredient.unit → unit_id (FK), breaking change
sans backfill assumé (pas de recette réelle en prod actuellement,
voir commentaire de migration) — mêmes garde-fous service-side que
ingredientId (404 UNIT_NOT_FOUND) et mêmes tests de couverture.
Ajoute Recipe.portions (combien de portions la recette produit telle
qu'écrite) — formulaire de création/édition, fiche détail, migration
Prisma (backfill à 4, même pattern que planning_item.portions).
Le sélecteur de recette du planning pré-remplit désormais son propre
champ "portions" depuis cette valeur au lieu de toujours démarrer à 1
(RecipeSummaryView.portions), tout en gardant PlanningItem.portions
indépendant (une recette peut être mise à l'échelle pour un créneau).
Couverture : tests API (création/édition/validation), scénarios
cypress (formulaire + préchargement en édition).
* chore: point de départ pour le refactor des tests Cypress
Sépare les tests Cypress en trois catégories, comme discuté :
1. Parcours utilisateur (cypress/e2e/) — scénarios Gherkin/Cucumber,
pilotés par @badeball/cypress-cucumber-preprocessor@22.2.0 (validé
sur experiment/cucumber-cypress, mergée). Ex. "En tant
qu'utilisateur, je peux créer un compte".
2. Layout applicatif (nouveau dossier à définir) — specs Cypress
classiques (pas de Gherkin), cy.visit() sur une vraie page, mais
centrées sur la disposition/visibilité des éléments, indépendamment
d'un parcours utilisateur scripté.
3. Composants génériques (cypress/component/, nouveau) — vrai
Component Testing Cypress, composant React monté isolément (pas de
routeur, pas de backend). Composants concernés aujourd'hui :
components/ui/{Checkbox,Radio,Dialog}.tsx.
Premier pas : mise en place de l'infra Component Testing (config +
devServer Vite + adapter React 18), validée sur un composant simple
avant de construire le reste.
* feat(web): infra Cypress Component Testing, premier test sur CheckboxOption
Étape 1 du refactor (voir PR) : met en place le vrai mode Component
Testing de Cypress, séparé de l'e2e — monte un composant React isolé
(pas de routeur, pas de backend), pour tester les composants
génériques (components/ui/) indépendamment de tout parcours
utilisateur.
- cypress.config.ts : nouveau bloc `component` (devServer Vite, réutilise
vite.config.ts de l'appli — même plugin React, même Sass). Le hook
GPU-disable est factorisé (`disableGpu`) puisque e2e et component ont
chacun leur propre `setupNodeEvents`, pas de config partagée par défaut.
- cypress/support/component.ts + component-index.html : fichiers de
support standards Cypress CT — importe le vrai global.scss de l'appli
(les composants génériques sont stylés via lui, pas de CSS scopé à eux).
- cypress/component/CheckboxOption.cy.tsx : 4 scénarios sur
components/ui/Checkbox.tsx (rendu du label, reflet du prop `checked`
sur l'input natif + la classe `is-selected`, callback `onChange` avec
la valeur inversée, comportement contrôlé via un wrapper avec état).
Dépendance ajoutée, épinglée : @cypress/vite-dev-server@5.2.1 (dernière
version sans peer dependency cypress >=14 — 6.0.3+ l'exige explicitement,
on est sur cypress@13.17.0).
Testé en local jusqu'au mur GPU/Electron habituel (config + devServer
Vite chargent sans erreur) — l'exécution réelle du montage reste à
vérifier via la CI.
* ci: exécute les tests de composants Cypress
Sans ça, `cypress/component/CheckboxOption.cy.tsx` (commit précédent)
ne tournait jamais en CI : `pnpm --filter web e2e` lance `cypress run`
sans `--component`, donc uniquement la suite e2e par défaut.
- apps/web/package.json : nouveau script `cy:run:component`
- ci.yml : étape dédiée après `pnpm --filter web e2e`, sans
start-server-and-test (Cypress lance son propre dev server Vite en
interne pour le component testing, pas besoin d'attendre l'appli
comme pour l'e2e)
* test(web): refactor user journeys into Cucumber scenarios
Convertit les parcours utilisateur (goal-driven, "en tant que X je peux
Y") en scénarios Gherkin, en réutilisant l'infra Cucumber déjà validée
par le smoke test (PR #24). Retire le smoke test jetable maintenant
superflu.
8 fichiers .feature ajoutés, chacun avec son fichier de step definitions
au même basename (convention de découverte du préprocesseur — voir
login-smoke.ts) :
- auth.feature : inscription (succès, erreur validation, email déjà
pris), connexion (succès, identifiants invalides), déconnexion
- onboarding.feature : les 3 scénarios déjà couverts (wizard complet,
étapes sautées, rejoindre un foyer pendant l'onboarding) — dépend de
household-settings.ts et preferences.ts pour ses steps de
création/rejoint de foyer et de sélection de régime/allergies
- household-settings.feature : créer un foyer, rejoindre par code
d'invitation, renommer (autosave), retirer un membre, supprimer le
foyer, quitter le foyer
- account.feature : suppression de compte (mauvais mot de passe,
succès, annulation)
- recipe-form.feature : les 4 scénarios déjà couverts inchangés (ajout
d'ingrédient + création, régression crypto.randomUUID, exclusion/
réinclusion d'ingrédient, préchargement + édition d'une recette
existante)
- recipes.feature : bascule favori, suppression d'une recette
- preferences.feature : autosave du régime, autosave des allergies
- user-preferences.feature : changement de thème (autosave)
En contrepartie, les anciens .cy.ts perdent uniquement les it() migrés
vers Gherkin — les scénarios de layout/affichage pur (catalogue de
recettes, tabs, recherche, panneau de détail, sidebar, planning grid,
etc.) restent en Cypress classique, conformément au découpage
"parcours utilisateur (Cucumber) vs layout (Cypress pur)" déjà en
place pour les component tests. auth.cy.ts, onboarding.cy.ts et
recipe-form.cy.ts sont supprimés : 100% de leur contenu a migré.
Les commentaires "voir auth.cy.ts pour la justification" désormais
obsolètes (fichier supprimé) sont remplacés par une explication
autonome du mock cy.intercept.
Vérifié statiquement : les 246 steps Gherkin des 8 .feature résolvent
chacun vers exactement une définition (0 non résolu, 0 ambigu) et
`pnpm exec biome check` est propre sur tout cypress/. Reste à confirmer
en CI que les scénarios passent réellement (pas seulement qu'ils se
résolvent).
* fix(web): fix cross-feature step discovery and slash-alternation bug
La CI de la refonte précédente (commit aaace15) a échoué : la
découverte par défaut du préprocesseur ne charge, pour un fichier
`foo.feature`, QUE `foo.ts` (co-localisé, même basename) et
`cypress/support/step_definitions/**` — pas les autres `.ts` du
dossier `cypress/e2e/`. onboarding.feature référençait donc des steps
qui ne vivaient que dans preferences.ts, household-settings.ts et
auth.ts, introuvables lors de son propre run.
Déplace les steps réellement partagés entre plusieurs .feature vers
cypress/support/step_definitions/ (chargé pour toutes les features) :
- reference-data.steps.ts : mocks des listes de référence régimes/
allergies (options ou vides) — partagé entre auth.feature et
onboarding.feature
- household-mutations.steps.ts : création/adhésion à un foyer et leurs
assertions — partagé entre household-settings.feature et
onboarding.feature
- profile-mutations.steps.ts : sélection du régime, mise à jour des
allergies et leur assertion — partagé entre preferences.feature et
onboarding.feature
Les définitions d'origine sont retirées de auth.ts/household-
settings.ts/onboarding.ts/preferences.ts pour éviter un step
"Ambiguous" (chargé deux fois pour la feature qui les définissait déjà
elle-même).
Corrige aussi un second bug distinct révélé par la même CI :
"the ingredient/diet catalog is available" (recipe-form.feature)
contient un "/" non échappé — en syntaxe Cucumber Expression, "/" hors
d'un paramètre {..} signifie une alternative de texte ("ingredient" OU
"diet catalog is available"), jamais le caractère littéral. Le texte
du .feature ne pouvait donc jamais matcher. Renommé sans "/" :
"the ingredient and diet catalog is available".
Le script de vérification statique utilisé pour valider aaace15 avant
push donnait une fausse confiance : il regroupait tous les steps de
tous les fichiers comme disponibles globalement pour chaque feature,
sans respecter ce scoping réel. Réécrit pour ne charger, par feature,
que son fichier co-localisé + step_definitions/ — et pour détecter les
patterns contenant un "/" non échappé. Résultat : toujours 246 steps,
0 non résolu, 0 ambigu, 0 pattern à slash non échappé, cette fois avec
un modèle de résolution fidèle au comportement réel du préprocesseur.
* test(web): cover every CheckboxOption/RadioOption behavior
Complète les component tests des deux seuls composants UI génériques
committés (Dialog.tsx est un WIP non commité d'une autre fonctionnalité
en cours — hors scope ici) pour couvrir tout leur comportement, pas
seulement le cas heureux.
CheckboxOption — 4 tests existants (rendu, checked/is-selected, onChange
au clic depuis unchecked, contrôlé) complétés par :
- onChange(false) au clic depuis l'état checked (symétrique du test
existant, qui ne couvrait que checked=false → true)
- fusion du className de l'appelant avec is-selected, dans les deux
sens (juste className, className+is-selected)
- class="" (chaîne vide, pas "false"/"null") quand aucun className
n'est passé et que checked=false — pin le comportement exact du
`.filter(Boolean).join(" ")`
- le clic sur le texte du label (pas seulement l'input) déclenche aussi
onChange — comportement natif du HTML dont la "carte sélectionnable"
de global.scss dépend entièrement
- le span .check-mark est aria-hidden
RadioOption — aucun test avant ce commit. Ajouté en couvrant en plus
ce qui distingue vraiment un radio d'un checkbox :
- name/value posés sur l'input natif
- onChange(value) au clic depuis unchecked
- AUCUN onChange au clic sur un radio déjà checked (contrairement à un
checkbox, un radio natif ne réémet pas `change` si l'état ne change
pas réellement)
- clic sur le label, className/is-selected, aria-hidden — mêmes
scénarios que CheckboxOption
- comportement de groupe mutuellement exclusif : 3 RadioOption
partageant `name="theme"` (mirroring UserPreferencesPage), un seul
sélectionné à la fois, y compris via `input:checked` natif du
navigateur
Non exécutable en local (limitation GPU/sandbox Electron documentée
dans le README, pré-existante) — à vérifier en CI.
* test(web): add layout/style regression suite for the app shell
Troisième catégorie du découpage des tests (parcours via Cucumber,
composants génériques via Component Testing, et maintenant layout pur
— indépendant de tout parcours utilisateur). Cypress classique, pas de
Gherkin : ce fichier teste la structure/l'apparence du shell
(AppLayout) lui-même, pas le contenu d'une page donnée.
Couvre spécifiquement les 4 axes demandés :
- Positionnement : la sidebar garde une largeur fixe (240px déplié,
68px replié) plaquée au coin haut-gauche, sur n'importe quelle page.
- Scroll : régression directe pour #21 — `.app-layout` reste borné
exactement à la hauteur du viewport (overflow: hidden), et une page
plus haute que le viewport scrolle uniquement dans `.app-content`
(via un spacer synthétique de 3000px injecté après le mount, pour
rester indépendant du contenu réel d'une page donnée) sans jamais
déplacer la sidebar ni scroller le document lui-même.
- Largeur des pages : autre régression directe pour #21 — le planning
et le catalogue de recettes remplissent toute la largeur disponible
de `.app-content`, tandis que la page "Liste de courses" et les
pages de paramètres restent centrées avec un espace égal de chaque
côté (le bug original : collées à gauche avec un grand vide à
droite).
- Breakpoint responsive (< 640px) : la sidebar bascule en barre
horizontale pleine largeur, masque le bouton collapse/la version,
et garde chaque lien de nav pleinement lisible (icône + label, avec
scroll horizontal) plutôt que de les écraser en pastilles de ~16px
sans texte — un mode de régression explicitement documenté en
commentaire dans AppLayout.scss mais jusqu'ici non testé.
- Thème de couleur : va au-delà de l'attribut `data-theme` déjà
couvert par user-preferences.cy.ts — vérifie les vraies valeurs de
couleur calculées (`getComputedStyle`) sur la sidebar, le lien de
nav actif et le fond de page, en clair et en sombre, confirmant que
la cascade CSS des tokens (_theme.scss) atteint réellement le rendu,
pas seulement que le JS pose le bon attribut.
Non exécutable en local (limitation GPU/sandbox Electron documentée
dans le README, pré-existante) — à vérifier en CI.