import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:ertl_filament_verwaltung/controller/home_controller.dart'; import 'package:ertl_filament_verwaltung/helper/storage_keys.dart'; import 'package:ertl_filament_verwaltung/models/kauf_filament_model.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 'package:ertl_filament_verwaltung/widgets/filament_card.dart'; import 'package:ertl_filament_verwaltung/widgets/filament_form.dart'; import 'helpers/memory_storage_service.dart'; void main() { late HomeController controller; setUp(() { PackageInfo.setMockInitialValues( appName: 'Filamentverwaltung', packageName: 'com.example.ertl_filament_verwaltung', version: '1.2.3', buildNumber: '42', buildSignature: '', ); Get.testMode = true; Get.put(MemoryStorageService()); controller = Get.put(HomeController()); }); tearDown(() => Get.reset()); Future showHome(WidgetTester tester, Size size) async { tester.view.physicalSize = size; tester.view.devicePixelRatio = 1; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget( GetMaterialApp(theme: AppTheme.light, home: const HomeView()), ); await tester.pumpAndSettle(); } KaufFilamentModel sample() => KaufFilamentModel( filamentId: 'sample', kaufdatum: DateTime(2026, 10, 27), haendler: 'Filament Shop', marke: 'Testmarke', material: 'PLA+', preis: 24.90, farbe: 'Ozeanblau', gewicht: '1000', ); testWidgets('Hamburger menu displays the installed version and build', ( tester, ) async { await showHome(tester, const Size(320, 640)); await tester.tap(find.byIcon(Icons.menu)); await tester.pumpAndSettle(); expect(find.text('Version 1.2.3'), findsOneWidget); expect(find.text('Build 42'), findsOneWidget); expect(tester.takeException(), isNull); }); testWidgets('Desktop shows both panes and edits PLA+', (tester) async { controller.filamente.add(sample()); await showHome(tester, const Size(1280, 900)); expect( tester.getTopLeft(find.byType(FilamentCard)).dx, greaterThan(tester.getTopRight(find.byType(FilamentForm)).dx), ); expect(find.text('Netto: 1000 g'), findsOneWidget); expect(find.text('Testmarke'), findsOneWidget); await tester.tap(find.byTooltip('Filament verwalten')); await tester.pumpAndSettle(); await tester.tap(find.text('Bearbeiten')); await tester.pumpAndSettle(); expect(find.text('Filament bearbeiten'), findsOneWidget); expect(controller.markeController.text, 'Testmarke'); expect(controller.material.value, 'PLA+'); expect(tester.takeException(), isNull); }); testWidgets('Small phone scrolls to save and shows the purchase', ( tester, ) async { await showHome(tester, const Size(320, 640)); controller.haendlerController.text = 'Filament Shop'; controller.markeController.text = 'Testmarke'; controller.farbeController.text = 'Ozeanblau'; controller.preisController.text = '24,90'; controller.gewichtController.text = '1000'; await tester.scrollUntilVisible( find.text('Filament speichern'), 200, scrollable: find.byType(Scrollable).first, ); await tester.pumpAndSettle(); await tester.tap(find.text('Filament speichern')); await tester.pumpAndSettle(); expect(controller.filamente, hasLength(1)); await tester.scrollUntilVisible( find.byType(FilamentCard), 200, scrollable: find.byType(Scrollable).first, ); await tester.pumpAndSettle(); expect(find.text('Testmarke'), findsOneWidget); expect(find.text('Netto: 1000 g'), findsOneWidget); expect(tester.takeException(), isNull); }); testWidgets('Empty inventory is accessible on a narrow screen', ( tester, ) async { await showHome(tester, const Size(320, 640)); await tester.scrollUntilVisible( find.text('Hier beginnt dein Bestand'), 200, scrollable: find.byType(Scrollable).first, ); await tester.pumpAndSettle(); expect( find.text('Hier beginnt dein Bestand').hitTestable(), findsOneWidget, ); expect(tester.takeException(), isNull); }); testWidgets('Custom material can be typed, selected and removed on a phone', ( tester, ) async { await showHome(tester, const Size(320, 640)); final field = find.byWidgetPredicate( (widget) => widget is TextFormField && widget.controller == controller.materialController, ); await tester.ensureVisible(field); await tester.enterText(field, 'PA12'); await tester.testTextInput.receiveAction(TextInputAction.done); await tester.ensureVisible(find.text('Art hinzufügen')); await tester.tap(find.text('Art hinzufügen')); await tester.pumpAndSettle(); expect(controller.materialien, contains('PA12')); await Scrollable.ensureVisible( tester.element(find.byTooltip('Material auswählen')), alignment: 0.5, ); await tester.pumpAndSettle(); await tester.tap(find.byTooltip('Material auswählen')); await tester.pumpAndSettle(); await tester.tap(find.text('PETG').last); await tester.pumpAndSettle(); expect(controller.materialController.text, 'PETG'); await tester.ensureVisible(find.text('Arten verwalten')); await tester.tap(find.text('Arten verwalten')); await tester.pumpAndSettle(); await tester.scrollUntilVisible( find.byTooltip('PA12 aus Liste entfernen'), 180, scrollable: find.descendant( of: find.byType(AlertDialog), matching: find.byType(Scrollable), ), ); await tester.tap(find.byTooltip('PA12 aus Liste entfernen')); await tester.pumpAndSettle(); expect(controller.materialien, isNot(contains('PA12'))); expect(tester.takeException(), isNull); await tester.tap(find.text('Schließen')); await tester.pumpAndSettle(); final reloaded = Get.put(HomeController(), tag: 'materials-reload'); expect(reloaded.materialien, isNot(contains('PA12'))); }); testWidgets('Saving adds custom material and removal preserves purchases', ( tester, ) async { await showHome(tester, const Size(1280, 900)); controller.haendlerController.text = 'Shop'; controller.markeController.text = 'Marke'; controller.farbeController.text = 'Rot'; controller.preisController.text = '20'; controller.gewichtController.text = '1000'; controller.materialController.text = ' PA12 '; await controller.saveFilament(); expect(controller.filamente.single.material, 'PA12'); final reloaded = Get.put(HomeController(), tag: 'custom-reload'); expect(reloaded.materialien, contains('PA12')); await controller.removeMaterial('PA12'); await controller.editFilament(controller.filamente.single); await tester.pumpAndSettle(); expect(controller.materialController.text, 'PA12'); expect(controller.materialien, isNot(contains('PA12'))); expect(controller.filamente.single.material, 'PA12'); expect(tester.takeException(), isNull); }); test( 'An empty material list survives reload and failed writes keep the list', () async { final storage = Get.find() as MemoryStorageService; controller.materialController.text = 'pa12'; expect(await controller.addMaterial(), isTrue); controller.materialController.text = ' PA12 '; expect(await controller.addMaterial(), isTrue); expect( controller.materialien.where((item) => item.toLowerCase() == 'pa12'), hasLength(1), ); storage.failWrites = true; expect(await controller.removeMaterial('pa12'), isFalse); expect(controller.materialien, contains('pa12')); expect(controller.materialMessage.value, isNotNull); storage.failWrites = false; for (final value in controller.materialien.toList()) { await controller.removeMaterial(value); } final reloaded = Get.put(HomeController(), tag: 'empty-reload'); expect(reloaded.materialien, isEmpty); expect(reloaded.materialController.text, isEmpty); expect( storage.getModelList(StorageKeys.materialien).single['werte'], isEmpty, ); }, ); }