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}