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.
13 from .task import TaskWarrior, Task, ReadOnlyDictView, local_zone, DATE_FORMAT
15 # http://taskwarrior.org/docs/design/task.html , Section: The Attributes
16 TASK_STANDARD_ATTRS = (
39 class TasklibTest(unittest.TestCase):
42 self.tmp = tempfile.mkdtemp(dir='.')
43 self.tw = TaskWarrior(data_location=self.tmp, taskrc_location='/')
46 shutil.rmtree(self.tmp)
49 class TaskFilterTest(TasklibTest):
51 def test_all_empty(self):
52 self.assertEqual(len(self.tw.tasks.all()), 0)
54 def test_all_non_empty(self):
55 Task(self.tw, description="test task").save()
56 self.assertEqual(len(self.tw.tasks.all()), 1)
57 self.assertEqual(self.tw.tasks.all()[0]['description'], 'test task')
58 self.assertEqual(self.tw.tasks.all()[0]['status'], 'pending')
60 def test_pending_non_empty(self):
61 Task(self.tw, description="test task").save()
62 self.assertEqual(len(self.tw.tasks.pending()), 1)
63 self.assertEqual(self.tw.tasks.pending()[0]['description'],
65 self.assertEqual(self.tw.tasks.pending()[0]['status'], 'pending')
67 def test_completed_empty(self):
68 Task(self.tw, description="test task").save()
69 self.assertEqual(len(self.tw.tasks.completed()), 0)
71 def test_completed_non_empty(self):
72 Task(self.tw, description="test task").save()
73 self.assertEqual(len(self.tw.tasks.completed()), 0)
74 self.tw.tasks.all()[0].done()
75 self.assertEqual(len(self.tw.tasks.completed()), 1)
77 def test_filtering_by_attribute(self):
78 Task(self.tw, description="no priority task").save()
79 Task(self.tw, priority="H", description="high priority task").save()
80 self.assertEqual(len(self.tw.tasks.all()), 2)
82 # Assert that the correct number of tasks is returned
83 self.assertEqual(len(self.tw.tasks.filter(priority="H")), 1)
85 # Assert that the correct tasks are returned
86 high_priority_task = self.tw.tasks.get(priority="H")
87 self.assertEqual(high_priority_task['description'], "high priority task")
89 def test_filtering_by_empty_attribute(self):
90 Task(self.tw, description="no priority task").save()
91 Task(self.tw, priority="H", description="high priority task").save()
92 self.assertEqual(len(self.tw.tasks.all()), 2)
94 # Assert that the correct number of tasks is returned
95 self.assertEqual(len(self.tw.tasks.filter(priority=None)), 1)
97 # Assert that the correct tasks are returned
98 no_priority_task = self.tw.tasks.get(priority=None)
99 self.assertEqual(no_priority_task['description'], "no priority task")
101 def test_filter_for_task_with_space_in_descripition(self):
102 task = Task(self.tw, description="test task")
105 filtered_task = self.tw.tasks.get(description="test task")
106 self.assertEqual(filtered_task['description'], "test task")
108 def test_filter_for_task_without_space_in_descripition(self):
109 task = Task(self.tw, description="test")
112 filtered_task = self.tw.tasks.get(description="test")
113 self.assertEqual(filtered_task['description'], "test")
115 def test_filter_for_task_with_space_in_project(self):
116 task = Task(self.tw, description="test", project="random project")
119 filtered_task = self.tw.tasks.get(project="random project")
120 self.assertEqual(filtered_task['project'], "random project")
122 def test_filter_for_task_without_space_in_project(self):
123 task = Task(self.tw, description="test", project="random")
126 filtered_task = self.tw.tasks.get(project="random")
127 self.assertEqual(filtered_task['project'], "random")
129 def test_filter_with_empty_uuid(self):
130 self.assertRaises(ValueError, lambda: self.tw.tasks.get(uuid=''))
132 def test_filter_dummy_by_status(self):
133 t = Task(self.tw, description="test")
136 tasks = self.tw.tasks.filter(status=t['status'])
137 self.assertEqual(list(tasks), [t])
139 def test_filter_dummy_by_uuid(self):
140 t = Task(self.tw, description="test")
143 tasks = self.tw.tasks.filter(uuid=t['uuid'])
144 self.assertEqual(list(tasks), [t])
146 def test_filter_dummy_by_entry(self):
147 t = Task(self.tw, description="test")
150 tasks = self.tw.tasks.filter(entry=t['entry'])
151 self.assertEqual(list(tasks), [t])
153 def test_filter_dummy_by_description(self):
154 t = Task(self.tw, description="test")
157 tasks = self.tw.tasks.filter(description=t['description'])
158 self.assertEqual(list(tasks), [t])
160 def test_filter_dummy_by_start(self):
161 t = Task(self.tw, description="test")
165 tasks = self.tw.tasks.filter(start=t['start'])
166 self.assertEqual(list(tasks), [t])
168 def test_filter_dummy_by_end(self):
169 t = Task(self.tw, description="test")
173 tasks = self.tw.tasks.filter(end=t['end'])
174 self.assertEqual(list(tasks), [t])
176 def test_filter_dummy_by_due(self):
177 t = Task(self.tw, description="test", due=datetime.datetime.now())
180 tasks = self.tw.tasks.filter(due=t['due'])
181 self.assertEqual(list(tasks), [t])
183 def test_filter_dummy_by_until(self):
184 t = Task(self.tw, description="test")
187 tasks = self.tw.tasks.filter(until=t['until'])
188 self.assertEqual(list(tasks), [t])
190 def test_filter_dummy_by_modified(self):
191 # Older TW version does not support bumping modified
193 if self.tw.version < six.text_type('2.2.0'):
194 # Python2.6 does not support SkipTest. As a workaround
195 # mark the test as passed by exiting.
196 if getattr(unittest, 'SkipTest', None) is not None:
197 raise unittest.SkipTest()
201 t = Task(self.tw, description="test")
204 tasks = self.tw.tasks.filter(modified=t['modified'])
205 self.assertEqual(list(tasks), [t])
207 def test_filter_dummy_by_scheduled(self):
208 t = Task(self.tw, description="test")
211 tasks = self.tw.tasks.filter(scheduled=t['scheduled'])
212 self.assertEqual(list(tasks), [t])
214 def test_filter_dummy_by_tags(self):
215 t = Task(self.tw, description="test", tags=["home"])
218 tasks = self.tw.tasks.filter(tags=t['tags'])
219 self.assertEqual(list(tasks), [t])
221 def test_filter_dummy_by_projects(self):
222 t = Task(self.tw, description="test", project="random")
225 tasks = self.tw.tasks.filter(project=t['project'])
226 self.assertEqual(list(tasks), [t])
228 def test_filter_by_priority(self):
229 t = Task(self.tw, description="test", priority="H")
232 tasks = self.tw.tasks.filter(priority=t['priority'])
233 self.assertEqual(list(tasks), [t])
236 class TaskTest(TasklibTest):
238 def test_create_unsaved_task(self):
239 # Make sure a new task is not saved unless explicitly called for
240 t = Task(self.tw, description="test task")
241 self.assertEqual(len(self.tw.tasks.all()), 0)
243 # TODO: once python 2.6 compatiblity is over, use context managers here
244 # and in all subsequent tests for assertRaises
246 def test_delete_unsaved_task(self):
247 t = Task(self.tw, description="test task")
248 self.assertRaises(Task.NotSaved, t.delete)
250 def test_complete_unsaved_task(self):
251 t = Task(self.tw, description="test task")
252 self.assertRaises(Task.NotSaved, t.done)
254 def test_refresh_unsaved_task(self):
255 t = Task(self.tw, description="test task")
256 self.assertRaises(Task.NotSaved, t.refresh)
258 def test_start_unsaved_task(self):
259 t = Task(self.tw, description="test task")
260 self.assertRaises(Task.NotSaved, t.start)
262 def test_delete_deleted_task(self):
263 t = Task(self.tw, description="test task")
267 self.assertRaises(Task.DeletedTask, t.delete)
269 def test_complete_completed_task(self):
270 t = Task(self.tw, description="test task")
274 self.assertRaises(Task.CompletedTask, t.done)
276 def test_start_completed_task(self):
277 t = Task(self.tw, description="test task")
281 self.assertRaises(Task.CompletedTask, t.start)
283 def test_complete_deleted_task(self):
284 t = Task(self.tw, description="test task")
288 self.assertRaises(Task.DeletedTask, t.done)
290 def test_start_completed_task(self):
291 t = Task(self.tw, description="test task")
295 self.assertRaises(Task.CompletedTask, t.start)
297 def test_starting_task(self):
298 t = Task(self.tw, description="test task")
299 now = t.datetime_normalizer(datetime.datetime.now())
303 self.assertTrue(now.replace(microsecond=0) <= t['start'])
304 self.assertEqual(t['status'], 'pending')
306 def test_completing_task(self):
307 t = Task(self.tw, description="test task")
308 now = t.datetime_normalizer(datetime.datetime.now())
312 self.assertTrue(now.replace(microsecond=0) <= t['end'])
313 self.assertEqual(t['status'], 'completed')
315 def test_deleting_task(self):
316 t = Task(self.tw, description="test task")
317 now = t.datetime_normalizer(datetime.datetime.now())
321 self.assertTrue(now.replace(microsecond=0) <= t['end'])
322 self.assertEqual(t['status'], 'deleted')
324 def test_modify_simple_attribute_without_space(self):
325 t = Task(self.tw, description="test")
328 self.assertEquals(t['description'], "test")
330 t['description'] = "test-modified"
333 self.assertEquals(t['description'], "test-modified")
335 def test_modify_simple_attribute_with_space(self):
336 # Space can pose problems with parsing
337 t = Task(self.tw, description="test task")
340 self.assertEquals(t['description'], "test task")
342 t['description'] = "test task modified"
345 self.assertEquals(t['description'], "test task modified")
347 def test_empty_dependency_set_of_unsaved_task(self):
348 t = Task(self.tw, description="test task")
349 self.assertEqual(t['depends'], set())
351 def test_empty_dependency_set_of_saved_task(self):
352 t = Task(self.tw, description="test task")
354 self.assertEqual(t['depends'], set())
356 def test_set_unsaved_task_as_dependency(self):
357 # Adds only one dependency to task with no dependencies
358 t = Task(self.tw, description="test task")
359 dependency = Task(self.tw, description="needs to be done first")
361 # We only save the parent task, dependency task is unsaved
363 t['depends'] = set([dependency])
365 self.assertRaises(Task.NotSaved, t.save)
367 def test_set_simple_dependency_set(self):
368 # Adds only one dependency to task with no dependencies
369 t = Task(self.tw, description="test task")
370 dependency = Task(self.tw, description="needs to be done first")
375 t['depends'] = set([dependency])
377 self.assertEqual(t['depends'], set([dependency]))
379 def test_set_complex_dependency_set(self):
380 # Adds two dependencies to task with no dependencies
381 t = Task(self.tw, description="test task")
382 dependency1 = Task(self.tw, description="needs to be done first")
383 dependency2 = Task(self.tw, description="needs to be done second")
389 t['depends'] = set([dependency1, dependency2])
391 self.assertEqual(t['depends'], set([dependency1, dependency2]))
393 def test_remove_from_dependency_set(self):
394 # Removes dependency from task with two dependencies
395 t = Task(self.tw, description="test task")
396 dependency1 = Task(self.tw, description="needs to be done first")
397 dependency2 = Task(self.tw, description="needs to be done second")
402 t['depends'] = set([dependency1, dependency2])
405 t['depends'].remove(dependency2)
408 self.assertEqual(t['depends'], set([dependency1]))
410 def test_add_to_dependency_set(self):
411 # Adds dependency to task with one dependencies
412 t = Task(self.tw, description="test task")
413 dependency1 = Task(self.tw, description="needs to be done first")
414 dependency2 = Task(self.tw, description="needs to be done second")
419 t['depends'] = set([dependency1])
422 t['depends'].add(dependency2)
425 self.assertEqual(t['depends'], set([dependency1, dependency2]))
427 def test_add_to_empty_dependency_set(self):
428 # Adds dependency to task with one dependencies
429 t = Task(self.tw, description="test task")
430 dependency = Task(self.tw, description="needs to be done first")
434 t['depends'].add(dependency)
437 self.assertEqual(t['depends'], set([dependency]))
439 def test_simple_dependency_set_save_repeatedly(self):
440 # Adds only one dependency to task with no dependencies
441 t = Task(self.tw, description="test task")
442 dependency = Task(self.tw, description="needs to be done first")
445 t['depends'] = set([dependency])
448 # We taint the task, but keep depends intact
449 t['description'] = "test task modified"
452 self.assertEqual(t['depends'], set([dependency]))
454 # We taint the task, but assign the same set to the depends
455 t['depends'] = set([dependency])
456 t['description'] = "test task modified again"
459 self.assertEqual(t['depends'], set([dependency]))
461 def test_compare_different_tasks(self):
462 # Negative: compare two different tasks
463 t1 = Task(self.tw, description="test task")
464 t2 = Task(self.tw, description="test task")
469 self.assertEqual(t1 == t2, False)
471 def test_compare_same_task_object(self):
472 # Compare Task object wit itself
473 t = Task(self.tw, description="test task")
476 self.assertEqual(t == t, True)
478 def test_compare_same_task(self):
479 # Compare the same task using two different objects
480 t1 = Task(self.tw, description="test task")
483 t2 = self.tw.tasks.get(uuid=t1['uuid'])
484 self.assertEqual(t1 == t2, True)
486 def test_compare_unsaved_tasks(self):
487 # t1 and t2 are unsaved tasks, considered to be unequal
488 # despite the content of data
489 t1 = Task(self.tw, description="test task")
490 t2 = Task(self.tw, description="test task")
492 self.assertEqual(t1 == t2, False)
494 def test_hash_unsaved_tasks(self):
495 # Considered equal, it's the same object
496 t1 = Task(self.tw, description="test task")
498 self.assertEqual(hash(t1) == hash(t2), True)
500 def test_hash_same_task(self):
501 # Compare the hash of the task using two different objects
502 t1 = Task(self.tw, description="test task")
505 t2 = self.tw.tasks.get(uuid=t1['uuid'])
506 self.assertEqual(t1.__hash__(), t2.__hash__())
508 def test_adding_task_with_priority(self):
509 t = Task(self.tw, description="test task", priority="M")
512 def test_removing_priority_with_none(self):
513 t = Task(self.tw, description="test task", priority="L")
516 # Remove the priority mark
520 # Assert that priority is not there after saving
521 self.assertEqual(t['priority'], None)
523 def test_adding_task_with_due_time(self):
524 t = Task(self.tw, description="test task", due=datetime.datetime.now())
527 def test_removing_due_time_with_none(self):
528 t = Task(self.tw, description="test task", due=datetime.datetime.now())
531 # Remove the due timestamp
535 # Assert that due timestamp is no longer there
536 self.assertEqual(t['due'], None)
538 def test_modified_fields_new_task(self):
541 # This should be empty with new task
542 self.assertEqual(set(t._modified_fields), set())
545 t['description'] = "test task"
546 self.assertEqual(set(t._modified_fields), set(['description']))
548 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
549 self.assertEqual(set(t._modified_fields), set(['description', 'due']))
551 t['project'] = "test project"
552 self.assertEqual(set(t._modified_fields),
553 set(['description', 'due', 'project']))
555 # List of modified fields should clear out when saved
557 self.assertEqual(set(t._modified_fields), set())
559 # Reassigning the fields with the same values now should not produce
561 t['description'] = "test task"
562 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
563 t['project'] = "test project"
564 self.assertEqual(set(t._modified_fields), set())
566 def test_modified_fields_loaded_task(self):
570 t['description'] = "test task"
571 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
572 t['project'] = "test project"
574 dependency = Task(self.tw, description="dependency")
576 t['depends'] = set([dependency])
578 # List of modified fields should clear out when saved
580 self.assertEqual(set(t._modified_fields), set())
582 # Get the task by using a filter by UUID
583 t2 = self.tw.tasks.get(uuid=t['uuid'])
585 # Reassigning the fields with the same values now should not produce
587 t['description'] = "test task"
588 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
589 t['project'] = "test project"
590 t['depends'] = set([dependency])
591 self.assertEqual(set(t._modified_fields), set())
593 def test_modified_fields_not_affected_by_reading(self):
596 for field in TASK_STANDARD_ATTRS:
599 self.assertEqual(set(t._modified_fields), set())
601 def test_setting_read_only_attrs_through_init(self):
602 # Test that we are unable to set readonly attrs through __init__
603 for readonly_key in Task.read_only_fields:
604 kwargs = {'description': 'test task', readonly_key: 'value'}
605 self.assertRaises(RuntimeError,
606 lambda: Task(self.tw, **kwargs))
608 def test_setting_read_only_attrs_through_setitem(self):
609 # Test that we are unable to set readonly attrs through __init__
610 for readonly_key in Task.read_only_fields:
611 t = Task(self.tw, description='test task')
612 self.assertRaises(RuntimeError,
613 lambda: t.__setitem__(readonly_key, 'value'))
615 def test_saving_unmodified_task(self):
616 t = Task(self.tw, description="test task")
620 def test_adding_tag_by_appending(self):
621 t = Task(self.tw, description="test task", tags=['test1'])
623 t['tags'].append('test2')
625 self.assertEqual(t['tags'], ['test1', 'test2'])
627 def test_adding_tag_by_appending_empty(self):
628 t = Task(self.tw, description="test task")
630 t['tags'].append('test')
632 self.assertEqual(t['tags'], ['test'])
634 def test_serializers_returning_empty_string_for_none(self):
635 # Test that any serializer returns '' when passed None
637 serializers = [getattr(t, serializer_name) for serializer_name in
638 filter(lambda x: x.startswith('serialize_'), dir(t))]
639 for serializer in serializers:
640 self.assertEqual(serializer(None), '')
642 def test_deserializer_returning_empty_value_for_empty_string(self):
643 # Test that any deserializer returns empty value when passed ''
645 deserializers = [getattr(t, deserializer_name) for deserializer_name in
646 filter(lambda x: x.startswith('deserialize_'), dir(t))]
647 for deserializer in deserializers:
648 self.assertTrue(deserializer('') in (None, [], set()))
650 def test_normalizers_handling_none(self):
651 # Test that any normalizer can handle None as a valid value
654 for key in TASK_STANDARD_ATTRS:
655 t._normalize(key, None)
657 def test_recurrent_task_generation(self):
658 today = datetime.date.today()
659 t = Task(self.tw, description="brush teeth",
660 due=today, recur="daily")
662 self.assertEqual(len(self.tw.tasks.pending()), 2)
664 class TaskFromHookTest(TasklibTest):
666 input_add_data = six.StringIO(
667 '{"description":"Buy some milk",'
668 '"entry":"20141118T050231Z",'
669 '"status":"pending",'
670 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
672 input_modify_data = six.StringIO(input_add_data.getvalue() + '\n' +
673 '{"description":"Buy some milk finally",'
674 '"entry":"20141118T050231Z",'
675 '"status":"completed",'
676 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
678 exported_raw_data = (
680 '"due":"20150101T232323Z",'
681 '"description":"test task"}')
683 def test_setting_up_from_add_hook_input(self):
684 t = Task.from_input(input_file=self.input_add_data, warrior=self.tw)
685 self.assertEqual(t['description'], "Buy some milk")
686 self.assertEqual(t.pending, True)
688 def test_setting_up_from_modified_hook_input(self):
689 t = Task.from_input(input_file=self.input_modify_data, modify=True,
691 self.assertEqual(t['description'], "Buy some milk finally")
692 self.assertEqual(t.pending, False)
693 self.assertEqual(t.completed, True)
695 self.assertEqual(t._original_data['status'], "pending")
696 self.assertEqual(t._original_data['description'], "Buy some milk")
697 self.assertEqual(set(t._modified_fields),
698 set(['status', 'description']))
700 def test_export_data(self):
701 t = Task(self.tw, description="test task",
703 due=pytz.utc.localize(datetime.datetime(2015,1,1,23,23,23)))
705 # Check that the output is a permutation of:
706 # {"project":"Home","description":"test task","due":"20150101232323Z"}
707 allowed_segments = self.exported_raw_data[1:-1].split(',')
709 '{' + ','.join(segments) + '}'
710 for segments in itertools.permutations(allowed_segments)
713 self.assertTrue(any(t.export_data() == expected
714 for expected in allowed_output))
716 class TimezoneAwareDatetimeTest(TasklibTest):
719 super(TimezoneAwareDatetimeTest, self).setUp()
720 self.zone = local_zone
721 self.localdate_naive = datetime.datetime(2015,2,2)
722 self.localtime_naive = datetime.datetime(2015,2,2,0,0,0)
723 self.localtime_aware = self.zone.localize(self.localtime_naive)
724 self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
726 def test_timezone_naive_datetime_setitem(self):
727 t = Task(self.tw, description="test task")
728 t['due'] = self.localtime_naive
729 self.assertEqual(t['due'], self.localtime_aware)
731 def test_timezone_naive_datetime_using_init(self):
732 t = Task(self.tw, description="test task", due=self.localtime_naive)
733 self.assertEqual(t['due'], self.localtime_aware)
735 def test_filter_by_naive_datetime(self):
736 t = Task(self.tw, description="task1", due=self.localtime_naive)
738 matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
739 self.assertEqual(len(matching_tasks), 1)
741 def test_serialize_naive_datetime(self):
742 t = Task(self.tw, description="task1", due=self.localtime_naive)
743 self.assertEqual(json.loads(t.export_data())['due'],
744 self.utctime_aware.strftime(DATE_FORMAT))
746 def test_timezone_naive_date_setitem(self):
747 t = Task(self.tw, description="test task")
748 t['due'] = self.localdate_naive
749 self.assertEqual(t['due'], self.localtime_aware)
751 def test_timezone_naive_date_using_init(self):
752 t = Task(self.tw, description="test task", due=self.localdate_naive)
753 self.assertEqual(t['due'], self.localtime_aware)
755 def test_filter_by_naive_date(self):
756 t = Task(self.tw, description="task1", due=self.localdate_naive)
758 matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
759 self.assertEqual(len(matching_tasks), 1)
761 def test_serialize_naive_date(self):
762 t = Task(self.tw, description="task1", due=self.localdate_naive)
763 self.assertEqual(json.loads(t.export_data())['due'],
764 self.utctime_aware.strftime(DATE_FORMAT))
766 def test_timezone_aware_datetime_setitem(self):
767 t = Task(self.tw, description="test task")
768 t['due'] = self.localtime_aware
769 self.assertEqual(t['due'], self.localtime_aware)
771 def test_timezone_aware_datetime_using_init(self):
772 t = Task(self.tw, description="test task", due=self.localtime_aware)
773 self.assertEqual(t['due'], self.localtime_aware)
775 def test_filter_by_aware_datetime(self):
776 t = Task(self.tw, description="task1", due=self.localtime_aware)
778 matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
779 self.assertEqual(len(matching_tasks), 1)
781 def test_serialize_aware_datetime(self):
782 t = Task(self.tw, description="task1", due=self.localtime_aware)
783 self.assertEqual(json.loads(t.export_data())['due'],
784 self.utctime_aware.strftime(DATE_FORMAT))
786 class AnnotationTest(TasklibTest):
789 super(AnnotationTest, self).setUp()
790 Task(self.tw, description="test task").save()
792 def test_adding_annotation(self):
793 task = self.tw.tasks.get()
794 task.add_annotation('test annotation')
795 self.assertEqual(len(task['annotations']), 1)
796 ann = task['annotations'][0]
797 self.assertEqual(ann['description'], 'test annotation')
799 def test_removing_annotation(self):
800 task = self.tw.tasks.get()
801 task.add_annotation('test annotation')
802 ann = task['annotations'][0]
804 self.assertEqual(len(task['annotations']), 0)
806 def test_removing_annotation_by_description(self):
807 task = self.tw.tasks.get()
808 task.add_annotation('test annotation')
809 task.remove_annotation('test annotation')
810 self.assertEqual(len(task['annotations']), 0)
812 def test_removing_annotation_by_obj(self):
813 task = self.tw.tasks.get()
814 task.add_annotation('test annotation')
815 ann = task['annotations'][0]
816 task.remove_annotation(ann)
817 self.assertEqual(len(task['annotations']), 0)
819 def test_annotation_after_modification(self):
820 task = self.tw.tasks.get()
821 task['project'] = 'test'
822 task.add_annotation('I should really do this task')
823 self.assertEqual(task['project'], 'test')
825 self.assertEqual(task['project'], 'test')
827 def test_serialize_annotations(self):
828 # Test that serializing annotations is possible
829 t = Task(self.tw, description="test")
832 t.add_annotation("annotation1")
833 t.add_annotation("annotation2")
835 data = t._serialize('annotations', t._data['annotations'])
837 self.assertEqual(len(data), 2)
838 self.assertEqual(type(data[0]), dict)
839 self.assertEqual(type(data[1]), dict)
841 self.assertEqual(data[0]['description'], "annotation1")
842 self.assertEqual(data[1]['description'], "annotation2")
845 class UnicodeTest(TasklibTest):
847 def test_unicode_task(self):
848 Task(self.tw, description="†åßk").save()
851 def test_non_unicode_task(self):
852 Task(self.tw, description="test task").save()
855 class ReadOnlyDictViewTest(unittest.TestCase):
858 self.sample = dict(l=[1,2,3], d={'k':'v'})
859 self.original_sample = copy.deepcopy(self.sample)
860 self.view = ReadOnlyDictView(self.sample)
862 def test_readonlydictview_getitem(self):
864 self.assertEqual(l, self.sample['l'])
866 # Assert that modification changed only copied value
868 self.assertNotEqual(l, self.sample['l'])
870 # Assert that viewed dict is not changed
871 self.assertEqual(self.sample, self.original_sample)
873 def test_readonlydictview_contains(self):
874 self.assertEqual('l' in self.view, 'l' in self.sample)
875 self.assertEqual('d' in self.view, 'd' in self.sample)
876 self.assertEqual('k' in self.view, 'k' in self.sample)
878 # Assert that viewed dict is not changed
879 self.assertEqual(self.sample, self.original_sample)
881 def test_readonlydictview_iter(self):
882 self.assertEqual(list(k for k in self.view),
883 list(k for k in self.sample))
885 # Assert the view is correct after modification
886 self.sample['new'] = 'value'
887 self.assertEqual(list(k for k in self.view),
888 list(k for k in self.sample))
890 def test_readonlydictview_len(self):
891 self.assertEqual(len(self.view), len(self.sample))
893 # Assert the view is correct after modification
894 self.sample['new'] = 'value'
895 self.assertEqual(len(self.view), len(self.sample))
897 def test_readonlydictview_get(self):
898 l = self.view.get('l')
899 self.assertEqual(l, self.sample.get('l'))
901 # Assert that modification changed only copied value
903 self.assertNotEqual(l, self.sample.get('l'))
905 # Assert that viewed dict is not changed
906 self.assertEqual(self.sample, self.original_sample)
908 def test_readonlydict_items(self):
909 view_items = self.view.items()
910 sample_items = list(self.sample.items())
911 self.assertEqual(view_items, sample_items)
913 view_items.append('newkey')
914 self.assertNotEqual(view_items, sample_items)
915 self.assertEqual(self.sample, self.original_sample)
917 def test_readonlydict_values(self):
918 view_values = self.view.values()
919 sample_values = list(self.sample.values())
920 self.assertEqual(view_values, sample_values)
922 view_list_item = list(filter(lambda x: type(x) is list,
924 view_list_item.append(4)
925 self.assertNotEqual(view_values, sample_values)
926 self.assertEqual(self.sample, self.original_sample)