222 lines
8.5 KiB
Dart
222 lines
8.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:get/get.dart';
|
|
|
|
import 'package:ertl_filament_verwaltung/controller/abfall_controller.dart';
|
|
import 'package:ertl_filament_verwaltung/helper/storage_keys.dart';
|
|
import 'package:ertl_filament_verwaltung/main.dart';
|
|
import 'package:ertl_filament_verwaltung/models/abfall_model.dart';
|
|
import 'package:ertl_filament_verwaltung/models/backup_model.dart';
|
|
import 'package:ertl_filament_verwaltung/pages/abfall_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 'helpers/memory_storage_service.dart';
|
|
|
|
void main() {
|
|
late MemoryStorageService storage;
|
|
setUp(() {
|
|
Get.testMode = true;
|
|
storage = MemoryStorageService();
|
|
Get.put<GetStorageService>(storage);
|
|
});
|
|
tearDown(() => Get.reset());
|
|
|
|
Future<AbfallController> show(WidgetTester tester) async {
|
|
tester.view.physicalSize = const Size(320, 900);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
final controller = Get.put(AbfallController());
|
|
await tester.pumpWidget(
|
|
GetMaterialApp(theme: AppTheme.light, home: const AbfallView()),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
return controller;
|
|
}
|
|
|
|
testWidgets('Home opens waste management and back returns home at 320px', (
|
|
tester,
|
|
) async {
|
|
tester.view.physicalSize = const Size(320, 900);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
await tester.pumpWidget(const MyApp());
|
|
await tester.pumpAndSettle();
|
|
expect(find.byTooltip('Bauteile verwalten'), findsOneWidget);
|
|
await tester.tap(find.byTooltip('Abfall verwalten'));
|
|
await tester.pumpAndSettle();
|
|
expect(Get.currentRoute, AbfallView.routeName);
|
|
expect(find.text('Abfallverwaltung'), findsOneWidget);
|
|
await tester.tap(find.byTooltip('Zurück'));
|
|
await tester.pumpAndSettle();
|
|
expect(Get.currentRoute, HomeView.routeName);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets(
|
|
'Save, edit, reload and delete keep totals and timestamps consistent',
|
|
(tester) async {
|
|
final controller = await show(tester);
|
|
final date = DateTime(2026, 10, 28, 14, 35);
|
|
controller.setDatum(date);
|
|
controller.setUhrzeit(const TimeOfDay(hour: 14, minute: 35));
|
|
controller.gewichtController.text = '12,5';
|
|
controller.kostenController.text = '1,25';
|
|
await controller.saveAbfall();
|
|
await tester.pumpAndSettle();
|
|
final first = controller.abfaelle.single;
|
|
expect(first.datumZeit, date);
|
|
expect(controller.summeAbfall, 12.5);
|
|
expect(controller.summeKosten, 1.25);
|
|
|
|
controller.datumZeit.value = date.add(const Duration(days: 1));
|
|
controller.gewichtController.text = '7,5';
|
|
controller.kostenController.text = '0,75';
|
|
await controller.saveAbfall();
|
|
await tester.pumpAndSettle();
|
|
expect(controller.abfaelle.first.datumZeit.isAfter(date), isTrue);
|
|
expect(controller.summeAbfall, 20);
|
|
expect(controller.summeKosten, 2);
|
|
controller.scrollController.jumpTo(
|
|
controller.scrollController.position.maxScrollExtent,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Summe Abfall'), findsOneWidget);
|
|
expect(find.text('20 g'), findsOneWidget);
|
|
|
|
final editing = controller.editAbfall(first);
|
|
await tester.pumpAndSettle();
|
|
await editing;
|
|
controller.gewichtController.text = '15';
|
|
controller.kostenController.text = '1,50';
|
|
await controller.saveAbfall();
|
|
await tester.pumpAndSettle();
|
|
controller.loadAbfaelle();
|
|
expect(controller.abfaelle.length, 2);
|
|
expect(controller.summeAbfall, 22.5);
|
|
expect(controller.summeKosten, 2.25);
|
|
expect(controller.abfaelle.last.abfallId, first.abfallId);
|
|
await controller.deleteAbfall(first.abfallId);
|
|
await tester.pumpAndSettle();
|
|
final reloaded = AbfallController()..onInit();
|
|
expect(reloaded.abfaelle.single.gewichtGramm, 7.5);
|
|
expect(reloaded.summeKosten, .75);
|
|
reloaded.onClose();
|
|
expect(tester.takeException(), isNull);
|
|
},
|
|
);
|
|
|
|
testWidgets('Kilopreis calculates from grams while costs remain editable', (
|
|
tester,
|
|
) async {
|
|
final controller = await show(tester);
|
|
Future<void> enter(TextEditingController field, String value) async {
|
|
final finder = find.byWidgetPredicate(
|
|
(widget) => widget is TextFormField && widget.controller == field,
|
|
);
|
|
await tester.ensureVisible(finder);
|
|
await tester.enterText(finder, value);
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
await enter(controller.kilopreisController, '20');
|
|
await enter(controller.gewichtController, '125');
|
|
expect(controller.kostenController.text, '2,50');
|
|
await enter(controller.kostenController, '3,10');
|
|
controller.setUhrzeit(const TimeOfDay(hour: 10, minute: 30));
|
|
await tester.pumpAndSettle();
|
|
expect(controller.kostenController.text, '3,10');
|
|
await enter(controller.gewichtController, '200');
|
|
expect(controller.kostenController.text, '4,00');
|
|
await enter(controller.kilopreisController, '30,5');
|
|
expect(controller.kostenController.text, '6,10');
|
|
await enter(controller.kostenController, '5,55');
|
|
await controller.saveAbfall();
|
|
await tester.pumpAndSettle();
|
|
expect(controller.abfaelle.single.kosten, 5.55);
|
|
expect(controller.kilopreisController.text, '30,5');
|
|
final editing = controller.editAbfall(controller.abfaelle.single);
|
|
await tester.pumpAndSettle();
|
|
await editing;
|
|
await tester.pumpAndSettle();
|
|
expect(controller.kostenController.text, '5,55');
|
|
await enter(controller.kilopreisController, '0');
|
|
expect(controller.kostenController.text, '0,00');
|
|
await enter(controller.kilopreisController, '');
|
|
await enter(controller.gewichtController, '250');
|
|
await enter(controller.kostenController, '2,75');
|
|
await controller.saveAbfall();
|
|
await tester.pumpAndSettle();
|
|
expect(controller.abfaelle.single.kosten, 2.75);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
testWidgets('Invalid inputs and failed writes preserve entries and form', (
|
|
tester,
|
|
) async {
|
|
final controller = await show(tester);
|
|
for (final weight in ['', '-1', '0', 'NaN', 'Infinity']) {
|
|
controller.gewichtController.text = weight;
|
|
controller.kostenController.text = '1';
|
|
await controller.saveAbfall();
|
|
expect(controller.abfaelle, isEmpty);
|
|
}
|
|
controller.gewichtController.text = '10';
|
|
for (final cost in ['', '-1', 'NaN', 'Infinity']) {
|
|
controller.kostenController.text = cost;
|
|
await controller.saveAbfall();
|
|
expect(controller.abfaelle, isEmpty);
|
|
}
|
|
controller.kostenController.text = '0';
|
|
storage.failWrites = true;
|
|
await controller.saveAbfall();
|
|
await tester.pumpAndSettle();
|
|
expect(controller.hasError.value, isTrue);
|
|
expect(controller.gewichtController.text, '10');
|
|
expect(controller.abfaelle, isEmpty);
|
|
storage.failWrites = false;
|
|
await controller.saveAbfall();
|
|
await tester.pumpAndSettle();
|
|
final saved = controller.abfaelle.single;
|
|
storage.failWrites = true;
|
|
await controller.deleteAbfall(saved.abfallId);
|
|
expect(controller.abfaelle.single.abfallId, saved.abfallId);
|
|
expect(controller.summeAbfall, 10);
|
|
expect(storage.getModelList(StorageKeys.abfaelle), hasLength(1));
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
test(
|
|
'Backups round-trip waste, accept older backups and reject invalid waste',
|
|
() {
|
|
final entry = AbfallModel(
|
|
abfallId: 'w1',
|
|
datumZeit: DateTime(2026, 10, 29, 9, 5),
|
|
gewichtGramm: 25,
|
|
kosten: 1.5,
|
|
);
|
|
final data = {
|
|
for (final key in BackupModel.keys) key: <Map<String, dynamic>>[],
|
|
};
|
|
data[StorageKeys.abfaelle] = [entry.toJson()];
|
|
final restored = BackupModel.decode(BackupModel(data).encode());
|
|
expect(restored.data[StorageKeys.abfaelle], [entry.toJson()]);
|
|
final legacy = {...data}..remove(StorageKeys.abfaelle);
|
|
expect(
|
|
BackupModel.decode(BackupModel(legacy).encode())
|
|
.data[StorageKeys.abfaelle],
|
|
isEmpty,
|
|
);
|
|
data[StorageKeys.abfaelle] = [
|
|
{...entry.toJson(), 'kosten': -1},
|
|
];
|
|
expect(
|
|
() => BackupModel.decode(BackupModel(data).encode()),
|
|
throwsFormatException,
|
|
);
|
|
},
|
|
);
|
|
}
|