884 lines
27 KiB
Dart
884 lines
27 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:votianlt_app/models/job.dart';
|
|
import 'package:votianlt_app/models/cargo_item.dart';
|
|
import 'package:votianlt_app/models/task.dart';
|
|
import 'package:votianlt_app/models/tasks/confirmation_task.dart';
|
|
import 'package:votianlt_app/models/tasks/photo_task.dart';
|
|
import 'package:votianlt_app/models/tasks/signature_task.dart';
|
|
import 'package:votianlt_app/models/tasks/barcode_task.dart';
|
|
import 'package:votianlt_app/models/tasks/todolist_task.dart';
|
|
import 'package:votianlt_app/models/tasks/comment_task.dart';
|
|
import 'package:votianlt_app/models/tasks/generic_task.dart';
|
|
|
|
/// Test data based on job_json.md documentation from
|
|
/// https://www.appcreation.de/download/job_json.md
|
|
void main() {
|
|
// Complete job JSON according to documentation
|
|
final Map<String, dynamic> completeJobJson = {
|
|
'job': {
|
|
'id': {'timestamp': 1705312200, '\$oid': '65a4b5c8d4e5f6a7b8c9d0e1'},
|
|
'jobNumber': 'JOB-2024-001',
|
|
'status': 'ASSIGNED',
|
|
'createdAt': '2024-01-15T10:30:00.000Z',
|
|
'updatedAt': '2024-01-15T14:45:00.000Z',
|
|
'createdBy': 'admin@example.com',
|
|
'customerSelection': 'Kunde ABC GmbH',
|
|
'pickupCompany': 'Absender GmbH',
|
|
'pickupSalutation': 'Herr',
|
|
'pickupFirstName': 'Max',
|
|
'pickupLastName': 'Mustermann',
|
|
'pickupPhone': '+49 123 456789',
|
|
'pickupStreet': 'Hauptstraße',
|
|
'pickupHouseNumber': '42',
|
|
'pickupAddressAddition': 'Hinterhaus',
|
|
'pickupZip': '10115',
|
|
'pickupCity': 'Berlin',
|
|
'deliveryCompany': 'Empfänger AG',
|
|
'deliverySalutation': 'Frau',
|
|
'deliveryFirstName': 'Erika',
|
|
'deliveryLastName': 'Musterfrau',
|
|
'deliveryPhone': '+49 987 654321',
|
|
'deliveryStreet': 'Nebenstraße',
|
|
'deliveryHouseNumber': '7a',
|
|
'deliveryAddressAddition': null,
|
|
'deliveryZip': '80331',
|
|
'deliveryCity': 'München',
|
|
'digitalProcessing': true,
|
|
'appUser': 'driver@example.com',
|
|
'pickupDate': '2024-01-16',
|
|
'deliveryDate': '2024-01-17',
|
|
'remark': 'Bitte vorsichtig behandeln',
|
|
'price': 149.99,
|
|
'draft': false,
|
|
},
|
|
'cargoItems': [
|
|
{
|
|
'id': {'timestamp': 1705312201},
|
|
'jobId': {'timestamp': 1705312200},
|
|
'description': 'Palette mit Elektronik',
|
|
'quantity': 2,
|
|
'weightKg': 150.5,
|
|
'lengthMm': 1200.0,
|
|
'widthMm': 800.0,
|
|
'heightMm': 1000.0,
|
|
},
|
|
{
|
|
'id': {'timestamp': 1705312202},
|
|
'jobId': {'timestamp': 1705312200},
|
|
'description': 'Karton mit Dokumenten',
|
|
'quantity': 5,
|
|
'weightKg': 12.0,
|
|
'lengthMm': 400.0,
|
|
'widthMm': 300.0,
|
|
'heightMm': 200.0,
|
|
},
|
|
],
|
|
'tasks': [
|
|
{
|
|
'id': {'timestamp': 1705312210},
|
|
'jobId': {'timestamp': 1705312200},
|
|
'completed': false,
|
|
'completedAt': null,
|
|
'completedBy': null,
|
|
'taskOrder': 1,
|
|
'taskSpecificData': {
|
|
'taskType': 'CONFIRMATION',
|
|
'buttonText': 'Abholung bestätigen',
|
|
},
|
|
},
|
|
{
|
|
'id': {'timestamp': 1705312211},
|
|
'jobId': {'timestamp': 1705312200},
|
|
'completed': true,
|
|
'completedAt': '2024-01-16T09:15:00.000Z',
|
|
'completedBy': 'driver@example.com',
|
|
'taskOrder': 2,
|
|
'taskSpecificData': {'taskType': 'SIGNATURE'},
|
|
},
|
|
{
|
|
'id': {'timestamp': 1705312212},
|
|
'jobId': {'timestamp': 1705312200},
|
|
'completed': false,
|
|
'completedAt': null,
|
|
'completedBy': null,
|
|
'taskOrder': 3,
|
|
'taskSpecificData': {
|
|
'taskType': 'PHOTO',
|
|
'minPhotoCount': 2,
|
|
'maxPhotoCount': 10,
|
|
},
|
|
},
|
|
{
|
|
'id': {'timestamp': 1705312213},
|
|
'jobId': {'timestamp': 1705312200},
|
|
'completed': false,
|
|
'completedAt': null,
|
|
'completedBy': null,
|
|
'taskOrder': 4,
|
|
'taskSpecificData': {
|
|
'taskType': 'BARCODE',
|
|
'minBarcodeCount': 1,
|
|
'maxBarcodeCount': 5,
|
|
},
|
|
},
|
|
{
|
|
'id': {'timestamp': 1705312214},
|
|
'jobId': {'timestamp': 1705312200},
|
|
'completed': false,
|
|
'completedAt': null,
|
|
'completedBy': null,
|
|
'taskOrder': 5,
|
|
'taskSpecificData': {
|
|
'taskType': 'TODOLIST',
|
|
'todoItems': [
|
|
'Ladung sichern',
|
|
'Dokumente prüfen',
|
|
'Unterschrift einholen',
|
|
],
|
|
},
|
|
},
|
|
{
|
|
'id': {'timestamp': 1705312215},
|
|
'jobId': {'timestamp': 1705312200},
|
|
'completed': false,
|
|
'completedAt': null,
|
|
'completedBy': null,
|
|
'taskOrder': 6,
|
|
'taskSpecificData': {
|
|
'taskType': 'COMMENT',
|
|
'commentText': '',
|
|
'required': true,
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
group('Job Parsing', () {
|
|
late Job job;
|
|
|
|
setUp(() {
|
|
job = Job.fromJson(completeJobJson);
|
|
});
|
|
|
|
test('parses job basic fields correctly', () {
|
|
expect(job.id, '1705312200');
|
|
expect(job.jobNumber, 'JOB-2024-001');
|
|
expect(job.status, 'ASSIGNED');
|
|
expect(job.createdBy, 'admin@example.com');
|
|
expect(job.customerSelection, 'Kunde ABC GmbH');
|
|
expect(job.appUser, 'driver@example.com');
|
|
expect(job.remark, 'Bitte vorsichtig behandeln');
|
|
});
|
|
|
|
test('parses pickup address correctly', () {
|
|
expect(job.pickupCompany, 'Absender GmbH');
|
|
expect(job.pickupSalutation, 'Herr');
|
|
expect(job.pickupFirstName, 'Max');
|
|
expect(job.pickupLastName, 'Mustermann');
|
|
expect(job.pickupPhone, '+49 123 456789');
|
|
expect(job.pickupStreet, 'Hauptstraße');
|
|
expect(job.pickupHouseNumber, '42');
|
|
expect(job.pickupAddressAddition, 'Hinterhaus');
|
|
expect(job.pickupZip, '10115');
|
|
expect(job.pickupCity, 'Berlin');
|
|
});
|
|
|
|
test('parses delivery address correctly', () {
|
|
expect(job.deliveryCompany, 'Empfänger AG');
|
|
expect(job.deliverySalutation, 'Frau');
|
|
expect(job.deliveryFirstName, 'Erika');
|
|
expect(job.deliveryLastName, 'Musterfrau');
|
|
expect(job.deliveryPhone, '+49 987 654321');
|
|
expect(job.deliveryStreet, 'Nebenstraße');
|
|
expect(job.deliveryHouseNumber, '7a');
|
|
expect(job.deliveryAddressAddition, '');
|
|
expect(job.deliveryZip, '80331');
|
|
expect(job.deliveryCity, 'München');
|
|
});
|
|
|
|
test('parses date strings correctly', () {
|
|
expect(job.pickupDate, '2024-01-16');
|
|
expect(job.deliveryDate, '2024-01-17');
|
|
});
|
|
|
|
test('parses DateTime from ISO string', () {
|
|
expect(job.createdAt, DateTime.utc(2024, 1, 15, 10, 30, 0));
|
|
expect(job.updatedAt, DateTime.utc(2024, 1, 15, 14, 45, 0));
|
|
});
|
|
|
|
test('parses DateTime from array format', () {
|
|
final jsonWithArrayDate = Map<String, dynamic>.from(completeJobJson);
|
|
final jobData = Map<String, dynamic>.from(jsonWithArrayDate['job']);
|
|
jobData['createdAt'] = [2024, 1, 15, 10, 30, 0, 0];
|
|
jobData['updatedAt'] = [2024, 1, 15, 14, 45, 0, 500000000];
|
|
jsonWithArrayDate['job'] = jobData;
|
|
|
|
final jobWithArrayDate = Job.fromJson(jsonWithArrayDate);
|
|
|
|
expect(jobWithArrayDate.createdAt, DateTime(2024, 1, 15, 10, 30, 0, 0));
|
|
expect(jobWithArrayDate.updatedAt.year, 2024);
|
|
expect(jobWithArrayDate.updatedAt.month, 1);
|
|
expect(jobWithArrayDate.updatedAt.day, 15);
|
|
});
|
|
|
|
test('parses price as double', () {
|
|
expect(job.price, 149.99);
|
|
});
|
|
|
|
test('parses boolean fields correctly', () {
|
|
expect(job.digitalProcessing, true);
|
|
expect(job.draft, false);
|
|
});
|
|
|
|
test('parses cargoItems array', () {
|
|
expect(job.cargoItems.length, 2);
|
|
});
|
|
|
|
test('parses tasks array', () {
|
|
expect(job.tasks.length, 6);
|
|
});
|
|
|
|
test('parses delivery stations and flattens station tasks', () {
|
|
final jsonWithStations = {
|
|
'job': {
|
|
'id': 'station-job-1',
|
|
'jobNumber': 'JOB-STATION-001',
|
|
'status': 'CREATED',
|
|
'deliveryCompany': 'Legacy Delivery',
|
|
'deliveryStreet': 'Legacy Street',
|
|
'deliveryHouseNumber': '1',
|
|
'deliveryZip': '12345',
|
|
'deliveryCity': 'Legacy City',
|
|
'deliveryCitiesDisplay': 'Boostedt -> Geesthacht',
|
|
'firstDeliveryCity': 'Boostedt',
|
|
'lastDeliveryCity': 'Geesthacht',
|
|
'deliveryStations': [
|
|
{
|
|
'stationOrder': 0,
|
|
'company': 'Volker Hinst',
|
|
'street': 'Vossbarg',
|
|
'houseNumber': '24',
|
|
'zip': '24893',
|
|
'city': 'Boostedt',
|
|
'tasks': [
|
|
{
|
|
'id': 'station-task-1',
|
|
'jobId': 'station-job-1',
|
|
'taskOrder': 0,
|
|
'description': 'Erste Station',
|
|
'displayName': 'Bestätigung',
|
|
'taskSpecificData': {
|
|
'taskType': 'CONFIRMATION',
|
|
'buttonText': 'Blubb',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
'stationOrder': 1,
|
|
'company': 'Timm GmbH',
|
|
'street': 'Gerhart-Hauptmann-Weg',
|
|
'houseNumber': '14',
|
|
'zip': '21502',
|
|
'city': 'Geesthacht',
|
|
'tasks': [
|
|
{
|
|
'id': 'station-task-2',
|
|
'jobId': 'station-job-1',
|
|
'taskOrder': 0,
|
|
'description': 'Zweite Station',
|
|
'displayName': 'Bestätigung',
|
|
'taskSpecificData': {
|
|
'taskType': 'CONFIRMATION',
|
|
'buttonText': 'Blubb',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
'cargoItems': <dynamic>[],
|
|
'tasks': [
|
|
{
|
|
'id': 'legacy-task',
|
|
'jobId': 'station-job-1',
|
|
'taskOrder': 0,
|
|
'description': 'Legacy',
|
|
'taskSpecificData': {
|
|
'taskType': 'CONFIRMATION',
|
|
'buttonText': 'Legacy',
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
final jobWithStations = Job.fromJson(jsonWithStations);
|
|
|
|
expect(jobWithStations.deliveryStations.length, 2);
|
|
expect(jobWithStations.tasks.length, 2);
|
|
expect(jobWithStations.tasks[0].stationOrder, 0);
|
|
expect(jobWithStations.tasks[1].stationOrder, 1);
|
|
expect(
|
|
jobWithStations.deliveryStations[0].tasks.first.id,
|
|
'station-task-1',
|
|
);
|
|
expect(jobWithStations.deliveryCitiesDisplay, 'Boostedt -> Geesthacht');
|
|
});
|
|
|
|
test('extracts ID from Map object with timestamp', () {
|
|
expect(job.id, '1705312200');
|
|
});
|
|
|
|
test('extracts ID from Map object with \$oid fallback', () {
|
|
final jsonWithOidOnly = {
|
|
'job': {
|
|
'id': {'\$oid': '65a4b5c8d4e5f6a7b8c9d0e1'},
|
|
'jobNumber': 'JOB-TEST',
|
|
'status': 'CREATED',
|
|
},
|
|
'cargoItems': <dynamic>[],
|
|
'tasks': <dynamic>[],
|
|
};
|
|
|
|
final jobWithOid = Job.fromJson(jsonWithOidOnly);
|
|
expect(jobWithOid.id, '65a4b5c8d4e5f6a7b8c9d0e1');
|
|
});
|
|
|
|
test('handles flat JSON structure (without nested job object)', () {
|
|
final flatJson = {
|
|
'id': 'flat-job-id-123',
|
|
'jobNumber': 'JOB-FLAT-001',
|
|
'status': 'CREATED',
|
|
'createdAt': '2024-01-15T10:30:00.000Z',
|
|
'updatedAt': '2024-01-15T10:30:00.000Z',
|
|
'createdBy': 'test@test.de',
|
|
'customerSelection': 'Test Kunde',
|
|
'pickupCompany': 'Test Firma',
|
|
'pickupFirstName': 'Test',
|
|
'pickupLastName': 'User',
|
|
'pickupPhone': '12345',
|
|
'pickupStreet': 'Teststr',
|
|
'pickupHouseNumber': '1',
|
|
'pickupAddressAddition': '',
|
|
'pickupZip': '12345',
|
|
'pickupCity': 'Teststadt',
|
|
'deliveryCompany': 'Ziel Firma',
|
|
'deliveryFirstName': 'Ziel',
|
|
'deliveryLastName': 'Person',
|
|
'deliveryPhone': '54321',
|
|
'deliveryStreet': 'Zielstr',
|
|
'deliveryHouseNumber': '2',
|
|
'deliveryAddressAddition': '',
|
|
'deliveryZip': '54321',
|
|
'deliveryCity': 'Zielstadt',
|
|
'digitalProcessing': false,
|
|
'appUser': 'user@test.de',
|
|
'pickupDate': '2024-01-20',
|
|
'deliveryDate': '2024-01-21',
|
|
'remark': '',
|
|
'price': 0.0,
|
|
'draft': true,
|
|
};
|
|
|
|
final flatJob = Job.fromJson(flatJson);
|
|
|
|
expect(flatJob.id, 'flat-job-id-123');
|
|
expect(flatJob.jobNumber, 'JOB-FLAT-001');
|
|
expect(flatJob.draft, true);
|
|
});
|
|
});
|
|
|
|
group('CargoItem Parsing', () {
|
|
late Job job;
|
|
|
|
setUp(() {
|
|
job = Job.fromJson(completeJobJson);
|
|
});
|
|
|
|
test('parses CargoItem fields correctly', () {
|
|
final cargoItem = job.cargoItems[0];
|
|
|
|
expect(cargoItem.id, '1705312201');
|
|
expect(cargoItem.jobId, '1705312200');
|
|
expect(cargoItem.description, 'Palette mit Elektronik');
|
|
expect(cargoItem.quantity, 2);
|
|
expect(cargoItem.weightKg, 150.5);
|
|
expect(cargoItem.lengthCm, 1200.0);
|
|
expect(cargoItem.widthCm, 800.0);
|
|
expect(cargoItem.heightCm, 1000.0);
|
|
});
|
|
|
|
test('parses multiple CargoItems', () {
|
|
expect(job.cargoItems.length, 2);
|
|
expect(job.cargoItems[0].description, 'Palette mit Elektronik');
|
|
expect(job.cargoItems[1].description, 'Karton mit Dokumenten');
|
|
});
|
|
|
|
test('extracts CargoItem ID from Map object', () {
|
|
expect(job.cargoItems[0].id, '1705312201');
|
|
expect(job.cargoItems[1].id, '1705312202');
|
|
});
|
|
|
|
test('handles CargoItem with simple string ID', () {
|
|
final cargoJson = {
|
|
'id': 'simple-string-id',
|
|
'jobId': 'simple-job-id',
|
|
'description': 'Test Item',
|
|
'quantity': 1,
|
|
'weightKg': 10.0,
|
|
'lengthMm': 100.0,
|
|
'widthMm': 100.0,
|
|
'heightMm': 100.0,
|
|
};
|
|
|
|
final cargoItem = CargoItem.fromJson(cargoJson);
|
|
|
|
expect(cargoItem.id, 'simple-string-id');
|
|
expect(cargoItem.jobId, 'simple-job-id');
|
|
});
|
|
});
|
|
|
|
group('Task Parsing', () {
|
|
late Job job;
|
|
|
|
setUp(() {
|
|
job = Job.fromJson(completeJobJson);
|
|
});
|
|
|
|
test('creates ConfirmationTask with buttonText', () {
|
|
final task = job.tasks[0];
|
|
|
|
expect(task, isA<ConfirmationTask>());
|
|
final confirmationTask = task as ConfirmationTask;
|
|
expect(confirmationTask.taskOrder, 1);
|
|
expect(confirmationTask.buttonText, 'Abholung bestätigen');
|
|
expect(confirmationTask.completed, false);
|
|
});
|
|
|
|
test('creates SignatureTask', () {
|
|
final task = job.tasks[1];
|
|
|
|
expect(task, isA<SignatureTask>());
|
|
expect(task.taskOrder, 2);
|
|
expect(task.completed, true);
|
|
expect(task.completedBy, 'driver@example.com');
|
|
});
|
|
|
|
test('creates PhotoTask with min/maxPhotoCount', () {
|
|
final task = job.tasks[2];
|
|
|
|
expect(task, isA<PhotoTask>());
|
|
final photoTask = task as PhotoTask;
|
|
expect(photoTask.taskOrder, 3);
|
|
expect(photoTask.minPhotoCount, 2);
|
|
expect(photoTask.maxPhotoCount, 10);
|
|
});
|
|
|
|
test('creates BarcodeTask with min/maxBarcodeCount', () {
|
|
final task = job.tasks[3];
|
|
|
|
expect(task, isA<BarcodeTask>());
|
|
final barcodeTask = task as BarcodeTask;
|
|
expect(barcodeTask.taskOrder, 4);
|
|
expect(barcodeTask.minBarcodeCount, 1);
|
|
expect(barcodeTask.maxBarcodeCount, 5);
|
|
});
|
|
|
|
test('creates TodoListTask with todoItems', () {
|
|
final task = job.tasks[4];
|
|
|
|
expect(task, isA<TodoListTask>());
|
|
final todoListTask = task as TodoListTask;
|
|
expect(todoListTask.taskOrder, 5);
|
|
expect(todoListTask.todoItems.length, 3);
|
|
expect(todoListTask.todoItems[0], 'Ladung sichern');
|
|
expect(todoListTask.todoItems[1], 'Dokumente prüfen');
|
|
expect(todoListTask.todoItems[2], 'Unterschrift einholen');
|
|
});
|
|
|
|
test('creates CommentTask with commentText and required', () {
|
|
final task = job.tasks[5];
|
|
|
|
expect(task, isA<CommentTask>());
|
|
final commentTask = task as CommentTask;
|
|
expect(commentTask.taskOrder, 6);
|
|
expect(commentTask.commentText, '');
|
|
expect(commentTask.required, true);
|
|
});
|
|
|
|
test('falls back to GenericTask for unknown task type', () {
|
|
final unknownTaskJson = {
|
|
'id': {'timestamp': 1705312299},
|
|
'jobId': {'timestamp': 1705312200},
|
|
'completed': false,
|
|
'taskOrder': 99,
|
|
'taskSpecificData': {'taskType': 'UNKNOWN_TYPE'},
|
|
};
|
|
|
|
final task = Task.fromJson(unknownTaskJson);
|
|
|
|
expect(task, isA<GenericTask>());
|
|
});
|
|
|
|
test('falls back to GenericTask when taskType is missing', () {
|
|
final noTypeTaskJson = {
|
|
'id': {'timestamp': 1705312298},
|
|
'jobId': {'timestamp': 1705312200},
|
|
'completed': false,
|
|
'taskOrder': 98,
|
|
'taskSpecificData': <String, dynamic>{},
|
|
};
|
|
|
|
final task = Task.fromJson(noTypeTaskJson);
|
|
|
|
expect(task, isA<GenericTask>());
|
|
});
|
|
|
|
test('parses completedAt from ISO string', () {
|
|
final task = job.tasks[1];
|
|
|
|
expect(task.completedAt, DateTime.utc(2024, 1, 16, 9, 15, 0));
|
|
});
|
|
|
|
test('parses completedAt from array format', () {
|
|
final taskJsonWithArrayDate = {
|
|
'id': {'timestamp': 1705312220},
|
|
'jobId': {'timestamp': 1705312200},
|
|
'completed': true,
|
|
'completedAt': [2024, 1, 16, 9, 15, 0, 0],
|
|
'completedBy': 'driver@example.com',
|
|
'taskOrder': 10,
|
|
'taskSpecificData': {'taskType': 'SIGNATURE'},
|
|
};
|
|
|
|
final task = Task.fromJson(taskJsonWithArrayDate);
|
|
|
|
expect(task.completedAt, DateTime(2024, 1, 16, 9, 15, 0, 0));
|
|
});
|
|
|
|
test('extracts task ID from Map object', () {
|
|
final task = job.tasks[0];
|
|
expect(task.id, '1705312210');
|
|
});
|
|
|
|
test('extracts task jobId from Map object', () {
|
|
final task = job.tasks[0];
|
|
expect(task.jobId, '1705312200');
|
|
});
|
|
});
|
|
|
|
group('Task Defaults', () {
|
|
test('ConfirmationTask uses default buttonText', () {
|
|
final taskJson = {
|
|
'id': 'task-1',
|
|
'jobId': 'job-1',
|
|
'taskOrder': 1,
|
|
'taskSpecificData': {'taskType': 'CONFIRMATION'},
|
|
};
|
|
|
|
final task = Task.fromJson(taskJson) as ConfirmationTask;
|
|
expect(task.buttonText, 'Bestätigen');
|
|
});
|
|
|
|
test('PhotoTask uses default min/max counts', () {
|
|
final taskJson = {
|
|
'id': 'task-2',
|
|
'jobId': 'job-1',
|
|
'taskOrder': 2,
|
|
'taskSpecificData': {'taskType': 'PHOTO'},
|
|
};
|
|
|
|
final task = Task.fromJson(taskJson) as PhotoTask;
|
|
expect(task.minPhotoCount, 1);
|
|
expect(task.maxPhotoCount, 5);
|
|
});
|
|
|
|
test('BarcodeTask uses default min/max counts', () {
|
|
final taskJson = {
|
|
'id': 'task-3',
|
|
'jobId': 'job-1',
|
|
'taskOrder': 3,
|
|
'taskSpecificData': {'taskType': 'BARCODE'},
|
|
};
|
|
|
|
final task = Task.fromJson(taskJson) as BarcodeTask;
|
|
expect(task.minBarcodeCount, 1);
|
|
expect(task.maxBarcodeCount, 10);
|
|
});
|
|
|
|
test('CommentTask uses default values', () {
|
|
final taskJson = {
|
|
'id': 'task-4',
|
|
'jobId': 'job-1',
|
|
'taskOrder': 4,
|
|
'taskSpecificData': {'taskType': 'COMMENT'},
|
|
};
|
|
|
|
final task = Task.fromJson(taskJson) as CommentTask;
|
|
expect(task.commentText, '');
|
|
expect(task.required, false);
|
|
});
|
|
|
|
test('TodoListTask handles empty todoItems', () {
|
|
final taskJson = {
|
|
'id': 'task-5',
|
|
'jobId': 'job-1',
|
|
'taskOrder': 5,
|
|
'taskSpecificData': {'taskType': 'TODOLIST'},
|
|
};
|
|
|
|
final task = Task.fromJson(taskJson) as TodoListTask;
|
|
expect(task.todoItems, isEmpty);
|
|
});
|
|
});
|
|
|
|
group('Edge Cases', () {
|
|
test('handles empty cargoItems array', () {
|
|
final jsonWithEmptyCargoItems = Map<String, dynamic>.from(
|
|
completeJobJson,
|
|
);
|
|
jsonWithEmptyCargoItems['cargoItems'] = <dynamic>[];
|
|
|
|
final job = Job.fromJson(jsonWithEmptyCargoItems);
|
|
|
|
expect(job.cargoItems, isEmpty);
|
|
});
|
|
|
|
test('handles empty tasks array', () {
|
|
final jsonWithEmptyTasks = Map<String, dynamic>.from(completeJobJson);
|
|
jsonWithEmptyTasks['tasks'] = <dynamic>[];
|
|
|
|
final job = Job.fromJson(jsonWithEmptyTasks);
|
|
|
|
expect(job.tasks, isEmpty);
|
|
});
|
|
|
|
test('handles missing optional fields with defaults', () {
|
|
final minimalJson = {
|
|
'job': {'jobNumber': 'JOB-MIN-001'},
|
|
'cargoItems': <dynamic>[],
|
|
'tasks': <dynamic>[],
|
|
};
|
|
|
|
final job = Job.fromJson(minimalJson);
|
|
|
|
expect(job.jobNumber, 'JOB-MIN-001');
|
|
expect(job.status, 'UNKNOWN');
|
|
expect(job.pickupCompany, '');
|
|
expect(job.deliveryCompany, '');
|
|
expect(job.digitalProcessing, false);
|
|
expect(job.price, 0.0);
|
|
expect(job.draft, false);
|
|
});
|
|
|
|
test('handles null values in optional fields', () {
|
|
final jsonWithNulls = {
|
|
'job': {
|
|
'id': 'null-test-id',
|
|
'jobNumber': 'JOB-NULL-001',
|
|
'status': 'CREATED',
|
|
'pickupSalutation': null,
|
|
'deliverySalutation': null,
|
|
'pickupAddressAddition': null,
|
|
'deliveryAddressAddition': null,
|
|
'remark': null,
|
|
},
|
|
'cargoItems': <dynamic>[],
|
|
'tasks': <dynamic>[],
|
|
};
|
|
|
|
final job = Job.fromJson(jsonWithNulls);
|
|
|
|
expect(job.pickupSalutation, isNull);
|
|
expect(job.deliverySalutation, isNull);
|
|
expect(job.pickupAddressAddition, '');
|
|
expect(job.deliveryAddressAddition, '');
|
|
expect(job.remark, '');
|
|
});
|
|
|
|
test('generates ID from jobNumber when ID is missing', () {
|
|
final jsonWithoutId = {
|
|
'job': {'jobNumber': 'JOB-NOID-001'},
|
|
'cargoItems': <dynamic>[],
|
|
'tasks': <dynamic>[],
|
|
};
|
|
|
|
final job = Job.fromJson(jsonWithoutId);
|
|
|
|
expect(job.id, 'jobnum:JOB-NOID-001');
|
|
});
|
|
});
|
|
|
|
group('Roundtrip (fromJson -> toJson -> fromJson)', () {
|
|
test('Job preserves data through serialization', () {
|
|
final original = Job.fromJson(completeJobJson);
|
|
final json = original.toJson();
|
|
final restored = Job.fromJson(json);
|
|
|
|
expect(restored.id, original.id);
|
|
expect(restored.jobNumber, original.jobNumber);
|
|
expect(restored.status, original.status);
|
|
expect(restored.pickupCompany, original.pickupCompany);
|
|
expect(restored.pickupCity, original.pickupCity);
|
|
expect(restored.deliveryCompany, original.deliveryCompany);
|
|
expect(restored.deliveryCity, original.deliveryCity);
|
|
expect(restored.price, original.price);
|
|
expect(restored.digitalProcessing, original.digitalProcessing);
|
|
});
|
|
|
|
test('CargoItem preserves data through serialization', () {
|
|
final original = Job.fromJson(completeJobJson).cargoItems[0];
|
|
final json = original.toJson();
|
|
final restored = CargoItem.fromJson(json);
|
|
|
|
expect(restored.id, original.id);
|
|
expect(restored.jobId, original.jobId);
|
|
expect(restored.description, original.description);
|
|
expect(restored.quantity, original.quantity);
|
|
expect(restored.weightKg, original.weightKg);
|
|
expect(restored.lengthCm, original.lengthCm);
|
|
});
|
|
|
|
test('ConfirmationTask preserves data through serialization', () {
|
|
final original =
|
|
Job.fromJson(completeJobJson).tasks[0] as ConfirmationTask;
|
|
final json = original.toJson();
|
|
final restored = Task.fromJson(json) as ConfirmationTask;
|
|
|
|
expect(restored.id, original.id);
|
|
expect(restored.jobId, original.jobId);
|
|
expect(restored.buttonText, original.buttonText);
|
|
expect(restored.taskOrder, original.taskOrder);
|
|
});
|
|
|
|
test('PhotoTask preserves data through serialization', () {
|
|
final original = Job.fromJson(completeJobJson).tasks[2] as PhotoTask;
|
|
final json = original.toJson();
|
|
final restored = Task.fromJson(json) as PhotoTask;
|
|
|
|
expect(restored.minPhotoCount, original.minPhotoCount);
|
|
expect(restored.maxPhotoCount, original.maxPhotoCount);
|
|
});
|
|
|
|
test('TodoListTask preserves data through serialization', () {
|
|
final original = Job.fromJson(completeJobJson).tasks[4] as TodoListTask;
|
|
final json = original.toJson();
|
|
final restored = Task.fromJson(json) as TodoListTask;
|
|
|
|
expect(restored.todoItems, original.todoItems);
|
|
});
|
|
|
|
test('CommentTask preserves data through serialization', () {
|
|
final original = Job.fromJson(completeJobJson).tasks[5] as CommentTask;
|
|
final json = original.toJson();
|
|
final restored = Task.fromJson(json) as CommentTask;
|
|
|
|
expect(restored.commentText, original.commentText);
|
|
expect(restored.required, original.required);
|
|
});
|
|
});
|
|
|
|
group('Job Status', () {
|
|
test('statusDisplayText returns German text for known statuses', () {
|
|
final statuses = {
|
|
'CREATED': 'Erstellt',
|
|
'PENDING': 'Wartend',
|
|
'ASSIGNED': 'Zugewiesen',
|
|
'IN_PROGRESS': 'In Bearbeitung',
|
|
'STARTED': 'In Bearbeitung',
|
|
'COMPLETED': 'Abgeschlossen',
|
|
'DONE': 'Abgeschlossen',
|
|
'CANCELLED': 'Abgebrochen',
|
|
'FAILED': 'Fehlgeschlagen',
|
|
};
|
|
|
|
for (final entry in statuses.entries) {
|
|
final json = {
|
|
'job': {
|
|
'id': 'status-test',
|
|
'jobNumber': 'JOB-STATUS',
|
|
'status': entry.key,
|
|
},
|
|
'cargoItems': <dynamic>[],
|
|
'tasks': <dynamic>[],
|
|
};
|
|
|
|
final job = Job.fromJson(json);
|
|
expect(
|
|
job.statusDisplayText,
|
|
entry.value,
|
|
reason: 'Status ${entry.key} should display as ${entry.value}',
|
|
);
|
|
}
|
|
});
|
|
|
|
test('statusColor returns correct color for statuses', () {
|
|
final statusColors = {
|
|
'CREATED': 'orange',
|
|
'ASSIGNED': 'orange',
|
|
'IN_PROGRESS': 'blue',
|
|
'COMPLETED': 'green',
|
|
'CANCELLED': 'red',
|
|
};
|
|
|
|
for (final entry in statusColors.entries) {
|
|
final json = {
|
|
'job': {
|
|
'id': 'color-test',
|
|
'jobNumber': 'JOB-COLOR',
|
|
'status': entry.key,
|
|
},
|
|
'cargoItems': <dynamic>[],
|
|
'tasks': <dynamic>[],
|
|
};
|
|
|
|
final job = Job.fromJson(json);
|
|
expect(
|
|
job.statusColor,
|
|
entry.value,
|
|
reason: 'Status ${entry.key} should have color ${entry.value}',
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
group('Job normalized()', () {
|
|
test('trims string fields', () {
|
|
final json = {
|
|
'job': {
|
|
'id': 'normalize-test',
|
|
'jobNumber': ' JOB-TRIM ',
|
|
'status': ' ASSIGNED ',
|
|
'pickupCompany': ' Test Company ',
|
|
'pickupCity': ' Berlin ',
|
|
},
|
|
'cargoItems': <dynamic>[],
|
|
'tasks': <dynamic>[],
|
|
};
|
|
|
|
final job = Job.fromJson(json).normalized();
|
|
|
|
expect(job.jobNumber, 'JOB-TRIM');
|
|
expect(job.status, 'ASSIGNED');
|
|
expect(job.pickupCompany, 'Test Company');
|
|
expect(job.pickupCity, 'Berlin');
|
|
});
|
|
|
|
test('converts null strings to empty strings', () {
|
|
final json = {
|
|
'job': {
|
|
'id': 'null-normalize-test',
|
|
'jobNumber': 'JOB-NULL',
|
|
'pickupSalutation': null,
|
|
},
|
|
'cargoItems': <dynamic>[],
|
|
'tasks': <dynamic>[],
|
|
};
|
|
|
|
final job = Job.fromJson(json).normalized();
|
|
|
|
expect(job.pickupSalutation, '');
|
|
});
|
|
});
|
|
}
|