add some new features
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
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/controller/bauteil_controller.dart';
|
||||
import 'package:ertl_filament_verwaltung/controller/home_controller.dart';
|
||||
import 'package:ertl_filament_verwaltung/helper/abfall_berechnung.dart';
|
||||
import 'package:ertl_filament_verwaltung/helper/storage_keys.dart';
|
||||
import 'package:ertl_filament_verwaltung/models/abfall_model.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/abfall_view.dart';
|
||||
import 'package:ertl_filament_verwaltung/pages/bauteil_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;
|
||||
KaufFilamentModel rolle(String id, double preis, String startgewicht) =>
|
||||
KaufFilamentModel(
|
||||
filamentId: id,
|
||||
kaufdatum: DateTime(2026),
|
||||
haendler: 'Shop',
|
||||
marke: id,
|
||||
material: 'PLA',
|
||||
preis: preis,
|
||||
farbe: id,
|
||||
gewicht: startgewicht,
|
||||
);
|
||||
BauteilModel teil(
|
||||
String id,
|
||||
double abfall,
|
||||
List<BauteilFilamentModel> filamente, {
|
||||
double? kosten,
|
||||
}) => BauteilModel(
|
||||
bauteilId: id,
|
||||
erstelltAm: DateTime(2026, 10, 29),
|
||||
name: id,
|
||||
verwendung: 'Test',
|
||||
druckdauerMinuten: 30,
|
||||
gewichtGramm: 20,
|
||||
abfallGramm: abfall,
|
||||
filamente: filamente,
|
||||
abfallKosten: kosten,
|
||||
);
|
||||
|
||||
setUp(() {
|
||||
Get.testMode = true;
|
||||
storage = MemoryStorageService();
|
||||
storage.tables[StorageKeys.filamente] = [rolle('a', 15, '750').toJson()];
|
||||
Get.put<GetStorageService>(storage);
|
||||
});
|
||||
tearDown(() => Get.reset());
|
||||
|
||||
Future<BauteilController> showParts(WidgetTester tester) async {
|
||||
final controller = Get.put(BauteilController());
|
||||
await tester.pumpWidget(
|
||||
GetMaterialApp(theme: AppTheme.light, home: const BauteilView()),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
return controller;
|
||||
}
|
||||
|
||||
void fill(BauteilController controller, {double waste = 5}) {
|
||||
controller.nameController.text = 'Halter';
|
||||
controller.verwendungController.text = 'Werkstatt';
|
||||
controller.minutenController.text = '30';
|
||||
controller.gewichtController.text = '20';
|
||||
controller.abfallController.text = '$waste';
|
||||
controller.toggleFilament('a', true);
|
||||
controller.verbrauchController('a').text = '${20 + waste}';
|
||||
}
|
||||
|
||||
Future<void> edit(
|
||||
WidgetTester tester,
|
||||
BauteilController controller,
|
||||
BauteilModel part,
|
||||
) async {
|
||||
final pending = controller.editBauteil(part);
|
||||
await tester.pumpAndSettle();
|
||||
await pending;
|
||||
}
|
||||
|
||||
testWidgets(
|
||||
'Part waste uses original 750g roll despite previous consumption; lifecycle reconciles once',
|
||||
(tester) async {
|
||||
// 600g already consumed: only 150g remain before the next print.
|
||||
storage.tables[StorageKeys.bauteile] = [
|
||||
teil('vorher', 0, [
|
||||
const BauteilFilamentModel(filamentId: 'a', verbrauchGramm: 600),
|
||||
]).toJson(),
|
||||
];
|
||||
storage.tables[StorageKeys.abfaelle] = [
|
||||
AbfallModel(
|
||||
abfallId: 'manual',
|
||||
datumZeit: DateTime(2026),
|
||||
gewichtGramm: 3,
|
||||
kosten: 1,
|
||||
).toJson(),
|
||||
];
|
||||
final home = Get.put(HomeController());
|
||||
final waste = Get.put(AbfallController());
|
||||
waste.kilopreisController.text =
|
||||
'999'; // Manual rate must never price a part.
|
||||
final controller = await showParts(tester);
|
||||
fill(controller);
|
||||
await controller.saveBauteil();
|
||||
await tester.pumpAndSettle();
|
||||
final saved = controller.bauteile.firstWhere(
|
||||
(entry) => entry.name == 'Halter',
|
||||
);
|
||||
expect(saved.abfallKosten, .1); // 15 / 750 * 5, not 15 / 150 * 5.
|
||||
expect(home.restgewicht(home.filamente.single), 125);
|
||||
expect(waste.summeAbfall, 8);
|
||||
expect(waste.summeKosten, closeTo(1.1, 1e-9));
|
||||
waste.loadAbfaelle();
|
||||
waste.loadAbfaelle();
|
||||
expect(waste.abfaelle, hasLength(2));
|
||||
expect(storage.getModelList(StorageKeys.abfaelle), hasLength(1));
|
||||
final linked = waste.abfaelle.firstWhere((entry) => entry.ausBauteil);
|
||||
await waste.deleteAbfall(linked.abfallId);
|
||||
await waste.editAbfall(linked);
|
||||
expect(waste.abfaelle, hasLength(2));
|
||||
expect(waste.editingId.value, isNull);
|
||||
|
||||
await edit(tester, controller, saved);
|
||||
controller.abfallController.text = '10';
|
||||
controller.verbrauchController('a').text = '30';
|
||||
await controller.saveBauteil();
|
||||
await tester.pumpAndSettle();
|
||||
expect(waste.summeAbfall, 13);
|
||||
expect(waste.summeKosten, closeTo(1.2, 1e-9));
|
||||
final changed = controller.bauteile.firstWhere(
|
||||
(entry) => entry.bauteilId == saved.bauteilId,
|
||||
);
|
||||
await controller.duplicateBauteil(changed);
|
||||
await tester.pumpAndSettle();
|
||||
expect(waste.summeAbfall, 23);
|
||||
expect(waste.summeKosten, closeTo(1.4, 1e-9));
|
||||
final copy = controller.bauteile.firstWhere(
|
||||
(entry) => entry.bauteilId != saved.bauteilId && entry.name == 'Halter',
|
||||
);
|
||||
await controller.deleteBauteil(copy.bauteilId);
|
||||
await tester.pumpAndSettle();
|
||||
expect(waste.summeAbfall, 13);
|
||||
await edit(tester, controller, changed);
|
||||
controller.abfallController.text = '0';
|
||||
controller.verbrauchController('a').text = '20';
|
||||
await controller.saveBauteil();
|
||||
await tester.pumpAndSettle();
|
||||
expect(waste.abfaelle.single.abfallId, 'manual');
|
||||
expect(waste.summeAbfall, 3);
|
||||
await controller.deleteBauteil(saved.bauteilId);
|
||||
await tester.pumpAndSettle();
|
||||
expect(waste.abfaelle.single.abfallId, 'manual');
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Frozen waste cost survives price changes, metadata edits, removed rolls and backups',
|
||||
(tester) async {
|
||||
final waste = Get.put(AbfallController());
|
||||
final controller = await showParts(tester);
|
||||
fill(controller, waste: 25);
|
||||
await controller.saveBauteil();
|
||||
await tester.pumpAndSettle();
|
||||
expect(waste.summeKosten, .5);
|
||||
storage.tables[StorageKeys.filamente] = [rolle('a', 30, '750').toJson()];
|
||||
await edit(tester, controller, controller.bauteile.single);
|
||||
controller.nameController.text = 'Neuer Name';
|
||||
await controller.saveBauteil();
|
||||
await tester.pumpAndSettle();
|
||||
expect(waste.summeKosten, .5);
|
||||
storage.tables[StorageKeys.filamente] = [];
|
||||
waste.loadAbfaelle();
|
||||
expect(waste.summeKosten, .5);
|
||||
expect(waste.abfaelle.single.kostenUnbekannt, isFalse);
|
||||
final backup = BackupModel({
|
||||
for (final key in BackupModel.keys) key: storage.getModelList(key),
|
||||
});
|
||||
final restored = BackupModel.decode(backup.encode());
|
||||
expect(
|
||||
BauteilModel.fromJson(restored.data[StorageKeys.bauteile]!.single)
|
||||
.abfallKosten,
|
||||
.5,
|
||||
);
|
||||
restored.data[StorageKeys.bauteile]!.single['abfallKosten'] = -1;
|
||||
expect(
|
||||
() => BackupModel.decode(BackupModel(restored.data).encode()),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Failed part writes create no waste and failed delete keeps linked waste',
|
||||
(tester) async {
|
||||
final waste = Get.put(AbfallController());
|
||||
final controller = await showParts(tester);
|
||||
fill(controller);
|
||||
storage.failWrites = true;
|
||||
await controller.saveBauteil();
|
||||
await tester.pumpAndSettle();
|
||||
expect(waste.abfaelle, isEmpty);
|
||||
expect(controller.nameController.text, 'Halter');
|
||||
storage.failWrites = false;
|
||||
await controller.saveBauteil();
|
||||
await tester.pumpAndSettle();
|
||||
expect(waste.summeKosten, .1);
|
||||
storage.failWrites = true;
|
||||
final deleting = controller.deleteBauteil(
|
||||
controller.bauteile.single.bauteilId,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await deleting;
|
||||
expect(waste.summeKosten, .1);
|
||||
expect(waste.summeAbfall, 5);
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'Multi-filament waste uses weighted roll prices and original weights',
|
||||
() {
|
||||
final part = teil('multi', 10, [
|
||||
const BauteilFilamentModel(filamentId: 'a', verbrauchGramm: 60),
|
||||
const BauteilFilamentModel(filamentId: 'b', verbrauchGramm: 40),
|
||||
]);
|
||||
// 6g at 20€/kg and 4g at 40€/kg = 0.28€.
|
||||
expect(
|
||||
AbfallBerechnung.kosten(part, [
|
||||
rolle('a', 15, '0,75 kg'),
|
||||
rolle('b', 20, '500'),
|
||||
]),
|
||||
.28,
|
||||
);
|
||||
expect(AbfallBerechnung.kosten(part, [rolle('a', 15, '750')]), isNull);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Existing parts appear with origin and unknown historical prices are labelled',
|
||||
(tester) async {
|
||||
storage.tables[StorageKeys.bauteile] = [
|
||||
teil('Altbestand', 25, [
|
||||
const BauteilFilamentModel(filamentId: 'a'),
|
||||
]).toJson(),
|
||||
teil('Fehlende Rolle', 5, [
|
||||
const BauteilFilamentModel(filamentId: 'deleted'),
|
||||
]).toJson(),
|
||||
];
|
||||
final waste = Get.put(AbfallController());
|
||||
expect(waste.summeAbfall, 30);
|
||||
expect(waste.summeKosten, .5);
|
||||
expect(
|
||||
waste.abfaelle.where((entry) => entry.kostenUnbekannt),
|
||||
hasLength(1),
|
||||
);
|
||||
expect(
|
||||
waste.abfaelle.where((entry) => entry.kostenNachtraeglich),
|
||||
hasLength(1),
|
||||
);
|
||||
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 AbfallView()),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
waste.scrollController.jumpTo(
|
||||
waste.scrollController.position.maxScrollExtent,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Abfallkosten: nicht ermittelbar'), findsOneWidget);
|
||||
expect(find.byTooltip('Abfalleintrag verwalten'), findsNothing);
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user