etm-powersync-app/test/kpi_ratios_test.dart
Patrick Schurig b3000ca2d5 fix(écran): trois défauts trouvés sur appareil, dont un de forme et un de moi
Passage sur le Redmi contre `.75`. Aucun des trois n'était visible en test.

1. LA PRODUCTION S'AFFICHAIT NÉGATIVE — « -8.9 kW · Solaire » sous 9 kW de
   soleil. `currentPowerProduction` est publié négatif par nymea et le service
   garde les signes bruts par politique ; la carte imprimait la valeur telle
   quelle. Ce qui rend le défaut net plutôt qu'opinable : la même carte
   normalisait DÉJÀ le réseau et la batterie par `.abs()` — la branche solaire
   était la seule oubliée, donc un oubli et non une convention.

2. LE LIBELLÉ SE COUPAIT AU MILIEU DU MOT — « Autoconsommat / ion », alors que
   le commentaire du code disait vouloir l'éviter. La cause n'est pas la
   longueur mais l'ÉCHELLE du texte : police système au-dessus de 100 %, et
   `maxLines: 2` ne protège de rien puisque Flutter coupe un mot long sans
   espace, sans même afficher l'ellipse. `FittedBox(scaleDown)` garde le mot
   entier et ne fait rien quand la place suffit.

3. MA LIGNE DES RATIOS RÉPÉTAIT LES TUILES — « Instantané depuis 09:23 —
   autoconsommation 52 %, autonomie 54 % » sous des tuiles affichant 52 % et
   54 %. Les deux chemins étaient corrects et les valeurs coïncidaient vraiment
   (bilan 51,8/54,1 sur 00:00→16:45, box 51,9/53,7 depuis 09:23). Rien de faux,
   et illisible quand même : je déclenchais sur l'ORIGINE, alors qu'un lecteur
   compare des NOMBRES. La ligne sort désormais quand l'origine diffère ET que
   l'écart dépasse 2 points ; le relevé du jour est devenu un test.

Vérifié à l'écran après correction : le libellé tient sur une ligne, la ligne des
ratios se tait. Le signe, lui, N'A PAS pu être revu — la production du banc était
retombée à 0 W à 17:03, et `abs(0) == 0`. Une correction qu'on ne peut pas voir
demande une garde qui se voit : `flux_signe_test.dart` la pose.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKM8FDNHZWogAsjjfB8pSw
2026-09-05 17:07:20 +02:00

133 lines
5.6 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,
);
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);
});
}