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, TaskQuerySet
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 = (
43 total_seconds_2_6 = lambda x: x.microseconds / 1e6 + x.seconds + x.days * 24 * 3600
46 class TasklibTest(unittest.TestCase):
49 self.tmp = tempfile.mkdtemp(dir='.')
50 self.tw = TaskWarrior(data_location=self.tmp, taskrc_location='/')
53 shutil.rmtree(self.tmp)
56 class TaskFilterTest(TasklibTest):
58 def test_all_empty(self):
59 self.assertEqual(len(self.tw.tasks.all()), 0)
61 def test_all_non_empty(self):
62 Task(self.tw, description="test task").save()
63 self.assertEqual(len(self.tw.tasks.all()), 1)
64 self.assertEqual(self.tw.tasks.all()[0]['description'], 'test task')
65 self.assertEqual(self.tw.tasks.all()[0]['status'], 'pending')
67 def test_pending_non_empty(self):
68 Task(self.tw, description="test task").save()
69 self.assertEqual(len(self.tw.tasks.pending()), 1)
70 self.assertEqual(self.tw.tasks.pending()[0]['description'],
72 self.assertEqual(self.tw.tasks.pending()[0]['status'], 'pending')
74 def test_completed_empty(self):
75 Task(self.tw, description="test task").save()
76 self.assertEqual(len(self.tw.tasks.completed()), 0)
78 def test_completed_non_empty(self):
79 Task(self.tw, description="test task").save()
80 self.assertEqual(len(self.tw.tasks.completed()), 0)
81 self.tw.tasks.all()[0].done()
82 self.assertEqual(len(self.tw.tasks.completed()), 1)
84 def test_filtering_by_attribute(self):
85 Task(self.tw, description="no priority task").save()
86 Task(self.tw, priority="H", description="high priority task").save()
87 self.assertEqual(len(self.tw.tasks.all()), 2)
89 # Assert that the correct number of tasks is returned
90 self.assertEqual(len(self.tw.tasks.filter(priority="H")), 1)
92 # Assert that the correct tasks are returned
93 high_priority_task = self.tw.tasks.get(priority="H")
94 self.assertEqual(high_priority_task['description'], "high priority task")
96 def test_filtering_by_empty_attribute(self):
97 Task(self.tw, description="no priority task").save()
98 Task(self.tw, priority="H", description="high priority task").save()
99 self.assertEqual(len(self.tw.tasks.all()), 2)
101 # Assert that the correct number of tasks is returned
102 self.assertEqual(len(self.tw.tasks.filter(priority=None)), 1)
104 # Assert that the correct tasks are returned
105 no_priority_task = self.tw.tasks.get(priority=None)
106 self.assertEqual(no_priority_task['description'], "no priority task")
108 def test_filter_for_task_with_space_in_descripition(self):
109 task = Task(self.tw, description="test task")
112 filtered_task = self.tw.tasks.get(description="test task")
113 self.assertEqual(filtered_task['description'], "test task")
115 def test_filter_for_task_without_space_in_descripition(self):
116 task = Task(self.tw, description="test")
119 filtered_task = self.tw.tasks.get(description="test")
120 self.assertEqual(filtered_task['description'], "test")
122 def test_filter_for_task_with_space_in_project(self):
123 task = Task(self.tw, description="test", project="random project")
126 filtered_task = self.tw.tasks.get(project="random project")
127 self.assertEqual(filtered_task['project'], "random project")
129 def test_filter_for_task_without_space_in_project(self):
130 task = Task(self.tw, description="test", project="random")
133 filtered_task = self.tw.tasks.get(project="random")
134 self.assertEqual(filtered_task['project'], "random")
136 def test_filter_with_empty_uuid(self):
137 self.assertRaises(ValueError, lambda: self.tw.tasks.get(uuid=''))
139 def test_filter_dummy_by_status(self):
140 t = Task(self.tw, description="test")
143 tasks = self.tw.tasks.filter(status=t['status'])
144 self.assertEqual(list(tasks), [t])
146 def test_filter_dummy_by_uuid(self):
147 t = Task(self.tw, description="test")
150 tasks = self.tw.tasks.filter(uuid=t['uuid'])
151 self.assertEqual(list(tasks), [t])
153 def test_filter_dummy_by_entry(self):
154 t = Task(self.tw, description="test")
157 tasks = self.tw.tasks.filter(entry=t['entry'])
158 self.assertEqual(list(tasks), [t])
160 def test_filter_dummy_by_description(self):
161 t = Task(self.tw, description="test")
164 tasks = self.tw.tasks.filter(description=t['description'])
165 self.assertEqual(list(tasks), [t])
167 def test_filter_dummy_by_start(self):
168 t = Task(self.tw, description="test")
172 tasks = self.tw.tasks.filter(start=t['start'])
173 self.assertEqual(list(tasks), [t])
175 def test_filter_dummy_by_end(self):
176 t = Task(self.tw, description="test")
180 tasks = self.tw.tasks.filter(end=t['end'])
181 self.assertEqual(list(tasks), [t])
183 def test_filter_dummy_by_due(self):
184 t = Task(self.tw, description="test", due=datetime.datetime.now())
187 tasks = self.tw.tasks.filter(due=t['due'])
188 self.assertEqual(list(tasks), [t])
190 def test_filter_dummy_by_until(self):
191 t = Task(self.tw, description="test")
194 tasks = self.tw.tasks.filter(until=t['until'])
195 self.assertEqual(list(tasks), [t])
197 def test_filter_dummy_by_modified(self):
198 # Older TW version does not support bumping modified
200 if self.tw.version < six.text_type('2.2.0'):
201 # Python2.6 does not support SkipTest. As a workaround
202 # mark the test as passed by exiting.
203 if getattr(unittest, 'SkipTest', None) is not None:
204 raise unittest.SkipTest()
208 t = Task(self.tw, description="test")
211 tasks = self.tw.tasks.filter(modified=t['modified'])
212 self.assertEqual(list(tasks), [t])
214 def test_filter_dummy_by_scheduled(self):
215 t = Task(self.tw, description="test")
218 tasks = self.tw.tasks.filter(scheduled=t['scheduled'])
219 self.assertEqual(list(tasks), [t])
221 def test_filter_dummy_by_tags(self):
222 t = Task(self.tw, description="test", tags=["home"])
225 tasks = self.tw.tasks.filter(tags=t['tags'])
226 self.assertEqual(list(tasks), [t])
228 def test_filter_dummy_by_projects(self):
229 t = Task(self.tw, description="test", project="random")
232 tasks = self.tw.tasks.filter(project=t['project'])
233 self.assertEqual(list(tasks), [t])
235 def test_filter_by_priority(self):
236 t = Task(self.tw, description="test", priority="H")
239 tasks = self.tw.tasks.filter(priority=t['priority'])
240 self.assertEqual(list(tasks), [t])
243 class TaskTest(TasklibTest):
245 def test_create_unsaved_task(self):
246 # Make sure a new task is not saved unless explicitly called for
247 t = Task(self.tw, description="test task")
248 self.assertEqual(len(self.tw.tasks.all()), 0)
250 # TODO: once python 2.6 compatiblity is over, use context managers here
251 # and in all subsequent tests for assertRaises
253 def test_delete_unsaved_task(self):
254 t = Task(self.tw, description="test task")
255 self.assertRaises(Task.NotSaved, t.delete)
257 def test_complete_unsaved_task(self):
258 t = Task(self.tw, description="test task")
259 self.assertRaises(Task.NotSaved, t.done)
261 def test_refresh_unsaved_task(self):
262 t = Task(self.tw, description="test task")
263 self.assertRaises(Task.NotSaved, t.refresh)
265 def test_start_unsaved_task(self):
266 t = Task(self.tw, description="test task")
267 self.assertRaises(Task.NotSaved, t.start)
269 def test_delete_deleted_task(self):
270 t = Task(self.tw, description="test task")
274 self.assertRaises(Task.DeletedTask, t.delete)
276 def test_complete_completed_task(self):
277 t = Task(self.tw, description="test task")
281 self.assertRaises(Task.CompletedTask, t.done)
283 def test_start_completed_task(self):
284 t = Task(self.tw, description="test task")
288 self.assertRaises(Task.CompletedTask, t.start)
290 def test_add_completed_task(self):
291 t = Task(self.tw, description="test", status="completed",
292 end=datetime.datetime.now())
295 def test_add_multiple_completed_tasks(self):
296 t1 = Task(self.tw, description="test1", status="completed",
297 end=datetime.datetime.now())
298 t2 = Task(self.tw, description="test2", status="completed",
299 end=datetime.datetime.now())
303 def test_complete_deleted_task(self):
304 t = Task(self.tw, description="test task")
308 self.assertRaises(Task.DeletedTask, t.done)
310 def test_starting_task(self):
311 t = Task(self.tw, description="test task")
312 now = t.datetime_normalizer(datetime.datetime.now())
316 self.assertTrue(now.replace(microsecond=0) <= t['start'])
317 self.assertEqual(t['status'], 'pending')
319 def test_completing_task(self):
320 t = Task(self.tw, description="test task")
321 now = t.datetime_normalizer(datetime.datetime.now())
325 self.assertTrue(now.replace(microsecond=0) <= t['end'])
326 self.assertEqual(t['status'], 'completed')
328 def test_deleting_task(self):
329 t = Task(self.tw, description="test task")
330 now = t.datetime_normalizer(datetime.datetime.now())
334 self.assertTrue(now.replace(microsecond=0) <= t['end'])
335 self.assertEqual(t['status'], 'deleted')
337 def test_started_task_active(self):
338 t = Task(self.tw, description="test task")
341 self.assertTrue(t.active)
343 def test_unstarted_task_inactive(self):
344 t = Task(self.tw, description="test task")
345 self.assertFalse(t.active)
347 self.assertFalse(t.active)
349 def test_start_active_task(self):
350 t = Task(self.tw, description="test task")
353 self.assertRaises(Task.ActiveTask, t.start)
355 def test_stop_completed_task(self):
356 t = Task(self.tw, description="test task")
361 self.assertRaises(Task.InactiveTask, t.stop)
363 t = Task(self.tw, description="test task")
367 self.assertRaises(Task.InactiveTask, t.stop)
369 def test_stop_deleted_task(self):
370 t = Task(self.tw, description="test task")
376 def test_stop_inactive_task(self):
377 t = Task(self.tw, description="test task")
380 self.assertRaises(Task.InactiveTask, t.stop)
382 t = Task(self.tw, description="test task")
387 self.assertRaises(Task.InactiveTask, t.stop)
389 def test_stopping_task(self):
390 t = Task(self.tw, description="test task")
391 now = t.datetime_normalizer(datetime.datetime.now())
396 self.assertEqual(t['end'], None)
397 self.assertEqual(t['status'], 'pending')
398 self.assertFalse(t.active)
400 def test_modify_simple_attribute_without_space(self):
401 t = Task(self.tw, description="test")
404 self.assertEquals(t['description'], "test")
406 t['description'] = "test-modified"
409 self.assertEquals(t['description'], "test-modified")
411 def test_modify_simple_attribute_with_space(self):
412 # Space can pose problems with parsing
413 t = Task(self.tw, description="test task")
416 self.assertEquals(t['description'], "test task")
418 t['description'] = "test task modified"
421 self.assertEquals(t['description'], "test task modified")
423 def test_empty_dependency_set_of_unsaved_task(self):
424 t = Task(self.tw, description="test task")
425 self.assertEqual(t['depends'], set())
427 def test_empty_dependency_set_of_saved_task(self):
428 t = Task(self.tw, description="test task")
430 self.assertEqual(t['depends'], set())
432 def test_set_unsaved_task_as_dependency(self):
433 # Adds only one dependency to task with no dependencies
434 t = Task(self.tw, description="test task")
435 dependency = Task(self.tw, description="needs to be done first")
437 # We only save the parent task, dependency task is unsaved
439 t['depends'] = set([dependency])
441 self.assertRaises(Task.NotSaved, t.save)
443 def test_set_simple_dependency_set(self):
444 # Adds only one dependency to task with no dependencies
445 t = Task(self.tw, description="test task")
446 dependency = Task(self.tw, description="needs to be done first")
451 t['depends'] = set([dependency])
453 self.assertEqual(t['depends'], set([dependency]))
455 def test_set_complex_dependency_set(self):
456 # Adds two dependencies to task with no dependencies
457 t = Task(self.tw, description="test task")
458 dependency1 = Task(self.tw, description="needs to be done first")
459 dependency2 = Task(self.tw, description="needs to be done second")
465 t['depends'] = set([dependency1, dependency2])
467 self.assertEqual(t['depends'], set([dependency1, dependency2]))
469 def test_remove_from_dependency_set(self):
470 # Removes dependency from task with two dependencies
471 t = Task(self.tw, description="test task")
472 dependency1 = Task(self.tw, description="needs to be done first")
473 dependency2 = Task(self.tw, description="needs to be done second")
478 t['depends'] = set([dependency1, dependency2])
481 t['depends'].remove(dependency2)
484 self.assertEqual(t['depends'], set([dependency1]))
486 def test_add_to_dependency_set(self):
487 # Adds dependency to task with one dependencies
488 t = Task(self.tw, description="test task")
489 dependency1 = Task(self.tw, description="needs to be done first")
490 dependency2 = Task(self.tw, description="needs to be done second")
495 t['depends'] = set([dependency1])
498 t['depends'].add(dependency2)
501 self.assertEqual(t['depends'], set([dependency1, dependency2]))
503 def test_add_to_empty_dependency_set(self):
504 # Adds dependency to task with one dependencies
505 t = Task(self.tw, description="test task")
506 dependency = Task(self.tw, description="needs to be done first")
510 t['depends'].add(dependency)
513 self.assertEqual(t['depends'], set([dependency]))
515 def test_simple_dependency_set_save_repeatedly(self):
516 # Adds only one dependency to task with no dependencies
517 t = Task(self.tw, description="test task")
518 dependency = Task(self.tw, description="needs to be done first")
521 t['depends'] = set([dependency])
524 # We taint the task, but keep depends intact
525 t['description'] = "test task modified"
528 self.assertEqual(t['depends'], set([dependency]))
530 # We taint the task, but assign the same set to the depends
531 t['depends'] = set([dependency])
532 t['description'] = "test task modified again"
535 self.assertEqual(t['depends'], set([dependency]))
537 def test_compare_different_tasks(self):
538 # Negative: compare two different tasks
539 t1 = Task(self.tw, description="test task")
540 t2 = Task(self.tw, description="test task")
545 self.assertEqual(t1 == t2, False)
547 def test_compare_same_task_object(self):
548 # Compare Task object wit itself
549 t = Task(self.tw, description="test task")
552 self.assertEqual(t == t, True)
554 def test_compare_same_task(self):
555 # Compare the same task using two different objects
556 t1 = Task(self.tw, description="test task")
559 t2 = self.tw.tasks.get(uuid=t1['uuid'])
560 self.assertEqual(t1 == t2, True)
562 def test_compare_unsaved_tasks(self):
563 # t1 and t2 are unsaved tasks, considered to be unequal
564 # despite the content of data
565 t1 = Task(self.tw, description="test task")
566 t2 = Task(self.tw, description="test task")
568 self.assertEqual(t1 == t2, False)
570 def test_hash_unsaved_tasks(self):
571 # Considered equal, it's the same object
572 t1 = Task(self.tw, description="test task")
574 self.assertEqual(hash(t1) == hash(t2), True)
576 def test_hash_same_task(self):
577 # Compare the hash of the task using two different objects
578 t1 = Task(self.tw, description="test task")
581 t2 = self.tw.tasks.get(uuid=t1['uuid'])
582 self.assertEqual(t1.__hash__(), t2.__hash__())
584 def test_adding_task_with_priority(self):
585 t = Task(self.tw, description="test task", priority="M")
588 def test_removing_priority_with_none(self):
589 t = Task(self.tw, description="test task", priority="L")
592 # Remove the priority mark
596 # Assert that priority is not there after saving
597 self.assertEqual(t['priority'], None)
599 def test_adding_task_with_due_time(self):
600 t = Task(self.tw, description="test task", due=datetime.datetime.now())
603 def test_removing_due_time_with_none(self):
604 t = Task(self.tw, description="test task", due=datetime.datetime.now())
607 # Remove the due timestamp
611 # Assert that due timestamp is no longer there
612 self.assertEqual(t['due'], None)
614 def test_modified_fields_new_task(self):
617 # This should be empty with new task
618 self.assertEqual(set(t._modified_fields), set())
621 t['description'] = "test task"
622 self.assertEqual(set(t._modified_fields), set(['description']))
624 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
625 self.assertEqual(set(t._modified_fields), set(['description', 'due']))
627 t['project'] = "test project"
628 self.assertEqual(set(t._modified_fields),
629 set(['description', 'due', 'project']))
631 # List of modified fields should clear out when saved
633 self.assertEqual(set(t._modified_fields), set())
635 # Reassigning the fields with the same values now should not produce
637 t['description'] = "test task"
638 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
639 t['project'] = "test project"
640 self.assertEqual(set(t._modified_fields), set())
642 def test_modified_fields_loaded_task(self):
646 t['description'] = "test task"
647 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
648 t['project'] = "test project"
650 dependency = Task(self.tw, description="dependency")
652 t['depends'] = set([dependency])
654 # List of modified fields should clear out when saved
656 self.assertEqual(set(t._modified_fields), set())
658 # Get the task by using a filter by UUID
659 t2 = self.tw.tasks.get(uuid=t['uuid'])
661 # Reassigning the fields with the same values now should not produce
663 t['description'] = "test task"
664 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
665 t['project'] = "test project"
666 t['depends'] = set([dependency])
667 self.assertEqual(set(t._modified_fields), set())
669 def test_modified_fields_not_affected_by_reading(self):
672 for field in TASK_STANDARD_ATTRS:
675 self.assertEqual(set(t._modified_fields), set())
677 def test_setting_read_only_attrs_through_init(self):
678 # Test that we are unable to set readonly attrs through __init__
679 for readonly_key in Task.read_only_fields:
680 kwargs = {'description': 'test task', readonly_key: 'value'}
681 self.assertRaises(RuntimeError,
682 lambda: Task(self.tw, **kwargs))
684 def test_setting_read_only_attrs_through_setitem(self):
685 # Test that we are unable to set readonly attrs through __init__
686 for readonly_key in Task.read_only_fields:
687 t = Task(self.tw, description='test task')
688 self.assertRaises(RuntimeError,
689 lambda: t.__setitem__(readonly_key, 'value'))
691 def test_saving_unmodified_task(self):
692 t = Task(self.tw, description="test task")
696 def test_adding_tag_by_appending(self):
697 t = Task(self.tw, description="test task", tags=['test1'])
699 t['tags'].add('test2')
701 self.assertEqual(t['tags'], set(['test1', 'test2']))
703 def test_adding_tag_twice(self):
704 t = Task(self.tw, description="test task", tags=['test1'])
706 t['tags'].add('test2')
707 t['tags'].add('test2')
709 self.assertEqual(t['tags'], set(['test1', 'test2']))
711 def test_adding_tag_by_appending_empty(self):
712 t = Task(self.tw, description="test task")
714 t['tags'].add('test')
716 self.assertEqual(t['tags'], set(['test']))
718 def test_serializers_returning_empty_string_for_none(self):
719 # Test that any serializer returns '' when passed None
721 serializers = [getattr(t, serializer_name) for serializer_name in
722 filter(lambda x: x.startswith('serialize_'), dir(t))]
723 for serializer in serializers:
724 self.assertEqual(serializer(None), '')
726 def test_deserializer_returning_empty_value_for_empty_string(self):
727 # Test that any deserializer returns empty value when passed ''
729 deserializers = [getattr(t, deserializer_name) for deserializer_name in
730 filter(lambda x: x.startswith('deserialize_'), dir(t))]
731 for deserializer in deserializers:
732 self.assertTrue(deserializer('') in (None, [], set()))
734 def test_normalizers_handling_none(self):
735 # Test that any normalizer can handle None as a valid value
738 for key in TASK_STANDARD_ATTRS:
739 t._normalize(key, None)
741 def test_recurrent_task_generation(self):
742 today = datetime.date.today()
743 t = Task(self.tw, description="brush teeth",
744 due=today, recur="daily")
746 self.assertEqual(len(self.tw.tasks.pending()), 2)
748 def test_modify_number_of_tasks_at_once(self):
749 for i in range(1, 100):
750 Task(self.tw, description="test task %d" % i, tags=['test']).save()
752 self.tw.execute_command(['+test', 'mod', 'unified', 'description'])
754 def test_return_all_from_executed_command(self):
755 Task(self.tw, description="test task", tags=['test']).save()
756 out, err, rc = self.tw.execute_command(['count'], return_all=True)
757 self.assertEqual(rc, 0)
759 def test_return_all_from_failed_executed_command(self):
760 Task(self.tw, description="test task", tags=['test']).save()
761 out, err, rc = self.tw.execute_command(['countinvalid'],
762 return_all=True, allow_failure=False)
763 self.assertNotEqual(rc, 0)
766 class TaskFromHookTest(TasklibTest):
768 input_add_data = six.StringIO(
769 '{"description":"Buy some milk",'
770 '"entry":"20141118T050231Z",'
771 '"status":"pending",'
772 '"start":"20141119T152233Z",'
773 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
775 input_modify_data = six.StringIO(input_add_data.getvalue() + '\n' +
776 '{"description":"Buy some milk finally",'
777 '"entry":"20141118T050231Z",'
778 '"status":"completed",'
779 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
781 exported_raw_data = (
783 '"due":"20150101T232323Z",'
784 '"description":"test task"}')
786 def test_setting_up_from_add_hook_input(self):
787 t = Task.from_input(input_file=self.input_add_data, backend=self.tw)
788 self.assertEqual(t['description'], "Buy some milk")
789 self.assertEqual(t.pending, True)
791 def test_setting_up_from_modified_hook_input(self):
792 t = Task.from_input(input_file=self.input_modify_data, modify=True,
794 self.assertEqual(t['description'], "Buy some milk finally")
795 self.assertEqual(t.pending, False)
796 self.assertEqual(t.completed, True)
798 self.assertEqual(t._original_data['status'], "pending")
799 self.assertEqual(t._original_data['description'], "Buy some milk")
800 self.assertEqual(set(t._modified_fields),
801 set(['status', 'description', 'start']))
803 def test_export_data(self):
804 t = Task(self.tw, description="test task",
806 due=pytz.utc.localize(datetime.datetime(2015,1,1,23,23,23)))
808 # Check that the output is a permutation of:
809 # {"project":"Home","description":"test task","due":"20150101232323Z"}
810 allowed_segments = self.exported_raw_data[1:-1].split(',')
812 '{' + ','.join(segments) + '}'
813 for segments in itertools.permutations(allowed_segments)
816 self.assertTrue(any(t.export_data() == expected
817 for expected in allowed_output))
819 class TimezoneAwareDatetimeTest(TasklibTest):
822 super(TimezoneAwareDatetimeTest, self).setUp()
823 self.zone = local_zone
824 self.localdate_naive = datetime.datetime(2015,2,2)
825 self.localtime_naive = datetime.datetime(2015,2,2,0,0,0)
826 self.localtime_aware = self.zone.localize(self.localtime_naive)
827 self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
829 def test_timezone_naive_datetime_setitem(self):
830 t = Task(self.tw, description="test task")
831 t['due'] = self.localtime_naive
832 self.assertEqual(t['due'], self.localtime_aware)
834 def test_timezone_naive_datetime_using_init(self):
835 t = Task(self.tw, description="test task", due=self.localtime_naive)
836 self.assertEqual(t['due'], self.localtime_aware)
838 def test_filter_by_naive_datetime(self):
839 t = Task(self.tw, description="task1", due=self.localtime_naive)
841 matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
842 self.assertEqual(len(matching_tasks), 1)
844 def test_serialize_naive_datetime(self):
845 t = Task(self.tw, description="task1", due=self.localtime_naive)
846 self.assertEqual(json.loads(t.export_data())['due'],
847 self.utctime_aware.strftime(DATE_FORMAT))
849 def test_timezone_naive_date_setitem(self):
850 t = Task(self.tw, description="test task")
851 t['due'] = self.localdate_naive
852 self.assertEqual(t['due'], self.localtime_aware)
854 def test_timezone_naive_date_using_init(self):
855 t = Task(self.tw, description="test task", due=self.localdate_naive)
856 self.assertEqual(t['due'], self.localtime_aware)
858 def test_filter_by_naive_date(self):
859 t = Task(self.tw, description="task1", due=self.localdate_naive)
861 matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
862 self.assertEqual(len(matching_tasks), 1)
864 def test_serialize_naive_date(self):
865 t = Task(self.tw, description="task1", due=self.localdate_naive)
866 self.assertEqual(json.loads(t.export_data())['due'],
867 self.utctime_aware.strftime(DATE_FORMAT))
869 def test_timezone_aware_datetime_setitem(self):
870 t = Task(self.tw, description="test task")
871 t['due'] = self.localtime_aware
872 self.assertEqual(t['due'], self.localtime_aware)
874 def test_timezone_aware_datetime_using_init(self):
875 t = Task(self.tw, description="test task", due=self.localtime_aware)
876 self.assertEqual(t['due'], self.localtime_aware)
878 def test_filter_by_aware_datetime(self):
879 t = Task(self.tw, description="task1", due=self.localtime_aware)
881 matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
882 self.assertEqual(len(matching_tasks), 1)
884 def test_serialize_aware_datetime(self):
885 t = Task(self.tw, description="task1", due=self.localtime_aware)
886 self.assertEqual(json.loads(t.export_data())['due'],
887 self.utctime_aware.strftime(DATE_FORMAT))
889 class DatetimeStringTest(TasklibTest):
891 def test_simple_now_conversion(self):
892 if self.tw.version < six.text_type('2.4.0'):
893 # Python2.6 does not support SkipTest. As a workaround
894 # mark the test as passed by exiting.
895 if getattr(unittest, 'SkipTest', None) is not None:
896 raise unittest.SkipTest()
900 t = Task(self.tw, description="test task", due="now")
901 now = local_zone.localize(datetime.datetime.now())
903 # Assert that both times are not more than 5 seconds apart
904 if sys.version_info < (2,7):
905 self.assertTrue(total_seconds_2_6(now - t['due']) < 5)
906 self.assertTrue(total_seconds_2_6(t['due'] - now) < 5)
908 self.assertTrue((now - t['due']).total_seconds() < 5)
909 self.assertTrue((t['due'] - now).total_seconds() < 5)
911 def test_simple_eoy_conversion(self):
912 if self.tw.version < six.text_type('2.4.0'):
913 # Python2.6 does not support SkipTest. As a workaround
914 # mark the test as passed by exiting.
915 if getattr(unittest, 'SkipTest', None) is not None:
916 raise unittest.SkipTest()
920 t = Task(self.tw, description="test task", due="eoy")
921 now = local_zone.localize(datetime.datetime.now())
922 eoy = local_zone.localize(datetime.datetime(
930 self.assertEqual(eoy, t['due'])
932 def test_complex_eoy_conversion(self):
933 if self.tw.version < six.text_type('2.4.0'):
934 # Python2.6 does not support SkipTest. As a workaround
935 # mark the test as passed by exiting.
936 if getattr(unittest, 'SkipTest', None) is not None:
937 raise unittest.SkipTest()
941 t = Task(self.tw, description="test task", due="eoy - 4 months")
942 now = local_zone.localize(datetime.datetime.now())
943 due_date = local_zone.localize(datetime.datetime(
950 )) - datetime.timedelta(0,4 * 30 * 86400)
951 self.assertEqual(due_date, t['due'])
953 def test_filtering_with_string_datetime(self):
954 if self.tw.version < six.text_type('2.4.0'):
955 # Python2.6 does not support SkipTest. As a workaround
956 # mark the test as passed by exiting.
957 if getattr(unittest, 'SkipTest', None) is not None:
958 raise unittest.SkipTest()
962 t = Task(self.tw, description="test task",
963 due=datetime.datetime.now() - datetime.timedelta(0,2))
965 self.assertEqual(len(self.tw.tasks.filter(due__before="now")), 1)
967 class AnnotationTest(TasklibTest):
970 super(AnnotationTest, self).setUp()
971 Task(self.tw, description="test task").save()
973 def test_adding_annotation(self):
974 task = self.tw.tasks.get()
975 task.add_annotation('test annotation')
976 self.assertEqual(len(task['annotations']), 1)
977 ann = task['annotations'][0]
978 self.assertEqual(ann['description'], 'test annotation')
980 def test_removing_annotation(self):
981 task = self.tw.tasks.get()
982 task.add_annotation('test annotation')
983 ann = task['annotations'][0]
985 self.assertEqual(len(task['annotations']), 0)
987 def test_removing_annotation_by_description(self):
988 task = self.tw.tasks.get()
989 task.add_annotation('test annotation')
990 task.remove_annotation('test annotation')
991 self.assertEqual(len(task['annotations']), 0)
993 def test_removing_annotation_by_obj(self):
994 task = self.tw.tasks.get()
995 task.add_annotation('test annotation')
996 ann = task['annotations'][0]
997 task.remove_annotation(ann)
998 self.assertEqual(len(task['annotations']), 0)
1000 def test_annotation_after_modification(self):
1001 task = self.tw.tasks.get()
1002 task['project'] = 'test'
1003 task.add_annotation('I should really do this task')
1004 self.assertEqual(task['project'], 'test')
1006 self.assertEqual(task['project'], 'test')
1008 def test_serialize_annotations(self):
1009 # Test that serializing annotations is possible
1010 t = Task(self.tw, description="test")
1013 t.add_annotation("annotation1")
1014 t.add_annotation("annotation2")
1016 data = t._serialize('annotations', t._data['annotations'])
1018 self.assertEqual(len(data), 2)
1019 self.assertEqual(type(data[0]), dict)
1020 self.assertEqual(type(data[1]), dict)
1022 self.assertEqual(data[0]['description'], "annotation1")
1023 self.assertEqual(data[1]['description'], "annotation2")
1026 class UnicodeTest(TasklibTest):
1028 def test_unicode_task(self):
1029 Task(self.tw, description=six.u("†åßk")).save()
1032 def test_filter_by_unicode_task(self):
1033 Task(self.tw, description=six.u("†åßk")).save()
1034 tasks = self.tw.tasks.filter(description=six.u("†åßk"))
1035 self.assertEqual(len(tasks), 1)
1037 def test_non_unicode_task(self):
1038 Task(self.tw, description="test task").save()
1041 class ReadOnlyDictViewTest(unittest.TestCase):
1044 self.sample = dict(l=[1,2,3], d={'k':'v'})
1045 self.original_sample = copy.deepcopy(self.sample)
1046 self.view = ReadOnlyDictView(self.sample)
1048 def test_readonlydictview_getitem(self):
1050 self.assertEqual(l, self.sample['l'])
1052 # Assert that modification changed only copied value
1054 self.assertNotEqual(l, self.sample['l'])
1056 # Assert that viewed dict is not changed
1057 self.assertEqual(self.sample, self.original_sample)
1059 def test_readonlydictview_contains(self):
1060 self.assertEqual('l' in self.view, 'l' in self.sample)
1061 self.assertEqual('d' in self.view, 'd' in self.sample)
1062 self.assertEqual('k' in self.view, 'k' in self.sample)
1064 # Assert that viewed dict is not changed
1065 self.assertEqual(self.sample, self.original_sample)
1067 def test_readonlydictview_iter(self):
1068 self.assertEqual(list(k for k in self.view),
1069 list(k for k in self.sample))
1071 # Assert the view is correct after modification
1072 self.sample['new'] = 'value'
1073 self.assertEqual(list(k for k in self.view),
1074 list(k for k in self.sample))
1076 def test_readonlydictview_len(self):
1077 self.assertEqual(len(self.view), len(self.sample))
1079 # Assert the view is correct after modification
1080 self.sample['new'] = 'value'
1081 self.assertEqual(len(self.view), len(self.sample))
1083 def test_readonlydictview_get(self):
1084 l = self.view.get('l')
1085 self.assertEqual(l, self.sample.get('l'))
1087 # Assert that modification changed only copied value
1089 self.assertNotEqual(l, self.sample.get('l'))
1091 # Assert that viewed dict is not changed
1092 self.assertEqual(self.sample, self.original_sample)
1094 def test_readonlydict_items(self):
1095 view_items = self.view.items()
1096 sample_items = list(self.sample.items())
1097 self.assertEqual(view_items, sample_items)
1099 view_items.append('newkey')
1100 self.assertNotEqual(view_items, sample_items)
1101 self.assertEqual(self.sample, self.original_sample)
1103 def test_readonlydict_values(self):
1104 view_values = self.view.values()
1105 sample_values = list(self.sample.values())
1106 self.assertEqual(view_values, sample_values)
1108 view_list_item = list(filter(lambda x: type(x) is list,
1110 view_list_item.append(4)
1111 self.assertNotEqual(view_values, sample_values)
1112 self.assertEqual(self.sample, self.original_sample)
1115 class LazyUUIDTaskTest(TasklibTest):
1118 super(LazyUUIDTaskTest, self).setUp()
1120 self.stored = Task(self.tw, description="this is test task")
1123 self.lazy = LazyUUIDTask(self.tw, self.stored['uuid'])
1125 def test_uuid_non_conversion(self):
1126 assert self.stored['uuid'] == self.lazy['uuid']
1127 assert type(self.lazy) is LazyUUIDTask
1129 def test_lazy_explicit_conversion(self):
1130 assert type(self.lazy) is LazyUUIDTask
1132 assert type(self.lazy) is Task
1134 def test_conversion_key(self):
1135 assert self.stored['description'] == self.lazy['description']
1136 assert type(self.lazy) is Task
1138 def test_conversion_attribute(self):
1139 assert type(self.lazy) is LazyUUIDTask
1140 assert self.lazy.completed is False
1141 assert type(self.lazy) is Task
1143 def test_normal_to_lazy_equality(self):
1144 assert self.stored == self.lazy
1145 assert type(self.lazy) is LazyUUIDTask
1147 def test_lazy_to_lazy_equality(self):
1148 lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
1149 lazy2 = LazyUUIDTask(self.tw, self.stored['uuid'])
1151 assert lazy1 == lazy2
1152 assert type(lazy1) is LazyUUIDTask
1153 assert type(lazy2) is LazyUUIDTask
1155 def test_lazy_in_queryset(self):
1156 tasks = self.tw.tasks.filter(uuid=self.stored['uuid'])
1158 assert self.lazy in tasks
1159 assert type(self.lazy) is LazyUUIDTask
1161 def test_lazy_saved(self):
1162 assert self.lazy.saved is True
1164 def test_lazy_modified(self):
1165 assert self.lazy.modified is False
1167 def test_lazy_modified_fields(self):
1168 assert self.lazy._modified_fields == set()
1171 class LazyUUIDTaskSetTest(TasklibTest):
1174 super(LazyUUIDTaskSetTest, self).setUp()
1176 self.task1 = Task(self.tw, description="task 1")
1177 self.task2 = Task(self.tw, description="task 2")
1178 self.task3 = Task(self.tw, description="task 3")
1190 self.lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1192 def test_length(self):
1193 assert len(self.lazy) == 3
1194 assert type(self.lazy) is LazyUUIDTaskSet
1196 def test_contains(self):
1197 assert self.task1 in self.lazy
1198 assert self.task2 in self.lazy
1199 assert self.task3 in self.lazy
1200 assert type(self.lazy) is LazyUUIDTaskSet
1202 def test_eq_lazy(self):
1203 new_lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1204 assert self.lazy == new_lazy
1205 assert not self.lazy != new_lazy
1206 assert type(self.lazy) is LazyUUIDTaskSet
1208 def test_eq_real(self):
1209 assert self.lazy == self.tw.tasks.all()
1210 assert self.tw.tasks.all() == self.lazy
1211 assert not self.lazy != self.tw.tasks.all()
1213 assert type(self.lazy) is LazyUUIDTaskSet
1215 def test_union(self):
1216 taskset = set([self.task1])
1217 lazyset = LazyUUIDTaskSet(
1219 (self.task2['uuid'], self.task3['uuid'])
1222 assert taskset | lazyset == self.lazy
1223 assert lazyset | taskset == self.lazy
1224 assert taskset.union(lazyset) == self.lazy
1225 assert lazyset.union(taskset) == self.lazy
1228 assert lazyset == self.lazy
1230 def test_difference(self):
1231 taskset = set([self.task1, self.task2])
1232 lazyset = LazyUUIDTaskSet(
1234 (self.task2['uuid'], self.task3['uuid'])
1237 assert taskset - lazyset == set([self.task1])
1238 assert lazyset - taskset == set([self.task3])
1239 assert taskset.difference(lazyset) == set([self.task1])
1240 assert lazyset.difference(taskset) == set([self.task3])
1243 assert lazyset == set([self.task3])
1245 def test_symmetric_difference(self):
1246 taskset = set([self.task1, self.task2])
1247 lazyset = LazyUUIDTaskSet(
1249 (self.task2['uuid'], self.task3['uuid'])
1252 assert taskset ^ lazyset == set([self.task1, self.task3])
1253 assert lazyset ^ taskset == set([self.task1, self.task3])
1254 assert taskset.symmetric_difference(lazyset) == set([self.task1, self.task3])
1255 assert lazyset.symmetric_difference(taskset) == set([self.task1, self.task3])
1258 assert lazyset == set([self.task1, self.task3])
1260 def test_intersection(self):
1261 taskset = set([self.task1, self.task2])
1262 lazyset = LazyUUIDTaskSet(
1264 (self.task2['uuid'], self.task3['uuid'])
1267 assert taskset & lazyset == set([self.task2])
1268 assert lazyset & taskset == set([self.task2])
1269 assert taskset.intersection(lazyset) == set([self.task2])
1270 assert lazyset.intersection(taskset) == set([self.task2])
1273 assert lazyset == set([self.task2])
1276 class TaskWarriorBackendTest(TasklibTest):
1278 def test_config(self):
1279 assert self.tw.config['nag'] == "You have more urgent tasks."
1280 assert self.tw.config['debug'] == "no"