feat: persist timetable and validate confirmed imports
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:timezone/data/latest.dart' as tzdata;
|
||||
import 'package:hepan_everyday/app/app_controller.dart';
|
||||
import 'package:hepan_everyday/domain/app_snapshot.dart';
|
||||
import 'package:hepan_everyday/domain/app_settings.dart';
|
||||
import 'package:hepan_everyday/domain/semester.dart';
|
||||
import 'package:hepan_everyday/domain/course.dart';
|
||||
import 'package:hepan_everyday/data/json_schedule_import.dart';
|
||||
|
||||
import '../support/fakes.dart';
|
||||
import '../domain/schedule_engine_test.dart' show course;
|
||||
|
||||
void main() {
|
||||
setUpAll(tzdata.initializeTimeZones);
|
||||
late MemoryStore store;
|
||||
late FakeNotifications notifications;
|
||||
late FakeCalendar calendar;
|
||||
late AppController controller;
|
||||
setUp(() {
|
||||
store = MemoryStore(
|
||||
AppSnapshot(
|
||||
courses: [course()],
|
||||
settings: AppSettings(
|
||||
semester: Semester(startDate: DateTime(2026, 9, 9), totalWeeks: 5),
|
||||
),
|
||||
),
|
||||
);
|
||||
notifications = FakeNotifications();
|
||||
calendar = FakeCalendar();
|
||||
controller = AppController(
|
||||
store,
|
||||
notifications,
|
||||
calendar,
|
||||
() => DateTime.utc(2026, 9, 8),
|
||||
);
|
||||
});
|
||||
tearDown(() => controller.dispose());
|
||||
test('failed saving does not change currently visible settings', () async {
|
||||
await controller.initialize();
|
||||
store.failSave = true;
|
||||
await controller.updateSettings(
|
||||
controller.snapshot.settings.copyWith(reminderMinutes: 30),
|
||||
);
|
||||
expect(controller.snapshot.settings.reminderMinutes, 15);
|
||||
expect(controller.lastError, isNotNull);
|
||||
});
|
||||
test('concurrent settings changes persist in user action order', () async {
|
||||
await controller.initialize();
|
||||
await Future.wait([
|
||||
controller.updateSettings(
|
||||
controller.snapshot.settings.copyWith(reminderMinutes: 30),
|
||||
),
|
||||
controller.updateSettings(
|
||||
controller.snapshot.settings.copyWith(reminderMinutes: 60),
|
||||
),
|
||||
]);
|
||||
expect(store.value!.settings.reminderMinutes, 60);
|
||||
});
|
||||
test('demo cannot replace real courses or create real reminders', () async {
|
||||
await controller.initialize();
|
||||
final previous = notifications.reconciliations;
|
||||
await controller.showDemo();
|
||||
expect(controller.isDemo, isTrue);
|
||||
expect(store.value!.courses.single.id, 'math');
|
||||
expect(notifications.reconciliations, previous);
|
||||
expect(calendar.synchronizations, 0);
|
||||
});
|
||||
test('unconfirmed empty import preserves saved timetable', () async {
|
||||
await controller.initialize();
|
||||
await controller.applyImport(
|
||||
const ImportDraft(
|
||||
courses: [],
|
||||
explicitlyEmpty: false,
|
||||
source: CourseSource.file,
|
||||
parserVersion: 'json-v1',
|
||||
),
|
||||
confirmedEmpty: false,
|
||||
);
|
||||
expect(controller.snapshot.courses.single.id, 'math');
|
||||
expect(controller.lastError, isNotNull);
|
||||
});
|
||||
test(
|
||||
'clearing courses cancels owned notifications and calendar events',
|
||||
() async {
|
||||
await controller.initialize();
|
||||
await controller.clearCourses();
|
||||
expect(store.value!.courses, isEmpty);
|
||||
expect(notifications.cancellations, 1);
|
||||
expect(calendar.deletions, 1);
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user