import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; import 'package:ertl_filament_verwaltung/controller/home_controller.dart'; import 'package:ertl_filament_verwaltung/helper/bauteil_preis_berechnung.dart'; import 'package:ertl_filament_verwaltung/helper/storage_keys.dart'; import 'package:ertl_filament_verwaltung/models/bauteil_model.dart'; import 'package:ertl_filament_verwaltung/models/backup_model.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 'helpers/memory_storage_service.dart'; void main() { late MemoryStorageService storage; late HomeController controller; setUp(() { Get.testMode = true; storage = MemoryStorageService(); Get.put(storage); controller = Get.put(HomeController()); }); 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); await tester.pumpWidget( GetMaterialApp(theme: AppTheme.light, home: const HomeView()), ); await tester.pumpAndSettle(); } void fill() { controller.haendlerController.text = 'Shop'; controller.markeController.text = 'Marke'; controller.farbeController.text = 'Blau'; controller.preisController.text = '20'; controller.gewichtController.text = '1000'; } Future edit(WidgetTester tester, KaufFilamentModel filament) async { final pending = controller.editFilament(filament); await tester.pumpAndSettle(); await pending; } testWidgets( 'Gross weight saves, reloads, edits, duplicates and can be cleared independently', (tester) async { await show(tester); fill(); final field = find.byWidgetPredicate( (widget) => widget is TextFormField && widget.controller == controller.bruttoGewichtController, ); await tester.ensureVisible(field); await tester.enterText(field, '1250,5'); await controller.saveFilament(); await tester.pumpAndSettle(); var saved = controller.filamente.single; expect(saved.gewichtGramm, 1000); expect(saved.bruttoGewichtGramm, 1250.5); expect(controller.bruttoGewichtController.text, isEmpty); controller.loadFilamente(); expect(controller.filamente.single.bruttoGewichtGramm, 1250.5); await edit(tester, saved); expect(controller.bruttoGewichtController.text, '1250,5'); controller.bruttoGewichtController.text = '1300'; await controller.saveFilament(); await tester.pumpAndSettle(); saved = controller.filamente.single; await controller.duplicateFilament(saved); expect(controller.filamente, hasLength(2)); expect( controller.filamente.map((item) => item.filamentId).toSet(), hasLength(2), ); expect( controller.filamente.every((item) => item.bruttoGewichtGramm == 1300), isTrue, ); await edit(tester, saved); controller.bruttoGewichtController.clear(); await controller.saveFilament(); await tester.pumpAndSettle(); final changed = controller.filamente.firstWhere( (item) => item.filamentId == saved.filamentId, ); expect(changed.bruttoGewichtGramm, isNull); expect(changed.gewichtGramm, 1000); expect( controller.filamente .firstWhere((item) => item.filamentId != saved.filamentId) .bruttoGewichtGramm, 1300, ); expect(tester.takeException(), isNull); }, ); testWidgets( 'Gross weight must be finite, positive and at least net weight; leaving it empty is allowed', (tester) async { await show(tester); fill(); for (final invalid in ['999', '0', '-1', 'NaN', 'Infinity', 'abc']) { controller.bruttoGewichtController.text = invalid; await controller.saveFilament(); await tester.pumpAndSettle(); expect(controller.filamente, isEmpty); expect(storage.getModelList(StorageKeys.filamente), isEmpty); } controller.bruttoGewichtController.clear(); await controller.saveFilament(); await tester.pumpAndSettle(); expect(controller.filamente.single.bruttoGewichtGramm, isNull); expect(tester.takeException(), isNull); }, ); test('Gross weight is backed up while old backups without gross weight still load', () { final rolle = KaufFilamentModel( filamentId: 'a', kaufdatum: DateTime(2026), haendler: 'Shop', marke: 'Marke', material: 'PLA', preis: 20, farbe: 'Blau', gewicht: '1kg', bruttoGewichtGramm: 1250, ); final data = { for (final key in BackupModel.keys) key: >[], }; data[StorageKeys.filamente] = [rolle.toJson()]; final restored = BackupModel.decode(BackupModel(data).encode()); final model = KaufFilamentModel.fromJson( restored.data[StorageKeys.filamente]!.single, ); expect(model.bruttoGewichtGramm, 1250); expect(model.gewichtGramm, 1000); data[StorageKeys.filamente]!.single.remove('bruttoGewichtGramm'); expect( KaufFilamentModel.fromJson( BackupModel.decode(BackupModel(data).encode()) .data[StorageKeys.filamente]! .single, ).bruttoGewichtGramm, isNull, ); for (final invalid in [999, 0, -1, 'invalid']) { data[StorageKeys.filamente]!.single['bruttoGewichtGramm'] = invalid; expect( () => BackupModel.decode(BackupModel(data).encode()), throwsFormatException, ); } }); test( 'Stock and all price calculations use only original net material weight', () { final rolle = KaufFilamentModel( filamentId: 'a', kaufdatum: DateTime(2026), haendler: 'Shop', marke: 'Marke', material: 'PLA', preis: 20, farbe: 'Blau', gewicht: '1000', bruttoGewichtGramm: 1500, ); controller.verbrauch['a'] = 200; expect(controller.restgewicht(rolle), 800); expect( controller.restgewicht(rolle.copyWith(bruttoGewichtGramm: 2000)), 800, ); final part = BauteilModel( bauteilId: 'part', erstelltAm: DateTime(2026), name: 'Teil', verwendung: 'Test', druckdauerMinuten: 1, gewichtGramm: 100, abfallGramm: 10, filamente: [ const BauteilFilamentModel(filamentId: 'a', verbrauchGramm: 110), ], ); final price = BauteilPreisBerechnung.berechne(part, [rolle], 50)!; expect(price.materialKosten, 2); expect(price.abfallKosten, .2); expect(price.gesamtpreis, 3.3); }, ); }