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):
51 self.tmp = tempfile.mkdtemp(dir='.')
52 self.tw = TaskWarrior(data_location=self.tmp, taskrc_location='/')
55 shutil.rmtree(self.tmp)
58 class TaskFilterTest(TasklibTest):
60 def test_all_empty(self):
61 self.assertEqual(len(self.tw.tasks.all()), 0)
63 def test_all_non_empty(self):
64 Task(self.tw, description='test task').save()
65 self.assertEqual(len(self.tw.tasks.all()), 1)
66 self.assertEqual(self.tw.tasks.all()[0]['description'], 'test task')
67 self.assertEqual(self.tw.tasks.all()[0]['status'], 'pending')
69 def test_pending_non_empty(self):
70 Task(self.tw, description='test task').save()
71 self.assertEqual(len(self.tw.tasks.pending()), 1)
73 self.tw.tasks.pending()[0]['description'],
76 self.assertEqual(self.tw.tasks.pending()[0]['status'], 'pending')
78 def test_completed_empty(self):
79 Task(self.tw, description='test task').save()
80 self.assertEqual(len(self.tw.tasks.completed()), 0)
82 def test_completed_non_empty(self):
83 Task(self.tw, description='test task').save()
84 self.assertEqual(len(self.tw.tasks.completed()), 0)
85 self.tw.tasks.all()[0].done()
86 self.assertEqual(len(self.tw.tasks.completed()), 1)
88 def test_deleted_empty(self):
89 Task(self.tw, description='test task').save()
90 self.assertEqual(len(self.tw.tasks.deleted()), 0)
92 def test_deleted_non_empty(self):
93 Task(self.tw, description='test task').save()
94 self.assertEqual(len(self.tw.tasks.deleted()), 0)
95 self.tw.tasks.all()[0].delete()
96 self.assertEqual(len(self.tw.tasks.deleted()), 1)
98 def test_waiting_empty(self):
99 Task(self.tw, description='test task').save()
100 self.assertEqual(len(self.tw.tasks.waiting()), 0)
102 def test_waiting_non_empty(self):
103 Task(self.tw, description='test task').save()
104 self.assertEqual(len(self.tw.tasks.waiting()), 0)
106 t = self.tw.tasks.all()[0]
107 t['wait'] = datetime.datetime.now() + datetime.timedelta(days=1)
110 self.assertEqual(len(self.tw.tasks.waiting()), 1)
112 def test_recurring_empty(self):
113 Task(self.tw, description='test task').save()
114 self.assertEqual(len(self.tw.tasks.recurring()), 0)
116 def test_recurring_non_empty(self):
119 description='test task',
121 due=datetime.datetime.now(),
123 self.assertEqual(len(self.tw.tasks.recurring()), 1)
125 def test_filtering_by_attribute(self):
126 Task(self.tw, description='no priority task').save()
127 Task(self.tw, priority='H', description='high priority task').save()
128 self.assertEqual(len(self.tw.tasks.all()), 2)
130 # Assert that the correct number of tasks is returned
131 self.assertEqual(len(self.tw.tasks.filter(priority='H')), 1)
133 # Assert that the correct tasks are returned
134 high_priority_task = self.tw.tasks.get(priority='H')
136 high_priority_task['description'],
137 'high priority task',
140 def test_filtering_by_empty_attribute(self):
141 Task(self.tw, description='no priority task').save()
142 Task(self.tw, priority='H', description='high priority task').save()
143 self.assertEqual(len(self.tw.tasks.all()), 2)
145 # Assert that the correct number of tasks is returned
146 self.assertEqual(len(self.tw.tasks.filter(priority=None)), 1)
148 # Assert that the correct tasks are returned
149 no_priority_task = self.tw.tasks.get(priority=None)
150 self.assertEqual(no_priority_task['description'], 'no priority task')
152 def test_filter_for_task_with_space_in_descripition(self):
153 task = Task(self.tw, description='test task')
156 filtered_task = self.tw.tasks.get(description='test task')
157 self.assertEqual(filtered_task['description'], 'test task')
159 def test_filter_for_task_without_space_in_descripition(self):
160 task = Task(self.tw, description='test')
163 filtered_task = self.tw.tasks.get(description='test')
164 self.assertEqual(filtered_task['description'], 'test')
166 def test_filter_for_task_with_space_in_project(self):
167 task = Task(self.tw, description='test', project='random project')
170 filtered_task = self.tw.tasks.get(project='random project')
171 self.assertEqual(filtered_task['project'], 'random project')
173 def test_filter_for_task_without_space_in_project(self):
174 task = Task(self.tw, description='test', project='random')
177 filtered_task = self.tw.tasks.get(project='random')
178 self.assertEqual(filtered_task['project'], 'random')
180 def test_filter_with_empty_uuid(self):
181 self.assertRaises(ValueError, lambda: self.tw.tasks.get(uuid=''))
183 def test_filter_dummy_by_status(self):
184 t = Task(self.tw, description='test')
187 tasks = self.tw.tasks.filter(status=t['status'])
188 self.assertEqual(list(tasks), [t])
190 def test_filter_dummy_by_uuid(self):
191 t = Task(self.tw, description='test')
194 tasks = self.tw.tasks.filter(uuid=t['uuid'])
195 self.assertEqual(list(tasks), [t])
197 def test_filter_dummy_by_entry(self):
198 t = Task(self.tw, description='test')
201 tasks = self.tw.tasks.filter(entry=t['entry'])
202 self.assertEqual(list(tasks), [t])
204 def test_filter_dummy_by_description(self):
205 t = Task(self.tw, description='test')
208 tasks = self.tw.tasks.filter(description=t['description'])
209 self.assertEqual(list(tasks), [t])
211 def test_filter_dummy_by_start(self):
212 t = Task(self.tw, description='test')
216 tasks = self.tw.tasks.filter(start=t['start'])
217 self.assertEqual(list(tasks), [t])
219 def test_filter_dummy_by_end(self):
220 t = Task(self.tw, description='test')
224 tasks = self.tw.tasks.filter(end=t['end'])
225 self.assertEqual(list(tasks), [t])
227 def test_filter_dummy_by_due(self):
228 t = Task(self.tw, description='test', due=datetime.datetime.now())
231 tasks = self.tw.tasks.filter(due=t['due'])
232 self.assertEqual(list(tasks), [t])
234 def test_filter_dummy_by_until(self):
235 t = Task(self.tw, description='test')
238 tasks = self.tw.tasks.filter(until=t['until'])
239 self.assertEqual(list(tasks), [t])
241 def test_filter_dummy_by_modified(self):
242 # Older TW version does not support bumping modified
244 if self.tw.version < six.text_type('2.2.0'):
245 # Python2.6 does not support SkipTest. As a workaround
246 # mark the test as passed by exiting.
247 if getattr(unittest, 'SkipTest', None) is not None:
248 raise unittest.SkipTest()
252 t = Task(self.tw, description='test')
255 tasks = self.tw.tasks.filter(modified=t['modified'])
256 self.assertEqual(list(tasks), [t])
258 def test_filter_dummy_by_scheduled(self):
259 t = Task(self.tw, description='test')
262 tasks = self.tw.tasks.filter(scheduled=t['scheduled'])
263 self.assertEqual(list(tasks), [t])
265 def test_filter_dummy_by_tags(self):
266 t = Task(self.tw, description='test', tags=['home'])
269 tasks = self.tw.tasks.filter(tags=t['tags'])
270 self.assertEqual(list(tasks), [t])
272 def test_filter_dummy_by_projects(self):
273 t = Task(self.tw, description='test', project='random')
276 tasks = self.tw.tasks.filter(project=t['project'])
277 self.assertEqual(list(tasks), [t])
279 def test_filter_by_priority(self):
280 t = Task(self.tw, description='test', priority='H')
283 tasks = self.tw.tasks.filter(priority=t['priority'])
284 self.assertEqual(list(tasks), [t])
287 class TaskTest(TasklibTest):
289 def test_create_unsaved_task(self):
290 # Make sure a new task is not saved unless explicitly called for
291 Task(self.tw, description='test task')
292 self.assertEqual(len(self.tw.tasks.all()), 0)
294 # TODO: once python 2.6 compatibility is over, use context managers here
295 # and in all subsequent tests for assertRaises
297 def test_delete_unsaved_task(self):
298 t = Task(self.tw, description='test task')
299 self.assertRaises(Task.NotSaved, t.delete)
301 def test_complete_unsaved_task(self):
302 t = Task(self.tw, description='test task')
303 self.assertRaises(Task.NotSaved, t.done)
305 def test_refresh_unsaved_task(self):
306 t = Task(self.tw, description='test task')
307 self.assertRaises(Task.NotSaved, t.refresh)
309 def test_start_unsaved_task(self):
310 t = Task(self.tw, description='test task')
311 self.assertRaises(Task.NotSaved, t.start)
313 def test_delete_deleted_task(self):
314 t = Task(self.tw, description='test task')
318 self.assertRaises(Task.DeletedTask, t.delete)
320 def test_complete_completed_task(self):
321 t = Task(self.tw, description='test task')
325 self.assertRaises(Task.CompletedTask, t.done)
327 def test_start_completed_task(self):
328 t = Task(self.tw, description='test task')
332 self.assertRaises(Task.CompletedTask, t.start)
334 def test_add_completed_task(self):
339 end=datetime.datetime.now(),
343 def test_add_multiple_completed_tasks(self):
348 end=datetime.datetime.now(),
354 end=datetime.datetime.now(),
359 def test_complete_deleted_task(self):
360 t = Task(self.tw, description='test task')
364 self.assertRaises(Task.DeletedTask, t.done)
366 def test_starting_task(self):
367 t = Task(self.tw, description='test task')
368 now = t.datetime_normalizer(datetime.datetime.now())
372 self.assertTrue(now.replace(microsecond=0) <= t['start'])
373 self.assertEqual(t['status'], 'pending')
375 def test_completing_task(self):
376 t = Task(self.tw, description='test task')
377 now = t.datetime_normalizer(datetime.datetime.now())
381 self.assertTrue(now.replace(microsecond=0) <= t['end'])
382 self.assertEqual(t['status'], 'completed')
384 def test_deleting_task(self):
385 t = Task(self.tw, description='test task')
386 now = t.datetime_normalizer(datetime.datetime.now())
390 self.assertTrue(now.replace(microsecond=0) <= t['end'])
391 self.assertEqual(t['status'], 'deleted')
393 def test_started_task_active(self):
394 t = Task(self.tw, description='test task')
397 self.assertTrue(t.active)
399 def test_unstarted_task_inactive(self):
400 t = Task(self.tw, description='test task')
401 self.assertFalse(t.active)
403 self.assertFalse(t.active)
405 def test_start_active_task(self):
406 t = Task(self.tw, description='test task')
409 self.assertRaises(Task.ActiveTask, t.start)
411 def test_stop_completed_task(self):
412 t = Task(self.tw, description='test task')
417 self.assertRaises(Task.InactiveTask, t.stop)
419 t = Task(self.tw, description='test task')
423 self.assertRaises(Task.InactiveTask, t.stop)
425 def test_stop_deleted_task(self):
426 t = Task(self.tw, description='test task')
432 def test_stop_inactive_task(self):
433 t = Task(self.tw, description='test task')
436 self.assertRaises(Task.InactiveTask, t.stop)
438 t = Task(self.tw, description='test task')
443 self.assertRaises(Task.InactiveTask, t.stop)
445 def test_stopping_task(self):
446 t = Task(self.tw, description='test task')
447 t.datetime_normalizer(datetime.datetime.now())
452 self.assertEqual(t['end'], None)
453 self.assertEqual(t['status'], 'pending')
454 self.assertFalse(t.active)
456 def test_modify_simple_attribute_without_space(self):
457 t = Task(self.tw, description='test')
460 self.assertEqual(t['description'], 'test')
462 t['description'] = 'test-modified'
465 self.assertEqual(t['description'], 'test-modified')
467 def test_modify_simple_attribute_with_space(self):
468 # Space can pose problems with parsing
469 t = Task(self.tw, description='test task')
472 self.assertEqual(t['description'], 'test task')
474 t['description'] = 'test task modified'
477 self.assertEqual(t['description'], 'test task modified')
479 def test_empty_dependency_set_of_unsaved_task(self):
480 t = Task(self.tw, description='test task')
481 self.assertEqual(t['depends'], set())
483 def test_empty_dependency_set_of_saved_task(self):
484 t = Task(self.tw, description='test task')
486 self.assertEqual(t['depends'], set())
488 def test_set_unsaved_task_as_dependency(self):
489 # Adds only one dependency to task with no dependencies
490 t = Task(self.tw, description='test task')
491 dependency = Task(self.tw, description='needs to be done first')
493 # We only save the parent task, dependency task is unsaved
495 t['depends'] = set([dependency])
497 self.assertRaises(Task.NotSaved, t.save)
499 def test_set_simple_dependency_set(self):
500 # Adds only one dependency to task with no dependencies
501 t = Task(self.tw, description='test task')
502 dependency = Task(self.tw, description='needs to be done first')
507 t['depends'] = set([dependency])
509 self.assertEqual(t['depends'], set([dependency]))
511 def test_set_complex_dependency_set(self):
512 # Adds two dependencies to task with no dependencies
513 t = Task(self.tw, description='test task')
514 dependency1 = Task(self.tw, description='needs to be done first')
515 dependency2 = Task(self.tw, description='needs to be done second')
521 t['depends'] = set([dependency1, dependency2])
523 self.assertEqual(t['depends'], set([dependency1, dependency2]))
525 def test_remove_from_dependency_set(self):
526 # Removes dependency from task with two dependencies
527 t = Task(self.tw, description='test task')
528 dependency1 = Task(self.tw, description='needs to be done first')
529 dependency2 = Task(self.tw, description='needs to be done second')
534 t['depends'] = set([dependency1, dependency2])
537 t['depends'].remove(dependency2)
540 self.assertEqual(t['depends'], set([dependency1]))
542 def test_add_to_dependency_set(self):
543 # Adds dependency to task with one 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])
554 t['depends'].add(dependency2)
557 self.assertEqual(t['depends'], set([dependency1, dependency2]))
559 def test_add_to_empty_dependency_set(self):
560 # Adds dependency to task with one dependencies
561 t = Task(self.tw, description='test task')
562 dependency = Task(self.tw, description='needs to be done first')
566 t['depends'].add(dependency)
569 self.assertEqual(t['depends'], set([dependency]))
571 def test_simple_dependency_set_save_repeatedly(self):
572 # Adds only one dependency to task with no dependencies
573 t = Task(self.tw, description='test task')
574 dependency = Task(self.tw, description='needs to be done first')
577 t['depends'] = set([dependency])
580 # We taint the task, but keep depends intact
581 t['description'] = 'test task modified'
584 self.assertEqual(t['depends'], set([dependency]))
586 # We taint the task, but assign the same set to the depends
587 t['depends'] = set([dependency])
588 t['description'] = 'test task modified again'
591 self.assertEqual(t['depends'], set([dependency]))
593 def test_compare_different_tasks(self):
594 # Negative: compare two different tasks
595 t1 = Task(self.tw, description='test task')
596 t2 = Task(self.tw, description='test task')
601 self.assertEqual(t1 == t2, False)
603 def test_compare_same_task_object(self):
604 # Compare Task object wit itself
605 t = Task(self.tw, description='test task')
608 self.assertEqual(t == t, True)
610 def test_compare_same_task(self):
611 # Compare the same task using two different objects
612 t1 = Task(self.tw, description='test task')
615 t2 = self.tw.tasks.get(uuid=t1['uuid'])
616 self.assertEqual(t1 == t2, True)
618 def test_compare_unsaved_tasks(self):
619 # t1 and t2 are unsaved tasks, considered to be unequal
620 # despite the content of data
621 t1 = Task(self.tw, description='test task')
622 t2 = Task(self.tw, description='test task')
624 self.assertEqual(t1 == t2, False)
626 def test_hash_unsaved_tasks(self):
627 # Considered equal, it's the same object
628 t1 = Task(self.tw, description='test task')
630 self.assertEqual(hash(t1) == hash(t2), True)
632 def test_hash_same_task(self):
633 # Compare the hash of the task using two different objects
634 t1 = Task(self.tw, description='test task')
637 t2 = self.tw.tasks.get(uuid=t1['uuid'])
638 self.assertEqual(t1.__hash__(), t2.__hash__())
640 def test_hash_unequal_unsaved_tasks(self):
641 # Compare the hash of the task using two different objects
642 t1 = Task(self.tw, description='test task 1')
643 t2 = Task(self.tw, description='test task 2')
645 self.assertNotEqual(t1.__hash__(), t2.__hash__())
647 def test_hash_unequal_saved_tasks(self):
648 # Compare the hash of the task using two different objects
649 t1 = Task(self.tw, description='test task 1')
650 t2 = Task(self.tw, description='test task 2')
655 self.assertNotEqual(t1.__hash__(), t2.__hash__())
657 def test_adding_task_with_priority(self):
658 t = Task(self.tw, description='test task', priority='M')
661 def test_removing_priority_with_none(self):
662 t = Task(self.tw, description='test task', priority='L')
665 # Remove the priority mark
669 # Assert that priority is not there after saving
670 self.assertEqual(t['priority'], None)
672 def test_adding_task_with_due_time(self):
673 t = Task(self.tw, description='test task', due=datetime.datetime.now())
676 def test_removing_due_time_with_none(self):
677 t = Task(self.tw, description='test task', due=datetime.datetime.now())
680 # Remove the due timestamp
684 # Assert that due timestamp is no longer there
685 self.assertEqual(t['due'], None)
687 def test_modified_fields_new_task(self):
690 # This should be empty with new task
691 self.assertEqual(set(t._modified_fields), set())
694 t['description'] = 'test task'
695 self.assertEqual(set(t._modified_fields), set(['description']))
697 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
698 self.assertEqual(set(t._modified_fields), set(['description', 'due']))
700 t['project'] = 'test project'
702 set(t._modified_fields),
703 set(['description', 'due', 'project']),
706 # List of modified fields should clear out when saved
708 self.assertEqual(set(t._modified_fields), set())
710 # Reassigning the fields with the same values now should not produce
712 t['description'] = 'test task'
713 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
714 t['project'] = 'test project'
715 self.assertEqual(set(t._modified_fields), set())
717 def test_modified_fields_loaded_task(self):
721 t['description'] = 'test task'
722 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
723 t['project'] = 'test project'
725 dependency = Task(self.tw, description='dependency')
727 t['depends'] = set([dependency])
729 # List of modified fields should clear out when saved
731 self.assertEqual(set(t._modified_fields), set())
733 # Get the task by using a filter by UUID
734 self.tw.tasks.get(uuid=t['uuid'])
736 # Reassigning the fields with the same values now should not produce
738 t['description'] = 'test task'
739 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
740 t['project'] = 'test project'
741 t['depends'] = set([dependency])
742 self.assertEqual(set(t._modified_fields), set())
744 def test_modified_fields_not_affected_by_reading(self):
747 for field in TASK_STANDARD_ATTRS:
750 self.assertEqual(set(t._modified_fields), set())
752 def test_setting_read_only_attrs_through_init(self):
753 # Test that we are unable to set readonly attrs through __init__
754 for readonly_key in Task.read_only_fields:
755 kwargs = {'description': 'test task', readonly_key: 'value'}
758 lambda: Task(self.tw, **kwargs),
761 def test_setting_read_only_attrs_through_setitem(self):
762 # Test that we are unable to set readonly attrs through __init__
763 for readonly_key in Task.read_only_fields:
764 t = Task(self.tw, description='test task')
767 lambda: t.__setitem__(readonly_key, 'value'),
770 def test_saving_unmodified_task(self):
771 t = Task(self.tw, description='test task')
775 def test_adding_tag_by_appending(self):
776 t = Task(self.tw, description='test task', tags=['test1'])
778 t['tags'].add('test2')
780 self.assertEqual(t['tags'], set(['test1', 'test2']))
782 def test_adding_tag_twice(self):
783 t = Task(self.tw, description='test task', tags=['test1'])
785 t['tags'].add('test2')
786 t['tags'].add('test2')
788 self.assertEqual(t['tags'], set(['test1', 'test2']))
790 def test_adding_tag_by_appending_empty(self):
791 t = Task(self.tw, description='test task')
793 t['tags'].add('test')
795 self.assertEqual(t['tags'], set(['test']))
797 def test_serializers_returning_empty_string_for_none(self):
798 # Test that any serializer returns '' when passed None
801 getattr(t, serializer_name)
802 for serializer_name in filter(
803 lambda x: x.startswith('serialize_'),
807 for serializer in serializers:
808 self.assertEqual(serializer(None), '')
810 def test_deserializer_returning_empty_value_for_empty_string(self):
811 # Test that any deserializer returns empty value when passed ''
814 getattr(t, deserializer_name)
815 for deserializer_name in filter(
816 lambda x: x.startswith('deserialize_'),
820 for deserializer in deserializers:
821 self.assertTrue(deserializer('') in (None, [], set()))
823 def test_normalizers_handling_none(self):
824 # Test that any normalizer can handle None as a valid value
827 for key in TASK_STANDARD_ATTRS:
828 t._normalize(key, None)
830 def test_recurrent_task_generation(self):
831 today = datetime.date.today()
834 description='brush teeth',
839 self.assertEqual(len(self.tw.tasks.pending()), 2)
841 def test_spawned_task_parent(self):
842 today = datetime.date.today()
845 description='brush teeth',
851 spawned = self.tw.tasks.pending().get(due=today)
852 assert spawned['parent'] == t
854 def test_modify_number_of_tasks_at_once(self):
855 for i in range(1, 100):
856 Task(self.tw, description='test task %d' % i, tags=['test']).save()
858 self.tw.execute_command(['+test', 'mod', 'unified', 'description'])
860 def test_return_all_from_executed_command(self):
861 Task(self.tw, description='test task', tags=['test']).save()
862 out, err, rc = self.tw.execute_command(['count'], return_all=True)
863 self.assertEqual(rc, 0)
865 def test_return_all_from_failed_executed_command(self):
866 Task(self.tw, description='test task', tags=['test']).save()
867 out, err, rc = self.tw.execute_command(
872 self.assertNotEqual(rc, 0)
875 class TaskFromHookTest(TasklibTest):
877 input_add_data = six.StringIO(
878 '{"description":"Buy some milk",'
879 '"entry":"20141118T050231Z",'
880 '"status":"pending",'
881 '"start":"20141119T152233Z",'
882 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}',
885 input_add_data_recurring = six.StringIO(
886 '{"description":"Mow the lawn",'
887 '"entry":"20160210T224304Z",'
888 '"parent":"62da6227-519c-42c2-915d-dccada926ad7",'
890 '"status":"pending",'
891 '"uuid":"81305335-0237-49ff-8e87-b3cdc2369cec"}',
894 input_modify_data = six.StringIO(
896 input_add_data.getvalue(),
898 '{"description":"Buy some milk finally",'
899 '"entry":"20141118T050231Z",'
900 '"status":"completed",'
901 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}'
906 exported_raw_data = (
908 '"due":"20150101T232323Z",'
909 '"description":"test task"}'
912 def test_setting_up_from_add_hook_input(self):
913 t = Task.from_input(input_file=self.input_add_data, backend=self.tw)
914 self.assertEqual(t['description'], 'Buy some milk')
915 self.assertEqual(t.pending, True)
917 def test_setting_up_from_add_hook_input_recurring(self):
919 input_file=self.input_add_data_recurring,
922 self.assertEqual(t['description'], 'Mow the lawn')
923 self.assertEqual(t.pending, True)
925 def test_setting_up_from_modified_hook_input(self):
927 input_file=self.input_modify_data,
931 self.assertEqual(t['description'], 'Buy some milk finally')
932 self.assertEqual(t.pending, False)
933 self.assertEqual(t.completed, True)
935 self.assertEqual(t._original_data['status'], 'pending')
936 self.assertEqual(t._original_data['description'], 'Buy some milk')
938 set(t._modified_fields),
939 set(['status', 'description', 'start']),
942 def test_export_data(self):
945 description='test task',
947 due=pytz.utc.localize(
948 datetime.datetime(2015, 1, 1, 23, 23, 23)),
951 # Check that the output is a permutation of:
952 # {"project":"Home","description":"test task","due":"20150101232323Z"}
953 allowed_segments = self.exported_raw_data[1:-1].split(',')
955 '{' + ','.join(segments) + '}'
956 for segments in itertools.permutations(allowed_segments)
960 any(t.export_data() == expected
961 for expected in allowed_output),
965 class TimezoneAwareDatetimeTest(TasklibTest):
968 super(TimezoneAwareDatetimeTest, self).setUp()
969 self.zone = local_zone
970 self.localdate_naive = datetime.datetime(2015, 2, 2)
971 self.localtime_naive = datetime.datetime(2015, 2, 2, 0, 0, 0)
972 self.localtime_aware = self.zone.localize(self.localtime_naive)
973 self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
975 def test_timezone_naive_datetime_setitem(self):
976 t = Task(self.tw, description='test task')
977 t['due'] = self.localtime_naive
978 self.assertEqual(t['due'], self.localtime_aware)
980 def test_timezone_naive_datetime_using_init(self):
981 t = Task(self.tw, description='test task', due=self.localtime_naive)
982 self.assertEqual(t['due'], self.localtime_aware)
984 def test_filter_by_naive_datetime(self):
985 t = Task(self.tw, description='task1', due=self.localtime_naive)
987 matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
988 self.assertEqual(len(matching_tasks), 1)
990 def test_serialize_naive_datetime(self):
991 t = Task(self.tw, description='task1', due=self.localtime_naive)
993 json.loads(t.export_data())['due'],
994 self.utctime_aware.strftime(DATE_FORMAT),
997 def test_timezone_naive_date_setitem(self):
998 t = Task(self.tw, description='test task')
999 t['due'] = self.localdate_naive
1000 self.assertEqual(t['due'], self.localtime_aware)
1002 def test_timezone_naive_date_using_init(self):
1003 t = Task(self.tw, description='test task', due=self.localdate_naive)
1004 self.assertEqual(t['due'], self.localtime_aware)
1006 def test_filter_by_naive_date(self):
1007 t = Task(self.tw, description='task1', due=self.localdate_naive)
1009 matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
1010 self.assertEqual(len(matching_tasks), 1)
1012 def test_serialize_naive_date(self):
1013 t = Task(self.tw, description='task1', due=self.localdate_naive)
1015 json.loads(t.export_data())['due'],
1016 self.utctime_aware.strftime(DATE_FORMAT),
1019 def test_timezone_aware_datetime_setitem(self):
1020 t = Task(self.tw, description='test task')
1021 t['due'] = self.localtime_aware
1022 self.assertEqual(t['due'], self.localtime_aware)
1024 def test_timezone_aware_datetime_using_init(self):
1025 t = Task(self.tw, description='test task', due=self.localtime_aware)
1026 self.assertEqual(t['due'], self.localtime_aware)
1028 def test_filter_by_aware_datetime(self):
1029 t = Task(self.tw, description='task1', due=self.localtime_aware)
1031 matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
1032 self.assertEqual(len(matching_tasks), 1)
1034 def test_serialize_aware_datetime(self):
1035 t = Task(self.tw, description='task1', due=self.localtime_aware)
1037 json.loads(t.export_data())['due'],
1038 self.utctime_aware.strftime(DATE_FORMAT),
1042 class DatetimeStringTest(TasklibTest):
1044 def test_simple_now_conversion(self):
1045 if self.tw.version < six.text_type('2.4.0'):
1046 # Python2.6 does not support SkipTest. As a workaround
1047 # mark the test as passed by exiting.
1048 if getattr(unittest, 'SkipTest', None) is not None:
1049 raise unittest.SkipTest()
1053 t = Task(self.tw, description='test task', due='now')
1054 now = local_zone.localize(datetime.datetime.now())
1056 # Assert that both times are not more than 5 seconds apart
1057 if sys.version_info < (2, 7):
1058 self.assertTrue(total_seconds_2_6(now - t['due']) < 5)
1059 self.assertTrue(total_seconds_2_6(t['due'] - now) < 5)
1061 self.assertTrue((now - t['due']).total_seconds() < 5)
1062 self.assertTrue((t['due'] - now).total_seconds() < 5)
1064 def test_simple_eoy_conversion(self):
1065 if self.tw.version < six.text_type('2.4.0'):
1066 # Python2.6 does not support SkipTest. As a workaround
1067 # mark the test as passed by exiting.
1068 if getattr(unittest, 'SkipTest', None) is not None:
1069 raise unittest.SkipTest()
1073 t = Task(self.tw, description='test task', due='eoy')
1074 now = local_zone.localize(datetime.datetime.now())
1075 eoy = local_zone.localize(datetime.datetime(
1083 self.assertEqual(eoy, t['due'])
1085 def test_complex_eoy_conversion(self):
1086 if self.tw.version < six.text_type('2.4.0'):
1087 # Python2.6 does not support SkipTest. As a workaround
1088 # mark the test as passed by exiting.
1089 if getattr(unittest, 'SkipTest', None) is not None:
1090 raise unittest.SkipTest()
1094 t = Task(self.tw, description='test task', due='eoy - 4 months')
1095 now = local_zone.localize(datetime.datetime.now())
1096 due_date = local_zone.localize(
1105 ) - datetime.timedelta(0, 4 * 30 * 86400)
1106 self.assertEqual(due_date, t['due'])
1108 def test_filtering_with_string_datetime(self):
1109 if self.tw.version < six.text_type('2.4.0'):
1110 # Python2.6 does not support SkipTest. As a workaround
1111 # mark the test as passed by exiting.
1112 if getattr(unittest, 'SkipTest', None) is not None:
1113 raise unittest.SkipTest()
1119 description='test task',
1120 due=datetime.datetime.now() - datetime.timedelta(0, 2),
1123 self.assertEqual(len(self.tw.tasks.filter(due__before='now')), 1)
1126 class AnnotationTest(TasklibTest):
1129 super(AnnotationTest, self).setUp()
1130 Task(self.tw, description='test task').save()
1132 def test_adding_annotation(self):
1133 task = self.tw.tasks.get()
1134 task.add_annotation('test annotation')
1135 self.assertEqual(len(task['annotations']), 1)
1136 ann = task['annotations'][0]
1137 self.assertEqual(ann['description'], 'test annotation')
1139 def test_removing_annotation(self):
1140 task = self.tw.tasks.get()
1141 task.add_annotation('test annotation')
1142 ann = task['annotations'][0]
1144 self.assertEqual(len(task['annotations']), 0)
1146 def test_removing_annotation_by_description(self):
1147 task = self.tw.tasks.get()
1148 task.add_annotation('test annotation')
1149 task.remove_annotation('test annotation')
1150 self.assertEqual(len(task['annotations']), 0)
1152 def test_removing_annotation_by_obj(self):
1153 task = self.tw.tasks.get()
1154 task.add_annotation('test annotation')
1155 ann = task['annotations'][0]
1156 task.remove_annotation(ann)
1157 self.assertEqual(len(task['annotations']), 0)
1159 def test_annotation_after_modification(self):
1160 task = self.tw.tasks.get()
1161 task['project'] = 'test'
1162 task.add_annotation('I should really do this task')
1163 self.assertEqual(task['project'], 'test')
1165 self.assertEqual(task['project'], 'test')
1167 def test_serialize_annotations(self):
1168 # Test that serializing annotations is possible
1169 t = Task(self.tw, description='test')
1172 t.add_annotation('annotation1')
1173 t.add_annotation('annotation2')
1175 data = t._serialize('annotations', t._data['annotations'])
1177 self.assertEqual(len(data), 2)
1178 self.assertEqual(type(data[0]), dict)
1179 self.assertEqual(type(data[1]), dict)
1181 self.assertEqual(data[0]['description'], 'annotation1')
1182 self.assertEqual(data[1]['description'], 'annotation2')
1185 class UnicodeTest(TasklibTest):
1187 def test_unicode_task(self):
1188 Task(self.tw, description=six.u('†åßk')).save()
1191 def test_filter_by_unicode_task(self):
1192 Task(self.tw, description=six.u('†åßk')).save()
1193 tasks = self.tw.tasks.filter(description=six.u('†åßk'))
1194 self.assertEqual(len(tasks), 1)
1196 def test_non_unicode_task(self):
1197 Task(self.tw, description='test task').save()
1201 class ReadOnlyDictViewTest(unittest.TestCase):
1204 self.sample = dict(sample_list=[1, 2, 3], sample_dict={'key': 'value'})
1205 self.original_sample = copy.deepcopy(self.sample)
1206 self.view = ReadOnlyDictView(self.sample)
1208 def test_readonlydictview_getitem(self):
1209 sample_list = self.view['sample_list']
1210 self.assertEqual(sample_list, self.sample['sample_list'])
1212 # Assert that modification changed only copied value
1213 sample_list.append(4)
1214 self.assertNotEqual(sample_list, self.sample['sample_list'])
1216 # Assert that viewed dict is not changed
1217 self.assertEqual(self.sample, self.original_sample)
1219 def test_readonlydictview_contains(self):
1220 self.assertEqual('sample_list' in self.view,
1221 'sample_list' in self.sample)
1222 self.assertEqual('sample_dict' in self.view,
1223 'sample_dict' in self.sample)
1224 self.assertEqual('key' in self.view, 'key' in self.sample)
1226 # Assert that viewed dict is not changed
1227 self.assertEqual(self.sample, self.original_sample)
1229 def test_readonlydictview_iter(self):
1231 list(key for key in self.view),
1232 list(key for key in self.sample),
1235 # Assert the view is correct after modification
1236 self.sample['new'] = 'value'
1238 list(key for key in self.view),
1239 list(key for key in self.sample),
1242 def test_readonlydictview_len(self):
1243 self.assertEqual(len(self.view), len(self.sample))
1245 # Assert the view is correct after modification
1246 self.sample['new'] = 'value'
1247 self.assertEqual(len(self.view), len(self.sample))
1249 def test_readonlydictview_get(self):
1250 sample_list = self.view.get('sample_list')
1251 self.assertEqual(sample_list, self.sample.get('sample_list'))
1253 # Assert that modification changed only copied value
1254 sample_list.append(4)
1255 self.assertNotEqual(sample_list, self.sample.get('sample_list'))
1257 # Assert that viewed dict is not changed
1258 self.assertEqual(self.sample, self.original_sample)
1260 def test_readonlydict_items(self):
1261 view_items = self.view.items()
1262 sample_items = list(self.sample.items())
1263 self.assertEqual(view_items, sample_items)
1265 view_items.append('newkey')
1266 self.assertNotEqual(view_items, sample_items)
1267 self.assertEqual(self.sample, self.original_sample)
1269 def test_readonlydict_values(self):
1270 view_values = self.view.values()
1271 sample_values = list(self.sample.values())
1272 self.assertEqual(view_values, sample_values)
1274 view_list_item = list(filter(lambda x: type(x) is list,
1276 view_list_item.append(4)
1277 self.assertNotEqual(view_values, sample_values)
1278 self.assertEqual(self.sample, self.original_sample)
1281 class LazyUUIDTaskTest(TasklibTest):
1284 super(LazyUUIDTaskTest, self).setUp()
1286 self.stored = Task(self.tw, description='this is test task')
1289 self.lazy = LazyUUIDTask(self.tw, self.stored['uuid'])
1291 def test_uuid_non_conversion(self):
1292 assert self.stored['uuid'] == self.lazy['uuid']
1293 assert type(self.lazy) is LazyUUIDTask
1295 def test_lazy_explicit_conversion(self):
1296 assert type(self.lazy) is LazyUUIDTask
1298 assert type(self.lazy) is Task
1300 def test_conversion_key(self):
1301 assert self.stored['description'] == self.lazy['description']
1302 assert type(self.lazy) is Task
1304 def test_conversion_attribute(self):
1305 assert type(self.lazy) is LazyUUIDTask
1306 assert self.lazy.completed is False
1307 assert type(self.lazy) is Task
1309 def test_normal_to_lazy_equality(self):
1310 assert self.stored == self.lazy
1311 assert not self.stored != self.lazy
1312 assert type(self.lazy) is LazyUUIDTask
1314 def test_lazy_to_lazy_equality(self):
1315 lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
1316 lazy2 = LazyUUIDTask(self.tw, self.stored['uuid'])
1318 assert lazy1 == lazy2
1319 assert not lazy1 != lazy2
1320 assert type(lazy1) is LazyUUIDTask
1321 assert type(lazy2) is LazyUUIDTask
1323 def test_normal_to_lazy_inequality(self):
1324 # Create a different UUID by changing the last letter
1325 wrong_uuid = self.stored['uuid']
1326 wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
1328 wrong_lazy = LazyUUIDTask(self.tw, wrong_uuid)
1330 assert not self.stored == wrong_lazy
1331 assert self.stored != wrong_lazy
1332 assert type(wrong_lazy) is LazyUUIDTask
1334 def test_lazy_to_lazy_inequality(self):
1335 # Create a different UUID by changing the last letter
1336 wrong_uuid = self.stored['uuid']
1337 wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
1339 lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
1340 lazy2 = LazyUUIDTask(self.tw, wrong_uuid)
1342 assert not lazy1 == lazy2
1343 assert lazy1 != lazy2
1344 assert type(lazy1) is LazyUUIDTask
1345 assert type(lazy2) is LazyUUIDTask
1347 def test_lazy_in_queryset(self):
1348 tasks = self.tw.tasks.filter(uuid=self.stored['uuid'])
1350 assert self.lazy in tasks
1351 assert type(self.lazy) is LazyUUIDTask
1353 def test_lazy_saved(self):
1354 assert self.lazy.saved is True
1356 def test_lazy_modified(self):
1357 assert self.lazy.modified is False
1359 def test_lazy_modified_fields(self):
1360 assert self.lazy._modified_fields == set()
1363 class LazyUUIDTaskSetTest(TasklibTest):
1366 super(LazyUUIDTaskSetTest, self).setUp()
1368 self.task1 = Task(self.tw, description='task 1')
1369 self.task2 = Task(self.tw, description='task 2')
1370 self.task3 = Task(self.tw, description='task 3')
1382 self.lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1384 def test_length(self):
1385 assert len(self.lazy) == 3
1386 assert type(self.lazy) is LazyUUIDTaskSet
1388 def test_contains(self):
1389 assert self.task1 in self.lazy
1390 assert self.task2 in self.lazy
1391 assert self.task3 in self.lazy
1392 assert type(self.lazy) is LazyUUIDTaskSet
1394 def test_eq_lazy(self):
1395 new_lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1396 assert self.lazy == new_lazy
1397 assert not self.lazy != new_lazy
1398 assert type(self.lazy) is LazyUUIDTaskSet
1400 def test_eq_real(self):
1401 assert self.lazy == self.tw.tasks.all()
1402 assert self.tw.tasks.all() == self.lazy
1403 assert not self.lazy != self.tw.tasks.all()
1405 assert type(self.lazy) is LazyUUIDTaskSet
1407 def test_union(self):
1408 taskset = set([self.task1])
1409 lazyset = LazyUUIDTaskSet(
1411 (self.task2['uuid'], self.task3['uuid']),
1414 assert taskset | lazyset == self.lazy
1415 assert lazyset | taskset == self.lazy
1416 assert taskset.union(lazyset) == self.lazy
1417 assert lazyset.union(taskset) == self.lazy
1420 assert lazyset == self.lazy
1422 def test_difference(self):
1423 taskset = set([self.task1, self.task2])
1424 lazyset = LazyUUIDTaskSet(
1426 (self.task2['uuid'], self.task3['uuid']),
1429 assert taskset - lazyset == set([self.task1])
1430 assert lazyset - taskset == set([self.task3])
1431 assert taskset.difference(lazyset) == set([self.task1])
1432 assert lazyset.difference(taskset) == set([self.task3])
1435 assert lazyset == set([self.task3])
1437 def test_symmetric_difference(self):
1438 taskset = set([self.task1, self.task2])
1439 lazyset = LazyUUIDTaskSet(
1441 (self.task2['uuid'], self.task3['uuid']),
1444 assert taskset ^ lazyset == set([self.task1, self.task3])
1445 assert lazyset ^ taskset == set([self.task1, self.task3])
1447 taskset.symmetric_difference(lazyset),
1448 set([self.task1, self.task3]),
1451 lazyset.symmetric_difference(taskset),
1452 set([self.task1, self.task3]),
1456 assert lazyset == set([self.task1, self.task3])
1458 def test_intersection(self):
1459 taskset = set([self.task1, self.task2])
1460 lazyset = LazyUUIDTaskSet(
1462 (self.task2['uuid'], self.task3['uuid']),
1465 assert taskset & lazyset == set([self.task2])
1466 assert lazyset & taskset == set([self.task2])
1467 assert taskset.intersection(lazyset) == set([self.task2])
1468 assert lazyset.intersection(taskset) == set([self.task2])
1471 assert lazyset == set([self.task2])
1474 class TaskWarriorBackendTest(TasklibTest):
1476 def test_config(self):
1477 assert self.tw.config['nag'] == 'You have more urgent tasks.'
1478 assert self.tw.config['default.command'] == 'next'
1479 assert self.tw.config['dependency.indicator'] == 'D'