import 'package:flutter_test/flutter_test.dart'; import 'package:votianlt_app/models/job.dart'; import 'package:votianlt_app/models/job_progress.dart'; Map 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 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); }); }