Files
Client/test/widget_data/widget_data_mapper_test.dart
T

386 lines
11 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:marianum_mobile/api/mhsl/custom_timetable_event/custom_timetable_event.dart';
import 'package:marianum_mobile/api/mhsl/custom_timetable_event/get/get_custom_timetable_event_response.dart';
import 'package:marianum_mobile/api/webuntis/queries/get_holidays/get_holidays_response.dart';
import 'package:marianum_mobile/api/webuntis/queries/get_timetable/get_timetable_response.dart';
import 'package:marianum_mobile/widget_data/widget_data.dart';
import 'package:marianum_mobile/widget_data/widget_data_mapper.dart';
CustomTimetableEvent _event({
required String id,
required String title,
required DateTime start,
required DateTime end,
}) => CustomTimetableEvent(
id: id,
title: title,
description: '',
startDate: start,
endDate: end,
color: 'orange',
rrule: '',
createdAt: start,
updatedAt: start,
);
GetTimetableResponseObject _lesson({
required int date,
required int startTime,
required int endTime,
String? code,
String? subjectName,
String? room,
int teacherId = 1,
String? teacherName,
String? teacherOrgname,
String? substText,
}) => GetTimetableResponseObject(
id: 1,
date: date,
startTime: startTime,
endTime: endTime,
code: code,
substText: substText,
kl: const [],
te: teacherName != null
? [
GetTimetableResponseObjectTeacher(
teacherId,
teacherName,
teacherName,
teacherOrgname == null ? null : 9,
teacherOrgname,
null,
),
]
: const [],
su: subjectName != null
? [
GetTimetableResponseObjectSubject(
5,
subjectName,
subjectName,
),
]
: const [],
ro: room != null
? [GetTimetableResponseObjectRoom(7, room, room)]
: const [],
);
void main() {
group('resolveDayAnchor', () {
test('weekday before cutoff stays today', () {
final anchor = WidgetDataMapper.resolveDayAnchor(
DateTime(2026, 5, 6, 10),
);
expect(anchor, DateTime(2026, 5, 6));
});
test('weekday after cutoff jumps to next school day', () {
final anchor = WidgetDataMapper.resolveDayAnchor(
DateTime(2026, 5, 6, 19),
);
expect(anchor, DateTime(2026, 5, 7));
});
test('Friday after cutoff jumps to Monday', () {
final anchor = WidgetDataMapper.resolveDayAnchor(
DateTime(2026, 5, 8, 18),
);
expect(anchor, DateTime(2026, 5, 11));
});
test('Saturday morning jumps to Monday', () {
final anchor = WidgetDataMapper.resolveDayAnchor(
DateTime(2026, 5, 9, 10),
);
expect(anchor, DateTime(2026, 5, 11));
});
test('Sunday evening jumps to Monday', () {
final anchor = WidgetDataMapper.resolveDayAnchor(
DateTime(2026, 5, 10, 22),
);
expect(anchor, DateTime(2026, 5, 11));
});
});
group('resolveWeekAnchor', () {
test('Tuesday returns the Monday of that week', () {
final anchor = WidgetDataMapper.resolveWeekAnchor(
DateTime(2026, 5, 5, 10),
);
expect(anchor, DateTime(2026, 5, 4));
});
test('Sunday returns next Monday', () {
final anchor = WidgetDataMapper.resolveWeekAnchor(
DateTime(2026, 5, 10, 10),
);
expect(anchor, DateTime(2026, 5, 11));
});
});
group('buildDayData', () {
final now = DateTime(2026, 5, 6, 10);
test('only includes lessons on the anchor day', () {
final lessons = [
_lesson(date: 20260506, startTime: 800, endTime: 845, subjectName: 'MA'),
_lesson(date: 20260507, startTime: 800, endTime: 845, subjectName: 'EN'),
];
final data = WidgetDataMapper.buildDayData(
now: now,
lessons: lessons,
subjects: null,
rooms: null,
holidays: null,
);
expect(data.lessons, hasLength(1));
expect(data.lessons.first.subjectShort, 'MA');
});
test('classifies cancelled and irregular lessons', () {
final lessons = [
_lesson(
date: 20260506,
startTime: 800,
endTime: 845,
subjectName: 'MA',
code: 'cancelled',
),
_lesson(
date: 20260506,
startTime: 900,
endTime: 945,
subjectName: 'EN',
code: 'irregular',
),
_lesson(
date: 20260506,
startTime: 1000,
endTime: 1045,
subjectName: 'BIO',
teacherName: 'Müller',
teacherOrgname: 'Schmidt',
),
];
final data = WidgetDataMapper.buildDayData(
now: now,
lessons: lessons,
subjects: null,
rooms: null,
holidays: null,
);
expect(
data.lessons.map((l) => l.status).toList(),
[
WidgetLessonStatus.cancelled,
WidgetLessonStatus.irregular,
WidgetLessonStatus.teacherChanged,
],
);
});
test('marks day as holiday when in holiday range', () {
final holidays = GetHolidaysResponse({
GetHolidaysResponseObject(
1,
'Pfingsten',
'Pfingstferien',
20260506,
20260510,
),
});
final data = WidgetDataMapper.buildDayData(
now: now,
lessons: const [],
subjects: null,
rooms: null,
holidays: holidays,
);
expect(data.isHoliday, isTrue);
expect(data.holidayName, 'Pfingstferien');
});
test('lessons are sorted by start time', () {
final lessons = [
_lesson(
date: 20260506,
startTime: 1000,
endTime: 1045,
subjectName: 'BIO',
),
_lesson(
date: 20260506,
startTime: 800,
endTime: 845,
subjectName: 'MA',
),
_lesson(
date: 20260506,
startTime: 900,
endTime: 945,
subjectName: 'EN',
),
];
final data = WidgetDataMapper.buildDayData(
now: now,
lessons: lessons,
subjects: null,
rooms: null,
holidays: null,
);
expect(
data.lessons.map((l) => l.subjectShort).toList(),
['MA', 'EN', 'BIO'],
);
});
});
group('event collision bumping', () {
final now = DateTime(2026, 5, 6, 10);
test('long event bumps every regular lesson it covers', () {
final lessons = [
_lesson(date: 20260506, startTime: 800, endTime: 845, subjectName: 'MA'),
_lesson(date: 20260506, startTime: 900, endTime: 945, subjectName: 'EN'),
_lesson(date: 20260506, startTime: 1000, endTime: 1045, subjectName: 'BIO'),
];
final events = GetCustomTimetableEventResponse([
_event(
id: 'a',
title: 'Wandertag',
start: DateTime(2026, 5, 6, 8),
end: DateTime(2026, 5, 6, 11),
),
]);
final data = WidgetDataMapper.buildDayData(
now: now,
lessons: lessons,
subjects: null,
rooms: null,
holidays: null,
customEvents: events,
);
expect(data.lessons, hasLength(3));
for (final l in data.lessons) {
expect(l.siblingCount, 1, reason: '${l.subjectShort} should be bumped');
}
});
test('event + same-slot duplicate regular: kept lesson shows +2', () {
// User scenario: a long custom event covers the slot, and Webuntis
// reports two regular lessons starting at the same time (different
// class group). The user wants "+2" — one for the hidden event, one
// for the parallel regular lesson — not just "+1".
final lessons = [
_lesson(date: 20260506, startTime: 900, endTime: 945, subjectName: 'EN'),
_lesson(date: 20260506, startTime: 900, endTime: 945, subjectName: 'MA'),
];
final events = GetCustomTimetableEventResponse([
_event(
id: 'long',
title: 'Wandertag',
start: DateTime(2026, 5, 6, 8),
end: DateTime(2026, 5, 6, 12),
),
]);
final data = WidgetDataMapper.buildDayData(
now: now,
lessons: lessons,
subjects: null,
rooms: null,
holidays: null,
customEvents: events,
);
expect(data.lessons, hasLength(1));
expect(data.lessons.first.siblingCount, 2);
});
test('multi-day event splits into one block per calendar day', () {
final events = GetCustomTimetableEventResponse([
_event(
id: 'multi',
title: 'Klassenfahrt',
start: DateTime(2026, 5, 4, 8),
end: DateTime(2026, 5, 6, 14),
),
]);
final data = WidgetDataMapper.buildWeekData(
now: DateTime(2026, 5, 5, 10),
lessons: const [],
subjects: null,
rooms: null,
holidays: null,
customEvents: events,
);
expect(data.lessons, hasLength(3));
expect(data.lessons[0].start, DateTime(2026, 5, 4, 8));
expect(data.lessons[0].end, DateTime(2026, 5, 4, 23, 59));
expect(data.lessons[1].start, DateTime(2026, 5, 5, 0));
expect(data.lessons[1].end, DateTime(2026, 5, 5, 23, 59));
expect(data.lessons[2].start, DateTime(2026, 5, 6, 0));
expect(data.lessons[2].end, DateTime(2026, 5, 6, 14));
});
test('two events covering the same regular lesson bump it twice', () {
final lessons = [
_lesson(date: 20260506, startTime: 900, endTime: 945, subjectName: 'EN'),
];
final events = GetCustomTimetableEventResponse([
_event(
id: 'long',
title: 'Termin lang',
start: DateTime(2026, 5, 6, 8),
end: DateTime(2026, 5, 6, 12),
),
_event(
id: 'short',
title: 'Termin kurz',
start: DateTime(2026, 5, 6, 9),
end: DateTime(2026, 5, 6, 10),
),
]);
final data = WidgetDataMapper.buildDayData(
now: now,
lessons: lessons,
subjects: null,
rooms: null,
holidays: null,
customEvents: events,
);
expect(data.lessons, hasLength(1));
expect(data.lessons.first.subjectShort, 'EN');
expect(data.lessons.first.siblingCount, 2);
});
});
group('buildWeekData', () {
final now = DateTime(2026, 5, 5, 10); // Tuesday
test('contains lessons across the school week', () {
final lessons = [
_lesson(date: 20260504, startTime: 800, endTime: 845, subjectName: 'MO'),
_lesson(date: 20260506, startTime: 800, endTime: 845, subjectName: 'WE'),
_lesson(date: 20260508, startTime: 800, endTime: 845, subjectName: 'FR'),
_lesson(date: 20260511, startTime: 800, endTime: 845, subjectName: 'NEXT'),
];
final data = WidgetDataMapper.buildWeekData(
now: now,
lessons: lessons,
subjects: null,
rooms: null,
holidays: null,
);
// Anchor is Mon 04.05.; week ends Fri 08.05. exclusive of next Mon
expect(data.anchorDate, DateTime(2026, 5, 4));
expect(
data.lessons.map((l) => l.subjectShort).toList(),
['MO', 'WE', 'FR'],
);
});
});
}