cli_list_show.rs

  1use assert_cmd::Command;
  2use predicates::prelude::*;
  3use tempfile::TempDir;
  4
  5fn td() -> Command {
  6    Command::cargo_bin("td").unwrap()
  7}
  8
  9fn init_tmp() -> TempDir {
 10    let tmp = TempDir::new().unwrap();
 11    td().arg("init").current_dir(&tmp).assert().success();
 12    tmp
 13}
 14
 15/// Create a task and return its JSON id.
 16fn create_task(dir: &TempDir, title: &str) -> String {
 17    let out = td()
 18        .args(["--json", "create", title])
 19        .current_dir(dir)
 20        .output()
 21        .unwrap();
 22    let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
 23    v["id"].as_str().unwrap().to_string()
 24}
 25
 26// ── list ─────────────────────────────────────────────────────────────
 27
 28#[test]
 29fn list_shows_created_tasks() {
 30    let tmp = init_tmp();
 31    create_task(&tmp, "Alpha");
 32    create_task(&tmp, "Bravo");
 33
 34    td().arg("list")
 35        .current_dir(&tmp)
 36        .assert()
 37        .success()
 38        .stdout(predicate::str::contains("Alpha"))
 39        .stdout(predicate::str::contains("Bravo"));
 40}
 41
 42#[test]
 43fn list_json_returns_array() {
 44    let tmp = init_tmp();
 45    create_task(&tmp, "One");
 46
 47    let out = td()
 48        .args(["--json", "list"])
 49        .current_dir(&tmp)
 50        .output()
 51        .unwrap();
 52    let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
 53    assert!(v.is_array(), "expected JSON array, got: {v}");
 54    assert_eq!(v.as_array().unwrap().len(), 1);
 55    assert_eq!(v[0]["title"].as_str().unwrap(), "One");
 56}
 57
 58#[test]
 59fn list_filter_by_status() {
 60    let tmp = init_tmp();
 61    create_task(&tmp, "Open task");
 62
 63    // No closed tasks yet.
 64    let out = td()
 65        .args(["--json", "list", "-s", "closed"])
 66        .current_dir(&tmp)
 67        .output()
 68        .unwrap();
 69    let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
 70    assert_eq!(v.as_array().unwrap().len(), 0);
 71}
 72
 73#[test]
 74fn list_filter_by_priority() {
 75    let tmp = init_tmp();
 76
 77    td().args(["create", "Low prio", "-p", "low"])
 78        .current_dir(&tmp)
 79        .assert()
 80        .success();
 81    td().args(["create", "High prio", "-p", "high"])
 82        .current_dir(&tmp)
 83        .assert()
 84        .success();
 85
 86    let out = td()
 87        .args(["--json", "list", "-p", "high"])
 88        .current_dir(&tmp)
 89        .output()
 90        .unwrap();
 91    let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
 92    let tasks = v.as_array().unwrap();
 93    assert_eq!(tasks.len(), 1);
 94    assert_eq!(tasks[0]["title"].as_str().unwrap(), "High prio");
 95}
 96
 97#[test]
 98fn list_filter_by_label() {
 99    let tmp = init_tmp();
100
101    td().args(["create", "Tagged", "-l", "urgent"])
102        .current_dir(&tmp)
103        .assert()
104        .success();
105    td().args(["create", "Untagged"])
106        .current_dir(&tmp)
107        .assert()
108        .success();
109
110    let out = td()
111        .args(["--json", "list", "-l", "urgent"])
112        .current_dir(&tmp)
113        .output()
114        .unwrap();
115    let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
116    let tasks = v.as_array().unwrap();
117    assert_eq!(tasks.len(), 1);
118    assert_eq!(tasks[0]["title"].as_str().unwrap(), "Tagged");
119}
120
121#[test]
122fn list_filter_by_effort() {
123    let tmp = init_tmp();
124
125    td().args(["create", "Easy", "-e", "low"])
126        .current_dir(&tmp)
127        .assert()
128        .success();
129    td().args(["create", "Hard", "-e", "high"])
130        .current_dir(&tmp)
131        .assert()
132        .success();
133
134    let out = td()
135        .args(["--json", "list", "-e", "low"])
136        .current_dir(&tmp)
137        .output()
138        .unwrap();
139    let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
140    let tasks = v.as_array().unwrap();
141    assert_eq!(tasks.len(), 1);
142    assert_eq!(tasks[0]["title"].as_str().unwrap(), "Easy");
143}
144
145// ── show ─────────────────────────────────────────────────────────────
146
147#[test]
148fn show_displays_task() {
149    let tmp = init_tmp();
150    let id = create_task(&tmp, "Details here");
151
152    td().args(["show", &id])
153        .current_dir(&tmp)
154        .assert()
155        .success()
156        .stdout(predicate::str::contains("Details here"))
157        .stdout(predicate::str::contains(&id));
158}
159
160#[test]
161fn show_json_includes_labels_and_blockers() {
162    let tmp = init_tmp();
163
164    td().args(["create", "With labels", "-l", "bug,ui"])
165        .current_dir(&tmp)
166        .assert()
167        .success();
168
169    // Get the id via list.
170    let out = td()
171        .args(["--json", "list"])
172        .current_dir(&tmp)
173        .output()
174        .unwrap();
175    let list: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
176    let id = list[0]["id"].as_str().unwrap();
177
178    let out = td()
179        .args(["--json", "show", id])
180        .current_dir(&tmp)
181        .output()
182        .unwrap();
183    let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
184    assert_eq!(v["title"].as_str().unwrap(), "With labels");
185
186    let labels = v["labels"].as_array().unwrap();
187    assert!(labels.contains(&serde_json::Value::String("bug".into())));
188    assert!(labels.contains(&serde_json::Value::String("ui".into())));
189
190    // Blockers should be present (even if empty).
191    assert!(v["blockers"].is_array());
192}
193
194#[test]
195fn show_nonexistent_task_fails() {
196    let tmp = init_tmp();
197
198    td().args(["show", "td-nope"])
199        .current_dir(&tmp)
200        .assert()
201        .failure()
202        .stderr(predicate::str::contains("not found"));
203}
204
205#[test]
206fn show_annotates_closed_blockers() {
207    let tmp = init_tmp();
208    let task = create_task(&tmp, "Blocked task");
209    let open_blocker = create_task(&tmp, "Still open");
210    let closed_blocker = create_task(&tmp, "Will close");
211
212    td().args(["dep", "add", &task, &open_blocker])
213        .current_dir(&tmp)
214        .assert()
215        .success();
216    td().args(["dep", "add", &task, &closed_blocker])
217        .current_dir(&tmp)
218        .assert()
219        .success();
220    td().args(["done", &closed_blocker])
221        .current_dir(&tmp)
222        .assert()
223        .success();
224
225    // Plural label, open blocker bare, closed one annotated.
226    td().args(["show", &task])
227        .current_dir(&tmp)
228        .assert()
229        .success()
230        .stdout(predicate::str::contains("blockers"))
231        .stdout(predicate::str::contains(&open_blocker))
232        .stdout(predicate::str::contains(&format!(
233            "{closed_blocker} [closed]"
234        )));
235}
236
237#[test]
238fn show_all_closed_blockers_prefixed() {
239    let tmp = init_tmp();
240    let task = create_task(&tmp, "Was blocked");
241    let blocker = create_task(&tmp, "Done now");
242
243    td().args(["dep", "add", &task, &blocker])
244        .current_dir(&tmp)
245        .assert()
246        .success();
247    td().args(["done", &blocker])
248        .current_dir(&tmp)
249        .assert()
250        .success();
251
252    // Singular label, [all closed] prefix.
253    td().args(["show", &task])
254        .current_dir(&tmp)
255        .assert()
256        .success()
257        .stdout(predicate::str::contains("blocker"))
258        .stdout(predicate::str::contains("[all closed]"))
259        .stdout(predicate::str::contains(&blocker));
260}
261
262#[test]
263fn show_single_open_blocker_singular_label() {
264    let tmp = init_tmp();
265    let task = create_task(&tmp, "Blocked");
266    let blocker = create_task(&tmp, "Blocking");
267
268    td().args(["dep", "add", &task, &blocker])
269        .current_dir(&tmp)
270        .assert()
271        .success();
272
273    let out = td()
274        .args(["show", &task])
275        .current_dir(&tmp)
276        .output()
277        .unwrap();
278    let stdout = String::from_utf8_lossy(&out.stdout);
279
280    // Singular "blocker", no "blockers".
281    assert!(stdout.contains("blocker"));
282    assert!(stdout.contains(&blocker));
283    // Should not contain [closed] or [all closed].
284    assert!(!stdout.contains("[closed]"));
285}