import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:hl_lieferservice/data/cache/attachment_cache.dart'; import 'package:hl_lieferservice/domain/entity/tour.dart'; import 'package:hl_lieferservice/domain/entity/tour_details.dart'; import 'package:hl_lieferservice/domain/repository/tour_repository.dart'; import 'package:hl_lieferservice/feature/delivery/bloc/tour_bloc.dart'; import 'package:hl_lieferservice/feature/delivery/bloc/tour_event.dart'; import 'package:hl_lieferservice/feature/delivery/bloc/tour_state.dart'; import 'package:hl_lieferservice/feature/delivery/overview/presentation/tour_day_watcher.dart'; import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart'; final _day1 = DateTime(2026, 10, 30, 22); final _day2 = DateTime(2026, 10, 31, 7); TourDetails _tourAm(DateTime date) => TourDetails( tour: Tour(id: 't-$date', accountId: 414, date: date, syncedAt: date), deliveries: const [], customers: const {}, contacts: const {}, articles: const {}, warehouses: const {}, notesByDeliveryId: const {}, creditsByDeliveryId: const {}, paymentsByDeliveryId: const {}, services: const [], serviceValuesByDeliveryId: const {}, contactSourcesByDeliveryId: const {}, contactChannelsBySourceId: const {}, ); /// Liefert die Tour zum aktuellen Testtag und zählt die Anfragen. class _Tours implements TourRepository { _Tours(this.now); final DateTime Function() now; int loads = 0; Object? failure; @override Future getMyTourDetails({DateTime? date}) async { loads++; final error = failure; if (error != null) throw error; return _tourAm(date ?? now()); } @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } class _NoCache implements AttachmentCache { @override Future retainOnly(Set ids) async {} @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } void main() { late DateTime now; late _Tours tours; late TourBloc bloc; setUp(() { now = _day1; tours = _Tours(() => now); bloc = TourBloc( tourRepository: tours, opBloc: OperationBloc(), attachmentCache: _NoCache(), clock: () => now, ); }); tearDown(() => bloc.close()); Future settle() => Future.delayed(const Duration(milliseconds: 5)); DateTime loadedDate() => (bloc.state as TourLoaded).details.tour.date; group('Bloc', () { test('neuer Tag: lädt die Tour des neuen Tages', () async { bloc.add(const LoadTour()); await settle(); now = _day2; bloc.add(const RefreshTourIfDayChanged()); await settle(); expect(loadedDate(), _day2); expect(tours.loads, 2); }); test('derselbe Tag: kein Request', () async { bloc.add(const LoadTour()); await settle(); now = _day1.add(const Duration(hours: 1)); bloc.add(const RefreshTourIfDayChanged()); await settle(); expect(tours.loads, 1); }); test('im Kalender gewählte Tour bleibt', () async { final gewaehlt = DateTime(2026, 10, 20); bloc.add(LoadTour.forDate(gewaehlt)); await settle(); now = _day2; bloc.add(const RefreshTourIfDayChanged()); await settle(); expect(loadedDate(), gewaehlt); expect(tours.loads, 1); }); test('kein Netz: alte Tour bleibt, nächster Versuch lädt nach', () async { bloc.add(const LoadTour()); await settle(); now = _day2; tours.failure = const TourRepositoryException('Server nicht erreichbar'); bloc.add(const RefreshTourIfDayChanged()); await settle(); expect(loadedDate(), _day1); expect((bloc.state as TourLoaded).refreshError, isNotNull); tours.failure = null; bloc.add(const RefreshTourIfDayChanged()); await settle(); expect(loadedDate(), _day2); }); test('vor dem ersten Laden passiert nichts', () async { bloc.add(const RefreshTourIfDayChanged()); await settle(); expect(tours.loads, 0); expect(bloc.state, isA()); }); }); group('TourDayWatcher', () { Future pumpWatcher(WidgetTester tester) async { await tester.runAsync(() async { bloc.add(const LoadTour()); await settle(); }); await tester.pumpWidget( BlocProvider.value( value: bloc, child: MaterialApp( home: TourDayWatcher( child: Builder( builder: (context) => Scaffold( body: TextButton( onPressed: () => Navigator.of(context).push( MaterialPageRoute( builder: (context) => Scaffold( appBar: AppBar(title: const Text('Lieferung')), ), ), ), child: const Text('öffnen'), ), ), ), ), ), ), ); } Future resume(WidgetTester tester) async { tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed); await tester.runAsync(settle); } testWidgets('Rückkehr in die App am neuen Tag lädt nach', (tester) async { await pumpWatcher(tester); // Der Test startet die App im Zustand „nicht aktiv", wie ein Wechsel. tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.paused); now = _day2; await resume(tester); expect(tours.loads, 2); expect(loadedDate(), _day2); }); testWidgets('offene Seite: erst beim Schließen wird nachgeladen', (tester) async { await pumpWatcher(tester); await tester.tap(find.text('öffnen')); await tester.pumpAndSettle(); tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.paused); now = _day2; await resume(tester); expect(tours.loads, 1, reason: 'Eingaben der offenen Seite schützen'); expect(loadedDate(), _day1); await tester.tap(find.byType(BackButton)); await tester.pumpAndSettle(); await tester.runAsync(settle); expect(tours.loads, 2); expect(loadedDate(), _day2); }); }); }