/// Les trois boutons de mode — **le seul lieu d'écriture de `SetChargingInfo`**. /// /// Ils ont vécu sur la tuile des favoris, sur des pastilles de 7 à 10 px qui avalaient les /// refus. Ils sont depuis le 2026-08-28 sur l'écran « Configurer » de la borne, en un seul /// exemplaire : deux endroits qui écrivent la même chose sont deux comportements qui /// divergent. 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_mode_buttons.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; EvChargerLive _v2c({String? apiMode = 'ChargingModeNormal'}) => EvChargerLive( thingId: '4d1adafd-cafc-4813-93b1-ca8933ce71ab', name: 'V2C Trydan', pluggedIn: false, charging: false, currentPowerW: 0, apiMode: apiMode, ); Widget _harness({ required EvChargerLive live, Future Function(ChargingMode)? onSetMode, }) => MaterialApp( home: Scaffold( body: EvModeButtons( live: live, onSetMode: onSetMode ?? (_) async => null, ), ), ); void main() { 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('PV')); await tester.pumpAndSettle(); expect(appels, 1); expect(find.textContaining('a refusé'), findsNothing); }); testWidgets('la sélection suit la BOX, pas la dernière commande', (tester) async { // `_v2c()` est en ChargingModeNormal : c'est Boost qui doit être allumé, et lui seul, // quoi qu'on ait tapé juste avant. await tester.pumpWidget(_harness(live: _v2c())); await tester.tap(find.text('PV')); await tester.pumpAndSettle(); final boost = tester.widget( find.widgetWithText(OutlinedButton, 'Boost')); final pv = tester.widget(find.widgetWithText(OutlinedButton, 'PV')); expect(boost.style?.backgroundColor?.resolve({}), isNot(Colors.transparent), reason: 'Boost est le mode que la box porte'); expect(pv.style?.backgroundColor?.resolve({}), Colors.transparent, reason: 'PV vient d\'être demandé, mais la box ne l\'a pas encore dit'); }); testWidgets('un mode non lu n\'allume RIEN', (tester) async { // Trois boutons éteints valent mieux qu'un allumé au hasard. await tester.pumpWidget(_harness(live: _v2c(apiMode: null))); for (final label in ['PV', 'Min+PV', 'Boost']) { final b = tester .widget(find.widgetWithText(OutlinedButton, label)); expect(b.style?.backgroundColor?.resolve({}), Colors.transparent); } }); testWidgets('les cibles tactiles font au moins 44 px', (tester) async { 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'); } }); }