add two views
This commit is contained in:
@@ -0,0 +1,295 @@
|
||||
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/entry_panel.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.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.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(storage.getModelList(HomeController.storageKey), hasLength(2));
|
||||
|
||||
await tester.scrollUntilVisible(
|
||||
find.byType(SavedEntryCard),
|
||||
200,
|
||||
scrollable: find.byType(Scrollable).first,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Bauraum geschlossen: Ja'), findsOneWidget);
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
);
|
||||
|
||||
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(
|
||||
'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);
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user