diff --git a/features/api/navigation/notifications-geolocalisees.feature b/features/api/navigation/notifications-geolocalisees.feature index 3d23abc..f80e8db 100644 --- a/features/api/navigation/notifications-geolocalisees.feature +++ b/features/api/navigation/notifications-geolocalisees.feature @@ -388,3 +388,102 @@ Fonctionnalité: API - Notifications géolocalisées et quota anti-spam | 5 | disponible | | 6 | atteint | | 7 | atteint | + + # Edge cases : haute vitesse + + Scénario: API calcule ETA correctement à 130 km/h + Étant donné qu'un contenu géolocalisé existe + Et que l'utilisateur roule à 130 km/h (36.1 m/s) + Et qu'il est à 252 mètres du point + Quand je POST /api/v1/geo-notifications/check-eta + """json + { + "user_id": "user123", + "speed_kmh": 130, + "distance_meters": 252 + } + """ + Alors le statut de réponse est 200 + Et la réponse contient: + """json + { + "eta_seconds": 7, + "speed_ms": 36.1, + "should_notify": true, + "notification_trigger": "eta_threshold" + } + """ + Et une notification est envoyée 252m avant le point + + Scénario: API calcule distance notification selon vitesse + Étant donné qu'un contenu géolocalisé existe + Quand l'utilisateur roule à 180 km/h (50 m/s) + Alors la notification est déclenchée à 350 mètres avant le point + Et après décompte 5s, user a parcouru 250m + Et le contenu démarre 100m avant le point + Et le système fonctionne même à vitesse extrême + + # Edge cases : multiples points proches + + Scénario: API gère multiples points géolocalisés proches (800m chacun) + Étant donné que 3 châteaux existent espacés de 800m chacun + | contenu | position_km | + | Château A | 0 | + | Château B | 0.8 | + | Château C | 1.6 | + Et que l'utilisateur roule à 50 km/h + Quand une notification Château A est envoyée et acceptée + Alors le quota passe à 1/6 + Et aucun cooldown n'est activé (notification acceptée) + Quand 57 secondes s'écoulent (temps pour parcourir 800m) + Et que l'utilisateur atteint Château B + Alors une notification Château B est envoyée + Car le quota n'est pas atteint (1/6) + Et il n'y a pas de cooldown actif + + Scénario: API active cooldown réduit après validations multiples + Étant donné que l'utilisateur a validé 2 notifications consécutives + Et que les notifications ont toutes été acceptées (clic "Suivant") + Quand une 3ème notification est ignorée + Alors le cooldown activé est 5 minutes (réduit) + Et non 10 minutes (standard) + Car l'utilisateur a montré de l'engagement précédemment + + Scénario: API ignore notifications si cooldown actif après ignorance + Étant donné qu'une notification a été ignorée à 10:00:00 + Et qu'un cooldown de 10 minutes est actif + Et que 3 contenus géolocalisés existent à 10:05, 10:08, 10:11 + Quand l'utilisateur passe devant le contenu à 10:05 + Alors aucune notification n'est envoyée (cooldown actif, reste 5 min) + Quand l'utilisateur passe devant le contenu à 10:08 + Alors aucune notification n'est envoyée (cooldown actif, reste 2 min) + Quand l'utilisateur passe devant le contenu à 10:11 + Alors une notification est envoyée (cooldown expiré après 10 min) + + # Edge cases : mode stationnement + + Scénario: API détecte mode stationnement (vitesse < 1 km/h pendant 2 min) + Étant donné que l'utilisateur roule à 50 km/h + Quand la vitesse passe à 0.5 km/h (arrêt complet) + Et que la vitesse reste < 1 km/h pendant 2 minutes consécutives + Alors le mode "stationnement" est activé + Et aucune notification géolocalisée n'est envoyée + Et le système bascule automatiquement en mode piéton + Et un événement "mode_stationnement_detected" est enregistré + + Scénario: API sort du mode stationnement quand vitesse > 5 km/h + Étant donné que le mode stationnement est actif depuis 30 minutes + Et que l'utilisateur était à l'arrêt près d'un château + Quand la vitesse passe à 20 km/h pendant 10 secondes + Alors le mode voiture est réactivé + Et les notifications géolocalisées reprennent + Et le quota horaire est vérifié avant nouvelle notification + + Scénario: API refuse notification si user arrêté longtemps près d'un point + Étant donné qu'un contenu géolocalisé existe à 30 mètres + Et que l'utilisateur est à l'arrêt (vitesse 0 km/h) depuis 3 minutes + Quand le système détecte la proximité + Alors le mode stationnement est actif + Et aucune notification n'est envoyée + Car l'utilisateur est probablement stationné (parking) + Et pas en mode conduite diff --git a/features/ui/navigation/commande-precedent.feature b/features/ui/navigation/commande-precedent.feature index 073971c..81b2f98 100644 --- a/features/ui/navigation/commande-precedent.feature +++ b/features/ui/navigation/commande-precedent.feature @@ -175,3 +175,92 @@ Fonctionnalité: Commande "Précédent" | 5:00 | | 7:23 | | 9:50 | + + # Comportement avec contenus géolocalisés + + Scénario: Précédent après avoir écouté un contenu géolocalisé ≥10s + Étant donné que j'écoute un contenu buffer "C1" + Et qu'une notification géolocalisée "Tour Eiffel" apparaît + Quand j'accepte la notification (décompte 5s) + Et que le contenu géolocalisé démarre + Et que je l'écoute pendant 42 secondes + Quand j'appuie sur "Suivant" (skip) + Alors le contenu buffer suivant "C2" démarre + Et le contenu géolocalisé entre dans l'historique: + """json + { + "id": "geo_tour_eiffel", + "position_seconds": 42, + "type": "geo_anchored" + } + """ + Quand j'appuie sur "Précédent" + Alors le contenu géolocalisé reprend à 42 secondes + Car j'ai écouté ≥ 10 secondes + + Scénario: Précédent après avoir écouté un contenu géolocalisé <10s + Étant donné que j'ai accepté un contenu géolocalisé "Château" + Et que je l'ai écouté pendant 5 secondes uniquement + Quand j'appuie sur "Suivant" (skip) + Alors le contenu buffer suivant démarre + Quand j'appuie sur "Précédent" + Alors je reviens au contenu AVANT le géolocalisé + Et le contenu géolocalisé n'est PAS rejoué + Car j'ai écouté < 10 secondes + + Scénario: Notification géolocalisée ignorée n'entre PAS dans historique + Étant donné que j'écoute un contenu "A" + Et qu'une notification géolocalisée apparaît (compteur 7→1) + Quand je n'appuie PAS sur "Suivant" (notification ignorée) + Alors le contenu "A" continue normalement + Et le contenu géolocalisé n'entre PAS dans l'historique + Quand j'appuie sur "Précédent" + Alors je reviens au contenu précédent "A-1" + Et le contenu géolocalisé ignoré n'apparaît jamais + + Scénario: Annulation décompte géolocalisé n'entre PAS dans historique + Étant donné qu'une notification géolocalisée est acceptée + Et que le décompte "5" démarre (5→4→3...) + Quand j'appuie sur "Suivant" pendant le décompte (annulation) + Alors le décompte est annulé + Et le contenu buffer suivant démarre + Et le contenu géolocalisé n'entre PAS dans l'historique + Quand j'appuie sur "Précédent" + Alors je reviens au contenu avant le décompte + Et le contenu géolocalisé annulé n'apparaît jamais + + Scénario: Historique mixte contenus buffer et géolocalisés + Étant donné que j'ai écouté dans l'ordre: + | contenu | type | position_seconds | + | Buffer 1 | contextuel | 180 | + | Géo Tour Eiffel | geo_anchored | 42 | + | Buffer 2 | neutre | 90 | + | Buffer 3 | contextuel | 30 | + Et que j'écoute maintenant "Buffer 4" depuis 2 secondes + Quand j'appuie sur "Précédent" (1ère fois) + Alors je reviens à "Buffer 3" à 30 secondes + Quand j'appuie sur "Précédent" (2ème fois, <10s) + Alors je reviens à "Buffer 2" à 90 secondes + Quand j'appuie sur "Précédent" (3ème fois, <10s) + Alors je reviens à "Géo Tour Eiffel" à 42 secondes + Quand j'appuie sur "Précédent" (4ème fois, <10s) + Alors je reviens à "Buffer 1" à 180 secondes + + Scénario: Contenu géolocalisé dans limite FIFO 10 contenus + Étant donné que j'ai un historique de 10 contenus incluant 3 géolocalisés: + | position | contenu | type | + | 1 | Buffer 1 | contextuel | + | 2 | Géo A | geo_anchored | + | 3 | Buffer 2 | neutre | + | 4 | Géo B | geo_anchored | + | 5-9 | Buffer 3-7 | contextuel | + | 10 | Géo C | geo_anchored | + Quand j'ajoute un 11ème contenu "Buffer 8" + Alors "Buffer 1" (le plus ancien) est supprimé + Et l'historique contient: + | position | contenu | + | 1 | Géo A | + | 2 | Buffer 2 | + | ... | ... | + | 10 | Buffer 8 | + Et les contenus géolocalisés suivent la même règle FIFO diff --git a/features/ui/navigation/contenus-geolocalises-voiture.feature b/features/ui/navigation/contenus-geolocalises-voiture.feature new file mode 100644 index 0000000..511df43 --- /dev/null +++ b/features/ui/navigation/contenus-geolocalises-voiture.feature @@ -0,0 +1,309 @@ +# language: fr +Fonctionnalité: UI - Contenus géolocalisés en mode voiture + En tant qu'utilisateur en mode voiture + Je veux recevoir des notifications visuelles minimalistes pour contenus géolocalisés + Afin d'écouter du contenu pertinent sans distraction dangereuse + + Contexte: + Étant donné que l'application RoadWave est ouverte (premier plan) + Et que l'utilisateur est connecté + Et qu'il est en mode voiture (vitesse ≥ 5 km/h) + + # Affichage notification minimaliste + + Scénario: Notification visuelle minimaliste 7 secondes avant arrivée + Étant donné que j'écoute un podcast "Voyage en France" + Et qu'un contenu géolocalisé "Tour Eiffel" existe à 98m + Quand l'API déclenche la notification (ETA 7s) + Alors un son bref (bip 0.5s) est joué + Et une icône 🏛️ apparaît en bas de l'écran + Et un compteur "7" s'affiche en grand (72pt, bold, blanc) + Et AUCUN texte n'est affiché (pas de titre, pas de description) + Et le podcast actuel continue de jouer normalement + + Scénario: Compteur décrémente visuellement pendant 7 secondes + Étant donné qu'une notification est affichée avec compteur "7" + Quand 1 seconde s'écoule + Alors le compteur affiche "6" + Quand 1 seconde s'écoule + Alors le compteur affiche "5" + Et ainsi de suite jusqu'à "1" + Quand le compteur atteint "0" + Alors la notification disparaît automatiquement + Et le contenu géolocalisé est perdu (non accepté) + + Scénario: Icône correspond au tag du contenu géolocalisé + Étant donné que des contenus géolocalisés existent avec différents tags + Quand une notification est affichée + Alors l'icône correspond au tag principal: + | Tag | Icône | Couleur | + | Culture générale | 🏛️ | Bleu | + | Histoire | 📜 | Marron | + | Voyage | ✈️ | Vert | + | Famille | 👨‍👩‍👧 | Orange | + | Musique | 🎵 | Rose | + | Sport | ⚽ | Rouge | + | Technologie | 💻 | Gris | + | Automobile | 🚗 | Noir | + | Politique | 🏛️ | Bleu foncé | + | Économie | 📈 | Vert foncé | + | Cryptomonnaie | ₿ | Or | + | Santé | 🏥 | Rouge clair | + | Amour | ❤️ | Rose vif | + + Scénario: Notification n'affiche AUCUN texte (sécurité routière) + Étant donné qu'une notification géolocalisée apparaît + Quand je consulte l'interface + Alors je ne vois AUCUN texte à lire: + ❌ Pas de titre du contenu + ❌ Pas de description + ❌ Pas de nom du créateur + ❌ Pas de durée + ❌ Pas de bouton "Annuler" ou "Plus tard" + Et je vois uniquement: + ✅ Une icône (emoji selon tag) + ✅ Un compteur chiffré (7→1) + Et c'est tout (minimalisme absolu) + + # Validation "Suivant" et décompte + + Scénario: User appuie sur "Suivant" pendant notification + Étant donné qu'une notification affiche "5" (5 secondes restantes) + Quand j'appuie sur le bouton "Suivant" (physique ou tactile) + Alors la notification disparaît avec fade out (0.3s) + Et un nouveau compteur "5" apparaît (décompte 5 secondes) + Et le podcast actuel continue de jouer normalement + Et aucune baisse de volume n'est appliquée + + Scénario: Décompte 5 secondes avec contenu actuel qui continue + Étant donné que j'ai cliqué "Suivant" pendant la notification + Et que le décompte "5" démarre + Quand les 5 secondes s'écoulent (5→4→3→2→1) + Alors le podcast actuel joue pendant tout le décompte + Et le volume reste à 100% (pas de duck) + Quand le compteur atteint "0" + Alors le podcast actuel fait fade out (0.3s) + Et le contenu géolocalisé fait fade in (0.3s) + Et la transition est fluide sans silence + + Scénario: Interface pendant décompte affiche texte contextuel + Étant donné que le décompte "3" est affiché + Quand je consulte l'écran + Alors je vois: + - Le compteur "3" en grand (72pt, centré) + - Le texte "Contenu géolocalisé arrive" en petit en dessous + - Le player actuel en haut (titre, barre de progression, contrôles) + Et l'interface est claire et non distrayante + + # Contenu actuel se termine pendant décompte + + Scénario: Contenu actuel se termine pendant décompte (buffer démarre) + Étant donné que j'écoute un podcast de 10 minutes + Et qu'il reste 2 secondes de lecture + Quand j'accepte un contenu géolocalisé (décompte 5s démarre) + Et que le podcast se termine après 2 secondes + Alors le contenu suivant du buffer démarre immédiatement + Et le décompte continue (3→2→1) + Quand le décompte atteint 0 + Alors le contenu buffer fait fade out + Et le contenu géolocalisé fait fade in + Et il n'y a jamais de silence + + # Annulation décompte + + Scénario: User appuie à nouveau sur "Suivant" pendant décompte (annulation) + Étant donné que le décompte "3" est affiché + Quand j'appuie à nouveau sur "Suivant" + Alors le décompte est annulé immédiatement + Et le contenu suivant du buffer démarre + Et le contenu géolocalisé est perdu + Et il n'entre PAS dans l'historique de navigation + + # Navigation avec contenus géolocalisés + + Scénario: Contenu géolocalisé accepté entre dans l'historique + Étant donné que j'ai accepté un contenu géolocalisé + Et que je l'ai écouté pendant 42 secondes + Quand j'appuie sur "Suivant" (skip) + Alors le contenu buffer suivant démarre + Et le contenu géolocalisé est ajouté à l'historique: + """json + { + "id": "geo_tour_eiffel", + "position_seconds": 42, + "listened_at": "2026-02-02T10:30:00Z", + "type": "geo_anchored" + } + """ + + Scénario: Commande "Précédent" après skip contenu géolocalisé (≥10s écoutés) + Étant donné que j'ai écouté un contenu géolocalisé pendant 42 secondes + Et que j'ai skippé vers le contenu suivant + Quand j'appuie sur "Précédent" + Alors le contenu géolocalisé reprend à 42 secondes + Car j'ai écouté ≥ 10 secondes (règle standard) + Et la position exacte est restaurée + + Scénario: Commande "Précédent" après skip rapide (<10s écoutés) + Étant donné que j'ai écouté un contenu géolocalisé pendant 5 secondes + Et que j'ai skippé vers le contenu suivant + Quand j'appuie sur "Précédent" + Alors le contenu AVANT le géolocalisé reprend + Car j'ai écouté < 10 secondes (règle standard) + Et le contenu géolocalisé est considéré comme non écouté + + Scénario: Notification ignorée n'entre PAS dans l'historique + Étant donné qu'une notification "Tour Eiffel" s'affiche + Et que je n'appuie PAS sur "Suivant" pendant 7 secondes + Quand la notification disparaît + Et que j'appuie sur "Précédent" + Alors je reviens au contenu AVANT la notification + Et le contenu géolocalisé ignoré n'apparaît pas dans l'historique + + # Conformité CarPlay / Android Auto + + Scénario: Mode CarPlay désactive overlay visuel (sonore uniquement) + Étant donné que l'app est connectée à CarPlay + Et qu'un contenu géolocalisé est détecté (ETA 7s) + Quand la notification est déclenchée + Alors UNIQUEMENT un son bref (bip) est joué + Et AUCUN overlay visuel n'est affiché: + ❌ Pas d'icône + ❌ Pas de compteur visuel + ❌ Pas de texte + Et je peux toujours appuyer sur bouton "Suivant" physique + Et le décompte 5s démarre après validation + + Scénario: Mode Android Auto désactive overlay visuel (sonore uniquement) + Étant donné que l'app est connectée à Android Auto + Et qu'un contenu géolocalisé est détecté (ETA 7s) + Quand la notification est déclenchée + Alors UNIQUEMENT une notification sonore système est jouée + Et AUCUN overlay visuel n'est affiché + Et je peux appuyer sur "Suivant" pour valider + Et le comportement est identique à CarPlay + + Scénario: Détection automatique CarPlay active mode sonore uniquement + Étant donné que je démarre l'app normalement (mode standard) + Quand je connecte mon téléphone à CarPlay + Alors le mode CarPlay est détecté automatiquement + Et les notifications géolocalisées passent en mode sonore uniquement + Quand je déconnecte CarPlay + Alors le mode standard reprend + Et les notifications affichent à nouveau icône + compteur + + # Son de notification + + Scénario: Son de notification configurable dans réglages + Étant donné que je vais dans Réglages > Notifications géolocalisées + Quand je consulte les options sonores + Alors je peux choisir: + | Option | Description | + | Bip système | Son système court (0.5s) | + | Son RoadWave personnalisé | "Ding" doux RoadWave | + | Muet | Visuel uniquement (pas de son) | + Et le volume suit le volume système notifications (pas media) + + Scénario: Pas de vibration en mode voiture + Étant donné qu'une notification géolocalisée est déclenchée + Quand le son est joué + Alors AUCUNE vibration n'est déclenchée + Car le téléphone est sur support voiture + Et la vibration est inutile + + # Feedback visuel + + Scénario: Transition fluide après validation notification + Étant donné qu'une notification affiche "7" + Quand j'appuie sur "Suivant" + Alors la transition visuelle est fluide: + 1. Fade out icône + compteur "7" (0.3s) + 2. Fade in nouveau compteur "5" (0.3s) + 3. Décompte 5→4→3→2→1 + 4. Fade out player actuel + fade in géolocalisé (0.3s) + Et aucune transition brusque ou clignotement + + Scénario: Compteur visuel grande taille pour lisibilité + Étant donné qu'une notification ou décompte est affiché + Quand je consulte l'interface + Alors le compteur respecte: + - Police: 72pt minimum + - Weight: Bold + - Couleur: Blanc (ou contraste élevé) + - Background: Noir semi-transparent (50% opacity) + - Position: Centré horizontalement, bas de l'écran + Et le chiffre est lisible en vision périphérique sans quitter la route + + # Quota et cooldown (side-effects UI) + + Scénario: Notification quota atteint n'affiche rien (silencieuse) + Étant donné que j'ai reçu 6 notifications géolocalisées dans l'heure + Et que je passe devant un 7ème contenu géolocalisé + Quand l'API refuse la notification (quota 429) + Alors AUCUNE notification n'est affichée côté UI + Et AUCUN son n'est joué + Et le contenu continue normalement + Et je ne suis pas interrompu + + Scénario: Notification cooldown actif n'affiche rien (silencieuse) + Étant donné qu'une notification précédente a été ignorée + Et qu'un cooldown de 10 minutes est actif + Quand je passe devant un contenu géolocalisé + Alors AUCUNE notification n'est affichée + Et le refus est silencieux (pas de message d'erreur) + + # Edge cases + + Scénario: Haute vitesse (130 km/h) - notification 252m avant + Étant donné que je roule à 130 km/h sur autoroute + Et qu'un contenu géolocalisé existe sur mon trajet + Quand l'API détecte ETA 7s (distance 252m) + Alors la notification s'affiche avec compteur "7" + Quand j'accepte (décompte 5s) + Alors le contenu géolocalisé démarre 72m AVANT le point + Et le timing est correct même à haute vitesse + + Scénario: App doit être au premier plan (mode voiture) + Étant donné que je suis en mode voiture + Et que l'app est en arrière-plan (minimisée) + Quand je passe devant un contenu géolocalisé + Alors AUCUNE notification n'est envoyée + Car l'app doit être ouverte pour mode voiture + Et la sécurité routière exige l'app visible + + Scénario: Notification disparaît si user change de contexte + Étant donné qu'une notification affiche "4" + Quand je minimise l'application + Alors la notification est annulée immédiatement + Et le contenu géolocalisé est perdu + Car l'app n'est plus au premier plan + + Plan du Scénario: Décompte visuel selon temps restant + Étant donné qu'une notification est affichée + Et que secondes se sont écoulées + Quand je consulte l'écran + Alors le compteur affiche + + Exemples: + | temps | chiffre | + | 0 | 7 | + | 1 | 6 | + | 2 | 5 | + | 3 | 4 | + | 4 | 3 | + | 5 | 2 | + | 6 | 1 | + | 7 | (disparu) | + + Plan du Scénario: Mode CarPlay/Android Auto selon connexion + Étant donné que l'app est + Quand je + Alors le mode devient + Et les notifications sont + + Exemples: + | mode_initial | action | mode_final | type_notif | + | standard | connecte CarPlay | CarPlay | sonore uniquement | + | standard | connecte Android Auto | Android Auto | sonore uniquement | + | CarPlay | déconnecte CarPlay | standard | sonore + visuel | + | Android Auto | déconnecte Android Auto | standard | sonore + visuel |