Signalé sur appareil de nuit : tuile Autoconsommation « — · pas mesurable »,
sur une installation dont les quatre compteurs marchent.
D'où vient le nombre : la tuile lit `DailyBalance`, pas `GetEnergyRatios`.
`selfConsumptionRate` y rend `null` dès que `productionKwh <= 0` — production
nulle depuis minuit, donc 0/0.
Le « — » est JUSTE et ne bouge pas. Un « 0 % » affirmerait qu'aucune production
n'a été autoconsommée : une phrase sur une production qui n'existe pas, soit un
zéro forgé de plus. Ce qui était faux, c'est le motif écrit dessous — il
accusait la mesure là où il n'y avait simplement rien à mesurer.
Les deux causes d'un taux absent sont désormais séparées :
- pas de bilan (échantillons, cumuls, compteur en recul) → « pas mesurable » ;
- dénominateur nul → « sans production » / « sans consommation ».
C'est la distinction habituelle du dépôt prise par l'autre bout : d'ordinaire on
empêche une absence de se dire zéro ; ici on l'empêche d'accuser la mesure.
Mesuré sur .75 au même instant : production 0 W, `selfConsumptionRate` absent de
GetEnergyRatios sur trois relevés, `autonomyRate: 0` présent — la box fait la
même distinction, sur la même arithmétique.
Le premier jet du test asserait `find.text('0 %'), findsNothing` : il mordait
sur le 0 % de l'Autonomie, qui est une mesure. Corrigé en le voyant échouer, et
la raison est dans le test.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKM8FDNHZWogAsjjfB8pSw
207 lines
8.9 KiB
Dart
207 lines
8.9 KiB
Dart
/// **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,
|
|
);
|
|
|
|
/// La NUIT du 2026-09-07 sur `.75` : consommation réelle, production nulle depuis minuit.
|
|
DailyBalance _bilanDeNuit() => DailyBalance(
|
|
from: DateTime(2026, 9, 7),
|
|
to: DateTime(2026, 9, 7, 2, 30),
|
|
periodStart: DateTime(2026, 9, 7),
|
|
sampleCount: 10,
|
|
productionKwh: 0.0,
|
|
consumptionKwh: 1.4,
|
|
importKwh: 1.4,
|
|
exportKwh: 0.0,
|
|
);
|
|
|
|
Widget _harnessBilan(DailyBalance b) => MaterialApp(
|
|
home: Scaffold(
|
|
body: SingleChildScrollView(
|
|
child: KpiRow(data: const EnergyData(), balance: b),
|
|
),
|
|
),
|
|
);
|
|
|
|
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);
|
|
});
|
|
|
|
|
|
group('un taux sans dénominateur — le « — » est juste, le MOTIF ne l\'était pas', () {
|
|
// Signalé sur appareil le 2026-09-07 : « Autoconsommation — · pas mesurable » de nuit,
|
|
// sur une installation dont les quatre compteurs marchaient. Deux causes distinctes
|
|
// portaient la même phrase, et celle qui s'affichait accusait la mesure.
|
|
testWidgets('sans production, la tuile dit « sans production »', (tester) async {
|
|
await tester.binding.setSurfaceSize(const Size(1000, 2000));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(_harnessBilan(_bilanDeNuit()));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.textContaining('Autoconsommation · sans production'), findsOneWidget);
|
|
// Le contrôle qui MORD : l'ancien motif ne doit plus être là pour cette tuile.
|
|
expect(find.textContaining('Autoconsommation · pas mesurable'), findsNothing);
|
|
// Et surtout PAS un zéro sur CETTE tuile : « 0 % » affirmerait qu'aucune production
|
|
// n'a été autoconsommée, une phrase sur une production qui n'existe pas. Le tiret y
|
|
// est, et il y est une seule fois — l'autre tuile, elle, a bien un nombre.
|
|
//
|
|
// ⚠️ Ne pas écrire `find.text('0 %'), findsNothing` : le 0 % de l'Autonomie est une
|
|
// MESURE et le contrôle mordrait sur elle. Première rédaction de ce test, corrigée
|
|
// en la voyant échouer.
|
|
expect(find.text('—'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('l\'autonomie, elle, a son dénominateur et vaut 0 %', (tester) async {
|
|
await tester.binding.setSurfaceSize(const Size(1000, 2000));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(_harnessBilan(_bilanDeNuit()));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Tout vient du réseau : 0 % est une MESURE, pas un trou. Les deux tuiles sont
|
|
// voisines et leurs deux « rien » sont de natures opposées — c'est le cas qui
|
|
// interdit de traiter les taux ensemble.
|
|
expect(find.text('0 %'), findsOneWidget);
|
|
expect(find.textContaining('Autonomie · '), findsNothing);
|
|
});
|
|
|
|
testWidgets('sans bilan du tout, le motif reste « pas mesurable »', (tester) async {
|
|
await tester.binding.setSurfaceSize(const Size(1000, 2000));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(MaterialApp(
|
|
home: Scaffold(
|
|
body: SingleChildScrollView(
|
|
child: KpiRow(data: const EnergyData(), balance: null),
|
|
),
|
|
),
|
|
));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.textContaining('Autoconsommation · pas mesurable'), findsOneWidget);
|
|
expect(find.textContaining('Autonomie · pas mesurable'), findsOneWidget);
|
|
});
|
|
});
|
|
}
|