373 lines
11 KiB
Dart
373 lines
11 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:marianum_mobile/api/marianumconnect/queries/timetable_get_holidays/timetable_get_holidays_response.dart';
|
|
import 'package:marianum_mobile/api/marianumconnect/queries/timetable_get_week/timetable_get_week_response.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/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,
|
|
);
|
|
|
|
McTimetableEntry _lesson({
|
|
required DateTime date,
|
|
required int startHhmm,
|
|
required int endHhmm,
|
|
String status = 'REGULAR',
|
|
String? subjectName,
|
|
String? room,
|
|
String? teacherName,
|
|
String? teacherOrgname,
|
|
String? substText,
|
|
}) => McTimetableEntry(
|
|
id: 1,
|
|
date: date,
|
|
startTime: DateTime(1970, 1, 1, startHhmm ~/ 100, startHhmm % 100),
|
|
endTime: DateTime(1970, 1, 1, endHhmm ~/ 100, endHhmm % 100),
|
|
subjects: subjectName != null ? [subjectName] : const [],
|
|
teachers: teacherName != null
|
|
? [
|
|
McTimetableTeacher(
|
|
shortName: teacherName,
|
|
displayName: teacherName,
|
|
originalShortName: teacherOrgname,
|
|
originalDisplayName: teacherOrgname,
|
|
),
|
|
]
|
|
: const [],
|
|
rooms: room != null ? [room] : const [],
|
|
classNames: const [],
|
|
lessonType: 'LESSON',
|
|
status: status,
|
|
substitutionText: substText,
|
|
lessonText: null,
|
|
infoText: null,
|
|
);
|
|
|
|
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: DateTime(2026, 5, 6), startHhmm: 800, endHhmm: 845, subjectName: 'MA'),
|
|
_lesson(date: DateTime(2026, 5, 7), startHhmm: 800, endHhmm: 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: DateTime(2026, 5, 6),
|
|
startHhmm: 800,
|
|
endHhmm: 845,
|
|
subjectName: 'MA',
|
|
status: 'CANCELLED',
|
|
),
|
|
_lesson(
|
|
date: DateTime(2026, 5, 6),
|
|
startHhmm: 900,
|
|
endHhmm: 945,
|
|
subjectName: 'EN',
|
|
status: 'IRREGULAR',
|
|
teacherName: 'Müller',
|
|
),
|
|
_lesson(
|
|
date: DateTime(2026, 5, 6),
|
|
startHhmm: 1000,
|
|
endHhmm: 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 = TimetableGetHolidaysResponse(
|
|
result: [
|
|
McHoliday(
|
|
shortName: 'Pfingsten',
|
|
longName: 'Pfingstferien',
|
|
startDate: DateTime(2026, 5, 6),
|
|
endDate: DateTime(2026, 5, 10),
|
|
),
|
|
],
|
|
);
|
|
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: DateTime(2026, 5, 6),
|
|
startHhmm: 1000,
|
|
endHhmm: 1045,
|
|
subjectName: 'BIO',
|
|
),
|
|
_lesson(
|
|
date: DateTime(2026, 5, 6),
|
|
startHhmm: 800,
|
|
endHhmm: 845,
|
|
subjectName: 'MA',
|
|
),
|
|
_lesson(
|
|
date: DateTime(2026, 5, 6),
|
|
startHhmm: 900,
|
|
endHhmm: 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: DateTime(2026, 5, 6), startHhmm: 800, endHhmm: 845, subjectName: 'MA'),
|
|
_lesson(date: DateTime(2026, 5, 6), startHhmm: 900, endHhmm: 945, subjectName: 'EN'),
|
|
_lesson(date: DateTime(2026, 5, 6), startHhmm: 1000, endHhmm: 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', () {
|
|
final lessons = [
|
|
_lesson(date: DateTime(2026, 5, 6), startHhmm: 900, endHhmm: 945, subjectName: 'EN'),
|
|
_lesson(date: DateTime(2026, 5, 6), startHhmm: 900, endHhmm: 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: DateTime(2026, 5, 6), startHhmm: 900, endHhmm: 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: DateTime(2026, 5, 4), startHhmm: 800, endHhmm: 845, subjectName: 'MO'),
|
|
_lesson(date: DateTime(2026, 5, 6), startHhmm: 800, endHhmm: 845, subjectName: 'WE'),
|
|
_lesson(date: DateTime(2026, 5, 8), startHhmm: 800, endHhmm: 845, subjectName: 'FR'),
|
|
_lesson(date: DateTime(2026, 5, 11), startHhmm: 800, endHhmm: 845, subjectName: 'NEXT'),
|
|
];
|
|
final data = WidgetDataMapper.buildWeekData(
|
|
now: now,
|
|
lessons: lessons,
|
|
subjects: null,
|
|
rooms: null,
|
|
holidays: null,
|
|
);
|
|
expect(data.anchorDate, DateTime(2026, 5, 4));
|
|
expect(
|
|
data.lessons.map((l) => l.subjectShort).toList(),
|
|
['MO', 'WE', 'FR'],
|
|
);
|
|
});
|
|
});
|
|
}
|