import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:machweis/app/app.dart';
import 'package:machweis/features/dashboard/presentation/app_shell.dart';
import 'package:machweis/features/reports/presentation/create_report_screen.dart';
import 'package:machweis/features/reports/presentation/reports_screen.dart';
import 'package:machweis/features/reports/data/demo_report_store.dart';
import 'package:machweis/features/reports/domain/report_validator.dart';
import 'package:machweis/features/reports/domain/report_export.dart';
import 'package:machweis/features/customers/data/customer_store.dart';
import 'package:machweis/core/app_version.dart';
void main() {
test('Alte Nachweise werden in Positionsdaten migriert', () {
final report = DemoReport.fromJson({
'id': 'legacy',
'customer': 'Alt-Kunde',
'site': 'Alt-Baustelle',
'activity': 'Verteilung geprüft',
'material': 'Sicherung',
'quantity': '2',
'date': '2026-01-01T08:00:00.000',
'startHour': 8,
'startMinute': 0,
'endHour': 9,
'endMinute': 0,
});
expect(report.activityItems, ['Verteilung geprüft']);
expect(report.materialItems.single.name, 'Sicherung');
expect(report.materialItems.single.quantity, '2');
});
test('Vollständigkeitsprüfung erkennt offene Pflichtangaben', () {
final report = DemoReport(
id: 'incomplete',
customer: 'Kunde',
site: 'Baustelle',
activity: '',
material: 'Kabel',
quantity: '',
date: DateTime(2026),
start: const TimeOfDay(hour: 10, minute: 0),
end: const TimeOfDay(hour: 9, minute: 0),
materialItems: const [
MaterialEntry(name: 'Kabel', quantity: '', unit: 'm'),
],
additionalOrder: 'Zusätzliche Steckdose',
);
expect(
ReportValidator.issues(report),
containsAll([
'Kein beteiligter Mitarbeiter ausgewählt',
'Mindestens eine ausgeführte Leistung fehlt',
'Arbeitsende muss nach Arbeitsbeginn liegen',
'Menge für Material 1 fehlt',
'Bestätigung des Zusatzauftrags fehlt',
]),
);
});
test('CSV-Export enthält strukturierte Berichtsdaten', () {
final report = DemoReport(
id: 'export',
customer: 'Müller; GmbH',
site: 'Werkhalle 1',
activity: 'Verteiler geprüft',
material: 'Kabel',
quantity: '12',
date: DateTime(2026, 8, 29),
start: const TimeOfDay(hour: 7, minute: 30),
end: const TimeOfDay(hour: 9, minute: 15),
participantIds: const ['max'],
activityItems: const ['Verteiler geprüft'],
materialItems: const [
MaterialEntry(name: 'Kabel', quantity: '12', unit: 'm'),
],
isFinalized: true,
finalizedAt: DateTime(2026, 8, 29, 9, 16),
breakMinutes: 30,
);
final document = ReportDocument(
report: report,
companyName: 'Elektro GmbH',
companyAddress: 'Hauptstraße 1',
companyContact: 'info@example.de',
reportFooter: 'Danke',
participantNames: const ['Max Mustermann'],
);
const exporter = CsvReportExporter();
final csv = exporter.asText(document);
expect(csv, contains('"Müller; GmbH"'));
expect(csv, contains('"Verteiler geprüft"'));
expect(csv, contains('"Kabel";"12";"m"'));
expect(csv, contains('"Max Mustermann"'));
expect(csv, contains('"Pause";"30 Minuten"'));
expect(csv, contains('"Nettoarbeitszeit";"1:15 Stunden"'));
expect(exporter.fileName(document), 'MACHWEIS_2026-08-29_Werkhalle_1.csv');
});
testWidgets('Demo-Einstieg führt zum Dashboard', (tester) async {
await tester.pumpWidget(const MachweisApp());
expect(find.text('MACHWEIS'), findsOneWidget);
expect(find.text('Demo starten'), findsOneWidget);
await tester.tap(find.text('Demo starten'));
await tester.pumpAndSettle();
expect(find.text('Als Demo-Nutzer anmelden'), findsOneWidget);
await tester.tap(find.text('Als Demo-Nutzer anmelden'));
await tester.pumpAndSettle();
expect(
find.byWidgetPredicate(
(widget) =>
widget is Text &&
(widget.data?.startsWith('Guten ') == true ||
widget.data?.startsWith('Gute Nacht') == true),
),
findsOneWidget,
);
expect(find.text('Neuen Nachweis erstellen'), findsOneWidget);
});
testWidgets('Smartphone-Layout verwendet erreichbare Bottom-Navigation', (
tester,
) async {
tester.view.physicalSize = const Size(390, 844);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.pumpWidget(const MaterialApp(home: AppShell()));
await tester.pumpAndSettle();
expect(find.byType(NavigationBar), findsOneWidget);
expect(find.byType(NavigationRail), findsNothing);
expect(find.text(AppVersion.label), findsOneWidget);
expect(tester.takeException(), isNull);
});
testWidgets('Web-Layout verwendet dauerhafte Seitennavigation', (
tester,
) async {
tester.view.physicalSize = const Size(1440, 900);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.pumpWidget(const MaterialApp(home: AppShell()));
await tester.pumpAndSettle();
expect(find.byType(NavigationRail), findsOneWidget);
expect(find.byType(NavigationBar), findsNothing);
expect(find.text(AppVersion.label), findsOneWidget);
expect(tester.takeException(), isNull);
});
testWidgets('Nachweiserfassung bleibt auf schmalem Smartphone überlauffrei', (
tester,
) async {
tester.view.physicalSize = const Size(360, 800);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.pumpWidget(const MaterialApp(home: CreateReportScreen()));
await tester.enterText(
find.widgetWithText(TextField, 'Kunde'),
'Testkunde',
);
await tester.enterText(
find.widgetWithText(TextField, 'Baustelle / Auftrag'),
'Baustelle',
);
await tester.tap(find.text('Weiter'));
await tester.pumpAndSettle();
expect(find.text('Was wurde gemacht?'), findsOneWidget);
expect(find.text('Baustellennotizen mit KI strukturieren'), findsOneWidget);
expect(tester.takeException(), isNull);
});
testWidgets('Kundensuche übernimmt gespeicherten Kunden', (tester) async {
const customer = Customer(
id: 'customer-search-test',
name: 'Suchkunde GmbH',
contact: '',
email: 'suchkunde@example.test',
phone: '',
address: 'Teststraße 1',
);
await CustomerStore.instance.add(customer);
addTearDown(() => CustomerStore.instance.remove(customer.id));
await tester.pumpWidget(const MaterialApp(home: CreateReportScreen()));
await tester.tap(find.text('Kunden suchen'));
await tester.pumpAndSettle();
await tester.enterText(
find.widgetWithText(TextField, 'Name eingeben'),
'Suchkunde',
);
await tester.tap(find.text('Suchkunde GmbH'));
await tester.pumpAndSettle();
expect(find.widgetWithText(TextField, 'Kunde'), findsOneWidget);
expect(
tester
.widget<TextField>(find.widgetWithText(TextField, 'Kunde'))
.controller
?.text,
'Suchkunde GmbH',
);
});
testWidgets('Berichtsaktionen bleiben auf schmalem iPhone geordnet', (
tester,
) async {
tester.view.physicalSize = const Size(360, 800);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
final reports = ReportsScreen(onCreate: () {});
final report = DemoReport(
id: 'dialog-layout-test',
customer: 'Kunde',
site: 'Schmale Baustelle',
activity: 'Montage',
material: '',
quantity: '',
date: DateTime(2026, 9, 1),
start: const TimeOfDay(hour: 7, minute: 30),
end: const TimeOfDay(hour: 16, minute: 0),
activityItems: const ['Montage'],
);
await tester.pumpWidget(
MaterialApp(
home: Builder(
builder: (context) => FilledButton(
onPressed: () => reports.showReport(context, report),
child: const Text('Öffnen'),
),
),
),
);
await tester.tap(find.text('Öffnen'));
await tester.pumpAndSettle();
final sign = tester.getRect(find.text('Unterschreiben'));
final close = tester.getRect(find.text('Schließen'));
expect(sign.bottom, lessThanOrEqualTo(close.top));
expect(tester.takeException(), isNull);
});
testWidgets('Nachweis-Workflow prüft Pflichtfelder', (tester) async {
await tester.pumpWidget(const MachweisApp());
await tester.tap(find.text('Demo starten'));
await tester.pumpAndSettle();
await tester.tap(find.text('Als Demo-Nutzer anmelden'));
await tester.pumpAndSettle();
await tester.tap(find.text('Nachweis starten'));
await tester.pumpAndSettle();
expect(find.text('Wo wurde gearbeitet?'), findsOneWidget);
await tester.tap(find.text('Weiter'));
await tester.pump();
expect(find.text('Bitte Kunde und Baustelle eintragen.'), findsOneWidget);
});
testWidgets('Nachweis wird in der Übersicht angezeigt', (tester) async {
await tester.pumpWidget(const MachweisApp());
await tester.tap(find.text('Demo starten'));
await tester.pumpAndSettle();
await tester.tap(find.text('Als Demo-Nutzer anmelden'));
await tester.pumpAndSettle();
await tester.tap(find.text('Nachweis starten'));
await tester.pumpAndSettle();
await tester.enterText(
find.widgetWithText(TextField, 'Kunde'),
'Testkunde',
);
await tester.enterText(
find.widgetWithText(TextField, 'Baustelle / Auftrag'),
'Testbaustelle',
);
await tester.tap(find.text('Weiter'));
await tester.pumpAndSettle();
await tester.enterText(
find.widgetWithText(TextField, 'Leistung 1'),
'Leitung montiert',
);
await tester.tap(find.text('Weiter'));
await tester.pumpAndSettle();
await tester.tap(find.text('Entwurf speichern'));
await tester.pumpAndSettle();
expect(find.text('Testbaustelle'), findsOneWidget);
expect(find.text('Bereit'), findsOneWidget);
});
test('Abgeschlossene Nachweise sind unveränderlich', () async {
final store = DemoReportStore.instance;
final report = store.reports.firstWhere(
(item) => item.site == 'Testbaustelle',
);
await store.finalize(report.id, const [
[0, 0],
[1, 1],
]);
final finalized = store.reports.firstWhere((item) => item.id == report.id);
await store.update(
DemoReport(
id: finalized.id,
customer: finalized.customer,
site: 'Manipuliert',
activity: finalized.activity,
material: finalized.material,
quantity: finalized.quantity,
date: finalized.date,
start: finalized.start,
end: finalized.end,
),
);
await store.remove(finalized.id);
expect(
store.reports.firstWhere((item) => item.id == finalized.id).site,
'Testbaustelle',
);
expect(
store.reports.firstWhere((item) => item.id == finalized.id).isFinalized,
isTrue,
);
final history = store.historyFor(finalized.id);
expect(history.map((entry) => entry.action), [
ReportAuditAction.finalized,
ReportAuditAction.created,
]);
expect(history.first.version, 2);
expect(history.last.snapshot.isFinalized, isFalse);
expect(history.first.snapshot.isFinalized, isTrue);
});
test('Bearbeitungen erzeugen Versionen und Löschspur', () async {
final store = DemoReportStore.instance;
final initial = DemoReport(
id: 'audit-test',
customer: 'Kunde A',
site: 'Baustelle A',
activity: 'Montage',
material: '',
quantity: '',
date: DateTime(2026, 8, 29),
start: const TimeOfDay(hour: 8, minute: 0),
end: const TimeOfDay(hour: 9, minute: 0),
);
await store.add(initial);
await store.update(
DemoReport(
id: initial.id,
customer: initial.customer,
site: 'Baustelle B',
activity: initial.activity,
material: initial.material,
quantity: initial.quantity,
date: initial.date,
start: initial.start,
end: initial.end,
),
);
await store.remove(initial.id);
expect(store.reports.where((item) => item.id == initial.id), isEmpty);
final history = store.historyFor(initial.id);
expect(history.map((entry) => entry.action), [
ReportAuditAction.deleted,
ReportAuditAction.updated,
ReportAuditAction.created,
]);
expect(history[1].version, 2);
expect(history.last.snapshot.site, 'Baustelle A');
expect(history.first.snapshot.site, 'Baustelle B');
});
}
