import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; import 'package:ertl_filament_verwaltung/controller/bauteil_controller.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/kauf_filament_model.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/bauteil_filament_auswahl.dart'; import 'helpers/memory_storage_service.dart'; void main() { late MemoryStorageService storage; late BauteilController controller; setUp(() { Get.testMode = true; storage = MemoryStorageService(); storage.tables[StorageKeys.filamente] = [ for (final entry in {'a': '100', 'b': '150'}.entries) KaufFilamentModel( filamentId: entry.key, kaufdatum: DateTime(2026), haendler: 'Shop', marke: 'Rolle ${entry.key}', material: 'PLA', preis: 10, farbe: 'Blau', gewicht: entry.value, ).toJson(), ]; storage.tables[StorageKeys.bauteile] = [ BauteilModel( bauteilId: 'vorher', erstelltAm: DateTime(2026), name: 'Vorher', verwendung: 'Test', druckdauerMinuten: 1, gewichtGramm: 40, abfallGramm: 0, filamente: [ const BauteilFilamentModel(filamentId: 'a', verbrauchGramm: 40), ], ).toJson(), ]; Get.put(storage); controller = Get.put(BauteilController()); }); 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: Scaffold( body: SingleChildScrollView( child: BauteilFilamentAuswahl(controller: controller), ), ), ), ); await tester.pumpAndSettle(); } Future select(WidgetTester tester, String id) async { await tester.ensureVisible(find.byType(DropdownButtonFormField)); await tester.tap(find.byType(DropdownButtonFormField)); await tester.pumpAndSettle(); await tester.tap(find.text(controller.filamentLabel(id)).last); await tester.pumpAndSettle(); } testWidgets( 'Insufficient roll warns with quantities and requires OK; second roll covers combined demand', (tester) async { controller.gewichtController.text = '150'; controller.abfallController.text = '10'; await show(tester); await select(tester, 'a'); expect(find.byType(AlertDialog), findsOneWidget); expect(find.text('Achtung: zu wenig Filament'), findsOneWidget); expect( find.textContaining('Benötigt (inklusive Abfall): 160 g'), findsOneWidget, ); expect(find.textContaining('Verfügbar: 60 g'), findsOneWidget); expect(find.textContaining('Es fehlen: 100 g'), findsOneWidget); await tester.tapAt(const Offset(2, 2)); await tester.pumpAndSettle(); expect(find.byType(AlertDialog), findsOneWidget); await tester.binding.handlePopRoute(); await tester.pumpAndSettle(); expect(find.byType(AlertDialog), findsOneWidget); await tester.tap(find.text('OK')); await tester.pumpAndSettle(); expect(find.byType(AlertDialog), findsNothing); expect(controller.selectedFilamente.keys, ['a']); await select(tester, 'b'); // 60g + 150g cover 160g; b alone would not. expect(find.byType(AlertDialog), findsNothing); expect(controller.selectedFilamente.keys, ['a', 'b']); expect(storage.getModelList(StorageKeys.bauteile), hasLength(1)); expect(tester.takeException(), isNull); }, ); testWidgets( 'Exact remaining amount produces no warning and selecting does not book consumption', (tester) async { controller.gewichtController.text = '50'; controller.abfallController.text = '10'; await show(tester); await select(tester, 'a'); expect(find.byType(AlertDialog), findsNothing); expect(controller.restgewichtGramm('a'), 60); expect(storage.getModelList(StorageKeys.bauteile), hasLength(1)); expect(tester.takeException(), isNull); }, ); test('Combined stock, decimal commas and editing credit use the correct available amount', () { controller.gewichtController.text = '200,5'; controller.abfallController.text = '9,5'; controller.toggleFilament('a', true); controller.toggleFilament('b', true); expect(controller.pruefeFilamentVorrat(), isNull); // Exactly 210g. controller.abfallController.text = '10'; expect( controller.pruefeFilamentVorrat(), contains('ausgewählten Rollen zusammen'), ); expect(controller.pruefeFilamentVorrat(), contains('Es fehlen: 0,5 g')); controller.toggleFilament('b', false); controller.gewichtController.text = '90'; controller.abfallController.text = '0'; expect(controller.pruefeFilamentVorrat(), isNotNull); controller.editingId.value = 'vorher'; // Replace existing 40g booking, not a new print. expect(controller.pruefeFilamentVorrat(), isNull); }); testWidgets( 'Without a valid requirement selection shows no shortage warning', (tester) async { await show(tester); await select(tester, 'a'); expect(find.byType(AlertDialog), findsNothing); for (final value in ['', 'NaN', 'Infinity', '-1', '0', 'abc']) { controller.gewichtController.text = value; expect(controller.pruefeFilamentVorrat(), isNull); } expect(tester.takeException(), isNull); }, ); }