154 lines
4.9 KiB
Dart
154 lines
4.9 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:timezone/data/latest.dart' as tzdata;
|
|
import 'package:hepan_everyday/services/local_notification_service.dart';
|
|
import 'package:hepan_everyday/services/notification_service.dart';
|
|
import 'package:hepan_everyday/domain/schedule_engine.dart';
|
|
import 'package:hepan_everyday/domain/app_settings.dart';
|
|
|
|
import '../domain/schedule_engine_test.dart' show course;
|
|
|
|
class AlarmGateway extends ReminderGateway {
|
|
ReminderPermissions permission = const ReminderPermissions(true, true);
|
|
final alarms = <int, ReminderRequest>{};
|
|
int permissionRequests = 0;
|
|
@override
|
|
Future<ReminderPermissions> permissions({required bool request}) async {
|
|
if (request) permissionRequests++;
|
|
return permission;
|
|
}
|
|
|
|
@override
|
|
Future<Set<int>> pendingIds() async => alarms.keys.toSet();
|
|
@override
|
|
Future<void> schedule(ReminderRequest value) async {
|
|
alarms[value.id] = value;
|
|
}
|
|
|
|
@override
|
|
Future<void> cancel(int id) async {
|
|
alarms.remove(id);
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
setUpAll(tzdata.initializeTimeZones);
|
|
final term = Semester(startDate: DateTime(2026, 9, 9), totalWeeks: 5);
|
|
final settings = AppSettings(semester: term);
|
|
late AlarmGateway gateway;
|
|
late LocalNotificationService service;
|
|
setUp(() {
|
|
gateway = AlarmGateway();
|
|
service = LocalNotificationService(gateway);
|
|
});
|
|
test(
|
|
'continuous course schedules one alarm fifteen minutes before school start',
|
|
() async {
|
|
final events = ScheduleEngine.occurrences([course(last: 2)], term);
|
|
final result = await service.reconcile(
|
|
events,
|
|
settings,
|
|
DateTime.utc(2026, 9, 9),
|
|
);
|
|
expect(result.scheduledCount, 1);
|
|
expect(gateway.alarms, hasLength(1));
|
|
expect(
|
|
gateway.alarms.values.single.fireAt.toUtc(),
|
|
DateTime.utc(2026, 9, 9, 0, 15),
|
|
);
|
|
expect(gateway.alarms.values.single.id, 1794407394);
|
|
expect(gateway.permissionRequests, 0);
|
|
},
|
|
);
|
|
test(
|
|
'repeat updates replace one alarm and use changed advance time',
|
|
() async {
|
|
final events = ScheduleEngine.occurrences([course()], term);
|
|
await service.reconcile(events, settings, DateTime.utc(2026, 9, 9));
|
|
await service.reconcile(
|
|
events,
|
|
settings.copyWith(reminderMinutes: 5),
|
|
DateTime.utc(2026, 9, 9),
|
|
);
|
|
expect(gateway.alarms, hasLength(1));
|
|
expect(
|
|
gateway.alarms.values.single.fireAt.toUtc(),
|
|
DateTime.utc(2026, 9, 9, 0, 25),
|
|
);
|
|
},
|
|
);
|
|
test(
|
|
'past reminder, demo and disabled settings remove pending own alarms',
|
|
() async {
|
|
final events = ScheduleEngine.occurrences([course()], term);
|
|
await service.reconcile(events, settings, DateTime.utc(2026, 9, 9));
|
|
expect(gateway.alarms, hasLength(1));
|
|
await service.reconcile(
|
|
events,
|
|
settings,
|
|
DateTime.utc(2026, 9, 9, 0, 20),
|
|
);
|
|
expect(gateway.alarms, isEmpty);
|
|
await service.reconcile(events, settings, DateTime.utc(2026, 9, 9));
|
|
expect(gateway.alarms, hasLength(1));
|
|
await service.reconcile(
|
|
events,
|
|
settings.copyWith(remindersEnabled: false),
|
|
DateTime.utc(2026, 9, 9),
|
|
);
|
|
expect(gateway.alarms, isEmpty);
|
|
},
|
|
);
|
|
test('denied notifications and denied exact alarm have distinct effective status', () async {
|
|
final events = ScheduleEngine.occurrences([course()], term);
|
|
gateway.permission = const ReminderPermissions(false, false);
|
|
expect(
|
|
(await service.reconcile(
|
|
events,
|
|
settings,
|
|
DateTime.utc(2026, 9, 9),
|
|
)).status,
|
|
ReminderStatus.permissionDenied,
|
|
);
|
|
expect(gateway.alarms, isEmpty);
|
|
gateway.permission = const ReminderPermissions(true, false);
|
|
expect(
|
|
(await service.reconcile(
|
|
events,
|
|
settings,
|
|
DateTime.utc(2026, 9, 9),
|
|
)).status,
|
|
ReminderStatus.inexact,
|
|
);
|
|
expect(gateway.alarms.values.single.exact, isFalse);
|
|
});
|
|
test('permission revoked after scheduling cancels previous alarms', () async {
|
|
final events = ScheduleEngine.occurrences([course()], term);
|
|
await service.reconcile(events, settings, DateTime.utc(2026, 9, 9));
|
|
gateway.permission = const ReminderPermissions(false, false);
|
|
final result = await service.reconcile(
|
|
events,
|
|
settings,
|
|
DateTime.utc(2026, 9, 9),
|
|
);
|
|
expect(result.status, ReminderStatus.permissionDenied);
|
|
expect(gateway.alarms, isEmpty);
|
|
});
|
|
test(
|
|
'large semester keeps at most four hundred earliest reminders',
|
|
() async {
|
|
final many = ScheduleEngine.occurrences(
|
|
List.generate(501, (i) => course(id: 'class-$i')),
|
|
term,
|
|
);
|
|
final result = await service.reconcile(
|
|
many,
|
|
settings,
|
|
DateTime.utc(2026, 9, 9),
|
|
);
|
|
expect(result.scheduledCount, 400);
|
|
expect(gateway.alarms, hasLength(400));
|
|
expect(result.message, contains('后续'));
|
|
},
|
|
);
|
|
}
|