Skip to content

Tests e2e — couverture

Ce fichier doit rester synchronisé avec e2e/specs/. À mettre à jour à chaque spec/test ajouté ou retiré — c'est l'inventaire de ce que la suite garantit réellement (et de ce qu'elle ne garantit pas).

Vue d'ensemble — 18 specs

L'inventaire assertion par assertion de chaque test vit dans e2e-summary.md.

SpecÉcran / parcoursTestsContrats backend vérifiés
smoke.spec.tsCold start1GET /travels, GET /api/travels/seaside/categories — et surtout : le teardown fail-loud prouve que le monde couvre tout le trafic de démarrage
home.spec.tsHome /6GET /travels?seaside=false (+ page=2 via infinite scroll) ; connecté : GET /api/customers (cache-busté ?_=) + GET /api/bookings, refetch des deux à chaque activation Home ; card « Mes voyages » balnéaire = durée calculée depuis l'occurrence
search.spec.tsOverlay recherche Home8GET /travels avec search=, seaside=false, category=
seaside.spec.tsListing /seasides4GET /travels avec seaside=true, seasideCategoryName=Baln MAJORQUE, search=
travel-details.spec.ts/voyage/:slug + sous-pages7GET /api/travels/:slug (dont 404 slug inconnu)
auth-login.spec.ts/login, /logout, session8POST /api/authentification (body), fetch customer du login allégé (lighter-response=true + ignoreTravels=true sur GET /api/customers), login réussi malgré un GET /api/bookings 500 (H6 — bookings non-fatals), non-appel de /refresh et de GET /api/customers sur 401, non-appel sur email malformé (Regle bloque, novalidate)
auth-signup.spec.ts/create-account4GET /api/customers/email-exists ; POST /api/customers (body complet, birthdate en yyyy-MM-dd) + auto-login
session-expired.spec.tsModal de connexion global (session morte)7POST /api/authentification/refresh (échec 401 ET 400 — la vraie réponse Horizon token-mort — → logout complet customer inclus + modal ; échec 500 transitoire → ni logout ni modal), re-login POST /api/authentification depuis le modal sans quitter la page (succès et mauvais identifiants) ; demi-état désync (customer persisté sans auth) → auto-guérison : logout complet + modal
logout-race.spec.tsCourse logout / fetch customer en vol (AUTH_BUG.md H1)1GET /api/customers gated résolu après « Oui, déconnecter » → la réponse est jetée (epoch de session) : localStorage.customer reste vide, Home reste anonyme. (Committé rouge le 2026-08-18 comme preuve du bug H1, vert depuis le fix epoch — cf. business-rules.md → « Epoch de session »)
analytics-consent.spec.tsModal de consentement analytics au démarrage4Persistance localStorage analyticsConsent (accepted/refused, re-prompt si dismiss) ; zéro requête PostHog garanti par le teardown fail-loud de chaque test de la suite (token vidé en e2e)
booking-wizard-catalog.spec.tsWizard voyage catalogue11POST /api/mobile/booking/initialize payload complet (connecté ET invité, dont acompte + bon cadeau : paymentDepositOnly, gifts[].id ; suppléments globaux booking.globalSupplements ; points consumeLoyaltyPoints) ; GET /api/gifts/:code ; PUT /api/customers (body scalaires, dates date-only, pas de Club 1970) ; GET .../payment-callback
booking-wizard-oneday.spec.tsWizard course d'un jour5POST /api/mobile/booking/initialize payload complet (invité ET connecté) ; GET /api/gifts/:code (bon partiel remainingValue, plafonné) ; GET /api/loyalty/winning-points (affichage mobileWinningPoints)
booking-wizard-seaside.spec.tsWizard balnéaire (chambres + pension, et vol-sec)5POST /api/mobile/booking/initialize payload complet (connecté ET invité, mealPlan par passager ; vol-sec : p.price bébé = 0 ; bus de nuit : checkIn/checkOut décalés)
special-offers.spec.tsOffres spéciales (teaser + calcul)4GET /api/travels/:slug (specialOffers occurrence + minPrice.*WithClubSpecialOffers) ; booking.discounts dans POST .../initialize (offre publique déduite invité, offre membres exclue invité, ×2 couple membre)
payment-callback.spec.ts/booking/confirmation5GET .../payment-callback?bookingId=
booking-details.spec.ts/booking/details/:bookingId5GET /api/bookings (recovery cold deep-link) ; GET /api/bookings/:id/confirmation-invoice (cache-buster ?_=, non-appel quand pending)
loyalty-points.spec.ts/loyalty-points4données loyalty du payload GET /api/customers (solde, pending, historique, expiration) ; fetch du mount en plein payload (pas de lighter-response — verrouille l'opt-out, les transactions brutes alimentant l'alerte d'expiration) ; pas de refetch à l'expansion de l'historique
personal-info.spec.ts/profile/edit2PUT /api/customers (scalaires only, birthdate date-only)

Détail par domaine (mapping sur workflows.md)

Workflow 1 — Recherche → détail → réservation → paiement

Listing & recherche (home, search, seaside)

  • Feed catalogue rendu depuis le mock (2 cards non-balnéaires), card « Prochains départs ».
  • Split catalogue/balnéaires : Home émet ?seaside=false, Seaside ?seaside=true — vérifié en query.
  • Overlay recherche = entrée d'historique (?overlay=search) : ouverture, fermeture au back sans quitter Home (ADR 0009).
  • Recherche texte : contrat search=<terme>, marqueur ?search=1, en-tête « n Résultats » ; back depuis les résultats = retour catalogue complet ; empty state + lien « Consultez le catalogue complet ».
  • Annulation de l'overlay = snapshot restauré (le BackButton de l'overlay rejette les éditions non soumises) ; onglet « Découvrir » = reset au catalogue complet depuis une recherche active.
  • Filtre par intérêt (category=courses-1-jour) et par destination balnéaire (seasideCategoryName=Baln MAJORQUE, format Baln {NOM}).
  • Champ texte Seaside (plus d'overlay côté balnéaire — cf. ADR 0012) : recherche au submit ; card seaside sans durée → pas de chip « n jours ».
  • Refetch connecté à chaque activation Home (GET /api/customers cache-busté + GET /api/bookings) — les points/réservations crédités pendant l'absence remontent.
  • Card « Mes voyages » (PendingTripCard) : pour un booking balnéaire (travel.duration absent), la durée affichée est calculée depuis l'occurrence (start/end), pas le champ backend — sinon « jour » sans nombre.
  • Feed long : pagination (page 2 via infinite scroll) + promo cards interleavées tous les 4 résultats (carte loyalty ×2 sur 14 voyages).

Détail voyage (travel-details)

  • Rendu par slug : titre, description, durée, highlights, prestations incluses, barre « Dès {prix} » + CTA « Réserver » ; suffixe « p.p. en ch. double » visible en multi-day, caché pour les one-day.
  • Sous-pages routes (ADR 0012) : departures (arrêts Sion/Lausanne), itinerary (jour par jour), comments — adressables par URL directe, back → détail.
  • Commentaires : seuls les approuvés s'affichent.
  • Slug inconnu : 404 backend → pas de contenu rendu (pas de crash).

Wizard de réservation (booking-wizard-catalog, booking-wizard-oneday, booking-wizard-seaside) — la matrice complète est couverte : invité ET connecté × one-day / multi-day / Seaside (6 happy paths complets jusqu'au paiement), plus le vol-sec Seaside (connecté) :

  • Catalogue multi-day, connecté (2 adultes + 1 enfant) : sélection date (2 occurrences) → chambre familiale → 3 formulaires passagers (civilité, nom, naissance, lieu de départ, assurance) → plan de sièges (sièges occupés seat-3/seat-4 affichés pris, sélection de 3 sièges) → paiement (totaux UI 3'600 CHF, préremplissage facturation/contact depuis le compte, date de naissance du résumé en heure locale dd.MM.yyyy — verrouille l'off-by-one UTC de la branche rooms, ne détecte la régression que grâce au timezoneId: 'Europe/Zurich' global de playwright.config.ts).
  • Catalogue multi-day, invité (2 adultes, chambre double) : formulaires passagers vierges (pas de préremplissage), facturation + contact saisis à la main, totaux 2'980 CHF, customer du payload = les données saisies (c'est lui qui crée le compte Horizon invité), confirmation guest (« confirmation par e-mail », pas de CTA réservation).
  • Course d'un jour, invité (2 adultes) : étape date skippée (occurrence unique), tarifs par tranche (89/69/49), champs assurance/pension absents (voyage sans chambre), facturation + contact saisis à la main, totaux 178 CHF.
  • Course d'un jour, connecté (1 adulte) : passager 1 prérempli depuis le compte (prénom/nom/naissance), facturation (#city Sion) et contact (#contact-email) préremplis, totaux 89 CHF, confirmation connectée (« Voir ma réservation »).
  • Seaside, connecté (2 adultes, chambre double) : les deux étapes date skippées (occurrence unique) — le wizard ouvre sur accommodations ; seule la chambre pricée s'affiche (la familiale sans prix d'occurrence est masquée) ; pension obligatoire (#mealPlan) — full board (+140) pour le passager 1, demi-pension incluse (0) pour le 2 ; assurance réelle (45) sur le passager 1 ; plan de sièges servi par catalog.vehicles. Totaux 2'765 CHF ; contrat : p.price = part chambre + pension (1'430 / 1'290, l'assurance exclue de p.price), passengers[].mealPlan = [2, 1], roomTypeCategories fabrique le séjour sur acc-hotel-majorque (Seaside sans days).
  • Seaside, invité (2 adultes) : formulaires vierges, pension incluse + « Aucune assurance », totaux 2'580 CHF, payload guest, confirmation guest.
  • Vol-sec Seaside, connecté (1 adulte + 1 bébé) : voyage makeVolSecSeasideTravel() (SEASIDE sans accommodation, poussé dans world.travels par le spec) → le wizard prend le chemin room-less (passenger-count/passenger-names) ; tranches vol-sec affichées (« Enfant de 2 ans révolus à 12 ans », « Bébé de moins de 2 ans ») avec bébé gratuit (0 CHF au sélecteur) ; étape passagers multi-type (fenêtre par groupe, passenger-type-group), pas de pension ni d'assurance, pas de téléphone pour le bébé ; totaux 890 CHF (le bébé n'ajoute rien) ; contrat : p.price = [890 adulte, 0 bébé], mealPlan absent, amountToPay 890.
  • Bus de nuit Seaside (connecté, chambre double) : voyage muté firstNightInTheBus/lastNightInTheBuscontrat : le séjour envoyé (roomTypeCategories[0].days[0]) atterrit sur les dates hôtelcheckIn = start +1 jour, checkOut = end −1 jour (jours calendaires via localDay), shape sans fuseau. Horizon persiste ces dates verbatim et la rooming list hôtel (Occupancy) filtre dessus — cf. business-rules.md → « CheckIn/CheckOut Seaside : les nuits en bus décalent le séjour hôtel ».
  • Acompte + bon cadeau (catalogue, connecté, chambre double) : voyage avec depositPercentage: 35 (muté dans le spec — le monde n'offre pas d'acompte par défaut), radio « Payer un acompte » (sans %), code cadeau validé (GET /api/gifts/:code → bon 200 CHF du monde, carte auto-repliée sur « Bon appliqué : 200.00 CHF ») ; résumé : lignes « Acompte » et « Solde à régler ultérieurement » (payment-balance 1'937 CHF), total à payer 843 CHF (= 2'980 × 35 % − 200) ; contrat : paymentDepositOnly: true, amountToPay/paymentAmount = 843, general.amount = total complet 2'780, gifts: [{ id: 'gift-noel' }].
  • Contrat initialize vérifié dans les sept happy paths (+ le test acompte) : occurrenceId, status (PendingWebOrMobile), seller: 'Mobile', nombre et prix par passager, amountToPay, customer, returnUrl vers /booking/confirmation.
  • Contrat initialize — dates wire locales et sans fuseau (ADR 0016), vérifié dans les sept happy paths sous le timezoneId: 'Europe/Zurich' épinglé : birthdates saisies au formulaire = valeur exacte yyyy-MM-ddT00:00:00 (un Date.toJSON UTC enverrait le jour précédent), birthdates préremplies du compte = shape sans fuseau (UNZONED_DATETIME) + jour calendaire de la fixture (localDay, fixtures midi-local), bookingDate et checkIn/checkOut (catalog : checkIn ; seaside : les deux) = shape sans fuseau.
  • Suppléments globaux (catalogue, connecté) : ligne « dont {description} » dans le résumé, montant flat×pax dans le total, booking.globalSupplements dans le payload (Horizon ne l'applique que si envoyé), p.price inchangé.
  • Points de fidélité au checkout (catalogue, connecté) : « Ajouter » applique tout le solde (−12 CHF sur 120 pts), payload consumeLoyaltyPoints: true + amountToPay réduit, puis solde optimiste à 0 sur la carte Home après la confirmation (masque le délai du worker Horizon).
  • Bon partiellement utilisé (one-day, invité) : la déduction = remainingValue (pas value), plafonnée au prix du voyage, avec le message « Il vous restera n CHF sur ce bon cadeau. » (UI seulement, pas de submit à 0).
  • PUT /api/customers (Valider de la carte Contact, connecté) : body scalaires uniquement (trim anti-hang iOS), birthdate date-only, pas de clubMembershipStart/End pour un client sans Club.
  • Tranches room-less one-day : 4 tiers affichés (« Bébé de moins de 3 ans » gratuit 0 CHF, « Enfant de 3 ans révolus à 12 ans », « Adolescent de 12 ans révolus à 18 ans »).
  • Lieu de départ : liste plate de villes triée alpha avec places restantes (Sion (40 places)) ; voyage sans stops → select absent et non exigé (required-si-proposé).
  • Validation guidante : tap sur le « Suivant » pseudo-disabled avec formulaires vides → « Champ requis » surligné.
  • Téléphone adulte : prérempli depuis customer.mobile (connecté) ; absent des tiers non-adultes (vol-sec bébé, bébé catalogue).
  • Points gagnés affichés = mobileWinningPoints du calculateur backend (« Vous gagnez 18 points » pour 89 CHF — mock ceil×2).
  • Filtre chambres (accordéon « Filtrer par nombre de personnes », >5 dispositions) : actif seulement déplié, capacité ≥ groupe, replié = tout réaffiché.
  • Assurances avec option non tarifée (catalogue, invité) : régression sorrente-capri — room type portant une option sans prix sur l'occurrence + assurances priceStart ≥ 1 → le select assurance s'affiche quand même (le filtre price la chambre/option réservée, pas rooms[0]/options[0]).
  • Child tier sans tranche junior (seaside) : disposition « 1 enfant » (jamais « bébé »), label date de naissance élargi « (0-15 ans) » + bornes min/max posées.
  • Pension/assurance vierges au premier rendu ('' — choix conscient forcé).
  • Plan de sièges multi-day sourcé de l'occurrence : le happy path catalogue tourne avec catalog.vehicles = [] — toute régression vers le catalog casserait l'étape sièges.
  • Saferpay : popup window.open intercepté (stub), retour simulé par deep-link.
  • Mécanique wizard : miroir étape ↔ URL (:stepName), back = exactement une étape en arrière (sélection conservée), deep-link froid vers une étape avancée → ré-init à l'étape 0 (bookingConstructor non persisté), ✕ = historique tronqué à [Home, travel-details] (un back post-sortie retombe toujours sur Home — ADR 0014).
  • Variantes : bookingState FULL → CTA « Liste d'attente » + arrivée sur /waiting-list ; noVehiclePlan → étape sièges skippée.
  • Liste d'attente — arrêt groupé + contrat de soumission (les trois types) : le dropdown « Lieu de départ souhaité » groupe les arrêts par ligne (<optgroup label="Valais">) et les étiquette "locality, place (hour)" (Sion, Gare routière (06:30) / Lausanne, Gare CFF (07:45)) — parité site (CustomerForm.availableStops). Vérifié sur catalogue (invité, branche else), Seaside (connecté, branche isSeaside) et one-day (connecté, picker « Ligne désirée » → arrêts filtrés). Sur Seaside et one-day, la soumission asserte le contrat POST /api/mail/add-waiting-list : requestedStop = le libellé complet (Sion, Gare routière (06:30)), requestedLine = le nom du trajet (Valais).
  • Badges de date liste d'attente / sur demande (catalogue, deux occurrences non réservables) : bookingState FULL → badge « Liste d'attente » (bg-secondary-80), TOO_LATE → « Sur demande » (bg-primary-50) — parité site (Occurrences.vue), verrouille l'appariement libellé/état contre l'ancienne inversion.

Offres spéciales (special-offers) — le rabais est une ligne booking.discounts agnostique du type de voyage (même chemin dans computeBookingPricing pour one-day / multi-day / Seaside), donc couvert sur le seul flow catalogue ; la logique de résolution (fenêtre, couple ×2, isPerPerson, éligibilité) est verrouillée en unitaire (utils/__tests__/special-offers.test.ts) :

  • Teaser (invité) : carte catalogue (brut 1'490 barré + prix club 1'390 + badge « Rabais Club Buchard »), en-tête détail, et chaque ligne de date (prix remisé, sans badge) — depuis minPrice.*WithClubSpecialOffers.
  • Offre publique, invité : ligne « Offre spéciale: … » −100 dans le résumé, total 2'880, booking.discounts = [{ type:0, value:100, showOnConfirmation:false, isCroisitour:false, description }], amountToPay 2'880.
  • Offre membres, invité : aucune ligne, prix plein 2'980, booking.discounts = [].
  • Offre membres, membre Couple (hasActiveClubMemberShip + clubMembershipType Couple seedés) : ligne « Offre spéciale club Buchard: … » −200 (×2), total 2'780, booking.discounts[0].value = 200.

Callback paiement (payment-callback)

  • Succès connecté : copy « espace client », CTA « Voir ma réservation » (deep-link /booking/details/:id) + « Retour à l'accueil ».
  • Succès invité : copy « confirmation par e-mail », CTA unique (pas de « Voir ma réservation » — route auth-gated).
  • Échecs : transaction CANCELED et erreur 500 de l'endpoint → écran d'échec.
  • Page terminale (router.replace) : un back ne re-rentre pas dans la confirmation (ADR 0009).

Détail réservation (booking-details)

  • Cold deep-link (route requiresAuth, invité → redirect /login) : le miss du store déclenche GET /api/bookings + retry (récupération post-paiement) ; rendu titre/durée/voyageurs/passagers.
  • Facture : bouton « Facture » actif (CONFIRMED) → GET invoice cache-busté ?_= ; booking PENDING_WEB_OR_MOBILE → « Facture bientôt disponible » pseudo-disabled, aucune requête au tap.
  • Ré-activation <keep-alive> : un booking rafraîchi par le refetch de Home (passager renommé) s'affiche au retour sur la page.

Loyauté (loyalty-points, + checkout dans booking-wizard-catalog)

  • Carte de solde (points + équivalent CHF), pastille « en attente » dépliable, alerte « n points expirent le {date} ».
  • Barre de palier modulo 1000 (50 pts / « Plus que 950 points… ») ; solde rond → message « Cumulez des points… ».
  • Historique : 3 max, « Voir tout l'historique » / « Réduire » inline sans refetch, libellés FR de l'enum.
  • Invité : page programme (étapes, simulateur ×2 app / ×1 web, FAQ) sans blocs de solde.
  • Consommation au checkout + solde optimiste : voir la section wizard ci-dessus.

Profil (personal-info)

  • /profile/edit : email de connexion + numéro de client readonly.
  • « Enregistrer » → PUT /api/customers : city édité transmis, birthdate date-only, body 100 % scalaire (trim anti-hang iOS). ⚠️ Anomalie relevée (non assertée) : sur ce chemin, PersonalInfoForm seede les dates Club absentes à new Date(0) → le PUT porte clubMembershipStart/End = 1970-01-01 (le chemin carte Contact du paiement, lui, les omet — c'est là que le comportement correct est verrouillé).

Workflow 2 — Création de compte (auth-signup)

  • Vérif email : 200 = « Cette addresse est déjà utilisée! », 404 = silencieux.
  • Validation Regle : soumission vide → « Champ requis ».
  • Signup complet : contrat POST /api/customers (dont birthdate en yyyy-MM-dd, civilité, pays) puis auto-login + atterrissage Home.

Auth & session (auth-login, session-expired, logout-race)

  • Login : validation, succès (contrat body + fetch customer/bookings), 401 → reste sur /login sans fetch profil avec erreur inline « La connexion a échoué… » (le toast natif est invisible derrière le backdrop du modal — l'inline couvre les deux contextes) ; un GET /bookings 500 n'annule plus le login (bookings non-fatals, AUTH_BUG.md H6 — session persistée + atterrissage Home vérifiés).
  • Formulaire (LoginForm, partagé page + modal) : email malformé → message Regle français (le novalidate du form neutralise la validation native du WebView, qui avalait le submit sans aucun message sur iOS) ; bouton désactivé pendant la requête (handler mock retardé).
  • Session seedée : survit à un reload, sans déclencher /refresh (JWT exp futur).
  • Logout : confirmation, retour Home, localStorage auth vidé.
  • Session expirée → modal de connexion sur place (ADR 0018) : JWT seedé expiré + refresh 401 ou 400 (la vraie réponse Horizon pour un token mort — AUTH_BUG.md H3) → modal global visible (?overlay=system-modal dans l'URL), logout complet vérifié (tokens vidés et store customer persisté nettoyé — plus de zombie H2) ; re-login depuis le modal (contrat POST /api/authentification) le ferme sans quitter la page ; back navigateur = dismiss sans navigation. (Couvre aussi, de fait, le host SystemModal et le service composables/modal.ts — le modal message-only n'a pas de spec dédié, aucun call-site produit à ce jour.)
  • Sessions mortes vs blips transitoires (garde de sessionExpired, cf. business-rules.md) : store customer seedé sans clé auth (demi-état zombie désync) → auto-guérison : logout complet + modal + store customer nettoyé (comportement inversé en août 2026 — avant le fix H2, ce demi-état ne déclenchait volontairement rien et restait bloqué) ; JWT expiré + refresh 500 (échec transitoire) → pas de modal et tokens toujours présents en localStorage (session préservée).
  • Course logout (AUTH_BUG.md H1, logout-race) : le refetch customer de Home (onActivated) est tenu en vol par un handler gated, l'utilisateur navigue Home → Profil → Compte → Déconnexion → « Oui, déconnecter » (taps SPA — un page.goto tuerait la requête), la gate est relâchée après le logout → la réponse est jetée par l'epoch de session : localStorage.customer ne contient pas eMail, Home reste anonyme. (Committé rouge comme preuve du bug le 2026-08-18, vert depuis le fix — il verrouille désormais la non-régression de l'epoch.)

Démarrage app (smoke)

  • Boot du shell (mode e2e vérifié via la dev-banner), trafic de cold start intégralement couvert par le monde.
  • Le spec opte out du seed de consentement (test.use({ analyticsConsent: 'none' }) — les autres specs démarrent 'refused' via la fixture auto, donc ne voient jamais le modal).
  • Lancement vierge → modal « Améliorer l'application » au-dessus de Home, backé par ?overlay=system-modal.
  • « Accepter » / « Refuser » → modal fermé sur place, décision persistée (localStorage.analyticsConsent), un reload ne re-demande pas.
  • Back navigateur = dismiss → reste indécis, un reload re-présente le modal.
  • « PostHog ne collecte rien » n'est pas un test dédié : c'est le teardown fail-loud du mockBackend de chaque test de la suite (toute requête posthog.com échouerait le test), plus le token vidé de .env.e2e.

Autres — confidentialité (others)

  • Collapsible « Confidentialité » fermé par défaut : le texte et les radios sont clippés (max-height: 0 + overflow: hidden) — asserté en not.toBeInViewport() (un contenu clippé garde une bounding box, toBeHidden() ne marche pas — recette à retenir pour tout contenu sous Collapsible).
  • Déplié : texte de consentement visible, radio pré-cochée selon le statut seedé (« Refuser » avec le seed par défaut, « Accepter » via test.use({ analyticsConsent: 'accepted' })).
  • Cocher « Accepter » → persisté (localStorage.analyticsConsent = { status: 'accepted' }), conservé après reload ; cocher « Refuser » depuis un état accepté → persisté refused.

Non couvert à ce jour (backlog assumé)

TrouDétail
Mot de passe oublié / resetEndpoints forgot/reset enregistrés dans le monde mais aucun spec ne drive l'UI
Refresh token (chemin positif)Le non-déclenchement (JWT valide, auth-login) et l'échec (session morte → modal, session-expired) sont vérifiés ; le flow « token expiré → refresh réussi → rejeu » n'est pas testé
Échec d'initializeLes branches d'erreur du wizard au paiement (initialize non-200) ne sont pas injectées — seuls les échecs du callback le sont
Étape departure-locationAucun voyage du monde n'est dynamique (stops variant par occurrence) — l'étape lieu de départ du wizard n'est jamais rendue
Mes avisGET /api/comments/mine répond [], jamais asserté ; création/édition d'avis non couvertes
Code cadeau invalideLa branche « Le code saisi est invalide. » (gift 404) n'est pas exercée (bon valide et bon partiel le sont)
Filtre dates du calendrierL'overlay DepartureCalendar n'est jamais driveé ; le wire n'émet qu'un param dates opaque — la règle « départ seulement » (1.2.0) n'est pas observable au contrat
Ouverture réelle de la facture / liens externesFileViewer et Browser.open (DeleteAccount → formulaire de contact, redirection Saferpay native) sont des bridges Capacitor sans équivalent web observable — seuls les contrats réseau sont assertés
One-way pricing (d8b0aa1)Le chemin de prix TripType.ONE_WAY existe mais aucune UI ne le déclenche — rien à driver tant qu'un sélecteur aller simple n'existe pas
PUT profil : dates Club epochAnomalie app relevée par personal-info.spec.ts (voir section Profil) — à corriger côté PersonalInfoForm avant de pouvoir l'asserter

Ces trous sont listés pour rester honnêtes sur la garantie réelle de la suite — les combler est le backlog naturel des prochains specs. En ajouter un = déplacer la ligne d'ici vers les sections couvertes ci-dessus.

Voir aussi

  • e2e-summary.md — l'inventaire assertion par assertion de chaque test.
  • e2e-overview.md — comment lire un spec (UI vs contrat).
  • e2e-writing-tests.md — comment combler un trou (checklist + recettes).
  • .claude/docs/domain/workflows.md — les parcours produit auxquels cette couverture se rapporte.

Contributors

No contributors

Changelog

No recent changes