import 'package:adhd_scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { group('Timeline item view model', () { final createdAt = DateTime(2026, 6, 20, 8); final start = DateTime(2026, 6, 20, 9); final end = DateTime(2026, 6, 20, 9, 30); const project = ProjectProfile( id: 'home', name: 'Home', colorKey: 'project-home', ); final mapper = TimelineItemMapper.fromProjects(const [project]); test('maps task fields to display tokens', () { final task = scheduledTask( id: 'dishes', title: 'Wash dishes', projectId: project.id, type: TaskType.flexible, reward: RewardLevel.high, difficulty: DifficultyLevel.hard, createdAt: createdAt, start: start, end: end, ); final item = mapper.fromTask(task); expect(item.id, 'dishes'); expect(item.displayTitle, 'Wash dishes'); expect(item.taskType, TaskType.flexible); expect(item.projectColorToken, 'project-home'); expect(item.backgroundToken, TimelineBackgroundToken.flexible); expect(item.rewardIconToken, TimelineRewardIconToken.high); expect(item.difficultyIconToken, TimelineDifficultyIconToken.hard); expect(item.category, TimelineItemCategory.taskCard); expect(item.quickActions, [ TimelineQuickAction.done, TimelineQuickAction.push, TimelineQuickAction.backlog, TimelineQuickAction.breakUp, ]); }); test('flexible task cards display duration while retaining position', () { final task = scheduledTask( id: 'laundry', title: 'Fold laundry', projectId: project.id, type: TaskType.flexible, durationMinutes: 25, createdAt: createdAt, start: start, end: start.add(const Duration(minutes: 25)), ); final item = mapper.fromTask(task); expect(item.showsExplicitTime, isFalse); expect(item.durationMinutes, 25); expect(item.start, start); expect(item.end, start.add(const Duration(minutes: 25))); }); test('flexible duration can fall back to scheduled interval', () { final task = scheduledTask( id: 'admin', title: 'Handle admin', projectId: project.id, type: TaskType.flexible, createdAt: createdAt, start: start, end: start.add(const Duration(minutes: 45)), ); final item = mapper.fromTask(task); expect(item.showsExplicitTime, isFalse); expect(item.durationMinutes, 45); }); test('inflexible critical and locked items show explicit times', () { for (final type in [ TaskType.inflexible, TaskType.critical, TaskType.locked, ]) { final task = scheduledTask( id: type.name, title: type.name, projectId: project.id, type: type, createdAt: createdAt, start: start, end: end, ); final item = mapper.fromTask(task); expect(item.showsExplicitTime, isTrue, reason: type.name); expect(item.start, start, reason: type.name); expect(item.end, end, reason: type.name); expect(item.durationMinutes, isNull, reason: type.name); } }); test('locked item is an overlay without normal task quick actions', () { final task = scheduledTask( id: 'sleep', title: 'Sleep', projectId: 'locked', type: TaskType.locked, createdAt: createdAt, start: DateTime(2026, 6, 20, 0), end: DateTime(2026, 6, 20, 7), ); final item = mapper.fromTask(task); expect(item.category, TimelineItemCategory.overlay); expect(item.projectColorToken, 'locked'); expect(item.backgroundToken, TimelineBackgroundToken.locked); expect(item.quickActions, isEmpty); }); test('hidden locked occurrences stay hidden until reveal mode is active', () { final occurrence = LockedBlockOccurrence( name: 'Work', interval: TimeInterval( start: DateTime(2026, 6, 20, 9), end: DateTime(2026, 6, 20, 17), ), hiddenByDefault: true, lockedBlockId: 'work-hours', projectId: project.id, ); final hiddenItem = mapper.fromLockedOccurrence( occurrence, revealHiddenLockedBlocks: false, ); final revealedItem = mapper.fromLockedOccurrence( occurrence, revealHiddenLockedBlocks: true, ); expect(hiddenItem, isNull); expect(revealedItem, isNotNull); expect(revealedItem!.id, 'locked-block:work-hours'); expect(revealedItem.displayTitle, 'Work'); expect(revealedItem.start, DateTime(2026, 6, 20, 9)); expect(revealedItem.end, DateTime(2026, 6, 20, 17)); expect(revealedItem.showsExplicitTime, isTrue); expect(revealedItem.category, TimelineItemCategory.overlay); expect(revealedItem.projectColorToken, 'project-home'); expect(revealedItem.quickActions, isEmpty); }); test('visible locked occurrences can produce overlays without reveal mode', () { final occurrence = LockedBlockOccurrence( name: 'Appointment', interval: TimeInterval( start: DateTime(2026, 6, 20, 11), end: DateTime(2026, 6, 20, 12), ), hiddenByDefault: false, overrideId: 'appointment-override', ); final item = mapper.fromLockedOccurrence( occurrence, revealHiddenLockedBlocks: false, ); expect(item, isNotNull); expect(item!.id, 'locked-override:appointment-override'); expect(item.displayTitle, 'Appointment'); expect(item.category, TimelineItemCategory.overlay); expect(item.taskType, TaskType.locked); expect(item.backgroundToken, TimelineBackgroundToken.locked); expect(item.rewardIconToken, TimelineRewardIconToken.notSet); expect(item.difficultyIconToken, TimelineDifficultyIconToken.notSet); expect(item.showsExplicitTime, isTrue); expect(item.quickActions, isEmpty); }); }); } Task scheduledTask({ required String id, required String title, required String projectId, required TaskType type, required DateTime createdAt, required DateTime start, required DateTime end, RewardLevel reward = RewardLevel.notSet, DifficultyLevel difficulty = DifficultyLevel.notSet, int? durationMinutes, }) { return Task( id: id, title: title, projectId: projectId, type: type, status: TaskStatus.planned, reward: reward, difficulty: difficulty, durationMinutes: durationMinutes, scheduledStart: start, scheduledEnd: end, createdAt: createdAt, updatedAt: createdAt, ); }