Skip to content

Commit 4d5527e

Browse files
authored
test(activity): cover the two cases the status-bar work deferred (#45)
Both were deferred for want of a TUI integration harness. One exists now (`TestAppBuilder` with `TestEventSource`), so there is no reason to keep leaning on "covered indirectly by unit tests". Seeding: `ActivityState::seed_from_db` takes the first row from `find_by_status(InProgress)`, which orders by `full_id`. The fixture lists `zulu` above `alpha` in the file, so row order and `full_id` order disagree and the test fails if seeding ever stops being deterministic. Plus the empty case, where nothing is in progress. Toggling: driven through the real key path rather than by calling `record_transition` directly, so it covers the wiring in `handle_toggle_status` and not just the state machine. Open to InProgress fills the slot; InProgress to Done clears it and pushes to the recently-completed tail. `tasks/tasks.md` now marks the status-bar and live-updates files complete, which they are.
1 parent c6afbc7 commit 4d5527e

3 files changed

Lines changed: 171 additions & 4 deletions

File tree

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
//! Integration tests for the status-bar activity sections.
2+
//!
3+
//! These cover the two cases `tasks/tasks.status-bar-activity.md` left
4+
//! deferred for want of a TUI harness that did not exist yet: the ordering
5+
//! `seed_from_db` uses to pick between several in-progress tasks, and a
6+
//! toggle driven through the real key path rather than through
7+
//! `ActivityState::record_transition` directly.
8+
9+
use lash_tui::activity::ActivityState;
10+
use lash_tui::testing::{keys, TestAppBuilder};
11+
use std::path::PathBuf;
12+
use std::time::Instant;
13+
use tempfile::TempDir;
14+
15+
/// Build a project containing `body` and index it, returning the temp dir
16+
/// (which must outlive the test), the db path, and the task file path.
17+
fn setup_project(body: &str) -> (TempDir, PathBuf) {
18+
let temp_dir = TempDir::new().unwrap();
19+
let project_root = temp_dir.path().to_path_buf();
20+
std::fs::create_dir(project_root.join(".lash")).unwrap();
21+
let db_path = project_root.join(".lash").join("lash.db");
22+
23+
let conn = lash_db::init_database(&db_path).unwrap();
24+
std::fs::write(project_root.join("tasks.md"), body).unwrap();
25+
26+
use lash_db::{Indexer, IndexerConfig};
27+
let parser_config = lash_types::LashConfig::default();
28+
let indexer_config = IndexerConfig::new(project_root).with_progress(false);
29+
let mut indexer = Indexer::new(&conn, indexer_config, &parser_config);
30+
indexer.index_project().unwrap();
31+
32+
(temp_dir, db_path)
33+
}
34+
35+
#[test]
36+
fn seeding_picks_the_smaller_full_id_when_several_tasks_are_in_progress() {
37+
// Two in-progress tasks, written in the order that would give the wrong
38+
// answer if seeding took whichever the DB happened to return first:
39+
// `zulu` is above `alpha` in the file, so insertion order and full_id
40+
// order disagree.
41+
let (_temp_dir, db_path) = setup_project(
42+
r"# Sample
43+
44+
@id: sample
45+
46+
## Tasks
47+
48+
- [>] Zulu task
49+
@id: zulu
50+
- [>] Alpha task
51+
@id: alpha
52+
",
53+
);
54+
let conn = lash_db::open_database(&db_path).unwrap();
55+
56+
let mut activity = ActivityState::default();
57+
activity.seed_from_db(&conn, Instant::now());
58+
59+
let seeded = activity
60+
.in_progress
61+
.as_ref()
62+
.expect("an in-progress task should have been seeded");
63+
assert_eq!(
64+
seeded.full_id, "sample#alpha",
65+
"seeding must be deterministic on full_id, not on row order"
66+
);
67+
}
68+
69+
#[test]
70+
fn seeding_leaves_in_progress_empty_when_nothing_is_in_progress() {
71+
let (_temp_dir, db_path) = setup_project(
72+
r"# Sample
73+
74+
@id: sample
75+
76+
## Tasks
77+
78+
- [ ] Open task
79+
- [x] Done task
80+
",
81+
);
82+
let conn = lash_db::open_database(&db_path).unwrap();
83+
84+
let mut activity = ActivityState::default();
85+
activity.seed_from_db(&conn, Instant::now());
86+
87+
assert!(activity.in_progress.is_none());
88+
}
89+
90+
/// Drive the app to the Tasks pane with the first task selected, then press
91+
/// Space `toggles` times. Returns the app so the caller can inspect state.
92+
fn app_after_toggles(db_path: &PathBuf, toggles: usize) -> lash_tui::testing::TestTuiApp {
93+
// Tab twice to move focus Navigation -> Description -> Detail (the tasks
94+
// pane), which is where Space toggles rather than warning.
95+
let mut events = vec![keys::tab(), keys::tab()];
96+
for _ in 0..toggles {
97+
events.push(keys::char(' '));
98+
}
99+
100+
let mut app = TestAppBuilder::new()
101+
.with_db(db_path)
102+
.with_size(80, 24)
103+
.with_events(events)
104+
.build()
105+
.unwrap();
106+
107+
// One tick per queued event, plus the initial render.
108+
for _ in 0..=(2 + toggles) {
109+
app.tick().unwrap();
110+
}
111+
app
112+
}
113+
114+
#[test]
115+
fn toggling_open_to_in_progress_fills_the_in_progress_slot() {
116+
let (_temp_dir, db_path) = setup_project(
117+
r"# Sample
118+
119+
@id: sample
120+
121+
## Tasks
122+
123+
- [ ] Alpha task
124+
@id: alpha
125+
",
126+
);
127+
128+
let app = app_after_toggles(&db_path, 1);
129+
130+
let in_progress = app
131+
.state()
132+
.activity
133+
.in_progress
134+
.as_ref()
135+
.expect("toggling Open -> InProgress should fill the in-progress slot");
136+
assert_eq!(in_progress.title, "Alpha task");
137+
assert!(
138+
app.state().activity.recently_completed.is_empty(),
139+
"nothing has completed yet"
140+
);
141+
}
142+
143+
#[test]
144+
fn toggling_in_progress_to_done_clears_the_slot_and_records_the_completion() {
145+
let (_temp_dir, db_path) = setup_project(
146+
r"# Sample
147+
148+
@id: sample
149+
150+
## Tasks
151+
152+
- [ ] Alpha task
153+
@id: alpha
154+
",
155+
);
156+
157+
// Open -> InProgress -> Done.
158+
let app = app_after_toggles(&db_path, 2);
159+
160+
assert!(
161+
app.state().activity.in_progress.is_none(),
162+
"completing the in-progress task must clear the slot"
163+
);
164+
let recent = &app.state().activity.recently_completed;
165+
assert_eq!(recent.len(), 1, "the completion should have been recorded");
166+
assert_eq!(recent[0].title, "Alpha task");
167+
}

‎tasks/tasks.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ Tasks are organized by major module/component. Each task file contains:
4444
### User Interfaces
4545
- [x] [tasks.tui.md](tasks.tui.md) - Terminal UI implementation ✅
4646
- [x] [tasks.task-creation-ui.md](tasks.task-creation-ui.md) - Task creation via CLI (`lash add`) and TUI form modal (Tasks 1-12 complete) ✅
47-
- [ ] [tasks.status-bar-activity.md](tasks.status-bar-activity.md) - Status bar in-progress + recently-completed activity sections
48-
- [ ] [tasks.live-updates.md](tasks.live-updates.md) - Live TUI updates (Store actor, file watcher, external-change reload)
47+
- [x] [tasks.status-bar-activity.md](tasks.status-bar-activity.md) - Status bar in-progress + recently-completed activity sections ✅
48+
- [x] [tasks.live-updates.md](tasks.live-updates.md) - Live TUI updates (Store actor, file watcher, external-change reload) ✅
4949

5050
### Advanced Features
5151
- [x] [tasks.agent-integration.md](tasks.agent-integration.md) - Agent prompts, token minimization (Tasks 1-6 complete) ✅

‎tasks/tasks.status-bar-activity.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ completion times.
7171

7272
- [x] Seed via existing `TaskRepository::find_by_status(InProgress)` (already ordered by `full_id` — no new query needed)
7373
- [x] Seed `state.activity.in_progress` in `TuiApp::new_with_scheme`
74-
- [ ] Test: DB with two in-progress tasks → smaller full_id wins *(deferred — relies on TUI integration harness; ordering itself is covered by existing repository tests)*
74+
- [x] Test: DB with two in-progress tasks → smaller full_id wins *(`crates/lash-tui/tests/activity_bar_tests.rs`; the fixture lists `zulu` above `alpha` so row order and full_id order disagree)*
7575

7676
---
7777

@@ -93,7 +93,7 @@ for its transient duration.
9393
- [x] Insert `record_transition` call after `update_markdown_task_status` succeeds (primary toggle path)
9494
- [x] Hook the three cascading handlers (`handle_confirm_cascading_complete`, `handle_confirm_linked_file_complete`, `handle_confirm_cascading_incomplete`) — initiating task only, not cascade results
9595
- [x] Existing tests for `handle_toggle_status` still pass
96-
- [ ] New end-to-end test: toggling Open→InProgress sets `activity.in_progress`; InProgress→Done clears it and pushes to recent *(deferred to Phase C when a more general TUI integration harness exists; covered indirectly by unit tests on `ActivityState::record_transition`)*
96+
- [x] New end-to-end test: toggling Open→InProgress sets `activity.in_progress`; InProgress→Done clears it and pushes to recent *(`crates/lash-tui/tests/activity_bar_tests.rs`, driven through `TestAppBuilder` and real Space key events)*
9797

9898
---
9999

0 commit comments

Comments
 (0)