219 lines
6.7 KiB
Dart
219 lines
6.7 KiB
Dart
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,
|
|
);
|
|
}
|