fix(ev): « Boost » devient « Manuel » — le nom promettait le contraire du comportement
Décision de Patrick : trois modes suffisent — Manuel, PV, Min+PV. Le boost n'est pas un quatrième mode, c'est la dérogation (+etm61). LE FAIT. Le bouton écrivait `ChargingModeNormal`, qui sort la borne de l'arbitrage et lui restitue ses valeurs manuelles stockées — manualChargingEnabled, manualMaxChargingCurrent, manualDesiredPhaseCount. Sur .75 elles valaient false / 6 A / 3 phases : mesuré le 2026-09-08 à 11:09:51, l'appui a ARRÊTÉ la charge et armé un verrou de 300 s. Pas une régression — c'est ce que Normal a toujours fait. C'est le nom qui mentait. Renommé jusque dans le code (`ChargingMode.boost` -> `ChargingMode.manuel`), pas seulement sur l'étiquette : un identifiant faux reste un piège pour le prochain lecteur, et c'est précisément ce qui a duré des mois ici. L'ordre met Manuel en tête, parce que ChargingModeNormal est aussi le mode d'office d'une borne détectée. ET LE RENOMMAGE NE SUFFISAIT PAS. « Manuel » dit QUI commande, pas ce que la borne va faire : tant que l'app n'offre pas de régler ces valeurs stockées, le mode veut dire « ce qui traînait la dernière fois ». Passer en manuel demande donc confirmation, et la confirmation dit la conséquence — la charge peut s'arrêter — AVANT, pas après. ⚠️ La confirmation ne se déclenche pas sur un ré-appui du mode déjà en place : une confirmation qu'on apprend à cliquer sans lire ne protège plus de rien. Un test le fige. Vérifié sur .75 (1.15.2+etm61) : SetLoadDerogation et ClearLoadDerogation sont dans Introspect, avec derogationEnded / durationS / restoredMode. Le contrat de la dérogation est écrit dans CLAUDE.md — durée obligatoire bornée [60, 21600], état lu dans decision.code == "DEROGATION_ACTIVE" et jamais dans le mode, fin au niveau de la trame avec six causes. Aucun bouton de boost n'est écrit : c'est le lot suivant. Et une sonde pour le moteur, faite au passage : la V2C est bien restée en ChargingModeNormal et elle est ABSENTE de loads[] — hors arbitrage. Leur hypothèse est confirmée sans redémarrage. 339 tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GKM8FDNHZWogAsjjfB8pSw
This commit is contained in:
parent
4d3f35d6d0
commit
5a51582729
57
CLAUDE.md
57
CLAUDE.md
@ -76,17 +76,68 @@ nymeaService.call('NymeaEnergy.SetChargingInfo', {
|
||||
|
||||
### Modes UI EV — 3 boutons + option échéance
|
||||
```
|
||||
[PV] [Min+PV] [Boost]
|
||||
↓
|
||||
[Manuel] [PV] [Min+PV]
|
||||
↓
|
||||
Option échéance ? → charge cible (targetPercentage) + heure (endDateTime)
|
||||
|
||||
Manuel → ChargingModeNormal
|
||||
PV → ChargingModeEco
|
||||
Min+PV → ChargingModeEcoWithMinCurrent
|
||||
Boost → ChargingModeNormal
|
||||
PV + échéance → ChargingModeEcoWithTargetTime + targetPercentage + endDateTime
|
||||
Min+PV + échéance → ChargingModeEcoMinWithTargetTime + targetPercentage + endDateTime
|
||||
```
|
||||
|
||||
> 🔴 **Le bouton s'appelait « Boost », et le nom était faux — il ARRÊTAIT la charge.**
|
||||
> `ChargingModeNormal` sort la borne de l'arbitrage et lui restitue ses valeurs manuelles
|
||||
> (`manualChargingEnabled`, `manualMaxChargingCurrent`, `manualDesiredPhaseCount`). Sur
|
||||
> `.75` elles valaient `false / 6 A / 3 phases` : mesuré le 2026-09-08 à 11:09:51, l'appui
|
||||
> a stoppé la charge et armé un verrou de 300 s. **Ce n'était pas une régression** — c'est
|
||||
> ce que `Normal` a toujours fait ; c'est le nom qui promettait le contraire.
|
||||
>
|
||||
> Renommé **jusque dans le code** (`ChargingMode.manuel`), pas seulement sur l'étiquette :
|
||||
> un identifiant faux reste un piège pour le prochain lecteur.
|
||||
>
|
||||
> ⚠️ **Et passer en Manuel se confirme.** « Manuel » dit qui commande, pas ce que la borne
|
||||
> va faire : tant que l'app n'offre pas de régler ces valeurs stockées, le mode veut dire
|
||||
> « ce qui traînait la dernière fois ». La confirmation le dit avant, plutôt que de le
|
||||
> laisser découvrir après.
|
||||
|
||||
### Le BOOST n'est pas un mode — c'est une DÉROGATION (`+etm61`)
|
||||
|
||||
```
|
||||
NymeaEnergy.SetLoadDerogation { loadId, durationS, o:targetWh }
|
||||
NymeaEnergy.ClearLoadDerogation { loadId }
|
||||
```
|
||||
|
||||
Vérifiées présentes dans `Introspect` sur `.75` en `1.15.2+etm61`. Elle se **superpose** au
|
||||
mode courant, sert la charge à son maximum **en tête de passe éco** — devant toute autre
|
||||
charge, quel que soit son rang — financée au réseau, et **s'efface d'elle-même**.
|
||||
|
||||
- **`durationS` est obligatoire**, bornée `[60, 21600]`, refusée hors de là (LM-1500). Donc
|
||||
**pas de « boost jusqu'à annulation »** : l'écran doit faire saisir une durée, le moteur
|
||||
refuserait le reste. `targetWh` est optionnel, en **énergie**, jamais en SoC — pas de
|
||||
communication véhicule (cf. LM-1009).
|
||||
- 🔴 **Ne pas lire le MODE pour savoir si un boost tourne.** Si la borne était en manuel, le
|
||||
moteur force Eco le temps de la dérogation, et l'écran verrait un mode que l'utilisateur
|
||||
n'a pas choisi. La vérité est sur la charge : `decision.code == "DEROGATION_ACTIVE"`, avec
|
||||
`endsAt` (ISO 8601 UTC) pour le compte à rebours, `maxW` / `gridW`, et `boundBy ∈ drawCap`
|
||||
(l'abonnement bride) `| loadCeiling` (la charge est à son plafond de cycle).
|
||||
- **La fin arrive au niveau de la TRAME, pas dans `loads[]`** — au débranchement la charge
|
||||
n'y est déjà plus. Champ `derogationEnded[]`, présent le seul cycle de la fin :
|
||||
|
||||
```jsonc
|
||||
"derogationEnded": [ { "loadId": "…", "cause": "durationElapsed", "restoredMode": 0 } ]
|
||||
```
|
||||
|
||||
`cause ∈ targetReached | unplugged | measurementLost | durationElapsed | modeChanged |
|
||||
cancelled` — **six causes, six messages** : cible atteinte (rien à faire) · mesure perdue
|
||||
(regarder le lien ou le compteur) · débranchement · durée écoulée · mode changé (quelqu'un a
|
||||
repris le volant) · annulée à la main.
|
||||
|
||||
Depuis `+etm61`, appuyer sur un bouton de mode pendant une dérogation la **termine
|
||||
proprement** (`cause: "modeChanged"`, `restoredMode` absent — on ne restitue pas, ce serait
|
||||
écraser le mode que l'utilisateur vient de choisir).
|
||||
|
||||
> ⚠️ **`minCurrent` n'existe nulle part dans `ChargingInfo`** — et c'est normal : ce n'est
|
||||
> pas un réglage. **Tranché le 2026-09-07.** Le plancher est une limite MATÉRIELLE, et elle
|
||||
> est déjà sur la box, dans la classe du Thing borne :
|
||||
|
||||
@ -139,7 +139,27 @@ class EnergyData {
|
||||
isPlausibleKwh(gridImportKwh) ? gridImportKwh : null;
|
||||
}
|
||||
|
||||
enum ChargingMode { pv, minPv, boost }
|
||||
/// Les trois modes de recharge offerts par l'écran.
|
||||
///
|
||||
/// 🔴 **`manuel` s'appelait `boost`, et le nom était FAUX.** Il pose
|
||||
/// `ChargingModeNormal`, qui **sort la borne de l'arbitrage** et restitue ses valeurs
|
||||
/// manuelles — `manualChargingEnabled`, `manualMaxChargingCurrent`,
|
||||
/// `manualDesiredPhaseCount`. Sur `.75` ces valeurs valaient `false / 6 A / 3 phases` :
|
||||
/// mesuré le 2026-09-08 à 11:09:51, le bouton « Boost » a donc **arrêté la charge** et armé
|
||||
/// un verrou de 300 s.
|
||||
///
|
||||
/// Ce n'était pas une régression : c'est ce que `Normal` a toujours fait. Le nom promettait
|
||||
/// le contraire du comportement, et c'est le nom qui a menti pendant des mois — d'où le
|
||||
/// renommage jusque dans le code, et pas seulement sur l'étiquette.
|
||||
///
|
||||
/// Le vrai boost existe depuis `+etm61`, et **ce n'est pas un mode** : c'est une
|
||||
/// dérogation (`NymeaEnergy.SetLoadDerogation`) qui se superpose au mode courant, sert la
|
||||
/// charge à son maximum en tête de passe éco, et s'efface d'elle-même. Elle aura son propre
|
||||
/// bouton d'action, avec une durée obligatoire — pas une entrée de ce sélecteur.
|
||||
///
|
||||
/// L'ordre compte : `manuel` en tête, parce que `ChargingModeNormal` est aussi le mode
|
||||
/// **d'office** d'une borne détectée (cf. `EvChargerLive.isUnmanaged`).
|
||||
enum ChargingMode { manuel, pv, minPv }
|
||||
|
||||
class HistoryPoint {
|
||||
final DateTime time;
|
||||
|
||||
@ -133,7 +133,7 @@ class EvChargerLive {
|
||||
/// une valeur inconnue — une borne peut porter un mode qu'aucune version d'app ne
|
||||
/// connaît, et deviner serait pire que dire « mode non reconnu ».
|
||||
ChargingMode? get uiMode => switch (apiMode) {
|
||||
'ChargingModeNormal' => ChargingMode.boost,
|
||||
'ChargingModeNormal' => ChargingMode.manuel,
|
||||
'ChargingModeEco' || 'ChargingModeEcoWithTargetTime' => ChargingMode.pv,
|
||||
'ChargingModeEcoWithMinCurrent' ||
|
||||
'ChargingModeEcoMinWithTargetTime' =>
|
||||
|
||||
@ -336,7 +336,7 @@ class _Mecanisme extends StatelessWidget {
|
||||
'branchée, et changent avec elle — les figer ici en ferait une seconde source '
|
||||
'de vérité, fausse dès le prochain véhicule.\n\n'
|
||||
'Ce qui se règle depuis cet écran : le libellé, le domaine, le rang. Le mode de '
|
||||
'recharge (PV, Min+PV, Boost) se règle depuis la tuile « Borne EV » des '
|
||||
'recharge (Manuel, PV, Min+PV) se règle depuis la tuile « Borne EV » des '
|
||||
'favoris — c\'est le seul endroit de l\'app qui écrit SetChargingInfo.',
|
||||
style: TextStyle(
|
||||
fontSize: 12, height: 1.4, color: EtmTokens.mutedOf(context)),
|
||||
@ -538,7 +538,7 @@ class _SectionBorne extends StatelessWidget {
|
||||
final base = switch (ui) {
|
||||
ChargingMode.pv => 'PV',
|
||||
ChargingMode.minPv => 'Min+PV',
|
||||
ChargingMode.boost => 'Boost',
|
||||
ChargingMode.manuel => 'Manuel',
|
||||
};
|
||||
return b.hasDeadlineMode ? 'lu : $base + échéance' : 'lu : $base';
|
||||
}
|
||||
|
||||
@ -1432,7 +1432,7 @@ class NymeaService extends ChangeNotifier {
|
||||
}) async {
|
||||
// (mode d'UI, échéance) → énumération publiée par la box.
|
||||
final String apiMode;
|
||||
if (mode == ChargingMode.boost) {
|
||||
if (mode == ChargingMode.manuel) {
|
||||
apiMode = 'ChargingModeNormal';
|
||||
} else if (mode == ChargingMode.pv && !deadline) {
|
||||
apiMode = 'ChargingModeEco';
|
||||
@ -1468,7 +1468,7 @@ class NymeaService extends ChangeNotifier {
|
||||
// avant d'atteindre le handler.
|
||||
info.remove('chargingState');
|
||||
|
||||
if (deadline && mode != ChargingMode.boost) {
|
||||
if (deadline && mode != ChargingMode.manuel) {
|
||||
info['targetPercentage'] = targetSoc;
|
||||
if (endTime != null) {
|
||||
info['endDateTime'] = endTime.millisecondsSinceEpoch ~/ 1000;
|
||||
|
||||
@ -153,7 +153,7 @@ class EvFavoriteTile extends StatelessWidget {
|
||||
final base = switch (ui) {
|
||||
ChargingMode.pv => 'PV',
|
||||
ChargingMode.minPv => 'Min+PV',
|
||||
ChargingMode.boost => 'Boost',
|
||||
ChargingMode.manuel => 'Manuel',
|
||||
};
|
||||
return live.hasDeadlineMode ? 'Mode $base + échéance' : 'Mode $base';
|
||||
}
|
||||
|
||||
@ -51,7 +51,47 @@ class _EvModeButtonsState extends State<EvModeButtons> {
|
||||
/// répondu, l'app ne sait pas ce que porte la borne.
|
||||
ChargingMode? _pending;
|
||||
|
||||
/// **Passer en Manuel se confirme.** Ce n'est pas de la prudence décorative.
|
||||
///
|
||||
/// `ChargingModeNormal` sort la borne de l'arbitrage et lui **restitue ses valeurs
|
||||
/// manuelles stockées** — `manualChargingEnabled`, `manualMaxChargingCurrent`,
|
||||
/// `manualDesiredPhaseCount`. Elles peuvent dire n'importe quoi, y compris « charge
|
||||
/// désactivée » : sur `.75` elles valaient `false / 6 A / 3 phases`, et le bouton — qui
|
||||
/// s'appelait alors « Boost » — a **arrêté la charge** le 2026-09-08 à 11:09:51, avec un
|
||||
/// verrou de 300 s derrière.
|
||||
///
|
||||
/// Le renommage seul ne suffisait pas : « Manuel » dit bien QUI commande, mais rien de
|
||||
/// ce que la borne va faire. Tant que l'app n'offre pas de régler ces valeurs, Manuel
|
||||
/// veut dire « ce qui traînait la dernière fois », et l'utilisateur a le droit de le
|
||||
/// savoir avant, pas de le découvrir après.
|
||||
Future<bool> _confirmerManuel() async {
|
||||
final ok = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('Rendre la main à la borne ?'),
|
||||
content: const Text(
|
||||
'En mode manuel, le HEMS ne pilote plus cette borne : elle reprend ses propres '
|
||||
'réglages enregistrés (charge autorisée ou non, courant, nombre de phases).\n\n'
|
||||
'Selon ce qu\'ils contiennent, la charge peut s\'arrêter.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx, false),
|
||||
child: const Text('Annuler')),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(ctx, true),
|
||||
child: const Text('Passer en manuel')),
|
||||
],
|
||||
),
|
||||
);
|
||||
return ok == true;
|
||||
}
|
||||
|
||||
Future<void> _set(ChargingMode m) async {
|
||||
if (m == ChargingMode.manuel && widget.live.uiMode != ChargingMode.manuel) {
|
||||
if (!await _confirmerManuel()) return;
|
||||
if (!mounted) return;
|
||||
}
|
||||
setState(() => _pending = m);
|
||||
final err = await widget.onSetMode(m);
|
||||
if (!mounted) return;
|
||||
@ -77,9 +117,9 @@ class _EvModeButtonsState extends State<EvModeButtons> {
|
||||
/// pour de vrai.
|
||||
Widget _bouton(ChargingMode m) {
|
||||
final (label, couleur) = switch (m) {
|
||||
ChargingMode.manuel => ('Manuel', EtmTokens.mutedOf(context)),
|
||||
ChargingMode.pv => ('PV', EtmTokens.amber),
|
||||
ChargingMode.minPv => ('Min+PV', EtmTokens.blue),
|
||||
ChargingMode.boost => ('Boost', EtmTokens.green),
|
||||
};
|
||||
final selected = widget.live.uiMode == m;
|
||||
final busy = _pending != null;
|
||||
|
||||
@ -71,7 +71,7 @@ void main() {
|
||||
// Un lien coupé n'invalide pas un réglage stocké dans la box : le cacher
|
||||
// priverait le client de la seule chose encore vraie à l'écran.
|
||||
await tester.pumpWidget(_harness(live: horsLigne()));
|
||||
expect(find.textContaining('Mode Boost'), findsOneWidget);
|
||||
expect(find.textContaining('Mode Manuel'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('`joignable == null` n\'est PAS hors ligne', (tester) async {
|
||||
@ -183,7 +183,7 @@ void main() {
|
||||
(tester) async {
|
||||
// Deux écrans qui écrivent SetChargingInfo sont deux comportements qui divergent.
|
||||
await tester.pumpWidget(_harness(live: _v2c()));
|
||||
for (final label in ['PV', 'Min+PV', 'Boost']) {
|
||||
for (final label in ['Manuel', 'PV', 'Min+PV']) {
|
||||
expect(find.widgetWithText(OutlinedButton, label), findsNothing,
|
||||
reason: '$label ne doit plus être écrivable depuis les favoris');
|
||||
}
|
||||
|
||||
@ -69,19 +69,19 @@ void main() {
|
||||
});
|
||||
|
||||
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,
|
||||
// `_v2c()` est en ChargingModeNormal : c'est Manuel 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<OutlinedButton>(
|
||||
find.widgetWithText(OutlinedButton, 'Boost'));
|
||||
final manuel = tester.widget<OutlinedButton>(
|
||||
find.widgetWithText(OutlinedButton, 'Manuel'));
|
||||
final pv =
|
||||
tester.widget<OutlinedButton>(find.widgetWithText(OutlinedButton, 'PV'));
|
||||
expect(boost.style?.backgroundColor?.resolve({}),
|
||||
expect(manuel.style?.backgroundColor?.resolve({}),
|
||||
isNot(Colors.transparent),
|
||||
reason: 'Boost est le mode que la box porte');
|
||||
reason: 'Manuel 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');
|
||||
});
|
||||
@ -89,7 +89,7 @@ void main() {
|
||||
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']) {
|
||||
for (final label in ['Manuel', 'PV', 'Min+PV']) {
|
||||
final b = tester
|
||||
.widget<OutlinedButton>(find.widgetWithText(OutlinedButton, label));
|
||||
expect(b.style?.backgroundColor?.resolve({}), Colors.transparent);
|
||||
@ -98,7 +98,7 @@ void main() {
|
||||
|
||||
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']) {
|
||||
for (final label in ['Manuel', 'PV', 'Min+PV']) {
|
||||
final taille = tester.getSize(find.ancestor(
|
||||
of: find.text(label),
|
||||
matching: find.byType(OutlinedButton),
|
||||
@ -107,4 +107,92 @@ void main() {
|
||||
reason: '$label : cible tactile trop petite');
|
||||
}
|
||||
});
|
||||
|
||||
group('« Manuel » se confirme — il peut ARRÊTER la charge', () {
|
||||
// Le bouton s'appelait « Boost ». `ChargingModeNormal` sort la borne de l'arbitrage et
|
||||
// lui restitue ses valeurs manuelles stockées : sur `.75` elles valaient
|
||||
// `manualChargingEnabled: false`, et l'appui a stoppé la charge le 2026-09-08 à
|
||||
// 11:09:51, avec un verrou de 300 s derrière.
|
||||
//
|
||||
// Renommer ne suffisait pas : « Manuel » dit QUI commande, pas ce que la borne va
|
||||
// faire. Tant que l'app n'offre pas de régler ces valeurs, le mode veut dire « ce qui
|
||||
// traînait la dernière fois ».
|
||||
|
||||
testWidgets('l\'appui n\'écrit RIEN tant qu\'on n\'a pas confirmé', (tester) async {
|
||||
var appels = 0;
|
||||
await tester.pumpWidget(_harness(
|
||||
live: _v2c(apiMode: 'ChargingModeEco'),
|
||||
onSetMode: (_) async {
|
||||
appels++;
|
||||
return null;
|
||||
},
|
||||
));
|
||||
|
||||
await tester.tap(find.text('Manuel'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('Rendre la main à la borne'), findsOneWidget);
|
||||
expect(appels, 0, reason: 'rien ne part avant que l\'utilisateur ait dit oui');
|
||||
expect(find.textContaining('la charge peut s\'arrêter'), findsOneWidget,
|
||||
reason: 'la conséquence se dit AVANT, pas après');
|
||||
|
||||
await tester.tap(find.text('Annuler'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(appels, 0);
|
||||
});
|
||||
|
||||
testWidgets('confirmé, il écrit une fois', (tester) async {
|
||||
var appels = 0;
|
||||
await tester.pumpWidget(_harness(
|
||||
live: _v2c(apiMode: 'ChargingModeEco'),
|
||||
onSetMode: (_) async {
|
||||
appels++;
|
||||
return null;
|
||||
},
|
||||
));
|
||||
|
||||
await tester.tap(find.text('Manuel'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Passer en manuel'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(appels, 1);
|
||||
});
|
||||
|
||||
testWidgets('déjà en manuel, aucune question — rien ne change', (tester) async {
|
||||
// Le contrôle qui MORD : la confirmation ne doit pas se déclencher sur un ré-appui
|
||||
// du mode déjà en place, sinon elle devient un péage qu'on apprend à cliquer sans
|
||||
// lire — et une confirmation qu'on ne lit plus ne protège plus de rien.
|
||||
var appels = 0;
|
||||
await tester.pumpWidget(_harness(
|
||||
live: _v2c(), // déjà ChargingModeNormal
|
||||
onSetMode: (_) async {
|
||||
appels++;
|
||||
return null;
|
||||
},
|
||||
));
|
||||
|
||||
await tester.tap(find.text('Manuel'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('Rendre la main'), findsNothing);
|
||||
expect(appels, 1);
|
||||
});
|
||||
|
||||
testWidgets('les autres modes ne demandent rien', (tester) async {
|
||||
var appels = 0;
|
||||
await tester.pumpWidget(_harness(
|
||||
live: _v2c(),
|
||||
onSetMode: (_) async {
|
||||
appels++;
|
||||
return null;
|
||||
},
|
||||
));
|
||||
|
||||
await tester.tap(find.text('Min+PV'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.textContaining('Rendre la main'), findsNothing);
|
||||
expect(appels, 1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -203,9 +203,9 @@ void main() {
|
||||
|
||||
expect(find.widgetWithText(OutlinedButton, 'PV'), findsOneWidget);
|
||||
expect(find.widgetWithText(OutlinedButton, 'Min+PV'), findsOneWidget);
|
||||
expect(find.widgetWithText(OutlinedButton, 'Boost'), findsOneWidget);
|
||||
expect(find.widgetWithText(OutlinedButton, 'Manuel'), findsOneWidget);
|
||||
// Le mode LU est affiché à côté, et c'est celui de la box.
|
||||
expect(find.textContaining('lu : Boost'), findsOneWidget);
|
||||
expect(find.textContaining('lu : Manuel'), findsOneWidget);
|
||||
// Et l'avertissement d'une borne neuve, là où on la met en service.
|
||||
expect(find.textContaining('hors arbitrage'), findsOneWidget);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user