/// **Deux grandeurs, deux phrases, aucune ne ment.** /// /// Les tuiles disent la JOURNÉE — différence de compteurs sur `periodStartedAt → maintenant`. /// La ligne des ratios dit ce que la BOX publie, qui part de la **baseline** et non du début /// de journée. /// /// Le cas est arrivé de lui-même le 2026-09-05 : `nymead` redémarré à 09:23, sonde à 09:26, /// et la box publiait **100 % d'autoconsommation**. Vrai sur trois minutes ; absurde intitulé /// « aujourd'hui ». C'est ce que le drapeau rend lisible — et c'est pour ça que la ligne ne /// s'affiche QUE lorsque l'origine diffère : quand les deux coïncident, une phrase de plus /// serait du bruit, et un texte permanent cesse d'être lu. library; import 'package:etm_powersync_app/features/dashboard/widgets/kpi_row.dart'; import 'package:etm_powersync_app/models/daily_balance.dart'; import 'package:etm_powersync_app/models/energy_data.dart'; import 'package:etm_powersync_app/models/energy_ratios_snapshot.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; /// Relevé `.75` du 2026-09-05 09:26, après le redémarrage de 09:23. EnergyRatiosSnapshot _releve({int measuredSince = 1788592995}) => EnergyRatiosSnapshot.fromJson({ 'autonomyRate': 28.570199887530475, 'selfConsumptionRate': 100, 'periodStartedAt': 1788559200, 'measuredSince': measuredSince, 'timeZone': 'Europe/Paris', }); DailyBalance _bilan() => DailyBalance( from: DateTime(2026, 9, 5, 0, 15), to: DateTime(2026, 9, 5, 9, 26), periodStart: DateTime(2026, 9, 5), sampleCount: 37, productionKwh: 8.0, consumptionKwh: 12.0, importKwh: 5.0, exportKwh: 1.0, ); Widget _harness({EnergyRatiosSnapshot? ratios}) => MaterialApp( home: Scaffold( body: SingleChildScrollView( child: KpiRow( data: const EnergyData(), balance: _bilan(), ratios: ratios), ), ), ); void main() { testWidgets('baseline reposée en journée : l\'origine est DITE', (tester) async { await tester.binding.setSurfaceSize(const Size(1000, 2000)); addTearDown(() => tester.binding.setSurfaceSize(null)); await tester.pumpWidget(_harness(ratios: _releve())); expect(find.textContaining('Instantané depuis 09:23'), findsOneWidget); // Le 100 % est affiché — il est vrai — mais jamais sous le mot « aujourd'hui ». expect(find.textContaining('autoconsommation 100 %'), findsOneWidget); }); testWidgets('les tuiles restent la JOURNÉE, calculée sur la fenêtre de la box', (tester) async { await tester.binding.setSurfaceSize(const Size(1000, 2000)); addTearDown(() => tester.binding.setSurfaceSize(null)); await tester.pumpWidget(_harness(ratios: _releve())); // Les anneaux animent leur valeur : sans laisser l'animation finir, ils affichent 0 — // une absence qui se lirait comme un succès si on cherchait justement une absence. await tester.pumpAndSettle(); expect(find.text('Aujourd\'hui'), findsOneWidget); // 8 kWh produits, 1 exporté -> 87,5 % ; et surtout PAS le 100 % de la box. expect(find.textContaining('88'), findsOneWidget); }); testWidgets('origine confondue avec le début de journée : rien de plus', (tester) async { await tester.binding.setSurfaceSize(const Size(1000, 2000)); addTearDown(() => tester.binding.setSurfaceSize(null)); // Le cas normal : la baseline date du début de journée. await tester.pumpWidget(_harness(ratios: _releve(measuredSince: 1788559200))); expect(find.textContaining('Instantané depuis'), findsNothing); }); testWidgets('les deux coïncident : la ligne se TAIT — vu sur appareil', (tester) async { await tester.binding.setSurfaceSize(const Size(1000, 2000)); addTearDown(() => tester.binding.setSurfaceSize(null)); // Relevé du 2026-09-05 16:52 : bilan 51,8 / 54,1 sur 00:00→16:45, box 51,9 / 53,7 // depuis 09:23. L'origine diffère bien, mais les nombres non — et la ligne écrivait // « 52 %, 54 % » juste sous des tuiles qui affichaient déjà 52 % et 54 %. final coincide = EnergyRatiosSnapshot.fromJson({ 'autonomyRate': 53.67, 'selfConsumptionRate': 51.92, 'periodStartedAt': 1788559200, 'measuredSince': 1788592995, 'timeZone': 'Europe/Paris', }); await tester.pumpWidget(MaterialApp( home: Scaffold( body: SingleChildScrollView( child: KpiRow( data: const EnergyData(), // 8 kWh produits / 1 exporté -> 87,5 % ; 12 consommés / 5 importés -> 58,3 %. // On force ici un bilan proche des valeurs de la box pour éprouver le seuil. balance: DailyBalance( from: DateTime(2026, 9, 5, 0, 15), to: DateTime(2026, 9, 5, 16, 45), periodStart: DateTime(2026, 9, 5), sampleCount: 72, productionKwh: 59.89, consumptionKwh: 57.31, importKwh: 26.31, exportKwh: 28.89, ), ratios: coincide, ), ), ), )); await tester.pumpAndSettle(); expect(find.textContaining('Instantané depuis'), findsNothing, reason: 'répéter les mêmes nombres oblige le lecteur à raisonner pour ' 'découvrir que les fenêtres diffèrent'); }); testWidgets('box antérieure : aucune ligne, aucune supposition', (tester) async { await tester.binding.setSurfaceSize(const Size(1000, 2000)); addTearDown(() => tester.binding.setSurfaceSize(null)); await tester.pumpWidget(_harness()); expect(find.textContaining('Instantané'), findsNothing); }); }