|
| 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 | +} |
0 commit comments