refactored broad range of the application, split files, modularized calendar and file views, centralized bottom sheets and clipboard handling, and implemented unit test coverage

This commit is contained in:
2026-05-08 19:05:16 +02:00
parent 3b1b0d0c19
commit c62a14645a
68 changed files with 4633 additions and 3141 deletions
+108
View File
@@ -0,0 +1,108 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:marianum_mobile/api/marianumcloud/webdav/queries/list_files/cacheable_file.dart';
import 'package:marianum_mobile/api/marianumcloud/webdav/queries/list_files/list_files_response.dart';
import 'package:marianum_mobile/view/pages/files/data/sort_options.dart';
CacheableFile _file({
required String name,
bool isDirectory = false,
int? size,
DateTime? modifiedAt,
}) =>
CacheableFile(
path: '/$name',
isDirectory: isDirectory,
name: name,
size: size,
modifiedAt: modifiedAt,
);
void main() {
group('SortOptions.options', () {
test('name comparator is alphabetic', () {
final cmp = SortOptions.getOption(SortOption.name).compare;
expect(cmp(_file(name: 'a'), _file(name: 'b')), lessThan(0));
expect(cmp(_file(name: 'b'), _file(name: 'a')), greaterThan(0));
expect(cmp(_file(name: 'a'), _file(name: 'a')), 0);
});
test('date comparator is chronological by modifiedAt', () {
final cmp = SortOptions.getOption(SortOption.date).compare;
final older = _file(name: 'a', modifiedAt: DateTime(2026, 1, 1));
final newer = _file(name: 'b', modifiedAt: DateTime(2026, 5, 1));
expect(cmp(older, newer), lessThan(0));
expect(cmp(newer, older), greaterThan(0));
});
test('size comparator pushes directories to the end (positional 1 vs 0)', () {
final cmp = SortOptions.getOption(SortOption.size).compare;
final dir = _file(name: 'd', isDirectory: true);
final file = _file(name: 'f', size: 100);
// (dir, file) → returns 1 (dir.isDirectory true) → file sorts before dir.
expect(cmp(dir, file), 1);
expect(cmp(file, dir), 0);
});
test('size comparator handles null sizes', () {
final cmp = SortOptions.getOption(SortOption.size).compare;
final noSize = _file(name: 'a');
final withSize = _file(name: 'b', size: 100);
// a.size == null → returns 0
expect(cmp(noSize, withSize), 0);
// b.size == null → returns 1
expect(cmp(withSize, noSize), 1);
});
test('size comparator orders by file size when both known', () {
final cmp = SortOptions.getOption(SortOption.size).compare;
expect(cmp(_file(name: 'a', size: 100), _file(name: 'b', size: 200)), lessThan(0));
expect(cmp(_file(name: 'a', size: 200), _file(name: 'b', size: 100)), greaterThan(0));
});
test('options map contains all enum values exactly once', () {
expect(SortOptions.options.keys.toSet(), SortOption.values.toSet());
});
});
group('ListFilesResponse.sortBy', () {
final folderA = _file(name: 'A', isDirectory: true);
final folderB = _file(name: 'B', isDirectory: true);
final fileA = _file(name: 'aaa', size: 100, modifiedAt: DateTime(2026, 1, 1));
final fileB = _file(name: 'bbb', size: 50, modifiedAt: DateTime(2026, 5, 1));
// Note: sortBy uses a string-buffer sort + compareTo descending. The actual
// list ordering reflects what users see in the file list.
test('foldersToTop=true keeps folders before files regardless of name', () {
final response = ListFilesResponse({fileA, fileB, folderA, folderB});
final sorted = response.sortBy(sortOption: SortOption.name);
final folderCount = sorted.takeWhile((f) => f.isDirectory).length;
expect(folderCount, 2, reason: 'both folders should sit at the top');
});
test('foldersToTop=false intermixes folders and files', () {
final response = ListFilesResponse({fileA, fileB, folderA, folderB});
final sorted = response.sortBy(sortOption: SortOption.name, foldersToTop: false);
final folderPositions = <int>[];
for (var i = 0; i < sorted.length; i++) {
if (sorted[i].isDirectory) folderPositions.add(i);
}
// Without foldersToTop, folders aren't guaranteed to be at the front:
// assert at least one folder is somewhere other than the very top of
// a folders-first ordering.
expect(folderPositions, isNot([0, 1]));
});
test('reversed flips the order within each section', () {
final response = ListFilesResponse({fileA, fileB});
final asc = response.sortBy(sortOption: SortOption.name, foldersToTop: false);
final desc = response.sortBy(
sortOption: SortOption.name, foldersToTop: false, reversed: true);
expect(desc, asc.reversed.toList());
});
test('empty input yields an empty list', () {
final response = ListFilesResponse({});
expect(response.sortBy(), isEmpty);
});
});
}