/// La tuile « Borne EV » **rendue muette** — éprouvée sur ce qu'elle affiche. /// /// Le défaut qu'elle corrigeait ne se voyait pas en test de fonction : les trois grandeurs /// affichées (« 3600 W », « 82 % solaire », « Mode PV ») étaient des valeurs par défaut de /// `EnergyData`, justes au sens du type et fausses à l'écran. Ces tests montent donc le /// widget et lisent le texte, comme ceux des notes de configuration. library; import 'package:etm_powersync_app/models/energy_data.dart' show ChargingMode; import 'package:etm_powersync_app/models/ev_charger_live.dart'; import 'package:etm_powersync_app/widgets/ev_favorite_tile.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; Widget _harness({ required EvChargerLive? live, Future Function(ChargingMode)? onSetMode, }) => MaterialApp( home: Scaffold( body: EvFavoriteTile( live: live, onSetMode: onSetMode ?? (_) async => null, ), ), ); /// La V2C du banc, telle que `.75` la publiait le 2026-08-28 : états `pluggedIn`, /// `charging`, `currentPower` sur le Thing, mode par `GetChargingInfos`. EvChargerLive _v2c({ bool? pluggedIn = false, bool? charging = false, double? powerW = 0, String? apiMode = 'ChargingModeNormal', }) => EvChargerLive( thingId: '4d1adafd-cafc-4813-93b1-ca8933ce71ab', name: 'V2C Trydan', pluggedIn: pluggedIn, charging: charging, currentPowerW: powerW, apiMode: apiMode, ); void main() { group('la tuile ne montre que ce que la box publie', () { testWidgets('sans borne, elle le dit — elle ne disparaît pas', (tester) async { // Retirer la tuile laisserait un trou dans une liste que le client a composée. await tester.pumpWidget(_harness(live: null)); expect(find.textContaining('Aucune borne déclarée'), findsOneWidget); expect(find.textContaining('W'), findsNothing); }); testWidgets('une borne qui ne publie rien n\'affiche AUCUN zéro', (tester) async { await tester.pumpWidget(_harness( live: _v2c( pluggedIn: null, charging: null, powerW: null, apiMode: null))); // Les trois absences se disent, chacune à sa place. expect(find.text('Puissance non publiée'), findsOneWidget); expect(find.textContaining('Branchement non publié'), findsOneWidget); expect(find.text('Mode non lu'), findsOneWidget); // Et surtout : pas de « 0 W », qui se lirait « la borne ne consomme rien ». expect(find.textContaining('0 W'), findsNothing); }); testWidgets('elle affiche la puissance MESURÉE et le mode DE LA BOX', (tester) async { await tester.pumpWidget(_harness( live: _v2c( pluggedIn: true, charging: true, powerW: 3907, apiMode: 'ChargingModeEco'))); expect(find.text('3907 W'), findsOneWidget); expect(find.text('En charge'), findsOneWidget); expect(find.text('Mode PV'), findsOneWidget); expect(find.text('V2C Trydan'), findsOneWidget); // Aucune part solaire : la box ne dit pas d'où vient le courant d'une borne. expect(find.textContaining('solaire'), findsNothing); }); testWidgets('un mode à échéance se lit comme tel, un mode inconnu aussi', (tester) async { await tester.pumpWidget( _harness(live: _v2c(apiMode: 'ChargingModeEcoMinWithTargetTime'))); expect(find.text('Mode Min+PV + échéance'), findsOneWidget); // Une box peut porter un mode qu'aucune version d'app ne connaît. Deviner serait // pire que le nommer. await tester.pumpWidget(_harness(live: _v2c(apiMode: 'ChargingModeFutur'))); await tester.pump(); expect(find.textContaining('Mode non reconnu'), findsOneWidget); }); testWidgets('branché mais à l\'arrêt n\'est pas « en charge »', (tester) async { await tester.pumpWidget( _harness(live: _v2c(pluggedIn: true, charging: false, powerW: 0))); expect(find.textContaining('branché, à l\'arrêt'), findsOneWidget); // Ici le zéro est une MESURE, pas une absence : la borne publie `currentPower`. expect(find.text('0 W'), findsOneWidget); }); }); group('l\'écriture du mode restitue le refus', () { testWidgets('un refus de la box est affiché, avec son code', (tester) async { await tester.pumpWidget(_harness( live: _v2c(), onSetMode: (_) async => 'EnergyErrorInvalidParameter', )); await tester.tap(find.text('PV')); await tester.pump(); // écriture en cours await tester.pump(); // réponse await tester.pump(); // SnackBar expect(find.textContaining('EnergyErrorInvalidParameter'), findsOneWidget); expect(find.textContaining('a refusé'), findsOneWidget); }); testWidgets('un succès n\'affiche rien — c\'est la box qui dira le mode', (tester) async { var appels = 0; await tester.pumpWidget(_harness( live: _v2c(), onSetMode: (m) async { appels++; return null; }, )); await tester.tap(find.text('Boost')); await tester.pumpAndSettle(); expect(appels, 1); expect(find.textContaining('a refusé'), findsNothing); // La sélection n'a pas bougé toute seule : la tuile affiche le mode que la box // portait à l'ouverture, et attend `ChargingInfoChanged` pour changer d'avis. expect(find.text('Mode Boost'), findsOneWidget); }); testWidgets('les cibles tactiles font au moins 44 px', (tester) async { // Les pastilles précédentes faisaient 7 à 10 px, et écrivaient pour de vrai. await tester.pumpWidget(_harness(live: _v2c())); for (final label in ['PV', 'Min+PV', 'Boost']) { final taille = tester.getSize(find.ancestor( of: find.text(label), matching: find.byType(OutlinedButton), )); expect(taille.height, greaterThanOrEqualTo(44), reason: '$label : cible tactile trop petite'); expect(taille.width, greaterThanOrEqualTo(44)); } }); }); }