import 'package:adhd_scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { group('Effective reminder profile resolution', () { const service = ReminderPolicyService(); final now = DateTime.utc(2026, 6, 25, 9); test('uses task override before project default and fallback', () { final project = ProjectProfile( id: 'home', name: 'Home', colorKey: 'green', defaultReminderProfile: ReminderProfile.gentle, ); final task = scheduledTask( id: 'task', now: now, reminderOverride: ReminderProfile.strict, ); final effective = service.resolveEffectiveProfile( task: task, project: project, ); expect(effective.profile, ReminderProfile.strict); expect(effective.source, EffectiveReminderProfileSource.taskOverride); }); test('uses project default before application fallback', () { final project = ProjectProfile( id: 'home', name: 'Home', colorKey: 'green', defaultReminderProfile: ReminderProfile.persistent, ); final task = scheduledTask(id: 'task', now: now); final effective = service.resolveEffectiveProfile( task: task, project: project, ); expect(effective.profile, ReminderProfile.persistent); expect(effective.source, EffectiveReminderProfileSource.projectDefault); }); test('uses fallback when no task or project value exists', () { final task = scheduledTask(id: 'task', now: now); final effective = service.resolveEffectiveProfile(task: task); expect(effective.profile, ReminderProfile.gentle); expect(effective.source, EffectiveReminderProfileSource.fallback); }); test('does not silently use learned project suggestions', () { final project = ProjectProfile( id: 'home', name: 'Home', colorKey: 'green', defaultReminderProfile: ReminderProfile.strict, ); final statistics = ProjectStatistics( projectId: 'home', completedTaskCount: 3, reminderProfileCounts: const {ReminderProfile.gentle: 3}, ); final suggestionResolution = const ProjectSuggestionService() .resolveDefaults(project: project, statistics: statistics); final task = scheduledTask(id: 'task', now: now); final effective = service.resolveEffectiveProfile( task: task, project: suggestionResolution.project, ); expect(suggestionResolution.suggestions.reminderProfile!.value, ReminderProfile.gentle); expect(effective.profile, ReminderProfile.strict); expect(effective.source, EffectiveReminderProfileSource.projectDefault); }); }); group('Reminder directives', () { const service = ReminderPolicyService(); final now = DateTime.utc(2026, 6, 25, 9); test('all non-silent reminder profiles produce normal flexible delivery', () { for (final profile in [ ReminderProfile.gentle, ReminderProfile.persistent, ReminderProfile.strict, ]) { final directive = service.directiveForTask( task: scheduledTask( id: 'task-${profile.name}', now: now, reminderOverride: profile, ), now: now, ); expect(directive.action, ReminderDirectiveAction.deliver); expect(directive.reason, ReminderDirectiveReason.deliverNormal); expect(directive.effectiveProfile, profile); } }); test('silent profile suppresses normal reminders', () { final directive = service.directiveForTask( task: scheduledTask( id: 'task', now: now, reminderOverride: ReminderProfile.silent, ), now: now, ); expect(directive.action, ReminderDirectiveAction.suppress); expect(directive.reason, ReminderDirectiveReason.suppressSilentProfile); }); test( 'defers before the scheduled window and delivers at the start boundary', () { final task = scheduledTask( id: 'task', now: now, start: DateTime.utc(2026, 6, 25, 10), ); final before = service.directiveForTask( task: task, now: DateTime.utc(2026, 6, 25, 9, 59), ); final atStart = service.directiveForTask( task: task, now: DateTime.utc(2026, 6, 25, 10), ); expect(before.action, ReminderDirectiveAction.defer); expect(before.reason, ReminderDirectiveReason.deferUntilTaskWindow); expect(before.nextEligibleAt, DateTime.utc(2026, 6, 25, 10)); expect(atStart.action, ReminderDirectiveAction.deliver); }); test('suppresses flexible reminders during active protected Free Slots', () { final freeSlot = freeSlotTask( id: 'rest', start: DateTime.utc(2026, 6, 25, 9), end: DateTime.utc(2026, 6, 25, 9, 30), createdAt: now, ); final flexible = scheduledTask(id: 'flexible', now: now); final atStart = service.directiveForTask( task: flexible, now: DateTime.utc(2026, 6, 25, 9), contextTasks: [freeSlot, flexible], ); final inside = service.directiveForTask( task: flexible, now: DateTime.utc(2026, 6, 25, 9, 29), contextTasks: [freeSlot, flexible], ); final atEnd = service.directiveForTask( task: flexible, now: DateTime.utc(2026, 6, 25, 9, 30), contextTasks: [freeSlot, flexible], ); expect(atStart.action, ReminderDirectiveAction.suppress); expect(atStart.reason, ReminderDirectiveReason.suppressProtectedRest); expect(atStart.protectedFreeSlotTaskId, 'rest'); expect(atStart.nextEligibleAt, DateTime.utc(2026, 6, 25, 9, 30)); expect(inside.action, ReminderDirectiveAction.suppress); expect(atEnd.action, ReminderDirectiveAction.deliver); }); test('required reminders may pass protected rest by typed policy', () { final freeSlot = freeSlotTask( id: 'rest', start: DateTime.utc(2026, 6, 25, 9), end: DateTime.utc(2026, 6, 25, 10), createdAt: now, ); final critical = scheduledTask( id: 'critical', now: now, type: TaskType.critical, reminderOverride: ReminderProfile.strict, ); final inflexiblePersistent = scheduledTask( id: 'inflexible-persistent', now: now, type: TaskType.inflexible, reminderOverride: ReminderProfile.persistent, ); final inflexibleGentle = scheduledTask( id: 'inflexible-gentle', now: now, type: TaskType.inflexible, reminderOverride: ReminderProfile.gentle, ); final criticalDirective = service.directiveForTask( task: critical, now: now, contextTasks: [freeSlot, critical], ); final persistentDirective = service.directiveForTask( task: inflexiblePersistent, now: now, contextTasks: [freeSlot, inflexiblePersistent], ); final gentleDirective = service.directiveForTask( task: inflexibleGentle, now: now, contextTasks: [freeSlot, inflexibleGentle], ); expect( criticalDirective.action, ReminderDirectiveAction.requireAcknowledgement, ); expect( criticalDirective.reason, ReminderDirectiveReason.requireRequiredAcknowledgement, ); expect(persistentDirective.action, ReminderDirectiveAction.deliver); expect(gentleDirective.action, ReminderDirectiveAction.defer); expect( gentleDirective.reason, ReminderDirectiveReason.deferRequiredDuringProtectedRest, ); expect(gentleDirective.nextEligibleAt, DateTime.utc(2026, 6, 25, 10)); }); test('suppresses locked free-slot completed and unscheduled records', () { final locked = scheduledTask( id: 'locked', now: now, type: TaskType.locked, ); final freeSlot = freeSlotTask( id: 'rest', start: now, end: now.add(const Duration(minutes: 30)), createdAt: now, ); final completed = scheduledTask( id: 'completed', now: now, status: TaskStatus.completed, ); final unscheduled = scheduledTask(id: 'unscheduled', now: now) .copyWith(clearSchedule: true); expect( service.directiveForTask(task: locked, now: now).reason, ReminderDirectiveReason.suppressHiddenLockedTime, ); expect( service.directiveForTask(task: freeSlot, now: now).reason, ReminderDirectiveReason.suppressFreeSlotRecord, ); expect( service.directiveForTask(task: completed, now: now).reason, ReminderDirectiveReason.suppressInactiveStatus, ); expect( service.directiveForTask(task: unscheduled, now: now).reason, ReminderDirectiveReason.suppressNoScheduledWindow, ); }); }); } Task scheduledTask({ required String id, required DateTime now, TaskType type = TaskType.flexible, TaskStatus status = TaskStatus.planned, ReminderProfile? reminderOverride, DateTime? start, }) { final scheduledStart = start ?? now; return Task( id: id, title: 'Task', projectId: 'home', type: type, status: status, durationMinutes: 30, scheduledStart: scheduledStart, scheduledEnd: scheduledStart.add(const Duration(minutes: 30)), reminderOverride: reminderOverride, createdAt: now, updatedAt: now, ); } Task freeSlotTask({ required String id, required DateTime start, required DateTime end, required DateTime createdAt, }) { return const FreeSlotService().create( id: id, title: 'Rest', start: start, end: end, createdAt: createdAt, ); }