merged develop

This commit is contained in:
2026-09-27 14:58:07 +02:00
188 changed files with 7474 additions and 2558 deletions
+21 -30
View File
@@ -11,13 +11,10 @@ CacheableFile _file({
bool isDirectory = false,
}) => CacheableFile(path: path, isDirectory: isDirectory, name: name);
Map<String, dynamic> _doc(ListFilesResponse listing) => {
'json': jsonEncode(listing.toJson()),
'lastupdate': 0,
};
String _payload(ListFilesResponse listing) => jsonEncode(listing.toJson());
void main() {
group('searchLocalCaches', () {
group('local cache index', () {
final root = ListFilesResponse({
_file(path: 'Documents/', name: 'Documents', isDirectory: true),
_file(path: 'Photos/', name: 'Photos', isDirectory: true),
@@ -27,51 +24,45 @@ void main() {
_file(path: 'Documents/Tax-Report.pdf', name: 'Tax-Report.pdf'),
_file(path: 'Documents/Notes.txt', name: 'Notes.txt'),
});
final docs = {
'/MarianumMobile/wd-folder-aaa': _doc(root),
'/MarianumMobile/wd-folder-bbb': _doc(documents),
'/MarianumMobile/get-room-ccc': {'json': '{}', 'lastupdate': 0},
};
final index = buildLocalCacheIndex([
_payload(root),
_payload(documents),
]);
test('matches by name case-insensitively across all caches', () async {
final hits = await searchLocalCaches('report', docs: docs);
test('matches by name case-insensitively across all caches', () {
final hits = searchLocalCacheIndex(index, 'report');
final paths = hits.map((f) => f.path).toSet();
expect(paths, {'Reports.pdf', 'Documents/Tax-Report.pdf'});
});
test('returns empty list for empty query', () async {
expect(await searchLocalCaches(' ', docs: docs), isEmpty);
test('returns empty list for empty query', () {
expect(searchLocalCacheIndex(index, ' '), isEmpty);
});
test('respects pathScope prefix', () async {
final hits = await searchLocalCaches(
test('respects pathScope prefix', () {
final hits = searchLocalCacheIndex(
index,
'report',
pathScope: ['Documents'],
docs: docs,
);
expect(hits.map((f) => f.path), ['Documents/Tax-Report.pdf']);
});
test('ignores non-folder cache documents', () async {
final hits = await searchLocalCaches('anything', docs: docs);
// Only documents starting with `wd-folder-` are scanned. The unrelated
// `get-room-ccc` doc must not crash the helper.
expect(hits, isEmpty);
test('skips payloads that are not folder listings', () {
expect(buildLocalCacheIndex(['not json', '{"foo": 1}']), isEmpty);
});
test('deduplicates entries that appear in multiple cached folders',
() async {
test('deduplicates entries that appear in multiple cached folders', () {
final shared = _file(
path: 'Documents/Tax-Report.pdf',
name: 'Tax-Report.pdf',
);
final dedupRoot = ListFilesResponse({shared});
final dedupDocs = {
'/MarianumMobile/wd-folder-aaa': _doc(dedupRoot),
'/MarianumMobile/wd-folder-bbb': _doc(dedupRoot),
};
final hits = await searchLocalCaches('tax', docs: dedupDocs);
expect(hits, hasLength(1));
final dedupIndex = buildLocalCacheIndex([
_payload(dedupRoot),
_payload(dedupRoot),
]);
expect(searchLocalCacheIndex(dedupIndex, 'tax'), hasLength(1));
});
});
}
@@ -0,0 +1,59 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:marianum_mobile/api/marianumconnect/queries/timetable_get_week/timetable_get_week_response.dart';
import 'package:marianum_mobile/view/pages/timetable/data/lesson_type_label.dart';
McTimetableEntry _entry({
String lessonType = 'LESSON',
String? substitutionText,
String? lessonText,
}) => McTimetableEntry(
id: 1,
date: DateTime(2026, 5, 4),
startTime: DateTime(1970, 1, 1, 8),
endTime: DateTime(1970, 1, 1, 15),
subjects: const [],
teachers: const [],
rooms: const ['A101'],
classNames: const ['8a'],
lessonType: lessonType,
status: 'REGULAR',
substitutionText: substitutionText,
lessonText: lessonText,
infoText: null,
);
void main() {
group('LessonTypeLabel.forEntry', () {
test('prefers the substitution text', () {
expect(
LessonTypeLabel.forEntry(
_entry(substitutionText: 'Klassenfahrt 8a', lessonText: 'Anderes'),
),
'Klassenfahrt 8a',
);
});
test('falls back to the lesson text', () {
expect(
LessonTypeLabel.forEntry(_entry(lessonText: ' Wandertag\n8a ')),
'Wandertag 8a',
);
});
test('falls back to "Event" with room when no text is set', () {
expect(
LessonTypeLabel.forEntry(_entry(substitutionText: ' ')),
'A101 Event',
);
});
test('keeps known lesson types over free text', () {
expect(
LessonTypeLabel.forEntry(
_entry(lessonType: 'BREAK_SUPERVISION', lessonText: 'Hof'),
),
'A101 Aufsicht',
);
});
});
}
@@ -65,10 +65,10 @@ void main() {
);
});
test('falls back to the teacher when the entry has no class', () {
test('shows only the room when the entry has no class', () {
expect(
_location(showClassInsteadOfTeacher: true, classNames: const []),
'A101\nMüller',
'A101',
);
});
});