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.
14 from .backends import TaskWarrior
15 from .task import Task, ReadOnlyDictView
16 from .lazy import LazyUUIDTask, LazyUUIDTaskSet
17 from .serializing import DATE_FORMAT, local_zone
19 # http://taskwarrior.org/docs/design/task.html , Section: The Attributes
20 TASK_STANDARD_ATTRS = (
44 def total_seconds_2_6(x):
45 return x.microseconds / 1e6 + x.seconds + x.days * 24 * 3600
48 class TasklibTest(unittest.TestCase):
50 def get_taskwarrior(self, **kwargs):
52 data_location=self.tmp,
55 tw_kwargs.update(kwargs)
56 return TaskWarrior(**tw_kwargs)
59 self.tmp = tempfile.mkdtemp(dir='.')
60 self.tw = self.get_taskwarrior()
63 shutil.rmtree(self.tmp)
66 class TaskWarriorTest(TasklibTest):
68 def test_custom_command(self):
69 # ensure that a custom command which contains multiple parts
70 # is properly split up
71 tw = self.get_taskwarrior(task_command='wsl task')
72 self.assertEqual(tw._get_task_command(), ['wsl', 'task'])
75 class TaskFilterTest(TasklibTest):
77 def test_all_empty(self):
78 self.assertEqual(len(self.tw.tasks.all()), 0)
80 def test_all_non_empty(self):
81 Task(self.tw, description='test task').save()
82 self.assertEqual(len(self.tw.tasks.all()), 1)
83 self.assertEqual(self.tw.tasks.all()[0]['description'], 'test task')
84 self.assertEqual(self.tw.tasks.all()[0]['status'], 'pending')
86 def test_pending_non_empty(self):
87 Task(self.tw, description='test task').save()
88 self.assertEqual(len(self.tw.tasks.pending()), 1)
90 self.tw.tasks.pending()[0]['description'],
93 self.assertEqual(self.tw.tasks.pending()[0]['status'], 'pending')
95 def test_completed_empty(self):
96 Task(self.tw, description='test task').save()
97 self.assertEqual(len(self.tw.tasks.completed()), 0)
99 def test_completed_non_empty(self):
100 Task(self.tw, description='test task').save()
101 self.assertEqual(len(self.tw.tasks.completed()), 0)
102 self.tw.tasks.all()[0].done()
103 self.assertEqual(len(self.tw.tasks.completed()), 1)
105 def test_deleted_empty(self):
106 Task(self.tw, description='test task').save()
107 self.assertEqual(len(self.tw.tasks.deleted()), 0)
109 def test_deleted_non_empty(self):
110 Task(self.tw, description='test task').save()
111 self.assertEqual(len(self.tw.tasks.deleted()), 0)
112 self.tw.tasks.all()[0].delete()
113 self.assertEqual(len(self.tw.tasks.deleted()), 1)
115 def test_waiting_empty(self):
116 Task(self.tw, description='test task').save()
117 self.assertEqual(len(self.tw.tasks.waiting()), 0)
119 def test_waiting_non_empty(self):
120 Task(self.tw, description='test task').save()
121 self.assertEqual(len(self.tw.tasks.waiting()), 0)
123 t = self.tw.tasks.all()[0]
124 t['wait'] = datetime.datetime.now() + datetime.timedelta(days=1)
127 self.assertEqual(len(self.tw.tasks.waiting()), 1)
129 def test_recurring_empty(self):
130 Task(self.tw, description='test task').save()
131 self.assertEqual(len(self.tw.tasks.recurring()), 0)
133 def test_recurring_non_empty(self):
136 description='test task',
138 due=datetime.datetime.now(),
140 self.assertEqual(len(self.tw.tasks.recurring()), 1)
142 def test_filtering_by_attribute(self):
143 Task(self.tw, description='no priority task').save()
144 Task(self.tw, priority='H', description='high priority task').save()
145 self.assertEqual(len(self.tw.tasks.all()), 2)
147 # Assert that the correct number of tasks is returned
148 self.assertEqual(len(self.tw.tasks.filter(priority='H')), 1)
150 # Assert that the correct tasks are returned
151 high_priority_task = self.tw.tasks.get(priority='H')
153 high_priority_task['description'],
154 'high priority task',
157 def test_filtering_by_empty_attribute(self):
158 Task(self.tw, description='no priority task').save()
159 Task(self.tw, priority='H', description='high priority task').save()
160 self.assertEqual(len(self.tw.tasks.all()), 2)
162 # Assert that the correct number of tasks is returned
163 self.assertEqual(len(self.tw.tasks.filter(priority=None)), 1)
165 # Assert that the correct tasks are returned
166 no_priority_task = self.tw.tasks.get(priority=None)
167 self.assertEqual(no_priority_task['description'], 'no priority task')
169 def test_filter_for_task_with_space_in_descripition(self):
170 task = Task(self.tw, description='test task')
173 filtered_task = self.tw.tasks.get(description='test task')
174 self.assertEqual(filtered_task['description'], 'test task')
176 def test_filter_for_task_without_space_in_descripition(self):
177 task = Task(self.tw, description='test')
180 filtered_task = self.tw.tasks.get(description='test')
181 self.assertEqual(filtered_task['description'], 'test')
183 def test_filter_for_task_with_space_in_project(self):
184 task = Task(self.tw, description='test', project='random project')
187 filtered_task = self.tw.tasks.get(project='random project')
188 self.assertEqual(filtered_task['project'], 'random project')
190 def test_filter_for_task_without_space_in_project(self):
191 task = Task(self.tw, description='test', project='random')
194 filtered_task = self.tw.tasks.get(project='random')
195 self.assertEqual(filtered_task['project'], 'random')
197 def test_filter_with_empty_uuid(self):
198 self.assertRaises(ValueError, lambda: self.tw.tasks.get(uuid=''))
200 def test_filter_dummy_by_status(self):
201 t = Task(self.tw, description='test')
204 tasks = self.tw.tasks.filter(status=t['status'])
205 self.assertEqual(list(tasks), [t])
207 def test_filter_dummy_by_uuid(self):
208 t = Task(self.tw, description='test')
211 tasks = self.tw.tasks.filter(uuid=t['uuid'])
212 self.assertEqual(list(tasks), [t])
214 def test_filter_dummy_by_entry(self):
215 t = Task(self.tw, description='test')
218 tasks = self.tw.tasks.filter(entry=t['entry'])
219 self.assertEqual(list(tasks), [t])
221 def test_filter_dummy_by_description(self):
222 t = Task(self.tw, description='test')
225 tasks = self.tw.tasks.filter(description=t['description'])
226 self.assertEqual(list(tasks), [t])
228 def test_filter_dummy_by_start(self):
229 t = Task(self.tw, description='test')
233 tasks = self.tw.tasks.filter(start=t['start'])
234 self.assertEqual(list(tasks), [t])
236 def test_filter_dummy_by_end(self):
237 t = Task(self.tw, description='test')
241 tasks = self.tw.tasks.filter(end=t['end'])
242 self.assertEqual(list(tasks), [t])
244 def test_filter_dummy_by_due(self):
245 t = Task(self.tw, description='test', due=datetime.datetime.now())
248 tasks = self.tw.tasks.filter(due=t['due'])
249 self.assertEqual(list(tasks), [t])
251 def test_filter_dummy_by_until(self):
252 t = Task(self.tw, description='test')
255 tasks = self.tw.tasks.filter(until=t['until'])
256 self.assertEqual(list(tasks), [t])
258 def test_filter_dummy_by_modified(self):
259 # Older TW version does not support bumping modified
261 if self.tw.version < six.text_type('2.2.0'):
262 # Python2.6 does not support SkipTest. As a workaround
263 # mark the test as passed by exiting.
264 if getattr(unittest, 'SkipTest', None) is not None:
265 raise unittest.SkipTest()
269 t = Task(self.tw, description='test')
272 tasks = self.tw.tasks.filter(modified=t['modified'])
273 self.assertEqual(list(tasks), [t])
275 def test_filter_dummy_by_scheduled(self):
276 t = Task(self.tw, description='test')
279 tasks = self.tw.tasks.filter(scheduled=t['scheduled'])
280 self.assertEqual(list(tasks), [t])
282 def test_filter_dummy_by_tags(self):
283 t = Task(self.tw, description='test', tags=['home'])
286 tasks = self.tw.tasks.filter(tags=t['tags'])
287 self.assertEqual(list(tasks), [t])
289 def test_filter_dummy_by_projects(self):
290 t = Task(self.tw, description='test', project='random')
293 tasks = self.tw.tasks.filter(project=t['project'])
294 self.assertEqual(list(tasks), [t])
296 def test_filter_by_priority(self):
297 t = Task(self.tw, description='test', priority='H')
300 tasks = self.tw.tasks.filter(priority=t['priority'])
301 self.assertEqual(list(tasks), [t])
304 class TaskTest(TasklibTest):
306 def test_create_unsaved_task(self):
307 # Make sure a new task is not saved unless explicitly called for
308 Task(self.tw, description='test task')
309 self.assertEqual(len(self.tw.tasks.all()), 0)
311 # TODO: once python 2.6 compatibility is over, use context managers here
312 # and in all subsequent tests for assertRaises
314 def test_delete_unsaved_task(self):
315 t = Task(self.tw, description='test task')
316 self.assertRaises(Task.NotSaved, t.delete)
318 def test_complete_unsaved_task(self):
319 t = Task(self.tw, description='test task')
320 self.assertRaises(Task.NotSaved, t.done)
322 def test_refresh_unsaved_task(self):
323 t = Task(self.tw, description='test task')
324 self.assertRaises(Task.NotSaved, t.refresh)
326 def test_start_unsaved_task(self):
327 t = Task(self.tw, description='test task')
328 self.assertRaises(Task.NotSaved, t.start)
330 def test_delete_deleted_task(self):
331 t = Task(self.tw, description='test task')
335 self.assertRaises(Task.DeletedTask, t.delete)
337 def test_complete_completed_task(self):
338 t = Task(self.tw, description='test task')
342 self.assertRaises(Task.CompletedTask, t.done)
344 def test_start_completed_task(self):
345 t = Task(self.tw, description='test task')
349 self.assertRaises(Task.CompletedTask, t.start)
351 def test_add_completed_task(self):
356 end=datetime.datetime.now(),
360 def test_add_multiple_completed_tasks(self):
365 end=datetime.datetime.now(),
371 end=datetime.datetime.now(),
376 def test_complete_deleted_task(self):
377 t = Task(self.tw, description='test task')
381 self.assertRaises(Task.DeletedTask, t.done)
383 def test_starting_task(self):
384 t = Task(self.tw, description='test task')
385 now = t.datetime_normalizer(datetime.datetime.now())
389 self.assertTrue(now.replace(microsecond=0) <= t['start'])
390 self.assertEqual(t['status'], 'pending')
392 def test_completing_task(self):
393 t = Task(self.tw, description='test task')
394 now = t.datetime_normalizer(datetime.datetime.now())
398 self.assertTrue(now.replace(microsecond=0) <= t['end'])
399 self.assertEqual(t['status'], 'completed')
401 def test_deleting_task(self):
402 t = Task(self.tw, description='test task')
403 now = t.datetime_normalizer(datetime.datetime.now())
407 self.assertTrue(now.replace(microsecond=0) <= t['end'])
408 self.assertEqual(t['status'], 'deleted')
410 def test_started_task_active(self):
411 t = Task(self.tw, description='test task')
414 self.assertTrue(t.active)
416 def test_unstarted_task_inactive(self):
417 t = Task(self.tw, description='test task')
418 self.assertFalse(t.active)
420 self.assertFalse(t.active)
422 def test_start_active_task(self):
423 t = Task(self.tw, description='test task')
426 self.assertRaises(Task.ActiveTask, t.start)
428 def test_stop_completed_task(self):
429 t = Task(self.tw, description='test task')
434 self.assertRaises(Task.InactiveTask, t.stop)
436 t = Task(self.tw, description='test task')
440 self.assertRaises(Task.InactiveTask, t.stop)
442 def test_stop_deleted_task(self):
443 t = Task(self.tw, description='test task')
449 def test_stop_inactive_task(self):
450 t = Task(self.tw, description='test task')
453 self.assertRaises(Task.InactiveTask, t.stop)
455 t = Task(self.tw, description='test task')
460 self.assertRaises(Task.InactiveTask, t.stop)
462 def test_stopping_task(self):
463 t = Task(self.tw, description='test task')
464 t.datetime_normalizer(datetime.datetime.now())
469 self.assertEqual(t['end'], None)
470 self.assertEqual(t['status'], 'pending')
471 self.assertFalse(t.active)
473 def test_modify_simple_attribute_without_space(self):
474 t = Task(self.tw, description='test')
477 self.assertEqual(t['description'], 'test')
479 t['description'] = 'test-modified'
482 self.assertEqual(t['description'], 'test-modified')
484 def test_modify_simple_attribute_with_space(self):
485 # Space can pose problems with parsing
486 t = Task(self.tw, description='test task')
489 self.assertEqual(t['description'], 'test task')
491 t['description'] = 'test task modified'
494 self.assertEqual(t['description'], 'test task modified')
496 def test_empty_dependency_set_of_unsaved_task(self):
497 t = Task(self.tw, description='test task')
498 self.assertEqual(t['depends'], set())
500 def test_empty_dependency_set_of_saved_task(self):
501 t = Task(self.tw, description='test task')
503 self.assertEqual(t['depends'], set())
505 def test_set_unsaved_task_as_dependency(self):
506 # Adds only one dependency to task with no dependencies
507 t = Task(self.tw, description='test task')
508 dependency = Task(self.tw, description='needs to be done first')
510 # We only save the parent task, dependency task is unsaved
512 t['depends'] = set([dependency])
514 self.assertRaises(Task.NotSaved, t.save)
516 def test_set_simple_dependency_set(self):
517 # Adds only one dependency to task with no dependencies
518 t = Task(self.tw, description='test task')
519 dependency = Task(self.tw, description='needs to be done first')
524 t['depends'] = set([dependency])
526 self.assertEqual(t['depends'], set([dependency]))
528 def test_set_complex_dependency_set(self):
529 # Adds two dependencies to task with no dependencies
530 t = Task(self.tw, description='test task')
531 dependency1 = Task(self.tw, description='needs to be done first')
532 dependency2 = Task(self.tw, description='needs to be done second')
538 t['depends'] = set([dependency1, dependency2])
540 self.assertEqual(t['depends'], set([dependency1, dependency2]))
542 def test_remove_from_dependency_set(self):
543 # Removes dependency from task with two dependencies
544 t = Task(self.tw, description='test task')
545 dependency1 = Task(self.tw, description='needs to be done first')
546 dependency2 = Task(self.tw, description='needs to be done second')
551 t['depends'] = set([dependency1, dependency2])
554 t['depends'].remove(dependency2)
557 self.assertEqual(t['depends'], set([dependency1]))
559 def test_add_to_dependency_set(self):
560 # Adds dependency to task with one dependencies
561 t = Task(self.tw, description='test task')
562 dependency1 = Task(self.tw, description='needs to be done first')
563 dependency2 = Task(self.tw, description='needs to be done second')
568 t['depends'] = set([dependency1])
571 t['depends'].add(dependency2)
574 self.assertEqual(t['depends'], set([dependency1, dependency2]))
576 def test_add_to_empty_dependency_set(self):
577 # Adds dependency to task with one dependencies
578 t = Task(self.tw, description='test task')
579 dependency = Task(self.tw, description='needs to be done first')
583 t['depends'].add(dependency)
586 self.assertEqual(t['depends'], set([dependency]))
588 def test_simple_dependency_set_save_repeatedly(self):
589 # Adds only one dependency to task with no dependencies
590 t = Task(self.tw, description='test task')
591 dependency = Task(self.tw, description='needs to be done first')
594 t['depends'] = set([dependency])
597 # We taint the task, but keep depends intact
598 t['description'] = 'test task modified'
601 self.assertEqual(t['depends'], set([dependency]))
603 # We taint the task, but assign the same set to the depends
604 t['depends'] = set([dependency])
605 t['description'] = 'test task modified again'
608 self.assertEqual(t['depends'], set([dependency]))
610 def test_compare_different_tasks(self):
611 # Negative: compare two different tasks
612 t1 = Task(self.tw, description='test task')
613 t2 = Task(self.tw, description='test task')
618 self.assertEqual(t1 == t2, False)
620 def test_compare_same_task_object(self):
621 # Compare Task object wit itself
622 t = Task(self.tw, description='test task')
625 self.assertEqual(t == t, True)
627 def test_compare_same_task(self):
628 # Compare the same task using two different objects
629 t1 = Task(self.tw, description='test task')
632 t2 = self.tw.tasks.get(uuid=t1['uuid'])
633 self.assertEqual(t1 == t2, True)
635 def test_compare_unsaved_tasks(self):
636 # t1 and t2 are unsaved tasks, considered to be unequal
637 # despite the content of data
638 t1 = Task(self.tw, description='test task')
639 t2 = Task(self.tw, description='test task')
641 self.assertEqual(t1 == t2, False)
643 def test_hash_unsaved_tasks(self):
644 # Considered equal, it's the same object
645 t1 = Task(self.tw, description='test task')
647 self.assertEqual(hash(t1) == hash(t2), True)
649 def test_hash_same_task(self):
650 # Compare the hash of the task using two different objects
651 t1 = Task(self.tw, description='test task')
654 t2 = self.tw.tasks.get(uuid=t1['uuid'])
655 self.assertEqual(t1.__hash__(), t2.__hash__())
657 def test_hash_unequal_unsaved_tasks(self):
658 # Compare the hash of the task using two different objects
659 t1 = Task(self.tw, description='test task 1')
660 t2 = Task(self.tw, description='test task 2')
662 self.assertNotEqual(t1.__hash__(), t2.__hash__())
664 def test_hash_unequal_saved_tasks(self):
665 # Compare the hash of the task using two different objects
666 t1 = Task(self.tw, description='test task 1')
667 t2 = Task(self.tw, description='test task 2')
672 self.assertNotEqual(t1.__hash__(), t2.__hash__())
674 def test_adding_task_with_priority(self):
675 t = Task(self.tw, description='test task', priority='M')
678 def test_removing_priority_with_none(self):
679 t = Task(self.tw, description='test task', priority='L')
682 # Remove the priority mark
686 # Assert that priority is not there after saving
687 self.assertEqual(t['priority'], None)
689 def test_adding_task_with_due_time(self):
690 t = Task(self.tw, description='test task', due=datetime.datetime.now())
693 def test_removing_due_time_with_none(self):
694 t = Task(self.tw, description='test task', due=datetime.datetime.now())
697 # Remove the due timestamp
701 # Assert that due timestamp is no longer there
702 self.assertEqual(t['due'], None)
704 def test_modified_fields_new_task(self):
707 # This should be empty with new task
708 self.assertEqual(set(t._modified_fields), set())
711 t['description'] = 'test task'
712 self.assertEqual(set(t._modified_fields), set(['description']))
714 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
715 self.assertEqual(set(t._modified_fields), set(['description', 'due']))
717 t['project'] = 'test project'
719 set(t._modified_fields),
720 set(['description', 'due', 'project']),
723 # List of modified fields should clear out when saved
725 self.assertEqual(set(t._modified_fields), set())
727 # Reassigning the fields with the same values now should not produce
729 t['description'] = 'test task'
730 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
731 t['project'] = 'test project'
732 self.assertEqual(set(t._modified_fields), set())
734 def test_modified_fields_loaded_task(self):
738 t['description'] = 'test task'
739 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
740 t['project'] = 'test project'
742 dependency = Task(self.tw, description='dependency')
744 t['depends'] = set([dependency])
746 # List of modified fields should clear out when saved
748 self.assertEqual(set(t._modified_fields), set())
750 # Get the task by using a filter by UUID
751 self.tw.tasks.get(uuid=t['uuid'])
753 # Reassigning the fields with the same values now should not produce
755 t['description'] = 'test task'
756 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
757 t['project'] = 'test project'
758 t['depends'] = set([dependency])
759 self.assertEqual(set(t._modified_fields), set())
761 def test_modified_fields_not_affected_by_reading(self):
764 for field in TASK_STANDARD_ATTRS:
767 self.assertEqual(set(t._modified_fields), set())
769 def test_setting_read_only_attrs_through_init(self):
770 # Test that we are unable to set readonly attrs through __init__
771 for readonly_key in Task.read_only_fields:
772 kwargs = {'description': 'test task', readonly_key: 'value'}
775 lambda: Task(self.tw, **kwargs),
778 def test_setting_read_only_attrs_through_setitem(self):
779 # Test that we are unable to set readonly attrs through __init__
780 for readonly_key in Task.read_only_fields:
781 t = Task(self.tw, description='test task')
784 lambda: t.__setitem__(readonly_key, 'value'),
787 def test_saving_unmodified_task(self):
788 t = Task(self.tw, description='test task')
792 def test_adding_tag_by_appending(self):
793 t = Task(self.tw, description='test task', tags=['test1'])
795 t['tags'].add('test2')
797 self.assertEqual(t['tags'], set(['test1', 'test2']))
799 def test_adding_tag_twice(self):
800 t = Task(self.tw, description='test task', tags=['test1'])
802 t['tags'].add('test2')
803 t['tags'].add('test2')
805 self.assertEqual(t['tags'], set(['test1', 'test2']))
807 def test_adding_tag_by_appending_empty(self):
808 t = Task(self.tw, description='test task')
810 t['tags'].add('test')
812 self.assertEqual(t['tags'], set(['test']))
814 def test_serializers_returning_empty_string_for_none(self):
815 # Test that any serializer returns '' when passed None
818 getattr(t, serializer_name)
819 for serializer_name in filter(
820 lambda x: x.startswith('serialize_'),
824 for serializer in serializers:
825 self.assertEqual(serializer(None), '')
827 def test_deserializer_returning_empty_value_for_empty_string(self):
828 # Test that any deserializer returns empty value when passed ''
831 getattr(t, deserializer_name)
832 for deserializer_name in filter(
833 lambda x: x.startswith('deserialize_'),
837 for deserializer in deserializers:
838 self.assertTrue(deserializer('') in (None, [], set()))
840 def test_normalizers_handling_none(self):
841 # Test that any normalizer can handle None as a valid value
844 for key in TASK_STANDARD_ATTRS:
845 t._normalize(key, None)
847 def test_recurrent_task_generation(self):
848 today = datetime.date.today()
851 description='brush teeth',
856 self.assertEqual(len(self.tw.tasks.pending()), 2)
858 def test_spawned_task_parent(self):
859 today = datetime.date.today()
862 description='brush teeth',
868 spawned = self.tw.tasks.pending().get(due=today)
869 assert spawned['parent'] == t
871 def test_modify_number_of_tasks_at_once(self):
872 for i in range(1, 100):
873 Task(self.tw, description='test task %d' % i, tags=['test']).save()
875 self.tw.execute_command(['+test', 'mod', 'unified', 'description'])
877 def test_return_all_from_executed_command(self):
878 Task(self.tw, description='test task', tags=['test']).save()
879 out, err, rc = self.tw.execute_command(['count'], return_all=True)
880 self.assertEqual(rc, 0)
882 def test_return_all_from_failed_executed_command(self):
883 Task(self.tw, description='test task', tags=['test']).save()
884 out, err, rc = self.tw.execute_command(
889 self.assertNotEqual(rc, 0)
892 class TaskFromHookTest(TasklibTest):
894 input_add_data = six.StringIO(
895 '{"description":"Buy some milk",'
896 '"entry":"20141118T050231Z",'
897 '"status":"pending",'
898 '"start":"20141119T152233Z",'
899 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}',
902 input_add_data_recurring = six.StringIO(
903 '{"description":"Mow the lawn",'
904 '"entry":"20160210T224304Z",'
905 '"parent":"62da6227-519c-42c2-915d-dccada926ad7",'
907 '"status":"pending",'
908 '"uuid":"81305335-0237-49ff-8e87-b3cdc2369cec"}',
911 input_modify_data = six.StringIO(
913 input_add_data.getvalue(),
915 '{"description":"Buy some milk finally",'
916 '"entry":"20141118T050231Z",'
917 '"status":"completed",'
918 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}'
923 exported_raw_data = (
925 '"due":"20150101T232323Z",'
926 '"description":"test task"}'
929 def test_setting_up_from_add_hook_input(self):
930 t = Task.from_input(input_file=self.input_add_data, backend=self.tw)
931 self.assertEqual(t['description'], 'Buy some milk')
932 self.assertEqual(t.pending, True)
934 def test_setting_up_from_add_hook_input_recurring(self):
936 input_file=self.input_add_data_recurring,
939 self.assertEqual(t['description'], 'Mow the lawn')
940 self.assertEqual(t.pending, True)
942 def test_setting_up_from_modified_hook_input(self):
944 input_file=self.input_modify_data,
948 self.assertEqual(t['description'], 'Buy some milk finally')
949 self.assertEqual(t.pending, False)
950 self.assertEqual(t.completed, True)
952 self.assertEqual(t._original_data['status'], 'pending')
953 self.assertEqual(t._original_data['description'], 'Buy some milk')
955 set(t._modified_fields),
956 set(['status', 'description', 'start']),
959 def test_export_data(self):
962 description='test task',
964 due=pytz.utc.localize(
965 datetime.datetime(2015, 1, 1, 23, 23, 23)),
968 # Check that the output is a permutation of:
969 # {"project":"Home","description":"test task","due":"20150101232323Z"}
970 allowed_segments = self.exported_raw_data[1:-1].split(',')
972 '{' + ','.join(segments) + '}'
973 for segments in itertools.permutations(allowed_segments)
977 any(t.export_data() == expected
978 for expected in allowed_output),
982 class TimezoneAwareDatetimeTest(TasklibTest):
985 super(TimezoneAwareDatetimeTest, self).setUp()
986 self.zone = local_zone
987 self.localdate_naive = datetime.datetime(2015, 2, 2)
988 self.localtime_naive = datetime.datetime(2015, 2, 2, 0, 0, 0)
989 self.localtime_aware = self.zone.localize(self.localtime_naive)
990 self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
992 def test_timezone_naive_datetime_setitem(self):
993 t = Task(self.tw, description='test task')
994 t['due'] = self.localtime_naive
995 self.assertEqual(t['due'], self.localtime_aware)
997 def test_timezone_naive_datetime_using_init(self):
998 t = Task(self.tw, description='test task', due=self.localtime_naive)
999 self.assertEqual(t['due'], self.localtime_aware)
1001 def test_filter_by_naive_datetime(self):
1002 t = Task(self.tw, description='task1', due=self.localtime_naive)
1004 matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
1005 self.assertEqual(len(matching_tasks), 1)
1007 def test_serialize_naive_datetime(self):
1008 t = Task(self.tw, description='task1', due=self.localtime_naive)
1010 json.loads(t.export_data())['due'],
1011 self.utctime_aware.strftime(DATE_FORMAT),
1014 def test_timezone_naive_date_setitem(self):
1015 t = Task(self.tw, description='test task')
1016 t['due'] = self.localdate_naive
1017 self.assertEqual(t['due'], self.localtime_aware)
1019 def test_timezone_naive_date_using_init(self):
1020 t = Task(self.tw, description='test task', due=self.localdate_naive)
1021 self.assertEqual(t['due'], self.localtime_aware)
1023 def test_filter_by_naive_date(self):
1024 t = Task(self.tw, description='task1', due=self.localdate_naive)
1026 matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
1027 self.assertEqual(len(matching_tasks), 1)
1029 def test_serialize_naive_date(self):
1030 t = Task(self.tw, description='task1', due=self.localdate_naive)
1032 json.loads(t.export_data())['due'],
1033 self.utctime_aware.strftime(DATE_FORMAT),
1036 def test_timezone_aware_datetime_setitem(self):
1037 t = Task(self.tw, description='test task')
1038 t['due'] = self.localtime_aware
1039 self.assertEqual(t['due'], self.localtime_aware)
1041 def test_timezone_aware_datetime_using_init(self):
1042 t = Task(self.tw, description='test task', due=self.localtime_aware)
1043 self.assertEqual(t['due'], self.localtime_aware)
1045 def test_filter_by_aware_datetime(self):
1046 t = Task(self.tw, description='task1', due=self.localtime_aware)
1048 matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
1049 self.assertEqual(len(matching_tasks), 1)
1051 def test_serialize_aware_datetime(self):
1052 t = Task(self.tw, description='task1', due=self.localtime_aware)
1054 json.loads(t.export_data())['due'],
1055 self.utctime_aware.strftime(DATE_FORMAT),
1059 class DatetimeStringTest(TasklibTest):
1061 def test_simple_now_conversion(self):
1062 if self.tw.version < six.text_type('2.4.0'):
1063 # Python2.6 does not support SkipTest. As a workaround
1064 # mark the test as passed by exiting.
1065 if getattr(unittest, 'SkipTest', None) is not None:
1066 raise unittest.SkipTest()
1070 t = Task(self.tw, description='test task', due='now')
1071 now = local_zone.localize(datetime.datetime.now())
1073 # Assert that both times are not more than 5 seconds apart
1074 if sys.version_info < (2, 7):
1075 self.assertTrue(total_seconds_2_6(now - t['due']) < 5)
1076 self.assertTrue(total_seconds_2_6(t['due'] - now) < 5)
1078 self.assertTrue((now - t['due']).total_seconds() < 5)
1079 self.assertTrue((t['due'] - now).total_seconds() < 5)
1081 def test_simple_eoy_conversion(self):
1082 if self.tw.version < six.text_type('2.4.0'):
1083 # Python2.6 does not support SkipTest. As a workaround
1084 # mark the test as passed by exiting.
1085 if getattr(unittest, 'SkipTest', None) is not None:
1086 raise unittest.SkipTest()
1090 t = Task(self.tw, description='test task', due='eoy')
1091 now = local_zone.localize(datetime.datetime.now())
1092 eoy = local_zone.localize(datetime.datetime(
1100 self.assertEqual(eoy, t['due'])
1102 def test_complex_eoy_conversion(self):
1103 if self.tw.version < six.text_type('2.4.0'):
1104 # Python2.6 does not support SkipTest. As a workaround
1105 # mark the test as passed by exiting.
1106 if getattr(unittest, 'SkipTest', None) is not None:
1107 raise unittest.SkipTest()
1111 t = Task(self.tw, description='test task', due='eoy - 4 months')
1112 now = local_zone.localize(datetime.datetime.now())
1113 due_date = local_zone.localize(
1122 ) - datetime.timedelta(0, 4 * 30 * 86400)
1123 self.assertEqual(due_date, t['due'])
1125 def test_filtering_with_string_datetime(self):
1126 if self.tw.version < six.text_type('2.4.0'):
1127 # Python2.6 does not support SkipTest. As a workaround
1128 # mark the test as passed by exiting.
1129 if getattr(unittest, 'SkipTest', None) is not None:
1130 raise unittest.SkipTest()
1136 description='test task',
1137 due=datetime.datetime.now() - datetime.timedelta(0, 2),
1140 self.assertEqual(len(self.tw.tasks.filter(due__before='now')), 1)
1143 class AnnotationTest(TasklibTest):
1146 super(AnnotationTest, self).setUp()
1147 Task(self.tw, description='test task').save()
1149 def test_adding_annotation(self):
1150 task = self.tw.tasks.get()
1151 task.add_annotation('test annotation')
1152 self.assertEqual(len(task['annotations']), 1)
1153 ann = task['annotations'][0]
1154 self.assertEqual(ann['description'], 'test annotation')
1156 def test_removing_annotation(self):
1157 task = self.tw.tasks.get()
1158 task.add_annotation('test annotation')
1159 ann = task['annotations'][0]
1161 self.assertEqual(len(task['annotations']), 0)
1163 def test_removing_annotation_by_description(self):
1164 task = self.tw.tasks.get()
1165 task.add_annotation('test annotation')
1166 task.remove_annotation('test annotation')
1167 self.assertEqual(len(task['annotations']), 0)
1169 def test_removing_annotation_by_obj(self):
1170 task = self.tw.tasks.get()
1171 task.add_annotation('test annotation')
1172 ann = task['annotations'][0]
1173 task.remove_annotation(ann)
1174 self.assertEqual(len(task['annotations']), 0)
1176 def test_annotation_after_modification(self):
1177 task = self.tw.tasks.get()
1178 task['project'] = 'test'
1179 task.add_annotation('I should really do this task')
1180 self.assertEqual(task['project'], 'test')
1182 self.assertEqual(task['project'], 'test')
1184 def test_serialize_annotations(self):
1185 # Test that serializing annotations is possible
1186 t = Task(self.tw, description='test')
1189 t.add_annotation('annotation1')
1190 t.add_annotation('annotation2')
1192 data = t._serialize('annotations', t._data['annotations'])
1194 self.assertEqual(len(data), 2)
1195 self.assertEqual(type(data[0]), dict)
1196 self.assertEqual(type(data[1]), dict)
1198 self.assertEqual(data[0]['description'], 'annotation1')
1199 self.assertEqual(data[1]['description'], 'annotation2')
1202 class UnicodeTest(TasklibTest):
1204 def test_unicode_task(self):
1205 Task(self.tw, description=six.u('†åßk')).save()
1208 def test_filter_by_unicode_task(self):
1209 Task(self.tw, description=six.u('†åßk')).save()
1210 tasks = self.tw.tasks.filter(description=six.u('†åßk'))
1211 self.assertEqual(len(tasks), 1)
1213 def test_non_unicode_task(self):
1214 Task(self.tw, description='test task').save()
1218 class ReadOnlyDictViewTest(unittest.TestCase):
1221 self.sample = dict(sample_list=[1, 2, 3], sample_dict={'key': 'value'})
1222 self.original_sample = copy.deepcopy(self.sample)
1223 self.view = ReadOnlyDictView(self.sample)
1225 def test_readonlydictview_getitem(self):
1226 sample_list = self.view['sample_list']
1227 self.assertEqual(sample_list, self.sample['sample_list'])
1229 # Assert that modification changed only copied value
1230 sample_list.append(4)
1231 self.assertNotEqual(sample_list, self.sample['sample_list'])
1233 # Assert that viewed dict is not changed
1234 self.assertEqual(self.sample, self.original_sample)
1236 def test_readonlydictview_contains(self):
1237 self.assertEqual('sample_list' in self.view,
1238 'sample_list' in self.sample)
1239 self.assertEqual('sample_dict' in self.view,
1240 'sample_dict' in self.sample)
1241 self.assertEqual('key' in self.view, 'key' in self.sample)
1243 # Assert that viewed dict is not changed
1244 self.assertEqual(self.sample, self.original_sample)
1246 def test_readonlydictview_iter(self):
1248 list(key for key in self.view),
1249 list(key for key in self.sample),
1252 # Assert the view is correct after modification
1253 self.sample['new'] = 'value'
1255 list(key for key in self.view),
1256 list(key for key in self.sample),
1259 def test_readonlydictview_len(self):
1260 self.assertEqual(len(self.view), len(self.sample))
1262 # Assert the view is correct after modification
1263 self.sample['new'] = 'value'
1264 self.assertEqual(len(self.view), len(self.sample))
1266 def test_readonlydictview_get(self):
1267 sample_list = self.view.get('sample_list')
1268 self.assertEqual(sample_list, self.sample.get('sample_list'))
1270 # Assert that modification changed only copied value
1271 sample_list.append(4)
1272 self.assertNotEqual(sample_list, self.sample.get('sample_list'))
1274 # Assert that viewed dict is not changed
1275 self.assertEqual(self.sample, self.original_sample)
1277 def test_readonlydict_items(self):
1278 view_items = self.view.items()
1279 sample_items = list(self.sample.items())
1280 self.assertEqual(view_items, sample_items)
1282 view_items.append('newkey')
1283 self.assertNotEqual(view_items, sample_items)
1284 self.assertEqual(self.sample, self.original_sample)
1286 def test_readonlydict_values(self):
1287 view_values = self.view.values()
1288 sample_values = list(self.sample.values())
1289 self.assertEqual(view_values, sample_values)
1291 view_list_item = list(filter(lambda x: type(x) is list,
1293 view_list_item.append(4)
1294 self.assertNotEqual(view_values, sample_values)
1295 self.assertEqual(self.sample, self.original_sample)
1298 class LazyUUIDTaskTest(TasklibTest):
1301 super(LazyUUIDTaskTest, self).setUp()
1303 self.stored = Task(self.tw, description='this is test task')
1306 self.lazy = LazyUUIDTask(self.tw, self.stored['uuid'])
1308 def test_uuid_non_conversion(self):
1309 assert self.stored['uuid'] == self.lazy['uuid']
1310 assert type(self.lazy) is LazyUUIDTask
1312 def test_lazy_explicit_conversion(self):
1313 assert type(self.lazy) is LazyUUIDTask
1315 assert type(self.lazy) is Task
1317 def test_conversion_key(self):
1318 assert self.stored['description'] == self.lazy['description']
1319 assert type(self.lazy) is Task
1321 def test_conversion_attribute(self):
1322 assert type(self.lazy) is LazyUUIDTask
1323 assert self.lazy.completed is False
1324 assert type(self.lazy) is Task
1326 def test_normal_to_lazy_equality(self):
1327 assert self.stored == self.lazy
1328 assert not self.stored != self.lazy
1329 assert type(self.lazy) is LazyUUIDTask
1331 def test_lazy_to_lazy_equality(self):
1332 lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
1333 lazy2 = LazyUUIDTask(self.tw, self.stored['uuid'])
1335 assert lazy1 == lazy2
1336 assert not lazy1 != lazy2
1337 assert type(lazy1) is LazyUUIDTask
1338 assert type(lazy2) is LazyUUIDTask
1340 def test_normal_to_lazy_inequality(self):
1341 # Create a different UUID by changing the last letter
1342 wrong_uuid = self.stored['uuid']
1343 wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
1345 wrong_lazy = LazyUUIDTask(self.tw, wrong_uuid)
1347 assert not self.stored == wrong_lazy
1348 assert self.stored != wrong_lazy
1349 assert type(wrong_lazy) is LazyUUIDTask
1351 def test_lazy_to_lazy_inequality(self):
1352 # Create a different UUID by changing the last letter
1353 wrong_uuid = self.stored['uuid']
1354 wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
1356 lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
1357 lazy2 = LazyUUIDTask(self.tw, wrong_uuid)
1359 assert not lazy1 == lazy2
1360 assert lazy1 != lazy2
1361 assert type(lazy1) is LazyUUIDTask
1362 assert type(lazy2) is LazyUUIDTask
1364 def test_lazy_in_queryset(self):
1365 tasks = self.tw.tasks.filter(uuid=self.stored['uuid'])
1367 assert self.lazy in tasks
1368 assert type(self.lazy) is LazyUUIDTask
1370 def test_lazy_saved(self):
1371 assert self.lazy.saved is True
1373 def test_lazy_modified(self):
1374 assert self.lazy.modified is False
1376 def test_lazy_modified_fields(self):
1377 assert self.lazy._modified_fields == set()
1380 class LazyUUIDTaskSetTest(TasklibTest):
1383 super(LazyUUIDTaskSetTest, self).setUp()
1385 self.task1 = Task(self.tw, description='task 1')
1386 self.task2 = Task(self.tw, description='task 2')
1387 self.task3 = Task(self.tw, description='task 3')
1399 self.lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1401 def test_length(self):
1402 assert len(self.lazy) == 3
1403 assert type(self.lazy) is LazyUUIDTaskSet
1405 def test_contains(self):
1406 assert self.task1 in self.lazy
1407 assert self.task2 in self.lazy
1408 assert self.task3 in self.lazy
1409 assert type(self.lazy) is LazyUUIDTaskSet
1411 def test_eq_lazy(self):
1412 new_lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1413 assert self.lazy == new_lazy
1414 assert not self.lazy != new_lazy
1415 assert type(self.lazy) is LazyUUIDTaskSet
1417 def test_eq_real(self):
1418 assert self.lazy == self.tw.tasks.all()
1419 assert self.tw.tasks.all() == self.lazy
1420 assert not self.lazy != self.tw.tasks.all()
1422 assert type(self.lazy) is LazyUUIDTaskSet
1424 def test_union(self):
1425 taskset = set([self.task1])
1426 lazyset = LazyUUIDTaskSet(
1428 (self.task2['uuid'], self.task3['uuid']),
1431 assert taskset | lazyset == self.lazy
1432 assert lazyset | taskset == self.lazy
1433 assert taskset.union(lazyset) == self.lazy
1434 assert lazyset.union(taskset) == self.lazy
1437 assert lazyset == self.lazy
1439 def test_difference(self):
1440 taskset = set([self.task1, self.task2])
1441 lazyset = LazyUUIDTaskSet(
1443 (self.task2['uuid'], self.task3['uuid']),
1446 assert taskset - lazyset == set([self.task1])
1447 assert lazyset - taskset == set([self.task3])
1448 assert taskset.difference(lazyset) == set([self.task1])
1449 assert lazyset.difference(taskset) == set([self.task3])
1452 assert lazyset == set([self.task3])
1454 def test_symmetric_difference(self):
1455 taskset = set([self.task1, self.task2])
1456 lazyset = LazyUUIDTaskSet(
1458 (self.task2['uuid'], self.task3['uuid']),
1461 assert taskset ^ lazyset == set([self.task1, self.task3])
1462 assert lazyset ^ taskset == set([self.task1, self.task3])
1464 taskset.symmetric_difference(lazyset),
1465 set([self.task1, self.task3]),
1468 lazyset.symmetric_difference(taskset),
1469 set([self.task1, self.task3]),
1473 assert lazyset == set([self.task1, self.task3])
1475 def test_intersection(self):
1476 taskset = set([self.task1, self.task2])
1477 lazyset = LazyUUIDTaskSet(
1479 (self.task2['uuid'], self.task3['uuid']),
1482 assert taskset & lazyset == set([self.task2])
1483 assert lazyset & taskset == set([self.task2])
1484 assert taskset.intersection(lazyset) == set([self.task2])
1485 assert lazyset.intersection(taskset) == set([self.task2])
1488 assert lazyset == set([self.task2])
1491 class TaskWarriorBackendTest(TasklibTest):
1493 def test_config(self):
1494 assert self.tw.config['nag'] == 'You have more urgent tasks.'
1495 assert self.tw.config['default.command'] == 'next'
1496 assert self.tw.config['dependency.indicator'] == 'D'