-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtodotxt_test.py
358 lines (282 loc) · 13.7 KB
/
todotxt_test.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
from todotxt import *
from pyexpect import expect
from unittest import TestCase
class TodoTest(TestCase):
def setUp(self):
super().setUp()
id_generator.counter = 0
def test_simple(self):
expect(str(Todo(''))) == ''
simple = 'I am a simple todo item id:1'
todo = Todo(simple)
expect(str(todo)) == simple
def test_done_item(self):
expect(Todo('fnord').is_done).is_false()
expect(Todo('x fnord').is_done).is_true()
expect(Todo('fnord status:done').is_done).is_true()
def test_contexts(self):
expect(Todo('foo').contexts) == []
expect(Todo('foo @context1 baz @context2').contexts) == ['context1', 'context2']
def test_projects(self):
expect(Todo('foo').projects) == []
expect(Todo('foo +project1 bar +project2').projects) == ['project1', 'project2']
def test_tags(self):
expect(Todo('foo').tags) == dict()
expect(Todo('foo id:234 tag2:val2').tags) == dict(
id='234',
tag2='val2',
)
expect(Todo('foo').has_tags('tag:')).is_false()
expect(Todo('foo bar:baz').has_tags('bar:')).is_true()
expect(Todo('foo bar:baz').has_tags('bar:quoox')).is_false()
expect(Todo('foo bar:baz').has_tags('bar:baz')).is_true()
expect(Todo('foo').has_no_tags('tag:')).is_true()
expect(Todo('foo bar:baz').has_no_tags('bar:')).is_false()
expect(Todo('foo bar:baz').has_no_tags('bar:quoox')).is_true()
expect(Todo('foo bar:baz').has_no_tags('bar:baz')).is_false()
def test_tags_with_spaces(self):
expect(Todo('foo foo:bar sprint:"fnordy fnord roughnecks"').tags).has_subdict(sprint="fnordy fnord roughnecks", foo='bar')
expect(Todo("foo foo:bar sprint:'fnordy fnord roughnecks'").tags).has_subdict(sprint="fnordy fnord roughnecks", foo='bar')
def test_status_property(self):
expect(Todo('foo').status) == 'new'
expect(Todo('x foo').status) == 'done'
expect(Todo('foo status:doing').status) == 'doing'
expect(Todo('foo status:something').status) == 'unknown'
def test_id_property(self):
todo = Todo('foo #23')
expect(todo.id) == '23'
def test_empty_todo_knows_it_is_virtual(self):
todo = Todo()
expect(todo.is_virtual) == True
def test_body(self):
todo = Todo(line='foo id:3', body=' foo\n bar')
expect(todo.body) == ' foo\n bar'
todo.add_body_line(' baz')
expect(todo.body) == ' foo\n bar\n baz'
expect(str(todo)) == dedent('''
foo id:3
foo
bar
baz
''').strip()
def test_to_json(self):
expect(Todo('foo #1').json).has_subdict(line='foo #1', body=None, id='1', status='new',
is_done=False, contexts=[], projects=[], tags={}, children=[])
expect(Todo('x foo @context tag:value, +project #1 status:doing').json).has_subdict(
line='x foo @context tag:value, +project #1 status:doing',
id='1', is_done=True, contexts=['context'], status='doing',
projects=['project'], tags={'tag': 'value', 'status': 'doing' },
)
expect(Todo('foo status:done').json).has_subdict(is_done=True)
todo = Todo('')
todo.json = dict(
line='foo id:1 removed:tag',
body=' baz quoox',
is_done=True,
tags={'id':'1', 'key':'value', 'status':'done'},
children=[],
)
expect(todo.is_done).is_true()
expect(todo.tags) == {'id':'1', 'key':'value'}
expect(todo.line) == 'x foo id:1 key:value'
expect(todo.body) == ' baz quoox'
todo = Todo('')
todo.json = dict(line='x foo #1', is_done=False)
expect(todo.line) == 'foo #1'
def test_from_json(self):
todo = Todo('task #1')
# Currently have no plan to update ids from client
todo.json = dict(id=3)
expect(todo.line) == 'task #3'
todo.json = dict(status='doing', id='1')
expect(todo.line) == 'task #1 status:doing'
todo.json = dict(status='done')
expect(todo.line) == 'x task #1'
# todo = Todo()
# todo.json = dict(body=None, children=[])
# expect(todo.line) == None
def test_id_is_ignored_if_none(self):
todo = Todo('task id:1')
todo.json = dict(id=None)
expect(todo.line) == 'task'
from textwrap import dedent
class MultipleTodosTest(TestCase):
def setUp(self):
super().setUp()
id_generator.counter = 0
def test_only_empty_lines(self):
todo = Todo.from_lines('')
expect(todo.is_virtual).is_true()
expect(todo.line) == None
expect(todo.body) == ''
todo = Todo.from_lines('\n \n \n ')
expect(todo.is_virtual).is_true()
expect(todo.line) == None
expect(todo.body) == '\n \n \n '
def test_multi_lines(self):
lines = "first id:1\nx second id:2\nthird id:2346 sprint:'fnordy fnord roughnecks'"
todo = Todo.from_lines(lines)
expect(todo.is_virtual).is_true()
expect(todo.children).has_length(3)
expect(str(todo.children[0])) == 'first id:1'
expect(todo.children[1].is_done).is_true()
expect(todo.children[2].tags) == { 'id': '2346', 'sprint': 'fnordy fnord roughnecks' }
expect(str(todo)) == lines.strip()
def test_virtual_root_object_has_only_body_and_children_when_serialized(self):
lines = 'first\nsecond'
virtual = Todo.from_lines(lines)
expect(virtual.is_virtual).is_true()
expect(virtual.json).has_key('body', 'children')
def test_retain_leading_whitespace_lines(self):
lines = "\n \n \ntask"
virtual = Todo.from_lines(lines)
expect(virtual.body) == '\n \n '
expect(virtual.line) == None
# self.fail(repr(virtual))
# import sys; sys.stdout = sys.__stdout__; from pdb import set_trace; set_trace()
expect(str(virtual)) == lines
def test_sub_tasks(self):
lines = "task\n x done subtask\n complex subtask id:2346 sprint:'fnordy fnord roughnecks'"
parent = Todo.from_lines(lines)
print(parent, repr(parent.line), parent.children)
expect(parent.line) == 'task'
expect(str(parent)) == lines
expect(parent.children[0].is_done).is_true()
print(parent.children)
expect(parent.children[1].tags) == { 'id': '2346', 'sprint': 'fnordy fnord roughnecks' }
def test_sub_sub_tasks(self):
parent = Todo.from_lines("first\n x second\n third id:2346 sprint:'fnordy fnord roughnecks'\n fourth +project1")
expect(parent.line).contains('first')
expect(parent.children).has_length(2)
expect(parent.children[0].children[0].tags) == { 'id': '2346', 'sprint': 'fnordy fnord roughnecks' }
expect(parent.children[1].projects) == ['project1']
def test_children_tagged(self):
parent = Todo.from_lines(dedent("""
parent
child1 status:doing
child2
child3 status:doing
child4 status:done
"""))
expect(parent.line).contains('parent')
doing = parent.children_tagged('status:doing')
expect(doing).has_length(2)
expect(doing[0].line).contains('child1')
expect(doing[1].line).contains('child3')
waiting = parent.children_not_tagged('status:doing', 'status:done')
expect(waiting).has_length(1)
expect(waiting[0].line).contains('child2')
def test_access_relevant_task_states_as_properties(self):
parent = Todo.from_lines(dedent('''
parent
new1
new2 status:new
unknown1 status:something
unknown2 status:something
doing status:doing
x done1
x done2 status:done
'''))
expect(parent.line).contains('parent')
expect(parent.children.tagged.new).has_length(2)
expect(parent.children.tagged.new[0].line).contains('new1')
expect(parent.children.tagged.new[1].line).contains('new2')
expect(parent.children.tagged.doing).has_length(1)
expect(parent.children.tagged.done).has_length(2)
expect(parent.children.tagged.unknown).has_length(2)
def test_json_serialization(self):
parent = Todo.from_lines(dedent('''
parent id:1
child id:2
'''))
expect(parent.json.get('children')[0]).has_subdict(line=' child id:2')
todo = Todo.from_lines(dedent('''
parent id:1
new1 id:2
new2 @phone id:3
doing status:doing id:4
x done1 id:5
'''))
recreated_todo = Todo()
recreated_todo.json = json.loads(json.dumps(todo.json))
expect(recreated_todo.line) == todo.line
for index, child in enumerate(todo.children):
expect(recreated_todo.children[index].line) == child.line
def test_empty_lines_are_attached_as_body_to_tasks(self):
# Sadly dedent will kill _all_ whitespace in empty lines, so can't use it here
lines = dedent("""\
foo id:1
bar id:2
baz id:3
quoox id:4
""").strip()
# dedent kills all spaces from empty lines!
virtual = Todo.from_lines(lines)
foo = virtual.children[0]
expect(foo.line) == 'foo id:1'
expect(foo.children).has_length(2)
bar = foo.children[0]
expect(bar.line) == ' bar id:2'
expect(bar.body) == ''
baz = foo.children[1]
expect(baz.line) == ' baz id:3'
expect(baz.body) == ''
def test_expanded_stories_eat_intermitent_empty_lines(self):
todo = Todo.from_lines('task\n double indented body')
expect(todo.body) == ' double indented body'
todo = Todo.from_lines('task\n body\n\n eats empty lines')
expect(todo.body) == ' body\n\n eats empty lines'
todo = Todo.from_lines('task\n body\n \n \n eats whitespace lines')
expect(todo.body) == ' body\n \n \n eats whitespace lines'
def test_leading_empty_lines_result_in_empty_body_of_virtual_parent(self):
virtual = Todo.from_lines('\n\n \n \ntask')
expect(virtual.is_virtual).is_true()
expect(virtual.body) == '\n\n \n '
def _test_expanded_stories_can_be_collapsed(self):
r"""
Not sure at all that this is a good idea. How do I want to represent collapsed stories?
How do I deal with the different storage formats that this requires? Because I need to decide
wether to save the expanded version to keep it or wether to give out the condensed version.
TODO
The idea here is that we want tasks to be expanded, so we can write their description down.
This should be
a) notified by a marker. Potentially /^ \s* + \s* /x, but also maybe just by being tagged with expand:true or @expanded
b) be indented two levels, so we can still recover subtasks (only one level indented)
first
This is a longer description of what this story is to implement.
It can of course be multiple lines and can use **markdown** or similar
formatting rules. Probably what your native bug tracker supports.
second < this is still a subtask of first
Leaning towards the idea of having a workflow with @contexts
* You write the description and it is parsed as a description.
* If a description is parsed, the tag @expanded is auto added
* If the tag is removed, the description is hidden on serialization
This should allow to easily add a description without having to add a
cumbersome @context while still allowing the list to be switched.
Ideally it would something like starting the task with + insted of -
As that would be even more minimal. But let's see. Currently no
prefix `-` is required to start a line, needing to add that seems to suck?
Maybe it would make integration into an operational transport enabled live
markdown editor more simple? Will have to see.
"""
class OperationApplication(TestCase):
def test_applies_uuids_to_each_task(self):
todo = Todo('fnord')
expect(todo).has_attr('uuid')
expect(todo.uuid).to_match(r'^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$')
expect(todo.json['uuid']) == todo.uuid
def test_can_lookup_tasks_by_uuid(self):
parent = Todo.from_lines('parent\n child1\n child2')
child1 = parent.children[0]
expect(child1.line).contains('child1')
expect(parent.task_by_uuid(child1.uuid)) == child1
def test_apply_change_tag(self):
todo = Todo('fnord')
todo.on_operation('change_tag', tags=dict(status='done'))
expect(todo.status) == 'done'
expect(todo.line) == 'x fnord'
def test_apply_add_child_task(self):
todo = Todo('fnord')
todo.on_operation('add_child', child=dict(line='yeehaw'))
expect(todo.children).has_length(1)
expect(todo.children[0]).line = ' yeehaw'