Files
ertl_filament_verwaltung/test/anwendung_bauteil_test.dart
T
2026-10-28 14:07:40 +01:00

413 lines
16 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart';
import 'package:ertl_filament_verwaltung/controller/anwendung_controller.dart';
import 'package:ertl_filament_verwaltung/controller/bauteil_controller.dart';
import 'package:ertl_filament_verwaltung/controller/home_controller.dart';
import 'package:ertl_filament_verwaltung/helper/input_validators.dart';
import 'package:ertl_filament_verwaltung/helper/sample_routes.dart';
import 'package:ertl_filament_verwaltung/models/anwendung_model.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/pages/anwendung_view.dart';
import 'package:ertl_filament_verwaltung/pages/bauteil_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 'package:ertl_filament_verwaltung/widgets/filament_card.dart';
import 'package:ertl_filament_verwaltung/widgets/bauteil_card.dart';
import 'helpers/memory_storage_service.dart';
void main() {
late MemoryStorageService storage;
final uuidPattern = RegExp(
r'^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$',
);
KaufFilamentModel filament(String id, String color) => KaufFilamentModel(
filamentId: id,
kaufdatum: DateTime(2026, 10, 27),
haendler: 'Shop',
marke: 'Marke $id',
material: 'PLA',
preis: 20,
farbe: color,
gewicht: '1000',
);
AnwendungModel profile(String id, String filamentId) => AnwendungModel(
anwendungId: id,
filamentId: filamentId,
erstelltAm: DateTime(2026, 10, 26),
druckbettTemperatur: 60,
druckTemperatur: 210,
bauraumGeschlossen: false,
);
setUp(() {
Get.testMode = true;
storage = MemoryStorageService();
storage.tables[HomeController.storageKey] = [
filament('a', 'Rot').toJson(),
filament('b', 'Blau').toJson(),
];
storage.tables[AnwendungController.storageKey] = [
profile('profile-b', 'b').toJson(),
];
Get.put<GetStorageService>(storage);
});
tearDown(() => Get.reset());
Future<void> show(
WidgetTester tester,
Widget page, {
bool narrow = false,
}) async {
tester.view.physicalSize = narrow
? const Size(320, 640)
: const Size(1100, 1400);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.pumpWidget(GetMaterialApp(theme: AppTheme.light, home: page));
await tester.pumpAndSettle();
}
void fillPart(BauteilController controller) {
controller.nameController.text = 'Zweifarbiger Halter';
controller.verwendungController.text = 'Werkstatt';
controller.stundenController.text = '2';
controller.minutenController.text = '35';
controller.gewichtController.text = '42,5';
controller.abfallController.text = '8,2';
controller.verbrauchController('a').text = '50,7';
controller.verbrauchController('b').text = '20,7';
}
test(
'Numeric validation accepts comma decimals and rejects invalid values',
() {
expect(InputValidators.number('42,5'), 42.5);
for (final value in ['NaN', 'Infinity', '-1', 'abc']) {
expect(InputValidators.nonNegative(value), isNotNull);
}
expect(InputValidators.positive('0'), isNotNull);
expect(InputValidators.nonNegative('0'), isNull);
expect(InputValidators.nonNegative('', optional: true), isNull);
expect(InputValidators.minutes('60'), isNotNull);
expect(InputValidators.hours('1,5'), isNotNull);
},
);
testWidgets(
'Profiles persist selected filament UUID and preserve other profiles',
(tester) async {
final controller = Get.put(AnwendungController(filamentId: 'a'));
await show(tester, const AnwendungView(), narrow: true);
controller.druckController.text = '220,5';
controller.empfohleneDrucktemperaturController.text = '220–260 °C';
controller.empfohlenerHeizbettbereichController.text = '0–60 °C';
controller.druckbettController.text = '65';
controller.bauraumController.text = '40';
controller.bauraumGeschlossen.value = true;
controller.sonstigesController.text = 'Luefter 50 %';
await controller.saveAnwendung();
await tester.pumpAndSettle();
final stored = storage.getModelList(AnwendungController.storageKey);
expect(stored, hasLength(2));
final saved = AnwendungModel.fromJson(stored.last);
expect(saved.anwendungId, matches(uuidPattern));
expect(saved.filamentId, 'a');
expect(saved.druckTemperatur, 220.5);
expect(saved.empfohleneDrucktemperatur, '220–260 °C');
expect(saved.empfohlenerHeizbettbereich, '0–60 °C');
expect(saved.druckbettTemperatur, 65);
expect(saved.bauraumGeschlossen, isTrue);
expect(saved.bauraumTemperatur, 40);
expect(saved.sonstiges, 'Luefter 50 %');
expect(controller.anwendungen, hasLength(1));
final reloaded = Get.put(
AnwendungController(filamentId: 'a'),
tag: 'reload',
);
expect(reloaded.anwendungen.single.anwendungId, saved.anwendungId);
expect(reloaded.druckController.text, '220.5');
expect(reloaded.empfohleneDrucktemperaturController.text, '220–260 °C');
expect(reloaded.empfohlenerHeizbettbereichController.text, '0–60 °C');
expect(reloaded.druckbettController.text, '65.0');
expect(reloaded.bauraumController.text, '40.0');
expect(reloaded.bauraumGeschlossen.value, isTrue);
expect(reloaded.sonstigesController.text, 'Luefter 50 %');
expect(storage.getModelList(HomeController.storageKey), hasLength(2));
expect(controller.druckController.text, '220,5');
expect(find.text('Gespeicherte Profile'), findsNothing);
expect(tester.takeException(), isNull);
},
);
testWidgets('Editing replaces the profile and keeps its identity', (
tester,
) async {
final otherProfile = storage
.getModelList(AnwendungController.storageKey)
.single;
final original = profile('profile-a', 'a');
storage.tables[AnwendungController.storageKey]!.add(original.toJson());
final controller = Get.put(AnwendungController(filamentId: 'a'));
await show(tester, const AnwendungView());
expect(controller.druckController.text, '210.0');
expect(controller.druckbettController.text, '60.0');
expect(controller.bauraumController.text, isEmpty);
controller.druckController.text = '225';
controller.sonstigesController.text = 'Aktualisiert';
storage.failWrites = true;
await controller.saveAnwendung();
expect(controller.hasError.value, isTrue);
expect(controller.anwendungen.single.druckTemperatur, 210);
expect(controller.druckController.text, '225');
storage.failWrites = false;
await controller.saveAnwendung();
controller.druckController.text = '230';
await controller.saveAnwendung();
final stored = storage.getModelList(AnwendungController.storageKey);
expect(stored, hasLength(2));
expect(stored.first, otherProfile);
final updated = AnwendungModel.fromJson(stored.last);
expect(updated.anwendungId, original.anwendungId);
expect(updated.erstelltAm, original.erstelltAm);
expect(updated.druckTemperatur, 230);
expect(updated.sonstiges, 'Aktualisiert');
expect(controller.anwendungen, hasLength(1));
});
testWidgets('Legacy profiles load the latest and save one per filament', (
tester,
) async {
final latest = AnwendungModel(
anwendungId: 'latest-a',
filamentId: 'a',
erstelltAm: DateTime(2026, 10, 27),
druckbettTemperatur: 70,
druckTemperatur: 240,
bauraumGeschlossen: true,
bauraumTemperatur: 45,
sonstiges: 'Neuestes Profil',
);
storage.tables[AnwendungController.storageKey]!.addAll([
latest.toJson(),
profile('older-a', 'a').toJson(),
]);
final controller = Get.put(AnwendungController(filamentId: 'a'));
await show(tester, const AnwendungView());
expect(controller.druckController.text, '240.0');
expect(controller.bauraumGeschlossen.value, isTrue);
expect(controller.sonstigesController.text, 'Neuestes Profil');
await controller.saveAnwendung();
final stored = storage.getModelList(AnwendungController.storageKey);
expect(stored, hasLength(2));
expect(stored.where((item) => item['filamentId'] == 'a'), hasLength(1));
expect(stored.last['anwendungId'], 'latest-a');
});
testWidgets('Profile write failure retains input and does not add history', (
tester,
) async {
final controller = Get.put(AnwendungController(filamentId: 'a'));
await show(tester, const AnwendungView());
controller.druckController.text = '220';
controller.druckbettController.text = '0';
storage.failWrites = true;
await controller.saveAnwendung();
expect(controller.hasError.value, isTrue);
expect(controller.druckController.text, '220');
expect(controller.anwendungen, isEmpty);
expect(controller.isSaving.value, isFalse);
storage.failWrites = false;
await controller.saveAnwendung();
expect(controller.anwendungen.single.bauraumTemperatur, isNull);
expect(controller.anwendungen.single.bauraumGeschlossen, isFalse);
});
testWidgets('Missing filament cannot create orphan profiles', (tester) async {
final controller = Get.put(AnwendungController(filamentId: 'missing'));
await show(tester, const AnwendungView());
await controller.saveAnwendung();
expect(controller.anwendungen, isEmpty);
expect(storage.getModelList(AnwendungController.storageKey), hasLength(1));
expect(find.textContaining('nicht gefunden'), findsOneWidget);
});
testWidgets('Inventory marks saved profiles and updates after creating one', (
tester,
) async {
tester.view.physicalSize = const Size(1280, 1400);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.pumpWidget(
GetMaterialApp(
theme: AppTheme.light,
initialRoute: HomeView.routeName,
getPages: SampleRouts.samplePages,
),
);
await tester.pumpAndSettle();
final home = Get.find<HomeController>();
Finder badgeFor(String id) => find.descendant(
of: find.byKey(ValueKey(id)),
matching: find.text('Profil erstellt'),
);
expect(badgeFor('a'), findsNothing);
expect(badgeFor('b'), findsOneWidget);
await tester.tap(find.byKey(const ValueKey('a')));
await tester.pumpAndSettle();
final profileController = Get.find<AnwendungController>();
profileController.druckController.text = '220';
profileController.druckbettController.text = '60';
storage.failWrites = true;
await profileController.saveAnwendung();
expect(home.filamentIdsMitProfil, isNot(contains('a')));
storage.failWrites = false;
await profileController.saveAnwendung();
Get.back<void>();
await tester.pumpAndSettle();
expect(badgeFor('a'), findsOneWidget);
expect(badgeFor('b'), findsOneWidget);
// Reloads after imports must also clear badges for removed profiles.
await storage.saveModelList(AnwendungController.storageKey, []);
home.loadFilamente();
await tester.pumpAndSettle();
expect(find.text('Profil erstellt'), findsNothing);
expect(tester.takeException(), isNull);
});
testWidgets(
'Multicolor part persists both filaments and matching processing profile',
(tester) async {
final controller = Get.put(BauteilController());
await show(tester, const BauteilView(), narrow: true);
fillPart(controller);
controller.toggleFilament('a', true);
controller.toggleFilament('b', true);
controller.verbrauchController('a').text = '30';
controller.selectProfile('a', 'profile-b');
expect(controller.selectedFilamente['a'], isNull);
controller.selectProfile('b', 'profile-b');
await tester.pumpAndSettle();
await controller.saveBauteil();
await tester.pumpAndSettle();
final stored = storage.getModelList(BauteilController.storageKey);
final saved = BauteilModel.fromJson(stored.single);
expect(saved.bauteilId, matches(uuidPattern));
expect(saved.name, 'Zweifarbiger Halter');
expect(saved.verwendung, 'Werkstatt');
expect(saved.druckdauerMinuten, 155);
expect(saved.gewichtGramm, 42.5);
expect(saved.abfallGramm, 8.2);
expect(saved.gesamtverbrauchGramm, closeTo(50.7, 0.001));
expect(saved.filamente.map((item) => item.filamentId), ['a', 'b']);
expect(saved.filamente.last.anwendungId, 'profile-b');
expect(controller.selectedFilamente, isEmpty);
final reloaded = Get.put(BauteilController(), tag: 'reload');
expect(reloaded.bauteile.single.filamente, hasLength(2));
expect(
storage.getModelList(AnwendungController.storageKey),
hasLength(1),
);
await tester.scrollUntilVisible(
find.byType(BauteilCard),
250,
scrollable: find.byType(Scrollable).first,
);
await tester.pumpAndSettle();
expect(find.text('Zweifarbiger Halter'), findsOneWidget);
expect(find.text('Mehrfarbdruck · 2 Filamente'), findsOneWidget);
expect(tester.takeException(), isNull);
},
);
testWidgets('Part requires filament, positive duration and valid weights', (
tester,
) async {
final controller = Get.put(BauteilController());
await show(tester, const BauteilView());
fillPart(controller);
await controller.saveBauteil();
expect(controller.message.value, contains('mindestens ein Filament'));
controller.toggleFilament('a', true);
controller.stundenController.text = '0';
controller.minutenController.text = '0';
await controller.saveBauteil();
expect(controller.message.value, contains('mindestens eine Minute'));
controller.minutenController.text = '30';
controller.abfallController.text = '-1';
await controller.saveBauteil();
expect(storage.getModelList(BauteilController.storageKey), isEmpty);
expect(controller.nameController.text, isNotEmpty);
});
testWidgets('Part write failure keeps selection and inputs for retry', (
tester,
) async {
final controller = Get.put(BauteilController());
await show(tester, const BauteilView());
fillPart(controller);
controller.toggleFilament('a', true);
storage.failWrites = true;
await controller.saveBauteil();
expect(controller.hasError.value, isTrue);
expect(controller.bauteile, isEmpty);
expect(controller.nameController.text, 'Zweifarbiger Halter');
expect(controller.selectedFilamente.keys, ['a']);
expect(controller.isSaving.value, isFalse);
});
testWidgets(
'Navigation opens a fresh controller for each selected filament',
(tester) async {
tester.view.physicalSize = const Size(1280, 900);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.pumpWidget(
GetMaterialApp(
theme: AppTheme.light,
initialRoute: HomeView.routeName,
getPages: SampleRouts.samplePages,
),
);
await tester.pumpAndSettle();
await tester.tap(find.byType(FilamentCard).first);
await tester.pumpAndSettle();
expect(Get.find<AnwendungController>().filamentId, 'a');
Get.find<AnwendungController>().sonstigesController.text =
'Nicht uebernehmen';
Get.back<void>();
await tester.pumpAndSettle();
expect(Get.isRegistered<AnwendungController>(), isFalse);
await tester.tap(find.byType(FilamentCard).last);
await tester.pumpAndSettle();
expect(Get.find<AnwendungController>().filamentId, 'b');
expect(Get.find<AnwendungController>().sonstigesController.text, isEmpty);
expect(Get.find<AnwendungController>().anwendungen, hasLength(1));
Get.back<void>();
await tester.pumpAndSettle();
await tester.tap(find.byTooltip('Bauteile verwalten'));
await tester.pumpAndSettle();
expect(find.byType(BauteilView), findsOneWidget);
expect(Get.find<BauteilController>().filamente, hasLength(2));
expect(tester.takeException(), isNull);
},
);
}