test(timeline): add timeline state regression suite
This commit is contained in:
parent
88434e3805
commit
abf069db1f
4 changed files with 171 additions and 5 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
# V1 Block 08 — Today Timeline State
|
# V1 Block 08 — Today Timeline State
|
||||||
|
|
||||||
Status: In progress — Chunks 8.1, 8.2, and 8.3 complete
|
Status: Complete
|
||||||
|
|
||||||
Purpose: Prepare domain/view-state data for the Today timeline without building full Flutter UI yet unless explicitly requested.
|
Purpose: Prepare domain/view-state data for the Today timeline without building full Flutter UI yet unless explicitly requested.
|
||||||
|
|
||||||
|
|
@ -149,6 +149,15 @@ Acceptance criteria:
|
||||||
- Mapping rules are tested without depending on a UI framework.
|
- Mapping rules are tested without depending on a UI framework.
|
||||||
- Existing scheduling, backlog, locked-block, and task-action tests still pass.
|
- Existing scheduling, backlog, locked-block, and task-action tests still pass.
|
||||||
|
|
||||||
|
Completed:
|
||||||
|
|
||||||
|
- Expanded timeline regression coverage for all task-type background tokens.
|
||||||
|
- Added exhaustive reward icon token coverage for every reward level plus `notSet`.
|
||||||
|
- Added exhaustive difficulty icon token coverage for every difficulty level plus `notSet`.
|
||||||
|
- Added focused compact-mode reduced-output regression coverage.
|
||||||
|
- Confirmed existing timeline tests cover flexible duration display, required explicit time display, reveal-gated locked overlays, locked overlay category, project color tokens, and flexible quick actions.
|
||||||
|
- Verified with `dart format lib test`, `dart analyze`, and `dart test`.
|
||||||
|
|
||||||
Commit suggestion:
|
Commit suggestion:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# V1 Block 08 — Today Timeline State
|
# V1 Block 08 — Today Timeline State
|
||||||
|
|
||||||
Status: In progress — Chunks 8.1, 8.2, and 8.3 complete
|
Status: Complete
|
||||||
|
|
||||||
Purpose: Prepare domain/view-state data for the Today timeline without building full Flutter UI yet unless explicitly requested.
|
Purpose: Prepare domain/view-state data for the Today timeline without building full Flutter UI yet unless explicitly requested.
|
||||||
|
|
||||||
|
|
@ -149,6 +149,15 @@ Acceptance criteria:
|
||||||
- Mapping rules are tested without depending on a UI framework.
|
- Mapping rules are tested without depending on a UI framework.
|
||||||
- Existing scheduling, backlog, locked-block, and task-action tests still pass.
|
- Existing scheduling, backlog, locked-block, and task-action tests still pass.
|
||||||
|
|
||||||
|
Completed:
|
||||||
|
|
||||||
|
- Expanded timeline regression coverage for all task-type background tokens.
|
||||||
|
- Added exhaustive reward icon token coverage for every reward level plus `notSet`.
|
||||||
|
- Added exhaustive difficulty icon token coverage for every difficulty level plus `notSet`.
|
||||||
|
- Added focused compact-mode reduced-output regression coverage.
|
||||||
|
- Confirmed existing timeline tests cover flexible duration display, required explicit time display, reveal-gated locked overlays, locked overlay category, project color tokens, and flexible quick actions.
|
||||||
|
- Verified with `dart format lib test`, `dart analyze`, and `dart test`.
|
||||||
|
|
||||||
Commit suggestion:
|
Commit suggestion:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Current Software Plan
|
# Current Software Plan
|
||||||
|
|
||||||
Execute active V1 block documents in numeric order. Blocks 01-07 are completed
|
Execute active V1 block documents in numeric order. Blocks 01-08 are completed
|
||||||
and archived; the next active block is Block 08.
|
and archived; the next active block is Block 09.
|
||||||
|
|
||||||
## Execution rules
|
## Execution rules
|
||||||
|
|
||||||
|
|
@ -33,6 +33,6 @@ Do not infer a new level name.
|
||||||
5. `V1_BLOCK_05_Recurring_Locked_Blocks.md`
|
5. `V1_BLOCK_05_Recurring_Locked_Blocks.md`
|
||||||
6. `V1_BLOCK_06_Task_Actions_State_Transitions_UPDATED.md` — archived
|
6. `V1_BLOCK_06_Task_Actions_State_Transitions_UPDATED.md` — archived
|
||||||
7. `V1_BLOCK_07_Child_Tasks.md` — archived
|
7. `V1_BLOCK_07_Child_Tasks.md` — archived
|
||||||
8. `V1_BLOCK_08_Today_Timeline_State.md`
|
8. `V1_BLOCK_08_Today_Timeline_State.md` — archived
|
||||||
9. `V1_BLOCK_09_Persistence_Preparation.md`
|
9. `V1_BLOCK_09_Persistence_Preparation.md`
|
||||||
10. `V1_BLOCK_10_Testing_Documentation_Handoff.md`
|
10. `V1_BLOCK_10_Testing_Documentation_Handoff.md`
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,96 @@ void main() {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('background token maps from every task type', () {
|
||||||
|
final expectedTokens = {
|
||||||
|
TaskType.flexible: TimelineBackgroundToken.flexible,
|
||||||
|
TaskType.inflexible: TimelineBackgroundToken.inflexible,
|
||||||
|
TaskType.critical: TimelineBackgroundToken.critical,
|
||||||
|
TaskType.locked: TimelineBackgroundToken.locked,
|
||||||
|
TaskType.surprise: TimelineBackgroundToken.surprise,
|
||||||
|
TaskType.freeSlot: TimelineBackgroundToken.freeSlot,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (final entry in expectedTokens.entries) {
|
||||||
|
final task = scheduledTask(
|
||||||
|
id: entry.key.name,
|
||||||
|
title: entry.key.name,
|
||||||
|
projectId: project.id,
|
||||||
|
type: entry.key,
|
||||||
|
createdAt: createdAt,
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
mapper.fromTask(task).backgroundToken,
|
||||||
|
entry.value,
|
||||||
|
reason: entry.key.name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reward icon token handles every reward level plus not set', () {
|
||||||
|
final expectedTokens = {
|
||||||
|
RewardLevel.notSet: TimelineRewardIconToken.notSet,
|
||||||
|
RewardLevel.veryLow: TimelineRewardIconToken.veryLow,
|
||||||
|
RewardLevel.low: TimelineRewardIconToken.low,
|
||||||
|
RewardLevel.medium: TimelineRewardIconToken.medium,
|
||||||
|
RewardLevel.high: TimelineRewardIconToken.high,
|
||||||
|
RewardLevel.veryHigh: TimelineRewardIconToken.veryHigh,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (final entry in expectedTokens.entries) {
|
||||||
|
final task = scheduledTask(
|
||||||
|
id: entry.key.name,
|
||||||
|
title: entry.key.name,
|
||||||
|
projectId: project.id,
|
||||||
|
type: TaskType.flexible,
|
||||||
|
reward: entry.key,
|
||||||
|
createdAt: createdAt,
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
mapper.fromTask(task).rewardIconToken,
|
||||||
|
entry.value,
|
||||||
|
reason: entry.key.name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('difficulty icon token handles every difficulty level plus not set',
|
||||||
|
() {
|
||||||
|
final expectedTokens = {
|
||||||
|
DifficultyLevel.notSet: TimelineDifficultyIconToken.notSet,
|
||||||
|
DifficultyLevel.veryEasy: TimelineDifficultyIconToken.veryEasy,
|
||||||
|
DifficultyLevel.easy: TimelineDifficultyIconToken.easy,
|
||||||
|
DifficultyLevel.medium: TimelineDifficultyIconToken.medium,
|
||||||
|
DifficultyLevel.hard: TimelineDifficultyIconToken.hard,
|
||||||
|
DifficultyLevel.veryHard: TimelineDifficultyIconToken.veryHard,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (final entry in expectedTokens.entries) {
|
||||||
|
final task = scheduledTask(
|
||||||
|
id: entry.key.name,
|
||||||
|
title: entry.key.name,
|
||||||
|
projectId: project.id,
|
||||||
|
type: TaskType.flexible,
|
||||||
|
difficulty: entry.key,
|
||||||
|
createdAt: createdAt,
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
mapper.fromTask(task).difficultyIconToken,
|
||||||
|
entry.value,
|
||||||
|
reason: entry.key.name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('flexible task cards display duration while retaining position', () {
|
test('flexible task cards display duration while retaining position', () {
|
||||||
final task = scheduledTask(
|
final task = scheduledTask(
|
||||||
id: 'laundry',
|
id: 'laundry',
|
||||||
|
|
@ -331,6 +421,64 @@ void main() {
|
||||||
expect(state.nextRequiredItem!.id, 'required');
|
expect(state.nextRequiredItem!.id, 'required');
|
||||||
expect(state.compactItems.map((item) => item.id), ['required']);
|
expect(state.compactItems.map((item) => item.id), ['required']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('compact mode returns only the reduced current and next items', () {
|
||||||
|
final current = scheduledTask(
|
||||||
|
id: 'current',
|
||||||
|
title: 'Current',
|
||||||
|
projectId: project.id,
|
||||||
|
type: TaskType.flexible,
|
||||||
|
createdAt: createdAt,
|
||||||
|
start: DateTime(2026, 6, 20, 9),
|
||||||
|
end: DateTime(2026, 6, 20, 10),
|
||||||
|
);
|
||||||
|
final nextRequired = scheduledTask(
|
||||||
|
id: 'next-required',
|
||||||
|
title: 'Next required',
|
||||||
|
projectId: project.id,
|
||||||
|
type: TaskType.inflexible,
|
||||||
|
createdAt: createdAt,
|
||||||
|
start: DateTime(2026, 6, 20, 10),
|
||||||
|
end: DateTime(2026, 6, 20, 10, 30),
|
||||||
|
);
|
||||||
|
final laterRequired = scheduledTask(
|
||||||
|
id: 'later-required',
|
||||||
|
title: 'Later required',
|
||||||
|
projectId: project.id,
|
||||||
|
type: TaskType.critical,
|
||||||
|
createdAt: createdAt,
|
||||||
|
start: DateTime(2026, 6, 20, 12),
|
||||||
|
end: DateTime(2026, 6, 20, 12, 30),
|
||||||
|
);
|
||||||
|
final laterFlexible = scheduledTask(
|
||||||
|
id: 'later-flexible',
|
||||||
|
title: 'Later flexible',
|
||||||
|
projectId: project.id,
|
||||||
|
type: TaskType.flexible,
|
||||||
|
createdAt: createdAt,
|
||||||
|
start: DateTime(2026, 6, 20, 13),
|
||||||
|
end: DateTime(2026, 6, 20, 13, 30),
|
||||||
|
);
|
||||||
|
|
||||||
|
final state = mapper.compactStateForTasks(
|
||||||
|
tasks: [laterFlexible, laterRequired, nextRequired, current],
|
||||||
|
now: DateTime(2026, 6, 20, 9, 15),
|
||||||
|
manualCompactModeEnabled: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(state.compactItems.map((item) => item.id), [
|
||||||
|
'current',
|
||||||
|
'next-required',
|
||||||
|
]);
|
||||||
|
expect(
|
||||||
|
state.compactItems.map((item) => item.id),
|
||||||
|
isNot(contains('later-required')),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
state.compactItems.map((item) => item.id),
|
||||||
|
isNot(contains('later-flexible')),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue