1use assert_cmd::Command;
2use predicates::prelude::*;
3use tempfile::TempDir;
4
5fn td(home: &TempDir) -> Command {
6 let mut cmd = Command::cargo_bin("td").unwrap();
7 cmd.env("HOME", home.path());
8 cmd
9}
10
11/// Initialise a temp directory and return it.
12fn init_tmp() -> TempDir {
13 let tmp = TempDir::new().unwrap();
14 td(&tmp)
15 .args(["init", "main"])
16 .current_dir(&tmp)
17 .assert()
18 .success();
19 tmp
20}
21
22#[test]
23fn create_prints_id_and_title() {
24 let tmp = init_tmp();
25
26 td(&tmp)
27 .args(["create", "My first task"])
28 .current_dir(&tmp)
29 .assert()
30 .success()
31 .stdout(predicate::str::contains("My first task"));
32}
33
34#[test]
35fn create_json_returns_task_object() {
36 let tmp = init_tmp();
37
38 td(&tmp)
39 .args(["--json", "create", "Buy milk"])
40 .current_dir(&tmp)
41 .assert()
42 .success()
43 .stdout(predicate::str::contains(r#""title":"Buy milk"#))
44 .stdout(predicate::str::contains(r#""status":"open"#))
45 .stdout(predicate::str::contains(r#""priority":"medium""#));
46}
47
48#[test]
49fn create_with_priority_and_type() {
50 let tmp = init_tmp();
51
52 td(&tmp)
53 .args(["--json", "create", "Urgent bug", "-p", "high", "-t", "bug"])
54 .current_dir(&tmp)
55 .assert()
56 .success()
57 .stdout(predicate::str::contains(r#""priority":"high""#))
58 .stdout(predicate::str::contains(r#""type":"bug"#));
59}
60
61#[test]
62fn create_with_description() {
63 let tmp = init_tmp();
64
65 td(&tmp)
66 .args([
67 "--json",
68 "create",
69 "Fix login",
70 "-d",
71 "The login page is broken",
72 ])
73 .current_dir(&tmp)
74 .assert()
75 .success()
76 .stdout(predicate::str::contains("The login page is broken"));
77}
78
79#[test]
80fn create_with_labels() {
81 let tmp = init_tmp();
82
83 td(&tmp)
84 .args(["--json", "create", "Labelled task", "-l", "frontend,urgent"])
85 .current_dir(&tmp)
86 .assert()
87 .success();
88
89 let out = td(&tmp)
90 .args(["--json", "list", "-l", "frontend"])
91 .current_dir(&tmp)
92 .output()
93 .unwrap();
94 let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
95 assert_eq!(v.as_array().unwrap().len(), 1);
96
97 let task = &v[0];
98 let labels = task["labels"].as_array().unwrap();
99 assert!(labels.contains(&serde_json::Value::String("frontend".to_string())));
100 assert!(labels.contains(&serde_json::Value::String("urgent".to_string())));
101}
102
103#[test]
104fn create_requires_title() {
105 let tmp = init_tmp();
106
107 td(&tmp)
108 .arg("create")
109 .current_dir(&tmp)
110 .assert()
111 .failure()
112 .stderr(predicate::str::contains("title required"));
113}
114
115#[test]
116fn create_subtask_under_parent() {
117 let tmp = init_tmp();
118
119 // Create parent, extract its id.
120 let parent_out = td(&tmp)
121 .args(["--json", "create", "Parent task"])
122 .current_dir(&tmp)
123 .output()
124 .unwrap();
125 let parent: serde_json::Value = serde_json::from_slice(&parent_out.stdout).unwrap();
126 let parent_id = parent["id"].as_str().unwrap();
127
128 // Create child under parent.
129 let child_out = td(&tmp)
130 .args(["--json", "create", "Child task", "--parent", parent_id])
131 .current_dir(&tmp)
132 .output()
133 .unwrap();
134 let child: serde_json::Value = serde_json::from_slice(&child_out.stdout).unwrap();
135 let child_id = child["id"].as_str().unwrap();
136
137 // Child id is its own ULID; relationship is represented by the parent field.
138 assert_ne!(child_id, parent_id);
139 assert_eq!(child["parent"].as_str().unwrap(), parent_id);
140}
141
142#[test]
143fn create_with_effort() {
144 let tmp = init_tmp();
145
146 let out = td(&tmp)
147 .args(["--json", "create", "Hard task", "-e", "high"])
148 .current_dir(&tmp)
149 .output()
150 .unwrap();
151 let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
152 assert_eq!(v["effort"].as_str().unwrap(), "high");
153}
154
155#[test]
156fn create_with_priority_label() {
157 let tmp = init_tmp();
158
159 let out = td(&tmp)
160 .args(["--json", "create", "Low prio", "-p", "low"])
161 .current_dir(&tmp)
162 .output()
163 .unwrap();
164 let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
165 assert_eq!(v["priority"].as_str().unwrap(), "low");
166}
167
168#[test]
169fn create_rejects_invalid_priority() {
170 let tmp = init_tmp();
171
172 td(&tmp)
173 .args(["create", "Bad", "-p", "urgent"])
174 .current_dir(&tmp)
175 .assert()
176 .failure()
177 .stderr(predicates::prelude::predicate::str::contains(
178 "invalid priority",
179 ));
180}
181
182#[test]
183fn create_rejects_invalid_effort() {
184 let tmp = init_tmp();
185
186 td(&tmp)
187 .args(["create", "Bad", "-e", "huge"])
188 .current_dir(&tmp)
189 .assert()
190 .failure()
191 .stderr(predicates::prelude::predicate::str::contains(
192 "invalid effort",
193 ));
194}