refactor: 重构提醒通知系统
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:social_app/core/notification/models/reminder_alarm.dart';
|
||||
import 'package:social_app/core/notification/services/reminder_reconcile_service.dart';
|
||||
import 'package:social_app/core/notification/services/reminder_scheduler_service.dart';
|
||||
|
||||
class _FakeReminderSchedulerService extends ReminderSchedulerService {
|
||||
int upsertCount = 0;
|
||||
int cancelCount = 0;
|
||||
List<ReminderAlarm> lastScheduled = const <ReminderAlarm>[];
|
||||
|
||||
@override
|
||||
Future<void> upsertEventReminders(
|
||||
ReminderEventSnapshot event, {
|
||||
DateTime? now,
|
||||
}) async {
|
||||
upsertCount += 1;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> cancelEventReminders(String eventId) async {
|
||||
cancelCount += 1;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> scheduleAlarms(List<ReminderAlarm> alarms) async {
|
||||
lastScheduled = alarms;
|
||||
}
|
||||
}
|
||||
|
||||
ReminderEventSnapshot _event({bool isArchived = false}) {
|
||||
return ReminderEventSnapshot(
|
||||
eventId: 'evt_lock',
|
||||
title: 'Review',
|
||||
startAt: DateTime(2026, 3, 30, 10, 0),
|
||||
endAt: DateTime(2026, 3, 30, 11, 0),
|
||||
timezone: 'Asia/Shanghai',
|
||||
reminderMinutes: 15,
|
||||
isArchived: isArchived,
|
||||
);
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('snooze suppresses reconcile before first snooze fire time', () async {
|
||||
final now = DateTime(2026, 3, 30, 9, 50);
|
||||
final scheduler = _FakeReminderSchedulerService();
|
||||
final service = ReminderReconcileService(
|
||||
scheduler: scheduler,
|
||||
nowProvider: () => now,
|
||||
);
|
||||
|
||||
await service.snooze10m(_event());
|
||||
expect(scheduler.cancelCount, 1);
|
||||
expect(scheduler.lastScheduled, isNotEmpty);
|
||||
expect(scheduler.lastScheduled.first.fireAt, DateTime(2026, 3, 30, 10, 0));
|
||||
|
||||
await service.reconcileEvent(_event(), now: DateTime(2026, 3, 30, 9, 59));
|
||||
expect(scheduler.upsertCount, 0);
|
||||
|
||||
await service.reconcileEvent(_event(), now: DateTime(2026, 3, 30, 10, 0));
|
||||
expect(scheduler.upsertCount, 1);
|
||||
});
|
||||
|
||||
test('archived event cancels reminder and clears suppress state', () async {
|
||||
final now = DateTime(2026, 3, 30, 9, 50);
|
||||
final scheduler = _FakeReminderSchedulerService();
|
||||
final service = ReminderReconcileService(
|
||||
scheduler: scheduler,
|
||||
nowProvider: () => now,
|
||||
);
|
||||
|
||||
await service.snooze10m(_event());
|
||||
expect(scheduler.cancelCount, 1);
|
||||
|
||||
await service.reconcileEvent(_event(isArchived: true), now: now);
|
||||
expect(scheduler.cancelCount, 2);
|
||||
|
||||
await service.reconcileEvent(_event(), now: DateTime(2026, 3, 30, 9, 55));
|
||||
expect(scheduler.upsertCount, 1);
|
||||
});
|
||||
}
|
||||
@@ -26,28 +26,44 @@ void main() {
|
||||
expect(alarms.last.fireAt, DateTime(2026, 3, 30, 10, 35));
|
||||
});
|
||||
|
||||
test(
|
||||
'buildAlarms compensates by scheduling near-now when remindAt passed',
|
||||
() {
|
||||
final event = ReminderEventSnapshot(
|
||||
eventId: 'evt_3',
|
||||
title: 'Review',
|
||||
startAt: DateTime(2026, 3, 30, 10, 0),
|
||||
endAt: DateTime(2026, 3, 30, 10, 20),
|
||||
timezone: 'Asia/Shanghai',
|
||||
reminderMinutes: 30,
|
||||
);
|
||||
final now = DateTime(2026, 3, 30, 10, 5, 0);
|
||||
test('buildAlarms starts from near-now when remindAt passed', () {
|
||||
final event = ReminderEventSnapshot(
|
||||
eventId: 'evt_3',
|
||||
title: 'Review',
|
||||
startAt: DateTime(2026, 3, 30, 10, 0),
|
||||
endAt: DateTime(2026, 3, 30, 10, 20),
|
||||
timezone: 'Asia/Shanghai',
|
||||
reminderMinutes: 30,
|
||||
);
|
||||
final now = DateTime(2026, 3, 30, 10, 5, 0);
|
||||
|
||||
final alarms = ReminderSchedulerService.buildAlarmsForEvent(
|
||||
event,
|
||||
now: now,
|
||||
);
|
||||
final alarms = ReminderSchedulerService.buildAlarmsForEvent(
|
||||
event,
|
||||
now: now,
|
||||
);
|
||||
|
||||
expect(alarms, isNotEmpty);
|
||||
expect(alarms.first.fireAt, now.add(const Duration(seconds: 5)));
|
||||
},
|
||||
);
|
||||
expect(alarms, isNotEmpty);
|
||||
expect(alarms.first.fireAt, DateTime(2026, 3, 30, 10, 5, 2));
|
||||
expect(alarms.last.fireAt, DateTime(2026, 3, 30, 10, 15, 2));
|
||||
});
|
||||
|
||||
test('buildAlarms returns empty when next cadence is after endAt', () {
|
||||
final event = ReminderEventSnapshot(
|
||||
eventId: 'evt_3b',
|
||||
title: 'Review',
|
||||
startAt: DateTime(2026, 3, 30, 10, 0),
|
||||
endAt: DateTime(2026, 3, 30, 10, 5),
|
||||
timezone: 'Asia/Shanghai',
|
||||
reminderMinutes: 30,
|
||||
);
|
||||
|
||||
final alarms = ReminderSchedulerService.buildAlarmsForEvent(
|
||||
event,
|
||||
now: DateTime(2026, 3, 30, 10, 5, 30),
|
||||
);
|
||||
|
||||
expect(alarms, isEmpty);
|
||||
});
|
||||
|
||||
test('buildAlarms returns empty when event already ended', () {
|
||||
final event = ReminderEventSnapshot(
|
||||
|
||||
Reference in New Issue
Block a user