/// **Quitter avec un brouillon en attente** — l'alerte, et ce qu'elle laisse derrière. /// /// Le brouillon vit dans `LoadConfigProvider`, pas dans l'écran : il survit à la /// navigation. Sans garde, on quittait en laissant une configuration modifiée invisible, /// qui réapparaissait au retour — ou disparaissait à la première relecture de la box, sans /// que personne ait choisi. library; import 'package:etm_powersync_app/providers/load_config_provider.dart'; import 'package:etm_powersync_app/widgets/garde_brouillon.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:provider/provider.dart'; List> _config() => [ {'id': 'a', 'label': 'A', 'priority': 1, 'domain': 'ecs'}, {'id': 'b', 'label': 'B', 'priority': 2, 'domain': 'ev'}, ]; /// Un écran poussé sur une pile, pour que « quitter » veuille dire quelque chose. Future _ouvrir(WidgetTester tester, LoadConfigProvider config) async { await tester.pumpWidget(ChangeNotifierProvider.value( value: config, child: MaterialApp( home: Builder( builder: (accueil) => Scaffold( body: Center( child: ElevatedButton( onPressed: () => Navigator.of(accueil).push(MaterialPageRoute( builder: (_) => const GardeBrouillon( child: Scaffold(body: Center(child: Text('écran de config'))), ), )), child: const Text('ouvrir'), ), ), ), ), ), )); await tester.tap(find.text('ouvrir')); await tester.pumpAndSettle(); expect(find.text('écran de config'), findsOneWidget); } void main() { testWidgets('sans brouillon, on sort sans être interrogé', (tester) async { // Une alerte qui se déclenche pour rien apprend à passer outre sans lire. final config = LoadConfigProvider()..adoptForTest(_config()); await _ouvrir(tester, config); final ctx = tester.element(find.text('écran de config')); Navigator.of(ctx).maybePop(); await tester.pumpAndSettle(); expect(find.text('Modifications non enregistrées'), findsNothing); expect(find.text('écran de config'), findsNothing, reason: 'sorti directement'); }); testWidgets('avec un brouillon, l\'alerte s\'interpose', (tester) async { final config = LoadConfigProvider()..adoptForTest(_config()); config.setLabel('a', 'A modifié'); expect(config.hasPendingChanges, isTrue); await _ouvrir(tester, config); final ctx = tester.element(find.text('écran de config')); Navigator.of(ctx).maybePop(); await tester.pumpAndSettle(); expect(find.text('Modifications non enregistrées'), findsOneWidget); // Elle dit l'état de la BOX, pas seulement celui de l'écran : c'est ce qui permet de // décider sans avoir à s'en souvenir. expect(find.textContaining('n\'ont pas été envoyés à la box'), findsOneWidget); // Et on est toujours sur l'écran tant qu'on n'a pas tranché. expect(find.text('écran de config'), findsOneWidget); }); testWidgets('« Rester » ne quitte pas, et ne touche pas au brouillon', (tester) async { final config = LoadConfigProvider()..adoptForTest(_config()); config.setLabel('a', 'A modifié'); await _ouvrir(tester, config); Navigator.of(tester.element(find.text('écran de config'))).maybePop(); await tester.pumpAndSettle(); await tester.tap(find.text('Rester')); await tester.pumpAndSettle(); expect(find.text('écran de config'), findsOneWidget); expect(config.hasPendingChanges, isTrue); }); testWidgets('« Quitter sans enregistrer » sort ET abandonne le brouillon', (tester) async { // Délibérément destructeur : un brouillon fantôme qui ressort trois écrans plus loin // est pire qu'une modification perdue qu'on a accepté de perdre. final config = LoadConfigProvider()..adoptForTest(_config()); config.setLabel('a', 'A modifié'); await _ouvrir(tester, config); Navigator.of(tester.element(find.text('écran de config'))).maybePop(); await tester.pumpAndSettle(); await tester.tap(find.text('Quitter sans enregistrer')); await tester.pumpAndSettle(); expect(find.text('écran de config'), findsNothing); expect(config.hasPendingChanges, isFalse); // La configuration adoptée n'a pas bougé : rien n'est parti à la box. expect(config.entries.firstWhere((e) => e.id == 'a').label, 'A'); }); }