Sperrt nachträgliche Aufgabenabschlüsse in App und Backend und zeigt erledigte Aufträge trotz offener optionaler Aufgaben grün an. Ergänzt Regressionstests, die Anredeauswahl und optionale Steuerangaben für Kunden. Enthält App-Version 0.9.25, Backend-Version 0.9.26 und das aktualisierte Frontend-Bundle.
161 lines
4.4 KiB
Dart
161 lines
4.4 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:votianlt_app/models/job.dart';
|
|
import 'package:votianlt_app/models/job_progress.dart';
|
|
|
|
Map<String, dynamic> task(
|
|
String id,
|
|
int order, {
|
|
bool completed = false,
|
|
bool optional = false,
|
|
}) => {
|
|
'id': id,
|
|
'jobId': 'job-1',
|
|
'stationOrder': order,
|
|
'completed': completed,
|
|
'optional': optional,
|
|
'taskSpecificData': {'taskType': 'CONFIRMATION', 'buttonText': 'Bestätigen'},
|
|
};
|
|
|
|
Job exampleJob({
|
|
String status = 'IN_PROGRESS',
|
|
List<Object> completedStations = const [],
|
|
bool mandatoryDone = true,
|
|
}) => Job.fromJson({
|
|
'id': 'job-1',
|
|
'status': status,
|
|
'completedStationOrders': completedStations,
|
|
'deliveryStations': [
|
|
{
|
|
'stationOrder': 0,
|
|
'tasks': [
|
|
task('required', 0, completed: mandatoryDone),
|
|
task('optional', 0, optional: true),
|
|
],
|
|
},
|
|
{
|
|
'stationOrder': 2,
|
|
'tasks': [task('final', 2, completed: true)],
|
|
},
|
|
],
|
|
});
|
|
|
|
void main() {
|
|
test(
|
|
'closed intermediate station locks optional tasks but leaves the next station open',
|
|
() {
|
|
final job = exampleJob(completedStations: [0]);
|
|
final progress = JobProgress(job);
|
|
expect(progress.isCompleted, isFalse);
|
|
expect(progress.canCompleteTask(job.tasks[1]), isFalse);
|
|
expect(progress.isStationClosed(0), isTrue);
|
|
expect(progress.isStationClosed(2), isFalse);
|
|
},
|
|
);
|
|
|
|
test(
|
|
'server completion is authoritative despite unfinished optional tasks',
|
|
() {
|
|
final job = exampleJob(status: 'COMPLETED');
|
|
final progress = JobProgress(job);
|
|
expect(progress.isCompleted, isTrue);
|
|
expect(progress.completedTaskCount, 2);
|
|
expect(progress.canCompleteTask(job.tasks[1]), isFalse);
|
|
expect(job.tasks[1].completed, isFalse);
|
|
},
|
|
);
|
|
|
|
test(
|
|
'all mandatory tasks alone do not complete the job without station confirmation',
|
|
() {
|
|
expect(JobProgress(exampleJob()).isCompleted, isFalse);
|
|
},
|
|
);
|
|
|
|
test(
|
|
'final station confirmation completes the job even with optional tasks open',
|
|
() {
|
|
expect(
|
|
JobProgress(exampleJob(completedStations: [0, 2])).isCompleted,
|
|
isTrue,
|
|
);
|
|
},
|
|
);
|
|
|
|
test(
|
|
'offline final station confirmation uses locally completed mandatory tasks',
|
|
() {
|
|
final progress = JobProgress(
|
|
exampleJob(mandatoryDone: false),
|
|
localCompletedStations: {0, 2},
|
|
taskStatuses: {'required': true},
|
|
);
|
|
expect(progress.isCompleted, isTrue);
|
|
expect(progress.completedTaskCount, 2);
|
|
},
|
|
);
|
|
|
|
test(
|
|
'final confirmation cannot bypass unfinished mandatory work at an earlier station',
|
|
() {
|
|
expect(
|
|
JobProgress(
|
|
exampleJob(mandatoryDone: false),
|
|
localCompletedStations: {2},
|
|
).isCompleted,
|
|
isFalse,
|
|
);
|
|
},
|
|
);
|
|
|
|
test(
|
|
'completed station confirmations survive normalization and JSON storage',
|
|
() {
|
|
final saved =
|
|
exampleJob(
|
|
completedStations: [0, '2', 2, 'invalid'],
|
|
).normalized().toJson();
|
|
final restored = Job.fromJson(saved);
|
|
expect(restored.completedStationOrders, [0, 2]);
|
|
expect(JobProgress(restored).isStationClosed(0), isTrue);
|
|
},
|
|
);
|
|
|
|
test(
|
|
'legacy job with no stations completes only after explicit station zero confirmation',
|
|
() {
|
|
final job = Job.fromJson({
|
|
'id': 'legacy',
|
|
'status': 'IN_PROGRESS',
|
|
'tasks': [task('optional', 0, optional: true)],
|
|
});
|
|
expect(JobProgress(job).isCompleted, isFalse);
|
|
expect(JobProgress(job, localCompletedStations: {0}).isCompleted, isTrue);
|
|
},
|
|
);
|
|
|
|
test('cancelled jobs remain locked and are not reported as completed', () {
|
|
final progress = JobProgress(
|
|
exampleJob(status: 'CANCELLED'),
|
|
localCompletedStations: {0, 2},
|
|
);
|
|
expect(progress.isCompleted, isFalse);
|
|
expect(progress.isStationClosed(2), isTrue);
|
|
});
|
|
|
|
test('embedded station order takes precedence over a stale task order', () {
|
|
final job = Job.fromJson({
|
|
'id': 'job-1',
|
|
'completedStationOrders': [0],
|
|
'deliveryStations': [
|
|
{
|
|
'stationOrder': 0,
|
|
'tasks': [task('optional', 8, optional: true)],
|
|
},
|
|
{'stationOrder': 1, 'tasks': []},
|
|
],
|
|
});
|
|
expect(job.tasks.single.stationOrder, 0);
|
|
expect(JobProgress(job).canCompleteTask(job.tasks.single), isFalse);
|
|
});
|
|
}
|