forked from eva/focus-flow
test(backlog): seed demo board data
This commit is contained in:
parent
60039be127
commit
82f1fd43b7
4 changed files with 476 additions and 14 deletions
|
|
@ -83,3 +83,20 @@ Freeform Tags Metadata.md` for a later migration-backed feature.
|
||||||
4. Focused application-management tests cover stable empty columns, bucket and
|
4. Focused application-management tests cover stable empty columns, bucket and
|
||||||
summary consistency, search/filter behavior, child previews, not-found detail
|
summary consistency, search/filter behavior, child previews, not-found detail
|
||||||
responses, read-only slot suggestions, and missing-duration handling.
|
responses, read-only slot suggestions, and missing-duration handling.
|
||||||
|
|
||||||
|
## Block 02 Demo Seed Data
|
||||||
|
|
||||||
|
1. The Flutter demo composition now seeds Backlog board data as real scheduler
|
||||||
|
`Task` and `ProjectProfile` records, not widget-local mock rows.
|
||||||
|
2. The seed matches the feasible textual targets from Chunk 2.7: 24 backlog
|
||||||
|
tasks, project counts of Home 5 / Work 8 / Personal 5 / Learning 4 / Side
|
||||||
|
Project 2, priority counts of High 7 / Medium 9 / Low 8, and 12h 15m total
|
||||||
|
estimated time.
|
||||||
|
3. The plan/mockup contains a count conflict: the mockup summary says 24 tasks,
|
||||||
|
while the listed column badge counts add to 22. The current seed keeps 24
|
||||||
|
repository-backed backlog tasks so the summary target stays truthful through
|
||||||
|
public read models.
|
||||||
|
4. `Review Q3 budget` is seeded with Work, 60 minutes, High priority,
|
||||||
|
medium reward, and easy difficulty. Finance/review freeform tags, notes text,
|
||||||
|
and exact drawer slot copy remain deferred with the approved metadata scope
|
||||||
|
decision above.
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,30 @@
|
||||||
|
|
||||||
part of '../demo_scheduler_composition.dart';
|
part of '../demo_scheduler_composition.dart';
|
||||||
|
|
||||||
|
/// Top-level helper that performs the `_demoProjects` operation for this file.
|
||||||
|
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
|
||||||
|
List<ProjectProfile> _demoProjects() {
|
||||||
|
return [
|
||||||
|
ProjectProfile(id: 'home', name: 'Home', colorKey: 'project-home'),
|
||||||
|
ProjectProfile(id: 'work', name: 'Work', colorKey: 'project-work'),
|
||||||
|
ProjectProfile(
|
||||||
|
id: 'personal',
|
||||||
|
name: 'Personal',
|
||||||
|
colorKey: 'project-personal',
|
||||||
|
),
|
||||||
|
ProjectProfile(
|
||||||
|
id: 'learning',
|
||||||
|
name: 'Learning',
|
||||||
|
colorKey: 'project-learning',
|
||||||
|
),
|
||||||
|
ProjectProfile(
|
||||||
|
id: 'side-project',
|
||||||
|
name: 'Side Project',
|
||||||
|
colorKey: 'project-side-project',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/// Top-level helper that performs the `_todaySeedTasks` operation for this file.
|
/// Top-level helper that performs the `_todaySeedTasks` operation for this file.
|
||||||
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
|
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
|
||||||
List<Task> _todaySeedTasks(CivilDate date, DateTime createdAt) {
|
List<Task> _todaySeedTasks(CivilDate date, DateTime createdAt) {
|
||||||
|
|
@ -123,6 +147,284 @@ List<Task> _todaySeedTasks(CivilDate date, DateTime createdAt) {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Top-level helper that performs the `_backlogSeedTasks` operation for this file.
|
||||||
|
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
|
||||||
|
List<Task> _backlogSeedTasks(CivilDate date, DateTime createdAt) {
|
||||||
|
return [
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'reply-to-3-emails',
|
||||||
|
title: 'Reply to 3 emails',
|
||||||
|
projectId: 'home',
|
||||||
|
order: 1,
|
||||||
|
priority: PriorityLevel.medium,
|
||||||
|
reward: RewardLevel.low,
|
||||||
|
difficulty: DifficultyLevel.easy,
|
||||||
|
durationMinutes: 10,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'update-project-status',
|
||||||
|
title: 'Update project status',
|
||||||
|
projectId: 'work',
|
||||||
|
order: 2,
|
||||||
|
priority: PriorityLevel.medium,
|
||||||
|
reward: RewardLevel.low,
|
||||||
|
difficulty: DifficultyLevel.easy,
|
||||||
|
durationMinutes: 15,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'schedule-dentist-appt',
|
||||||
|
title: 'Schedule dentist appt',
|
||||||
|
projectId: 'personal',
|
||||||
|
order: 3,
|
||||||
|
priority: PriorityLevel.medium,
|
||||||
|
reward: RewardLevel.low,
|
||||||
|
difficulty: DifficultyLevel.easy,
|
||||||
|
durationMinutes: 10,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'clean-up-downloads-folder',
|
||||||
|
title: 'Clean up downloads folder',
|
||||||
|
projectId: 'home',
|
||||||
|
order: 4,
|
||||||
|
priority: PriorityLevel.medium,
|
||||||
|
reward: RewardLevel.low,
|
||||||
|
difficulty: DifficultyLevel.easy,
|
||||||
|
durationMinutes: 10,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'plan-tomorrow',
|
||||||
|
title: 'Plan tomorrow',
|
||||||
|
projectId: 'work',
|
||||||
|
order: 5,
|
||||||
|
priority: PriorityLevel.medium,
|
||||||
|
reward: RewardLevel.low,
|
||||||
|
difficulty: DifficultyLevel.easy,
|
||||||
|
durationMinutes: 10,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'review-q3-budget',
|
||||||
|
title: 'Review Q3 budget',
|
||||||
|
projectId: 'work',
|
||||||
|
order: 6,
|
||||||
|
priority: PriorityLevel.high,
|
||||||
|
reward: RewardLevel.medium,
|
||||||
|
difficulty: DifficultyLevel.easy,
|
||||||
|
durationMinutes: 60,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'plan-next-week',
|
||||||
|
title: 'Plan next week',
|
||||||
|
projectId: 'work',
|
||||||
|
order: 7,
|
||||||
|
priority: PriorityLevel.high,
|
||||||
|
reward: RewardLevel.high,
|
||||||
|
difficulty: DifficultyLevel.medium,
|
||||||
|
durationMinutes: 30,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'define-weekly-goals',
|
||||||
|
title: 'Define weekly goals',
|
||||||
|
projectId: 'work',
|
||||||
|
order: 8,
|
||||||
|
priority: PriorityLevel.high,
|
||||||
|
reward: RewardLevel.medium,
|
||||||
|
difficulty: DifficultyLevel.medium,
|
||||||
|
durationMinutes: 15,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'time-block-schedule',
|
||||||
|
title: 'Time block schedule',
|
||||||
|
projectId: 'work',
|
||||||
|
order: 9,
|
||||||
|
priority: PriorityLevel.high,
|
||||||
|
reward: RewardLevel.medium,
|
||||||
|
difficulty: DifficultyLevel.medium,
|
||||||
|
durationMinutes: 15,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'organize-kitchen-drawers',
|
||||||
|
title: 'Organize kitchen drawers',
|
||||||
|
projectId: 'home',
|
||||||
|
order: 10,
|
||||||
|
priority: PriorityLevel.low,
|
||||||
|
reward: RewardLevel.low,
|
||||||
|
difficulty: DifficultyLevel.medium,
|
||||||
|
durationMinutes: 45,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'renew-car-registration',
|
||||||
|
title: 'Renew car registration',
|
||||||
|
projectId: 'home',
|
||||||
|
order: 11,
|
||||||
|
priority: PriorityLevel.high,
|
||||||
|
reward: RewardLevel.low,
|
||||||
|
difficulty: DifficultyLevel.medium,
|
||||||
|
durationMinutes: 25,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'build-analytics-dashboard',
|
||||||
|
title: 'Build analytics dashboard',
|
||||||
|
projectId: 'work',
|
||||||
|
order: 12,
|
||||||
|
priority: PriorityLevel.high,
|
||||||
|
reward: RewardLevel.high,
|
||||||
|
difficulty: DifficultyLevel.hard,
|
||||||
|
durationMinutes: 120,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'write-marketing-strategy',
|
||||||
|
title: 'Write marketing strategy',
|
||||||
|
projectId: 'work',
|
||||||
|
order: 13,
|
||||||
|
priority: PriorityLevel.high,
|
||||||
|
reward: RewardLevel.high,
|
||||||
|
difficulty: DifficultyLevel.medium,
|
||||||
|
durationMinutes: 90,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'complete-online-course',
|
||||||
|
title: 'Complete online course',
|
||||||
|
projectId: 'learning',
|
||||||
|
order: 14,
|
||||||
|
priority: PriorityLevel.medium,
|
||||||
|
reward: RewardLevel.high,
|
||||||
|
difficulty: DifficultyLevel.medium,
|
||||||
|
durationMinutes: 90,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'module-1-foundations',
|
||||||
|
title: 'Module 1: Foundations',
|
||||||
|
projectId: 'learning',
|
||||||
|
order: 15,
|
||||||
|
priority: PriorityLevel.medium,
|
||||||
|
reward: RewardLevel.medium,
|
||||||
|
difficulty: DifficultyLevel.hard,
|
||||||
|
durationMinutes: 15,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'module-2-deep-dive',
|
||||||
|
title: 'Module 2: Deep Dive',
|
||||||
|
projectId: 'learning',
|
||||||
|
order: 16,
|
||||||
|
priority: PriorityLevel.low,
|
||||||
|
reward: RewardLevel.medium,
|
||||||
|
difficulty: DifficultyLevel.hard,
|
||||||
|
durationMinutes: 15,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'module-3-practice',
|
||||||
|
title: 'Module 3: Practice',
|
||||||
|
projectId: 'learning',
|
||||||
|
order: 17,
|
||||||
|
priority: PriorityLevel.low,
|
||||||
|
reward: RewardLevel.medium,
|
||||||
|
difficulty: DifficultyLevel.hard,
|
||||||
|
durationMinutes: 15,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'learn-guitar',
|
||||||
|
title: 'Learn guitar',
|
||||||
|
projectId: 'personal',
|
||||||
|
order: 18,
|
||||||
|
priority: PriorityLevel.low,
|
||||||
|
reward: RewardLevel.low,
|
||||||
|
difficulty: DifficultyLevel.medium,
|
||||||
|
durationMinutes: 30,
|
||||||
|
backlogTags: const {BacklogTag.wishlist},
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'start-youtube-channel',
|
||||||
|
title: 'Start YouTube channel',
|
||||||
|
projectId: 'side-project',
|
||||||
|
order: 19,
|
||||||
|
priority: PriorityLevel.low,
|
||||||
|
reward: RewardLevel.low,
|
||||||
|
difficulty: DifficultyLevel.medium,
|
||||||
|
durationMinutes: 20,
|
||||||
|
backlogTags: const {BacklogTag.wishlist},
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'travel-to-japan',
|
||||||
|
title: 'Travel to Japan',
|
||||||
|
projectId: 'personal',
|
||||||
|
order: 20,
|
||||||
|
priority: PriorityLevel.medium,
|
||||||
|
reward: RewardLevel.medium,
|
||||||
|
difficulty: DifficultyLevel.medium,
|
||||||
|
durationMinutes: 30,
|
||||||
|
backlogTags: const {BacklogTag.wishlist},
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'home-office-upgrade',
|
||||||
|
title: 'Home office upgrade',
|
||||||
|
projectId: 'home',
|
||||||
|
order: 21,
|
||||||
|
priority: PriorityLevel.low,
|
||||||
|
reward: RewardLevel.low,
|
||||||
|
difficulty: DifficultyLevel.medium,
|
||||||
|
durationMinutes: 20,
|
||||||
|
backlogTags: const {BacklogTag.wishlist},
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'read-2-books',
|
||||||
|
title: 'Read 2 books',
|
||||||
|
projectId: 'personal',
|
||||||
|
order: 22,
|
||||||
|
priority: PriorityLevel.low,
|
||||||
|
reward: RewardLevel.low,
|
||||||
|
difficulty: DifficultyLevel.medium,
|
||||||
|
durationMinutes: 15,
|
||||||
|
backlogTags: const {BacklogTag.wishlist},
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'sketch-app-icon-ideas',
|
||||||
|
title: 'Sketch app icon ideas',
|
||||||
|
projectId: 'side-project',
|
||||||
|
order: 23,
|
||||||
|
priority: PriorityLevel.medium,
|
||||||
|
reward: RewardLevel.medium,
|
||||||
|
difficulty: DifficultyLevel.easy,
|
||||||
|
durationMinutes: 15,
|
||||||
|
backlogTags: const {BacklogTag.wishlist},
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
_backlogSeedTask(
|
||||||
|
id: 'archive-photo-backlog',
|
||||||
|
title: 'Archive photo backlog',
|
||||||
|
projectId: 'personal',
|
||||||
|
order: 24,
|
||||||
|
priority: PriorityLevel.low,
|
||||||
|
reward: RewardLevel.low,
|
||||||
|
difficulty: DifficultyLevel.medium,
|
||||||
|
durationMinutes: 15,
|
||||||
|
backlogTags: const {BacklogTag.wishlist},
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/// Top-level helper that performs the `_seedTask` operation for this file.
|
/// Top-level helper that performs the `_seedTask` operation for this file.
|
||||||
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
|
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
|
||||||
Task _seedTask({
|
Task _seedTask({
|
||||||
|
|
@ -161,3 +463,34 @@ Task _seedTask({
|
||||||
updatedAt: completedAt ?? createdAt,
|
updatedAt: completedAt ?? createdAt,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Top-level helper that performs the `_backlogSeedTask` operation for this file.
|
||||||
|
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
|
||||||
|
Task _backlogSeedTask({
|
||||||
|
required String id,
|
||||||
|
required String title,
|
||||||
|
required String projectId,
|
||||||
|
required int order,
|
||||||
|
required PriorityLevel priority,
|
||||||
|
required RewardLevel reward,
|
||||||
|
required DifficultyLevel difficulty,
|
||||||
|
required int durationMinutes,
|
||||||
|
required DateTime createdAt,
|
||||||
|
Set<BacklogTag> backlogTags = const <BacklogTag>{},
|
||||||
|
}) {
|
||||||
|
final orderedCreatedAt = createdAt.add(Duration(minutes: order));
|
||||||
|
return Task(
|
||||||
|
id: id,
|
||||||
|
title: title,
|
||||||
|
projectId: projectId,
|
||||||
|
type: TaskType.flexible,
|
||||||
|
status: TaskStatus.backlog,
|
||||||
|
priority: priority,
|
||||||
|
reward: reward,
|
||||||
|
difficulty: difficulty,
|
||||||
|
durationMinutes: durationMinutes,
|
||||||
|
backlogTags: backlogTags,
|
||||||
|
createdAt: orderedCreatedAt,
|
||||||
|
updatedAt: orderedCreatedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,24 +91,13 @@ class DemoSchedulerComposition implements SchedulerAppComposition {
|
||||||
CivilDate.fromDateTime((clock ?? const SystemClock()).now());
|
CivilDate.fromDateTime((clock ?? const SystemClock()).now());
|
||||||
final readAt = _instant(resolvedDate, 15, 55);
|
final readAt = _instant(resolvedDate, 15, 55);
|
||||||
final createdAt = _instant(resolvedDate, 8);
|
final createdAt = _instant(resolvedDate, 8);
|
||||||
final project = ProjectProfile(
|
final projects = _demoProjects();
|
||||||
id: projectId,
|
|
||||||
name: 'Home',
|
|
||||||
colorKey: 'project-home',
|
|
||||||
);
|
|
||||||
final tasks = <Task>[
|
final tasks = <Task>[
|
||||||
if (includeTodayItems) ..._todaySeedTasks(resolvedDate, createdAt),
|
if (includeTodayItems) ..._todaySeedTasks(resolvedDate, createdAt),
|
||||||
if (includeBacklogItems)
|
if (includeBacklogItems) ..._backlogSeedTasks(resolvedDate, createdAt),
|
||||||
Task.quickCapture(
|
|
||||||
id: 'later-backlog-note',
|
|
||||||
title: 'Review reusable grocery list',
|
|
||||||
projectId: projectId,
|
|
||||||
createdAt: createdAt,
|
|
||||||
updatedAt: createdAt,
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
final store = InMemoryApplicationUnitOfWork(
|
final store = InMemoryApplicationUnitOfWork(
|
||||||
initialProjects: [project],
|
initialProjects: projects,
|
||||||
initialOwnerSettings: [
|
initialOwnerSettings: [
|
||||||
OwnerSettings(
|
OwnerSettings(
|
||||||
ownerId: ownerId,
|
ownerId: ownerId,
|
||||||
|
|
@ -191,6 +180,22 @@ class DemoSchedulerComposition implements SchedulerAppComposition {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a generic read controller for the Backlog board.
|
||||||
|
UiReadController<BacklogBoardQueryResult> createBacklogBoardController() {
|
||||||
|
return ApplicationReadController<BacklogBoardQueryResult>(
|
||||||
|
read: () {
|
||||||
|
return managementUseCases.getBacklogBoard(
|
||||||
|
GetBacklogBoardRequest(
|
||||||
|
context: context('ui-read-backlog-board'),
|
||||||
|
localDate: date,
|
||||||
|
sortKey: BacklogSortKey.age,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isEmpty: (state) => state.summary.totalTaskCount == 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates the command controller used by interactive scheduler controls.
|
/// Creates the command controller used by interactive scheduler controls.
|
||||||
@override
|
@override
|
||||||
SchedulerCommandController createCommandController({
|
SchedulerCommandController createCommandController({
|
||||||
|
|
|
||||||
107
apps/focus_flow_flutter/test/app/demo_backlog_seed_test.dart
Normal file
107
apps/focus_flow_flutter/test/app/demo_backlog_seed_test.dart
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
// SPDX-FileCopyrightText: 2026 FocusFlow contributors
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
/// Tests Demo Backlog Seed behavior in the FocusFlow Flutter app.
|
||||||
|
library;
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:scheduler_core/scheduler_core.dart';
|
||||||
|
|
||||||
|
import 'package:focus_flow_flutter/app/demo_scheduler_composition.dart';
|
||||||
|
|
||||||
|
/// Runs demo Backlog seed tests.
|
||||||
|
void main() {
|
||||||
|
test('seeded backlog board exposes mockup-scale domain data', () async {
|
||||||
|
final composition = DemoSchedulerComposition.seeded(
|
||||||
|
selectedDate: CivilDate(2025, 5, 20),
|
||||||
|
);
|
||||||
|
addTearDown(composition.dispose);
|
||||||
|
|
||||||
|
final board = (await composition.managementUseCases.getBacklogBoard(
|
||||||
|
GetBacklogBoardRequest(
|
||||||
|
context: composition.context('demo-backlog-board'),
|
||||||
|
localDate: composition.date,
|
||||||
|
sortKey: BacklogSortKey.age,
|
||||||
|
),
|
||||||
|
)).requireValue;
|
||||||
|
final items = board.columns.expand((column) => column.items).toList();
|
||||||
|
|
||||||
|
expect(board.summary.totalTaskCount, 24);
|
||||||
|
expect(board.summary.totalEstimatedMinutes, 735);
|
||||||
|
expect(_distributionCounts(board.summary.projectDistribution), {
|
||||||
|
'Home': 5,
|
||||||
|
'Learning': 4,
|
||||||
|
'Personal': 5,
|
||||||
|
'Side Project': 2,
|
||||||
|
'Work': 8,
|
||||||
|
});
|
||||||
|
expect(_distributionCounts(board.summary.priorityDistribution), {
|
||||||
|
'High': 7,
|
||||||
|
'Medium': 9,
|
||||||
|
'Low': 8,
|
||||||
|
});
|
||||||
|
expect(items.map((item) => item.title), containsAll(_visibleSeedTitles));
|
||||||
|
|
||||||
|
final review = items.singleWhere(
|
||||||
|
(item) => item.title == 'Review Q3 budget',
|
||||||
|
);
|
||||||
|
expect(review.projectName, 'Work');
|
||||||
|
expect(review.durationMinutes, 60);
|
||||||
|
expect(review.priority, PriorityLevel.high);
|
||||||
|
expect(review.reward, RewardLevel.medium);
|
||||||
|
expect(review.difficulty, DifficultyLevel.easy);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('seeded review detail stays within deferred metadata scope', () async {
|
||||||
|
final composition = DemoSchedulerComposition.seeded(
|
||||||
|
selectedDate: CivilDate(2025, 5, 20),
|
||||||
|
);
|
||||||
|
addTearDown(composition.dispose);
|
||||||
|
|
||||||
|
final detail = (await composition.managementUseCases.getBacklogTaskDetail(
|
||||||
|
GetBacklogTaskDetailRequest(
|
||||||
|
context: composition.context('demo-review-detail'),
|
||||||
|
localDate: composition.date,
|
||||||
|
taskId: 'review-q3-budget',
|
||||||
|
),
|
||||||
|
)).requireValue;
|
||||||
|
|
||||||
|
expect(detail.item.title, 'Review Q3 budget');
|
||||||
|
expect(detail.notes, isNull);
|
||||||
|
expect(detail.tags, isEmpty);
|
||||||
|
expect(detail.suggestedSlots, isNotEmpty);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Visible task titles from the Backlog board mockup that must be present in the seed.
|
||||||
|
const _visibleSeedTitles = <String>[
|
||||||
|
'Reply to 3 emails',
|
||||||
|
'Update project status',
|
||||||
|
'Schedule dentist appt',
|
||||||
|
'Clean up downloads folder',
|
||||||
|
'Plan tomorrow',
|
||||||
|
'Review Q3 budget',
|
||||||
|
'Plan next week',
|
||||||
|
'Define weekly goals',
|
||||||
|
'Time block schedule',
|
||||||
|
'Organize kitchen drawers',
|
||||||
|
'Build analytics dashboard',
|
||||||
|
'Write marketing strategy',
|
||||||
|
'Complete online course',
|
||||||
|
'Module 1: Foundations',
|
||||||
|
'Module 2: Deep Dive',
|
||||||
|
'Module 3: Practice',
|
||||||
|
'Learn guitar',
|
||||||
|
'Start YouTube channel',
|
||||||
|
'Travel to Japan',
|
||||||
|
'Home office upgrade',
|
||||||
|
'Read 2 books',
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Top-level helper that performs the `_distributionCounts` operation for this file.
|
||||||
|
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
|
||||||
|
Map<String, int> _distributionCounts(
|
||||||
|
BacklogDistributionReadModel distribution,
|
||||||
|
) {
|
||||||
|
return {for (final entry in distribution.entries) entry.label: entry.count};
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue