Skip to content

Commit 384bc47

Browse files
authored
Implement Decoding Tests (#336)
1 parent 8f5e3e6 commit 384bc47

22 files changed

Lines changed: 2331 additions & 7 deletions

test/api_test.dart

Lines changed: 0 additions & 7 deletions
This file was deleted.

test/eatApi/cafeteria_test.dart

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import 'dart:convert';
2+
3+
import 'package:campus_flutter/placesComponent/model/cafeterias/cafeteria.dart';
4+
import 'package:campus_flutter/placesComponent/model/cafeterias/dish.dart';
5+
import 'package:campus_flutter/placesComponent/model/cafeterias/meal_plan.dart';
6+
import 'package:flutter_test/flutter_test.dart';
7+
8+
void main() {
9+
Map<String, dynamic> j(String s) => jsonDecode(s) as Map<String, dynamic>;
10+
11+
group('Cafeteria JSON decoding', () {
12+
test('decodes a full cafeteria with opening hours', () {
13+
final json = j('''
14+
{
15+
"location": {
16+
"latitude": 48.14735,
17+
"longitude": 11.56774,
18+
"address": "Arcisstraße 17, 80333 München"
19+
},
20+
"name": "Mensa Arcisstraße",
21+
"canteen_id": "mensa-arcisstr",
22+
"queue_status": "low",
23+
"open_hours": {
24+
"mon": {"start": "11:00", "end": "14:00"},
25+
"tue": {"start": "11:00", "end": "14:00"},
26+
"wed": {"start": "11:00", "end": "14:00"},
27+
"thu": {"start": "11:00", "end": "14:00"},
28+
"fri": {"start": "11:00", "end": "13:30"}
29+
}
30+
}
31+
''');
32+
final cafeteria = Cafeteria.fromJson(json);
33+
34+
expect(cafeteria.name, 'Mensa Arcisstraße');
35+
expect(cafeteria.id, 'mensa-arcisstr');
36+
expect(cafeteria.queueStatusApi, 'low');
37+
expect(cafeteria.location.latitude, closeTo(48.14735, 0.0001));
38+
expect(cafeteria.location.longitude, closeTo(11.56774, 0.0001));
39+
expect(cafeteria.location.address, 'Arcisstraße 17, 80333 München');
40+
41+
expect(cafeteria.openingHours, isNotNull);
42+
expect(cafeteria.openingHours!.mon?.start, '11:00');
43+
expect(cafeteria.openingHours!.fri?.end, '13:30');
44+
45+
// Weekdays 1–5 all return (true, OpeningHour)
46+
// 2024-04-15 Mon, 2024-04-16 Tue … 2024-04-19 Fri, 2024-04-20 Sat
47+
expect(cafeteria.openingHoursForDate(DateTime(2024, 4, 15)).$1, isTrue); // Monday
48+
expect(cafeteria.openingHoursForDate(DateTime(2024, 4, 16)).$1, isTrue); // Tuesday
49+
expect(cafeteria.openingHoursForDate(DateTime(2024, 4, 17)).$1, isTrue); // Wednesday
50+
expect(cafeteria.openingHoursForDate(DateTime(2024, 4, 18)).$1, isTrue); // Thursday
51+
expect(cafeteria.openingHoursForDate(DateTime(2024, 4, 19)).$1, isTrue); // Friday
52+
expect(cafeteria.openingHoursForDate(DateTime(2024, 4, 20)).$1, isFalse); // Saturday
53+
expect(cafeteria.openingHoursForDate(DateTime(2024, 4, 21)).$1, isFalse); // Sunday
54+
// null date → default case
55+
expect(cafeteria.openingHoursForDate(null).$1, isFalse);
56+
});
57+
58+
test('decodes a cafeteria without optional opening hours', () {
59+
final json = j('''
60+
{
61+
"location": {"latitude": 48.265, "longitude": 11.668, "address": "Boltzmannstraße 15, 85748 Garching"},
62+
"name": "StuBistro Garching",
63+
"canteen_id": "stubistro-garching",
64+
"queue_status": null
65+
}
66+
''');
67+
final cafeteria = Cafeteria.fromJson(json);
68+
69+
expect(cafeteria.openingHours, isNull);
70+
expect(cafeteria.queueStatusApi, isNull);
71+
});
72+
73+
test('Cafeterias wrapper decodes list from "data" key', () {
74+
final json = j('''
75+
{
76+
"data": [
77+
{
78+
"location": {"latitude": 48.14735, "longitude": 11.56774, "address": "Arcisstraße 17"},
79+
"name": "Mensa Arcisstraße",
80+
"canteen_id": "mensa-arcisstr",
81+
"queue_status": null
82+
},
83+
{
84+
"location": {"latitude": 48.265, "longitude": 11.668, "address": "Boltzmannstraße 15"},
85+
"name": "StuBistro Garching",
86+
"canteen_id": "stubistro-garching",
87+
"queue_status": null
88+
}
89+
]
90+
}
91+
''');
92+
final cafeterias = Cafeterias.fromJson(json);
93+
94+
expect(cafeterias.cafeterias.length, 2);
95+
expect(cafeterias.cafeterias[0].id, 'mensa-arcisstr');
96+
expect(cafeterias.cafeterias[1].id, 'stubistro-garching');
97+
});
98+
});
99+
100+
group('Dish JSON decoding', () {
101+
test('decodes a dish with prices and labels', () {
102+
final json = j('''
103+
{
104+
"name": "Schnitzel mit Pommes",
105+
"prices": {
106+
"students": {"base_price": 2.50, "price_per_unit": null, "unit": null},
107+
"staff": {"base_price": 4.00, "price_per_unit": null, "unit": null}
108+
},
109+
"labels": ["veal", "gluten"],
110+
"dish_type": "Hauptspeise"
111+
}
112+
''');
113+
final dish = Dish.fromJson(json);
114+
115+
expect(dish.name, 'Schnitzel mit Pommes');
116+
expect(dish.dishType, 'Hauptspeise');
117+
expect(dish.labels, containsAll(['veal', 'gluten']));
118+
expect(dish.prices['students']?.basePrice, closeTo(2.50, 0.001));
119+
expect(dish.prices['staff']?.basePrice, closeTo(4.00, 0.001));
120+
});
121+
122+
test('decodes a dish with no labels', () {
123+
final json = j('''
124+
{
125+
"name": "Apfelschorle",
126+
"prices": {},
127+
"labels": [],
128+
"dish_type": "Beilage"
129+
}
130+
''');
131+
final dish = Dish.fromJson(json);
132+
133+
expect(dish.labels, isEmpty);
134+
expect(dish.prices, isEmpty);
135+
});
136+
});
137+
138+
group('MealPlan / MensaMenu JSON decoding', () {
139+
test('decodes a weekly meal plan with multiple days', () {
140+
final json = j('''
141+
{
142+
"number": 16,
143+
"year": 2024,
144+
"days": [
145+
{
146+
"date": "2024-04-15",
147+
"dishes": [
148+
{
149+
"name": "Pasta",
150+
"prices": {"students": {"base_price": 1.80}},
151+
"labels": ["vegan"],
152+
"dish_type": "Hauptspeise"
153+
}
154+
]
155+
},
156+
{
157+
"date": "2024-04-16",
158+
"dishes": []
159+
}
160+
]
161+
}
162+
''');
163+
final plan = MealPlan.fromJson(json);
164+
165+
expect(plan.week, 16);
166+
expect(plan.year, 2024);
167+
expect(plan.days.length, 2);
168+
expect(plan.days[0].date, DateTime.parse('2024-04-15'));
169+
expect(plan.days[0].dishes.length, 1);
170+
expect(plan.days[0].dishes[0].name, 'Pasta');
171+
expect(plan.days[1].dishes, isEmpty);
172+
});
173+
174+
test('decodes an empty meal plan', () {
175+
final json = j('{"number": 1, "year": 2024, "days": []}');
176+
final plan = MealPlan.fromJson(json);
177+
178+
expect(plan.days, isEmpty);
179+
});
180+
});
181+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import 'dart:convert';
2+
3+
import 'package:campus_flutter/base/networking/base/api_response.dart';
4+
import 'package:flutter_test/flutter_test.dart';
5+
6+
void main() {
7+
group('ApiResponse.fromJson', () {
8+
test('decodes when root JSON is a Map', () {
9+
final json = jsonDecode('{"name": "Test", "value": 42}');
10+
final extras = <String, dynamic>{};
11+
12+
final response = ApiResponse<Map<String, dynamic>>.fromJson(
13+
json,
14+
extras,
15+
(j) => j,
16+
);
17+
18+
expect(response.data['name'], 'Test');
19+
expect(response.data['value'], 42);
20+
expect(response.saved, isNull);
21+
});
22+
23+
test('wraps root JSON List in {"data": [...]} before calling create', () {
24+
final json = jsonDecode('[{"id": 1}, {"id": 2}]');
25+
final extras = <String, dynamic>{};
26+
27+
final response = ApiResponse<List<dynamic>>.fromJson(
28+
json,
29+
extras,
30+
(j) => j['data'] as List<dynamic>,
31+
);
32+
33+
expect(response.data.length, 2);
34+
expect((response.data[0] as Map)['id'], 1);
35+
});
36+
37+
test('threads "saved" DateTime through from extras', () {
38+
final savedAt = DateTime(2024, 4, 15, 10, 0, 0);
39+
final json = jsonDecode('{"key": "value"}');
40+
final extras = <String, dynamic>{'saved': savedAt};
41+
42+
final response = ApiResponse<Map<String, dynamic>>.fromJson(
43+
json,
44+
extras,
45+
(j) => j,
46+
);
47+
48+
expect(response.saved, savedAt);
49+
});
50+
51+
test('saved is null when not present in extras', () {
52+
final json = jsonDecode('{"key": "value"}');
53+
final extras = <String, dynamic>{};
54+
55+
final response = ApiResponse<Map<String, dynamic>>.fromJson(
56+
json,
57+
extras,
58+
(j) => j,
59+
);
60+
61+
expect(response.saved, isNull);
62+
});
63+
64+
test('passes decoded data through the create function', () {
65+
final json = jsonDecode('{"count": "5"}');
66+
final extras = <String, dynamic>{};
67+
68+
final response = ApiResponse<int>.fromJson(
69+
json,
70+
extras,
71+
(j) => int.parse(j['count'] as String),
72+
);
73+
74+
expect(response.data, 5);
75+
});
76+
});
77+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import 'package:campus_flutter/base/util/read_list_value.dart';
2+
import 'package:flutter_test/flutter_test.dart';
3+
4+
void main() {
5+
group('readListValue', () {
6+
test('returns empty list when key is absent', () {
7+
final result = readListValue({'other': 'value'}, 'location');
8+
expect(result, isEmpty);
9+
});
10+
11+
test('returns empty list when value is null', () {
12+
final result = readListValue({'location': null}, 'location');
13+
expect(result, isEmpty);
14+
});
15+
16+
test('passes a List through unchanged', () {
17+
final result = readListValue({
18+
'location': ['Room A', 'Room B'],
19+
}, 'location');
20+
expect(result, ['Room A', 'Room B']);
21+
});
22+
23+
test('wraps a String in a list', () {
24+
final result = readListValue({'location': 'Room A'}, 'location');
25+
expect(result, ['Room A']);
26+
});
27+
28+
test('wraps a Map in a list', () {
29+
final map = {'nummer': '001', 'gebaeude': 'MI'};
30+
final result = readListValue({'raum': map}, 'raum');
31+
expect(result, [map]);
32+
});
33+
34+
test('returns empty list for numeric value (unrecognised type)', () {
35+
final result = readListValue({'count': 42}, 'count');
36+
expect(result, isEmpty);
37+
});
38+
39+
test('preserves list with mixed-type entries', () {
40+
final result = readListValue({
41+
'items': ['a', 'b', 'c'],
42+
}, 'items');
43+
expect(result.length, 3);
44+
});
45+
});
46+
}

0 commit comments

Comments
 (0)