All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
10 from .task import TaskWarrior, Task
12 # http://taskwarrior.org/docs/design/task.html , Section: The Attributes
13 TASK_STANDARD_ATTRS = (
36 class TasklibTest(unittest.TestCase):
39 self.tmp = tempfile.mkdtemp(dir='.')
40 self.tw = TaskWarrior(data_location=self.tmp)
43 shutil.rmtree(self.tmp)
46 class TaskFilterTest(TasklibTest):
48 def test_all_empty(self):
49 self.assertEqual(len(self.tw.tasks.all()), 0)
51 def test_all_non_empty(self):
52 Task(self.tw, description="test task").save()
53 self.assertEqual(len(self.tw.tasks.all()), 1)
54 self.assertEqual(self.tw.tasks.all()[0]['description'], 'test task')
55 self.assertEqual(self.tw.tasks.all()[0]['status'], 'pending')
57 def test_pending_non_empty(self):
58 Task(self.tw, description="test task").save()
59 self.assertEqual(len(self.tw.tasks.pending()), 1)
60 self.assertEqual(self.tw.tasks.pending()[0]['description'],
62 self.assertEqual(self.tw.tasks.pending()[0]['status'], 'pending')
64 def test_completed_empty(self):
65 Task(self.tw, description="test task").save()
66 self.assertEqual(len(self.tw.tasks.completed()), 0)
68 def test_completed_non_empty(self):
69 Task(self.tw, description="test task").save()
70 self.assertEqual(len(self.tw.tasks.completed()), 0)
71 self.tw.tasks.all()[0].done()
72 self.assertEqual(len(self.tw.tasks.completed()), 1)
74 def test_filtering_by_attribute(self):
75 Task(self.tw, description="no priority task").save()
76 Task(self.tw, priority="H", description="high priority task").save()
77 self.assertEqual(len(self.tw.tasks.all()), 2)
79 # Assert that the correct number of tasks is returned
80 self.assertEqual(len(self.tw.tasks.filter(priority="H")), 1)
82 # Assert that the correct tasks are returned
83 high_priority_task = self.tw.tasks.get(priority="H")
84 self.assertEqual(high_priority_task['description'], "high priority task")
86 def test_filtering_by_empty_attribute(self):
87 Task(self.tw, description="no priority task").save()
88 Task(self.tw, priority="H", description="high priority task").save()
89 self.assertEqual(len(self.tw.tasks.all()), 2)
91 # Assert that the correct number of tasks is returned
92 self.assertEqual(len(self.tw.tasks.filter(priority=None)), 1)
94 # Assert that the correct tasks are returned
95 no_priority_task = self.tw.tasks.get(priority=None)
96 self.assertEqual(no_priority_task['description'], "no priority task")
98 def test_filter_for_task_with_space_in_descripition(self):
99 task = Task(self.tw, description="test task")
102 filtered_task = self.tw.tasks.get(description="test task")
103 self.assertEqual(filtered_task['description'], "test task")
105 def test_filter_for_task_without_space_in_descripition(self):
106 task = Task(self.tw, description="test")
109 filtered_task = self.tw.tasks.get(description="test")
110 self.assertEqual(filtered_task['description'], "test")
112 def test_filter_for_task_with_space_in_project(self):
113 task = Task(self.tw, description="test", project="random project")
116 filtered_task = self.tw.tasks.get(project="random project")
117 self.assertEqual(filtered_task['project'], "random project")
119 def test_filter_for_task_without_space_in_project(self):
120 task = Task(self.tw, description="test", project="random")
123 filtered_task = self.tw.tasks.get(project="random")
124 self.assertEqual(filtered_task['project'], "random")
127 class TaskTest(TasklibTest):
129 def test_create_unsaved_task(self):
130 # Make sure a new task is not saved unless explicitly called for
131 t = Task(self.tw, description="test task")
132 self.assertEqual(len(self.tw.tasks.all()), 0)
134 # TODO: once python 2.6 compatiblity is over, use context managers here
135 # and in all subsequent tests for assertRaises
137 def test_delete_unsaved_task(self):
138 t = Task(self.tw, description="test task")
139 self.assertRaises(Task.NotSaved, t.delete)
141 def test_complete_unsaved_task(self):
142 t = Task(self.tw, description="test task")
143 self.assertRaises(Task.NotSaved, t.done)
145 def test_refresh_unsaved_task(self):
146 t = Task(self.tw, description="test task")
147 self.assertRaises(Task.NotSaved, t.refresh)
149 def test_delete_deleted_task(self):
150 t = Task(self.tw, description="test task")
154 self.assertRaises(Task.DeletedTask, t.delete)
156 def test_complete_completed_task(self):
157 t = Task(self.tw, description="test task")
161 self.assertRaises(Task.CompletedTask, t.done)
163 def test_complete_deleted_task(self):
164 t = Task(self.tw, description="test task")
168 self.assertRaises(Task.DeletedTask, t.done)
170 def test_modify_simple_attribute_without_space(self):
171 t = Task(self.tw, description="test")
174 self.assertEquals(t['description'], "test")
176 t['description'] = "test-modified"
179 self.assertEquals(t['description'], "test-modified")
181 def test_modify_simple_attribute_with_space(self):
182 # Space can pose problems with parsing
183 t = Task(self.tw, description="test task")
186 self.assertEquals(t['description'], "test task")
188 t['description'] = "test task modified"
191 self.assertEquals(t['description'], "test task modified")
193 def test_empty_dependency_set_of_unsaved_task(self):
194 t = Task(self.tw, description="test task")
195 self.assertEqual(t['depends'], set())
197 def test_empty_dependency_set_of_saved_task(self):
198 t = Task(self.tw, description="test task")
200 self.assertEqual(t['depends'], set())
202 def test_set_unsaved_task_as_dependency(self):
203 # Adds only one dependency to task with no dependencies
204 t = Task(self.tw, description="test task")
205 dependency = Task(self.tw, description="needs to be done first")
207 # We only save the parent task, dependency task is unsaved
209 t['depends'] = set([dependency])
211 self.assertRaises(Task.NotSaved, t.save)
213 def test_set_simple_dependency_set(self):
214 # Adds only one dependency to task with no dependencies
215 t = Task(self.tw, description="test task")
216 dependency = Task(self.tw, description="needs to be done first")
221 t['depends'] = set([dependency])
223 self.assertEqual(t['depends'], set([dependency]))
225 def test_set_complex_dependency_set(self):
226 # Adds two dependencies to task with no dependencies
227 t = Task(self.tw, description="test task")
228 dependency1 = Task(self.tw, description="needs to be done first")
229 dependency2 = Task(self.tw, description="needs to be done second")
235 t['depends'] = set([dependency1, dependency2])
237 self.assertEqual(t['depends'], set([dependency1, dependency2]))
239 def test_remove_from_dependency_set(self):
240 # Removes dependency from task with two dependencies
241 t = Task(self.tw, description="test task")
242 dependency1 = Task(self.tw, description="needs to be done first")
243 dependency2 = Task(self.tw, description="needs to be done second")
248 t['depends'] = set([dependency1, dependency2])
251 t['depends'].remove(dependency2)
254 self.assertEqual(t['depends'], set([dependency1]))
256 def test_add_to_dependency_set(self):
257 # Adds dependency to task with one dependencies
258 t = Task(self.tw, description="test task")
259 dependency1 = Task(self.tw, description="needs to be done first")
260 dependency2 = Task(self.tw, description="needs to be done second")
265 t['depends'] = set([dependency1])
268 t['depends'].add(dependency2)
271 self.assertEqual(t['depends'], set([dependency1, dependency2]))
273 def test_add_to_empty_dependency_set(self):
274 # Adds dependency to task with one dependencies
275 t = Task(self.tw, description="test task")
276 dependency = Task(self.tw, description="needs to be done first")
280 t['depends'].add(dependency)
283 self.assertEqual(t['depends'], set([dependency]))
285 def test_simple_dependency_set_save_repeatedly(self):
286 # Adds only one dependency to task with no dependencies
287 t = Task(self.tw, description="test task")
288 dependency = Task(self.tw, description="needs to be done first")
291 t['depends'] = set([dependency])
294 # We taint the task, but keep depends intact
295 t['description'] = "test task modified"
298 self.assertEqual(t['depends'], set([dependency]))
300 # We taint the task, but assign the same set to the depends
301 t['depends'] = set([dependency])
302 t['description'] = "test task modified again"
305 self.assertEqual(t['depends'], set([dependency]))
307 def test_compare_different_tasks(self):
308 # Negative: compare two different tasks
309 t1 = Task(self.tw, description="test task")
310 t2 = Task(self.tw, description="test task")
315 self.assertEqual(t1 == t2, False)
317 def test_compare_same_task_object(self):
318 # Compare Task object wit itself
319 t = Task(self.tw, description="test task")
322 self.assertEqual(t == t, True)
324 def test_compare_same_task(self):
325 # Compare the same task using two different objects
326 t1 = Task(self.tw, description="test task")
329 t2 = self.tw.tasks.get(uuid=t1['uuid'])
330 self.assertEqual(t1 == t2, True)
332 def test_compare_unsaved_tasks(self):
333 # t1 and t2 are unsaved tasks, considered to be unequal
334 # despite the content of data
335 t1 = Task(self.tw, description="test task")
336 t2 = Task(self.tw, description="test task")
338 self.assertEqual(t1 == t2, False)
340 def test_hash_unsaved_tasks(self):
341 # Considered equal, it's the same object
342 t1 = Task(self.tw, description="test task")
344 self.assertEqual(hash(t1) == hash(t2), True)
346 def test_hash_same_task(self):
347 # Compare the hash of the task using two different objects
348 t1 = Task(self.tw, description="test task")
351 t2 = self.tw.tasks.get(uuid=t1['uuid'])
352 self.assertEqual(t1.__hash__(), t2.__hash__())
354 def test_adding_task_with_priority(self):
355 t = Task(self.tw, description="test task", priority="M")
358 def test_removing_priority_with_none(self):
359 t = Task(self.tw, description="test task", priority="L")
362 # Remove the priority mark
366 # Assert that priority is not there after saving
367 self.assertEqual(t['priority'], None)
369 def test_adding_task_with_due_time(self):
370 t = Task(self.tw, description="test task", due=datetime.datetime.now())
373 def test_removing_due_time_with_none(self):
374 t = Task(self.tw, description="test task", due=datetime.datetime.now())
377 # Remove the due timestamp
381 # Assert that due timestamp is no longer there
382 self.assertEqual(t['due'], None)
384 def test_modified_fields_new_task(self):
387 # This should be empty with new task
388 self.assertEqual(set(t._modified_fields), set())
391 t['description'] = "test task"
392 self.assertEqual(set(t._modified_fields), set(['description']))
394 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
395 self.assertEqual(set(t._modified_fields), set(['description', 'due']))
397 t['project'] = "test project"
398 self.assertEqual(set(t._modified_fields),
399 set(['description', 'due', 'project']))
401 # List of modified fields should clear out when saved
403 self.assertEqual(set(t._modified_fields), set())
405 # Reassigning the fields with the same values now should not produce
407 t['description'] = "test task"
408 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
409 t['project'] = "test project"
410 self.assertEqual(set(t._modified_fields), set())
412 def test_modified_fields_loaded_task(self):
416 t['description'] = "test task"
417 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
418 t['project'] = "test project"
420 dependency = Task(self.tw, description="dependency")
422 t['depends'] = set([dependency])
424 # List of modified fields should clear out when saved
426 self.assertEqual(set(t._modified_fields), set())
428 # Get the task by using a filter by UUID
429 t2 = self.tw.tasks.get(uuid=t['uuid'])
431 # Reassigning the fields with the same values now should not produce
433 t['description'] = "test task"
434 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
435 t['project'] = "test project"
436 t['depends'] = set([dependency])
437 self.assertEqual(set(t._modified_fields), set())
439 def test_modified_fields_not_affected_by_reading(self):
442 for field in TASK_STANDARD_ATTRS:
445 self.assertEqual(set(t._modified_fields), set())
447 def test_setting_read_only_attrs_through_init(self):
448 # Test that we are unable to set readonly attrs through __init__
449 for readonly_key in Task.read_only_fields:
450 kwargs = {'description': 'test task', readonly_key: 'value'}
451 self.assertRaises(RuntimeError,
452 lambda: Task(self.tw, **kwargs))
454 def test_setting_read_only_attrs_through_setitem(self):
455 # Test that we are unable to set readonly attrs through __init__
456 for readonly_key in Task.read_only_fields:
457 t = Task(self.tw, description='test task')
458 self.assertRaises(RuntimeError,
459 lambda: t.__setitem__(readonly_key, 'value'))
461 def test_saving_unmodified_task(self):
462 t = Task(self.tw, description="test task")
466 def test_adding_tag_by_appending(self):
467 t = Task(self.tw, description="test task", tags=['test1'])
469 t['tags'].append('test2')
471 self.assertEqual(t['tags'], ['test1', 'test2'])
473 def test_adding_tag_by_appending_empty(self):
474 t = Task(self.tw, description="test task")
476 t['tags'].append('test')
478 self.assertEqual(t['tags'], ['test'])
480 def test_serializers_returning_empty_string_for_none(self):
481 # Test that any serializer returns '' when passed None
483 serializers = [getattr(t, serializer_name) for serializer_name in
484 filter(lambda x: x.startswith('serialize_'), dir(t))]
485 for serializer in serializers:
486 self.assertEqual(serializer(None), '')
488 def test_deserializer_returning_empty_value_for_empty_string(self):
489 # Test that any deserializer returns empty value when passed ''
491 deserializers = [getattr(t, deserializer_name) for deserializer_name in
492 filter(lambda x: x.startswith('deserialize_'), dir(t))]
493 for deserializer in deserializers:
494 self.assertTrue(deserializer('') in (None, [], set()))
498 class TaskFromHookTest(TasklibTest):
500 input_add_data = six.StringIO(
501 '{"description":"Buy some milk",'
502 '"entry":"20141118T050231Z",'
503 '"status":"pending",'
504 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
506 input_modify_data = six.StringIO(input_add_data.getvalue() + '\n' +
507 '{"description":"Buy some milk finally",'
508 '"entry":"20141118T050231Z",'
509 '"status":"completed",'
510 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
512 exported_raw_data = (
514 '"due":"20150101T232323Z",'
515 '"description":"test task"}')
517 def test_setting_up_from_add_hook_input(self):
518 t = Task.from_input(input_file=self.input_add_data)
519 self.assertEqual(t['description'], "Buy some milk")
520 self.assertEqual(t.pending, True)
522 def test_setting_up_from_modified_hook_input(self):
523 t = Task.from_input(input_file=self.input_modify_data, modify=True)
524 self.assertEqual(t['description'], "Buy some milk finally")
525 self.assertEqual(t.pending, False)
526 self.assertEqual(t.completed, True)
528 self.assertEqual(t._original_data['status'], "pending")
529 self.assertEqual(t._original_data['description'], "Buy some milk")
530 self.assertEqual(set(t._modified_fields),
531 set(['status', 'description']))
533 def test_export_data(self):
534 t = Task(self.tw, description="test task",
535 project="Home", due=datetime.datetime(2015,1,1,23,23,23))
537 # Check that the output is a permutation of:
538 # {"project":"Home","description":"test task","due":"20150101232323Z"}
539 allowed_segments = self.exported_raw_data[1:-1].split(',')
541 '{' + ','.join(segments) + '}'
542 for segments in itertools.permutations(allowed_segments)
545 self.assertTrue(any(t.export_data() == expected
546 for expected in allowed_output))
549 class AnnotationTest(TasklibTest):
552 super(AnnotationTest, self).setUp()
553 Task(self.tw, description="test task").save()
555 def test_adding_annotation(self):
556 task = self.tw.tasks.get()
557 task.add_annotation('test annotation')
558 self.assertEqual(len(task['annotations']), 1)
559 ann = task['annotations'][0]
560 self.assertEqual(ann['description'], 'test annotation')
562 def test_removing_annotation(self):
563 task = self.tw.tasks.get()
564 task.add_annotation('test annotation')
565 ann = task['annotations'][0]
567 self.assertEqual(len(task['annotations']), 0)
569 def test_removing_annotation_by_description(self):
570 task = self.tw.tasks.get()
571 task.add_annotation('test annotation')
572 task.remove_annotation('test annotation')
573 self.assertEqual(len(task['annotations']), 0)
575 def test_removing_annotation_by_obj(self):
576 task = self.tw.tasks.get()
577 task.add_annotation('test annotation')
578 ann = task['annotations'][0]
579 task.remove_annotation(ann)
580 self.assertEqual(len(task['annotations']), 0)
582 def test_annotation_after_modification(self):
583 task = self.tw.tasks.get()
584 task['project'] = 'test'
585 task.add_annotation('I should really do this task')
586 self.assertEqual(task['project'], 'test')
588 self.assertEqual(task['project'], 'test')
591 class UnicodeTest(TasklibTest):
593 def test_unicode_task(self):
594 Task(self.tw, description="†åßk").save()
597 def test_non_unicode_task(self):
598 Task(self.tw, description="test task").save()