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.
11 from .task import TaskWarrior, Task
13 # http://taskwarrior.org/docs/design/task.html , Section: The Attributes
14 TASK_STANDARD_ATTRS = (
37 class TasklibTest(unittest.TestCase):
40 self.tmp = tempfile.mkdtemp(dir='.')
41 self.tw = TaskWarrior(data_location=self.tmp)
44 shutil.rmtree(self.tmp)
47 class TaskFilterTest(TasklibTest):
49 def test_all_empty(self):
50 self.assertEqual(len(self.tw.tasks.all()), 0)
52 def test_all_non_empty(self):
53 Task(self.tw, description="test task").save()
54 self.assertEqual(len(self.tw.tasks.all()), 1)
55 self.assertEqual(self.tw.tasks.all()[0]['description'], 'test task')
56 self.assertEqual(self.tw.tasks.all()[0]['status'], 'pending')
58 def test_pending_non_empty(self):
59 Task(self.tw, description="test task").save()
60 self.assertEqual(len(self.tw.tasks.pending()), 1)
61 self.assertEqual(self.tw.tasks.pending()[0]['description'],
63 self.assertEqual(self.tw.tasks.pending()[0]['status'], 'pending')
65 def test_completed_empty(self):
66 Task(self.tw, description="test task").save()
67 self.assertEqual(len(self.tw.tasks.completed()), 0)
69 def test_completed_non_empty(self):
70 Task(self.tw, description="test task").save()
71 self.assertEqual(len(self.tw.tasks.completed()), 0)
72 self.tw.tasks.all()[0].done()
73 self.assertEqual(len(self.tw.tasks.completed()), 1)
75 def test_filtering_by_attribute(self):
76 Task(self.tw, description="no priority task").save()
77 Task(self.tw, priority="H", description="high priority task").save()
78 self.assertEqual(len(self.tw.tasks.all()), 2)
80 # Assert that the correct number of tasks is returned
81 self.assertEqual(len(self.tw.tasks.filter(priority="H")), 1)
83 # Assert that the correct tasks are returned
84 high_priority_task = self.tw.tasks.get(priority="H")
85 self.assertEqual(high_priority_task['description'], "high priority task")
87 def test_filtering_by_empty_attribute(self):
88 Task(self.tw, description="no priority task").save()
89 Task(self.tw, priority="H", description="high priority task").save()
90 self.assertEqual(len(self.tw.tasks.all()), 2)
92 # Assert that the correct number of tasks is returned
93 self.assertEqual(len(self.tw.tasks.filter(priority=None)), 1)
95 # Assert that the correct tasks are returned
96 no_priority_task = self.tw.tasks.get(priority=None)
97 self.assertEqual(no_priority_task['description'], "no priority task")
99 def test_filter_for_task_with_space_in_descripition(self):
100 task = Task(self.tw, description="test task")
103 filtered_task = self.tw.tasks.get(description="test task")
104 self.assertEqual(filtered_task['description'], "test task")
106 def test_filter_for_task_without_space_in_descripition(self):
107 task = Task(self.tw, description="test")
110 filtered_task = self.tw.tasks.get(description="test")
111 self.assertEqual(filtered_task['description'], "test")
113 def test_filter_for_task_with_space_in_project(self):
114 task = Task(self.tw, description="test", project="random project")
117 filtered_task = self.tw.tasks.get(project="random project")
118 self.assertEqual(filtered_task['project'], "random project")
120 def test_filter_for_task_without_space_in_project(self):
121 task = Task(self.tw, description="test", project="random")
124 filtered_task = self.tw.tasks.get(project="random")
125 self.assertEqual(filtered_task['project'], "random")
128 class TaskTest(TasklibTest):
130 def test_create_unsaved_task(self):
131 # Make sure a new task is not saved unless explicitly called for
132 t = Task(self.tw, description="test task")
133 self.assertEqual(len(self.tw.tasks.all()), 0)
135 # TODO: once python 2.6 compatiblity is over, use context managers here
136 # and in all subsequent tests for assertRaises
138 def test_delete_unsaved_task(self):
139 t = Task(self.tw, description="test task")
140 self.assertRaises(Task.NotSaved, t.delete)
142 def test_complete_unsaved_task(self):
143 t = Task(self.tw, description="test task")
144 self.assertRaises(Task.NotSaved, t.done)
146 def test_refresh_unsaved_task(self):
147 t = Task(self.tw, description="test task")
148 self.assertRaises(Task.NotSaved, t.refresh)
150 def test_delete_deleted_task(self):
151 t = Task(self.tw, description="test task")
155 self.assertRaises(Task.DeletedTask, t.delete)
157 def test_complete_completed_task(self):
158 t = Task(self.tw, description="test task")
162 self.assertRaises(Task.CompletedTask, t.done)
164 def test_complete_deleted_task(self):
165 t = Task(self.tw, description="test task")
169 self.assertRaises(Task.DeletedTask, t.done)
171 def test_modify_simple_attribute_without_space(self):
172 t = Task(self.tw, description="test")
175 self.assertEquals(t['description'], "test")
177 t['description'] = "test-modified"
180 self.assertEquals(t['description'], "test-modified")
182 def test_modify_simple_attribute_with_space(self):
183 # Space can pose problems with parsing
184 t = Task(self.tw, description="test task")
187 self.assertEquals(t['description'], "test task")
189 t['description'] = "test task modified"
192 self.assertEquals(t['description'], "test task modified")
194 def test_empty_dependency_set_of_unsaved_task(self):
195 t = Task(self.tw, description="test task")
196 self.assertEqual(t['depends'], set())
198 def test_empty_dependency_set_of_saved_task(self):
199 t = Task(self.tw, description="test task")
201 self.assertEqual(t['depends'], set())
203 def test_set_unsaved_task_as_dependency(self):
204 # Adds only one dependency to task with no dependencies
205 t = Task(self.tw, description="test task")
206 dependency = Task(self.tw, description="needs to be done first")
208 # We only save the parent task, dependency task is unsaved
210 t['depends'] = set([dependency])
212 self.assertRaises(Task.NotSaved, t.save)
214 def test_set_simple_dependency_set(self):
215 # Adds only one dependency to task with no dependencies
216 t = Task(self.tw, description="test task")
217 dependency = Task(self.tw, description="needs to be done first")
222 t['depends'] = set([dependency])
224 self.assertEqual(t['depends'], set([dependency]))
226 def test_set_complex_dependency_set(self):
227 # Adds two dependencies to task with no dependencies
228 t = Task(self.tw, description="test task")
229 dependency1 = Task(self.tw, description="needs to be done first")
230 dependency2 = Task(self.tw, description="needs to be done second")
236 t['depends'] = set([dependency1, dependency2])
238 self.assertEqual(t['depends'], set([dependency1, dependency2]))
240 def test_remove_from_dependency_set(self):
241 # Removes dependency from task with two dependencies
242 t = Task(self.tw, description="test task")
243 dependency1 = Task(self.tw, description="needs to be done first")
244 dependency2 = Task(self.tw, description="needs to be done second")
249 t['depends'] = set([dependency1, dependency2])
252 t['depends'].remove(dependency2)
255 self.assertEqual(t['depends'], set([dependency1]))
257 def test_add_to_dependency_set(self):
258 # Adds dependency to task with one dependencies
259 t = Task(self.tw, description="test task")
260 dependency1 = Task(self.tw, description="needs to be done first")
261 dependency2 = Task(self.tw, description="needs to be done second")
266 t['depends'] = set([dependency1])
269 t['depends'].add(dependency2)
272 self.assertEqual(t['depends'], set([dependency1, dependency2]))
274 def test_add_to_empty_dependency_set(self):
275 # Adds dependency to task with one dependencies
276 t = Task(self.tw, description="test task")
277 dependency = Task(self.tw, description="needs to be done first")
281 t['depends'].add(dependency)
284 self.assertEqual(t['depends'], set([dependency]))
286 def test_simple_dependency_set_save_repeatedly(self):
287 # Adds only one dependency to task with no dependencies
288 t = Task(self.tw, description="test task")
289 dependency = Task(self.tw, description="needs to be done first")
292 t['depends'] = set([dependency])
295 # We taint the task, but keep depends intact
296 t['description'] = "test task modified"
299 self.assertEqual(t['depends'], set([dependency]))
301 # We taint the task, but assign the same set to the depends
302 t['depends'] = set([dependency])
303 t['description'] = "test task modified again"
306 self.assertEqual(t['depends'], set([dependency]))
308 def test_compare_different_tasks(self):
309 # Negative: compare two different tasks
310 t1 = Task(self.tw, description="test task")
311 t2 = Task(self.tw, description="test task")
316 self.assertEqual(t1 == t2, False)
318 def test_compare_same_task_object(self):
319 # Compare Task object wit itself
320 t = Task(self.tw, description="test task")
323 self.assertEqual(t == t, True)
325 def test_compare_same_task(self):
326 # Compare the same task using two different objects
327 t1 = Task(self.tw, description="test task")
330 t2 = self.tw.tasks.get(uuid=t1['uuid'])
331 self.assertEqual(t1 == t2, True)
333 def test_compare_unsaved_tasks(self):
334 # t1 and t2 are unsaved tasks, considered to be unequal
335 # despite the content of data
336 t1 = Task(self.tw, description="test task")
337 t2 = Task(self.tw, description="test task")
339 self.assertEqual(t1 == t2, False)
341 def test_hash_unsaved_tasks(self):
342 # Considered equal, it's the same object
343 t1 = Task(self.tw, description="test task")
345 self.assertEqual(hash(t1) == hash(t2), True)
347 def test_hash_same_task(self):
348 # Compare the hash of the task using two different objects
349 t1 = Task(self.tw, description="test task")
352 t2 = self.tw.tasks.get(uuid=t1['uuid'])
353 self.assertEqual(t1.__hash__(), t2.__hash__())
355 def test_adding_task_with_priority(self):
356 t = Task(self.tw, description="test task", priority="M")
359 def test_removing_priority_with_none(self):
360 t = Task(self.tw, description="test task", priority="L")
363 # Remove the priority mark
367 # Assert that priority is not there after saving
368 self.assertEqual(t['priority'], None)
370 def test_adding_task_with_due_time(self):
371 t = Task(self.tw, description="test task", due=datetime.datetime.now())
374 def test_removing_due_time_with_none(self):
375 t = Task(self.tw, description="test task", due=datetime.datetime.now())
378 # Remove the due timestamp
382 # Assert that due timestamp is no longer there
383 self.assertEqual(t['due'], None)
385 def test_modified_fields_new_task(self):
388 # This should be empty with new task
389 self.assertEqual(set(t._modified_fields), set())
392 t['description'] = "test task"
393 self.assertEqual(set(t._modified_fields), set(['description']))
395 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
396 self.assertEqual(set(t._modified_fields), set(['description', 'due']))
398 t['project'] = "test project"
399 self.assertEqual(set(t._modified_fields),
400 set(['description', 'due', 'project']))
402 # List of modified fields should clear out when saved
404 self.assertEqual(set(t._modified_fields), set())
406 # Reassigning the fields with the same values now should not produce
408 t['description'] = "test task"
409 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
410 t['project'] = "test project"
411 self.assertEqual(set(t._modified_fields), set())
413 def test_modified_fields_loaded_task(self):
417 t['description'] = "test task"
418 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
419 t['project'] = "test project"
421 dependency = Task(self.tw, description="dependency")
423 t['depends'] = set([dependency])
425 # List of modified fields should clear out when saved
427 self.assertEqual(set(t._modified_fields), set())
429 # Get the task by using a filter by UUID
430 t2 = self.tw.tasks.get(uuid=t['uuid'])
432 # Reassigning the fields with the same values now should not produce
434 t['description'] = "test task"
435 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
436 t['project'] = "test project"
437 t['depends'] = set([dependency])
438 self.assertEqual(set(t._modified_fields), set())
440 def test_modified_fields_not_affected_by_reading(self):
443 for field in TASK_STANDARD_ATTRS:
446 self.assertEqual(set(t._modified_fields), set())
448 def test_setting_read_only_attrs_through_init(self):
449 # Test that we are unable to set readonly attrs through __init__
450 for readonly_key in Task.read_only_fields:
451 kwargs = {'description': 'test task', readonly_key: 'value'}
452 self.assertRaises(RuntimeError,
453 lambda: Task(self.tw, **kwargs))
455 def test_setting_read_only_attrs_through_setitem(self):
456 # Test that we are unable to set readonly attrs through __init__
457 for readonly_key in Task.read_only_fields:
458 t = Task(self.tw, description='test task')
459 self.assertRaises(RuntimeError,
460 lambda: t.__setitem__(readonly_key, 'value'))
462 def test_saving_unmodified_task(self):
463 t = Task(self.tw, description="test task")
467 def test_adding_tag_by_appending(self):
468 t = Task(self.tw, description="test task", tags=['test1'])
470 t['tags'].append('test2')
472 self.assertEqual(t['tags'], ['test1', 'test2'])
474 def test_adding_tag_by_appending_empty(self):
475 t = Task(self.tw, description="test task")
477 t['tags'].append('test')
479 self.assertEqual(t['tags'], ['test'])
481 def test_serializers_returning_empty_string_for_none(self):
482 # Test that any serializer returns '' when passed None
484 serializers = [getattr(t, serializer_name) for serializer_name in
485 filter(lambda x: x.startswith('serialize_'), dir(t))]
486 for serializer in serializers:
487 self.assertEqual(serializer(None), '')
489 def test_deserializer_returning_empty_value_for_empty_string(self):
490 # Test that any deserializer returns empty value when passed ''
492 deserializers = [getattr(t, deserializer_name) for deserializer_name in
493 filter(lambda x: x.startswith('deserialize_'), dir(t))]
494 for deserializer in deserializers:
495 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')
590 def test_serialize_annotations(self):
591 # Test that serializing annotations is possible
592 t = Task(self.tw, description="test")
595 t.add_annotation("annotation1")
596 t.add_annotation("annotation2")
598 data = t._serialize('annotations', t._data['annotations'])
600 self.assertEqual(len(data), 2)
601 self.assertEqual(type(data[0]), dict)
602 self.assertEqual(type(data[1]), dict)
604 self.assertEqual(data[0]['description'], "annotation1")
605 self.assertEqual(data[1]['description'], "annotation2")
608 class UnicodeTest(TasklibTest):
610 def test_unicode_task(self):
611 Task(self.tw, description="†åßk").save()
614 def test_non_unicode_task(self):
615 Task(self.tw, description="test task").save()