import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; import 'package:ertl_filament_verwaltung/controller/abfall_controller.dart'; import 'package:ertl_filament_verwaltung/helper/storage_keys.dart'; import 'package:ertl_filament_verwaltung/main.dart'; import 'package:ertl_filament_verwaltung/models/abfall_model.dart'; import 'package:ertl_filament_verwaltung/models/backup_model.dart'; import 'package:ertl_filament_verwaltung/pages/abfall_view.dart'; import 'package:ertl_filament_verwaltung/pages/home_view.dart'; import 'package:ertl_filament_verwaltung/services/get_storage_services.dart'; import 'package:ertl_filament_verwaltung/theme/app_theme.dart'; import 'helpers/memory_storage_service.dart'; void main() { late MemoryStorageService storage; setUp(() { Get.testMode = true; storage = MemoryStorageService(); Get.put(storage); }); tearDown(() => Get.reset()); Future show(WidgetTester tester) async { tester.view.physicalSize = const Size(320, 900); tester.view.devicePixelRatio = 1; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); final controller = Get.put(AbfallController()); await tester.pumpWidget( GetMaterialApp(theme: AppTheme.light, home: const AbfallView()), ); await tester.pumpAndSettle(); return controller; } testWidgets('Home opens waste management and back returns home at 320px', ( tester, ) async { tester.view.physicalSize = const Size(320, 900); tester.view.devicePixelRatio = 1; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget(const MyApp()); await tester.pumpAndSettle(); expect(find.byTooltip('Bauteile verwalten'), findsOneWidget); await tester.tap(find.byTooltip('Abfall verwalten')); await tester.pumpAndSettle(); expect(Get.currentRoute, AbfallView.routeName); expect(find.text('Abfallverwaltung'), findsOneWidget); await tester.tap(find.byTooltip('Zurück')); await tester.pumpAndSettle(); expect(Get.currentRoute, HomeView.routeName); expect(tester.takeException(), isNull); }); testWidgets( 'Save, edit, reload and delete keep totals and timestamps consistent', (tester) async { final controller = await show(tester); final date = DateTime(2026, 10, 28, 14, 35); controller.setDatum(date); controller.setUhrzeit(const TimeOfDay(hour: 14, minute: 35)); controller.gewichtController.text = '12,5'; controller.kostenController.text = '1,25'; await controller.saveAbfall(); await tester.pumpAndSettle(); final first = controller.abfaelle.single; expect(first.datumZeit, date); expect(controller.summeAbfall, 12.5); expect(controller.summeKosten, 1.25); controller.datumZeit.value = date.add(const Duration(days: 1)); controller.gewichtController.text = '7,5'; controller.kostenController.text = '0,75'; await controller.saveAbfall(); await tester.pumpAndSettle(); expect(controller.abfaelle.first.datumZeit.isAfter(date), isTrue); expect(controller.summeAbfall, 20); expect(controller.summeKosten, 2); controller.scrollController.jumpTo( controller.scrollController.position.maxScrollExtent, ); await tester.pumpAndSettle(); expect(find.text('Summe Abfall'), findsOneWidget); expect(find.text('20 g'), findsOneWidget); final editing = controller.editAbfall(first); await tester.pumpAndSettle(); await editing; controller.gewichtController.text = '15'; controller.kostenController.text = '1,50'; await controller.saveAbfall(); await tester.pumpAndSettle(); controller.loadAbfaelle(); expect(controller.abfaelle.length, 2); expect(controller.summeAbfall, 22.5); expect(controller.summeKosten, 2.25); expect(controller.abfaelle.last.abfallId, first.abfallId); await controller.deleteAbfall(first.abfallId); await tester.pumpAndSettle(); final reloaded = AbfallController()..onInit(); expect(reloaded.abfaelle.single.gewichtGramm, 7.5); expect(reloaded.summeKosten, .75); reloaded.onClose(); expect(tester.takeException(), isNull); }, ); testWidgets('Kilopreis calculates from grams while costs remain editable', ( tester, ) async { final controller = await show(tester); Future enter(TextEditingController field, String value) async { final finder = find.byWidgetPredicate( (widget) => widget is TextFormField && widget.controller == field, ); await tester.ensureVisible(finder); await tester.enterText(finder, value); await tester.pumpAndSettle(); } await enter(controller.kilopreisController, '20'); await enter(controller.gewichtController, '125'); expect(controller.kostenController.text, '2,50'); await enter(controller.kostenController, '3,10'); controller.setUhrzeit(const TimeOfDay(hour: 10, minute: 30)); await tester.pumpAndSettle(); expect(controller.kostenController.text, '3,10'); await enter(controller.gewichtController, '200'); expect(controller.kostenController.text, '4,00'); await enter(controller.kilopreisController, '30,5'); expect(controller.kostenController.text, '6,10'); await enter(controller.kostenController, '5,55'); await controller.saveAbfall(); await tester.pumpAndSettle(); expect(controller.abfaelle.single.kosten, 5.55); expect(controller.kilopreisController.text, '30,5'); final editing = controller.editAbfall(controller.abfaelle.single); await tester.pumpAndSettle(); await editing; await tester.pumpAndSettle(); expect(controller.kostenController.text, '5,55'); await enter(controller.kilopreisController, '0'); expect(controller.kostenController.text, '0,00'); await enter(controller.kilopreisController, ''); await enter(controller.gewichtController, '250'); await enter(controller.kostenController, '2,75'); await controller.saveAbfall(); await tester.pumpAndSettle(); expect(controller.abfaelle.single.kosten, 2.75); expect(tester.takeException(), isNull); }); testWidgets('Invalid inputs and failed writes preserve entries and form', ( tester, ) async { final controller = await show(tester); for (final weight in ['', '-1', '0', 'NaN', 'Infinity']) { controller.gewichtController.text = weight; controller.kostenController.text = '1'; await controller.saveAbfall(); expect(controller.abfaelle, isEmpty); } controller.gewichtController.text = '10'; for (final cost in ['', '-1', 'NaN', 'Infinity']) { controller.kostenController.text = cost; await controller.saveAbfall(); expect(controller.abfaelle, isEmpty); } controller.kostenController.text = '0'; storage.failWrites = true; await controller.saveAbfall(); await tester.pumpAndSettle(); expect(controller.hasError.value, isTrue); expect(controller.gewichtController.text, '10'); expect(controller.abfaelle, isEmpty); storage.failWrites = false; await controller.saveAbfall(); await tester.pumpAndSettle(); final saved = controller.abfaelle.single; storage.failWrites = true; await controller.deleteAbfall(saved.abfallId); expect(controller.abfaelle.single.abfallId, saved.abfallId); expect(controller.summeAbfall, 10); expect(storage.getModelList(StorageKeys.abfaelle), hasLength(1)); expect(tester.takeException(), isNull); }); test( 'Backups round-trip waste, accept older backups and reject invalid waste', () { final entry = AbfallModel( abfallId: 'w1', datumZeit: DateTime(2026, 10, 29, 9, 5), gewichtGramm: 25, kosten: 1.5, ); final data = { for (final key in BackupModel.keys) key: >[], }; data[StorageKeys.abfaelle] = [entry.toJson()]; final restored = BackupModel.decode(BackupModel(data).encode()); expect(restored.data[StorageKeys.abfaelle], [entry.toJson()]); final legacy = {...data}..remove(StorageKeys.abfaelle); expect( BackupModel.decode(BackupModel(legacy).encode()) .data[StorageKeys.abfaelle], isEmpty, ); data[StorageKeys.abfaelle] = [ {...entry.toJson(), 'kosten': -1}, ]; expect( () => BackupModel.decode(BackupModel(data).encode()), throwsFormatException, ); }, ); }