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)
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 raise unittest.SkipTest()
196 t = Task(self.tw, description="test")
199 tasks = self.tw.tasks.filter(modified=t['modified'])
200 self.assertEqual(list(tasks), [t])
202 def test_filter_dummy_by_scheduled(self):
203 t = Task(self.tw, description="test")
206 tasks = self.tw.tasks.filter(scheduled=t['scheduled'])
207 self.assertEqual(list(tasks), [t])
209 def test_filter_dummy_by_tags(self):
210 t = Task(self.tw, description="test", tags=["home"])
213 tasks = self.tw.tasks.filter(tags=t['tags'])
214 self.assertEqual(list(tasks), [t])
216 def test_filter_dummy_by_projects(self):
217 t = Task(self.tw, description="test", project="random")
220 tasks = self.tw.tasks.filter(project=t['project'])
221 self.assertEqual(list(tasks), [t])
223 def test_filter_by_priority(self):
224 t = Task(self.tw, description="test", priority="H")
227 tasks = self.tw.tasks.filter(priority=t['priority'])
228 self.assertEqual(list(tasks), [t])
231 class TaskTest(TasklibTest):
233 def test_create_unsaved_task(self):
234 # Make sure a new task is not saved unless explicitly called for
235 t = Task(self.tw, description="test task")
236 self.assertEqual(len(self.tw.tasks.all()), 0)
238 # TODO: once python 2.6 compatiblity is over, use context managers here
239 # and in all subsequent tests for assertRaises
241 def test_delete_unsaved_task(self):
242 t = Task(self.tw, description="test task")
243 self.assertRaises(Task.NotSaved, t.delete)
245 def test_complete_unsaved_task(self):
246 t = Task(self.tw, description="test task")
247 self.assertRaises(Task.NotSaved, t.done)
249 def test_refresh_unsaved_task(self):
250 t = Task(self.tw, description="test task")
251 self.assertRaises(Task.NotSaved, t.refresh)
253 def test_start_unsaved_task(self):
254 t = Task(self.tw, description="test task")
255 self.assertRaises(Task.NotSaved, t.start)
257 def test_delete_deleted_task(self):
258 t = Task(self.tw, description="test task")
262 self.assertRaises(Task.DeletedTask, t.delete)
264 def test_complete_completed_task(self):
265 t = Task(self.tw, description="test task")
269 self.assertRaises(Task.CompletedTask, t.done)
271 def test_start_completed_task(self):
272 t = Task(self.tw, description="test task")
276 self.assertRaises(Task.CompletedTask, t.start)
278 def test_complete_deleted_task(self):
279 t = Task(self.tw, description="test task")
283 self.assertRaises(Task.DeletedTask, t.done)
285 def test_start_completed_task(self):
286 t = Task(self.tw, description="test task")
290 self.assertRaises(Task.CompletedTask, t.start)
292 def test_starting_task(self):
293 t = Task(self.tw, description="test task")
294 now = t.datetime_normalizer(datetime.datetime.now())
298 self.assertTrue(now.replace(microsecond=0) <= t['start'])
299 self.assertEqual(t['status'], 'pending')
301 def test_completing_task(self):
302 t = Task(self.tw, description="test task")
303 now = t.datetime_normalizer(datetime.datetime.now())
307 self.assertTrue(now.replace(microsecond=0) <= t['end'])
308 self.assertEqual(t['status'], 'completed')
310 def test_deleting_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['end'])
317 self.assertEqual(t['status'], 'deleted')
319 def test_modify_simple_attribute_without_space(self):
320 t = Task(self.tw, description="test")
323 self.assertEquals(t['description'], "test")
325 t['description'] = "test-modified"
328 self.assertEquals(t['description'], "test-modified")
330 def test_modify_simple_attribute_with_space(self):
331 # Space can pose problems with parsing
332 t = Task(self.tw, description="test task")
335 self.assertEquals(t['description'], "test task")
337 t['description'] = "test task modified"
340 self.assertEquals(t['description'], "test task modified")
342 def test_empty_dependency_set_of_unsaved_task(self):
343 t = Task(self.tw, description="test task")
344 self.assertEqual(t['depends'], set())
346 def test_empty_dependency_set_of_saved_task(self):
347 t = Task(self.tw, description="test task")
349 self.assertEqual(t['depends'], set())
351 def test_set_unsaved_task_as_dependency(self):
352 # Adds only one dependency to task with no dependencies
353 t = Task(self.tw, description="test task")
354 dependency = Task(self.tw, description="needs to be done first")
356 # We only save the parent task, dependency task is unsaved
358 t['depends'] = set([dependency])
360 self.assertRaises(Task.NotSaved, t.save)
362 def test_set_simple_dependency_set(self):
363 # Adds only one dependency to task with no dependencies
364 t = Task(self.tw, description="test task")
365 dependency = Task(self.tw, description="needs to be done first")
370 t['depends'] = set([dependency])
372 self.assertEqual(t['depends'], set([dependency]))
374 def test_set_complex_dependency_set(self):
375 # Adds two dependencies to task with no dependencies
376 t = Task(self.tw, description="test task")
377 dependency1 = Task(self.tw, description="needs to be done first")
378 dependency2 = Task(self.tw, description="needs to be done second")
384 t['depends'] = set([dependency1, dependency2])
386 self.assertEqual(t['depends'], set([dependency1, dependency2]))
388 def test_remove_from_dependency_set(self):
389 # Removes dependency from task with two dependencies
390 t = Task(self.tw, description="test task")
391 dependency1 = Task(self.tw, description="needs to be done first")
392 dependency2 = Task(self.tw, description="needs to be done second")
397 t['depends'] = set([dependency1, dependency2])
400 t['depends'].remove(dependency2)
403 self.assertEqual(t['depends'], set([dependency1]))
405 def test_add_to_dependency_set(self):
406 # Adds dependency to task with one dependencies
407 t = Task(self.tw, description="test task")
408 dependency1 = Task(self.tw, description="needs to be done first")
409 dependency2 = Task(self.tw, description="needs to be done second")
414 t['depends'] = set([dependency1])
417 t['depends'].add(dependency2)
420 self.assertEqual(t['depends'], set([dependency1, dependency2]))
422 def test_add_to_empty_dependency_set(self):
423 # Adds dependency to task with one dependencies
424 t = Task(self.tw, description="test task")
425 dependency = Task(self.tw, description="needs to be done first")
429 t['depends'].add(dependency)
432 self.assertEqual(t['depends'], set([dependency]))
434 def test_simple_dependency_set_save_repeatedly(self):
435 # Adds only one dependency to task with no dependencies
436 t = Task(self.tw, description="test task")
437 dependency = Task(self.tw, description="needs to be done first")
440 t['depends'] = set([dependency])
443 # We taint the task, but keep depends intact
444 t['description'] = "test task modified"
447 self.assertEqual(t['depends'], set([dependency]))
449 # We taint the task, but assign the same set to the depends
450 t['depends'] = set([dependency])
451 t['description'] = "test task modified again"
454 self.assertEqual(t['depends'], set([dependency]))
456 def test_compare_different_tasks(self):
457 # Negative: compare two different tasks
458 t1 = Task(self.tw, description="test task")
459 t2 = Task(self.tw, description="test task")
464 self.assertEqual(t1 == t2, False)
466 def test_compare_same_task_object(self):
467 # Compare Task object wit itself
468 t = Task(self.tw, description="test task")
471 self.assertEqual(t == t, True)
473 def test_compare_same_task(self):
474 # Compare the same task using two different objects
475 t1 = Task(self.tw, description="test task")
478 t2 = self.tw.tasks.get(uuid=t1['uuid'])
479 self.assertEqual(t1 == t2, True)
481 def test_compare_unsaved_tasks(self):
482 # t1 and t2 are unsaved tasks, considered to be unequal
483 # despite the content of data
484 t1 = Task(self.tw, description="test task")
485 t2 = Task(self.tw, description="test task")
487 self.assertEqual(t1 == t2, False)
489 def test_hash_unsaved_tasks(self):
490 # Considered equal, it's the same object
491 t1 = Task(self.tw, description="test task")
493 self.assertEqual(hash(t1) == hash(t2), True)
495 def test_hash_same_task(self):
496 # Compare the hash of the task using two different objects
497 t1 = Task(self.tw, description="test task")
500 t2 = self.tw.tasks.get(uuid=t1['uuid'])
501 self.assertEqual(t1.__hash__(), t2.__hash__())
503 def test_adding_task_with_priority(self):
504 t = Task(self.tw, description="test task", priority="M")
507 def test_removing_priority_with_none(self):
508 t = Task(self.tw, description="test task", priority="L")
511 # Remove the priority mark
515 # Assert that priority is not there after saving
516 self.assertEqual(t['priority'], None)
518 def test_adding_task_with_due_time(self):
519 t = Task(self.tw, description="test task", due=datetime.datetime.now())
522 def test_removing_due_time_with_none(self):
523 t = Task(self.tw, description="test task", due=datetime.datetime.now())
526 # Remove the due timestamp
530 # Assert that due timestamp is no longer there
531 self.assertEqual(t['due'], None)
533 def test_modified_fields_new_task(self):
536 # This should be empty with new task
537 self.assertEqual(set(t._modified_fields), set())
540 t['description'] = "test task"
541 self.assertEqual(set(t._modified_fields), set(['description']))
543 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
544 self.assertEqual(set(t._modified_fields), set(['description', 'due']))
546 t['project'] = "test project"
547 self.assertEqual(set(t._modified_fields),
548 set(['description', 'due', 'project']))
550 # List of modified fields should clear out when saved
552 self.assertEqual(set(t._modified_fields), set())
554 # Reassigning the fields with the same values now should not produce
556 t['description'] = "test task"
557 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
558 t['project'] = "test project"
559 self.assertEqual(set(t._modified_fields), set())
561 def test_modified_fields_loaded_task(self):
565 t['description'] = "test task"
566 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
567 t['project'] = "test project"
569 dependency = Task(self.tw, description="dependency")
571 t['depends'] = set([dependency])
573 # List of modified fields should clear out when saved
575 self.assertEqual(set(t._modified_fields), set())
577 # Get the task by using a filter by UUID
578 t2 = self.tw.tasks.get(uuid=t['uuid'])
580 # Reassigning the fields with the same values now should not produce
582 t['description'] = "test task"
583 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
584 t['project'] = "test project"
585 t['depends'] = set([dependency])
586 self.assertEqual(set(t._modified_fields), set())
588 def test_modified_fields_not_affected_by_reading(self):
591 for field in TASK_STANDARD_ATTRS:
594 self.assertEqual(set(t._modified_fields), set())
596 def test_setting_read_only_attrs_through_init(self):
597 # Test that we are unable to set readonly attrs through __init__
598 for readonly_key in Task.read_only_fields:
599 kwargs = {'description': 'test task', readonly_key: 'value'}
600 self.assertRaises(RuntimeError,
601 lambda: Task(self.tw, **kwargs))
603 def test_setting_read_only_attrs_through_setitem(self):
604 # Test that we are unable to set readonly attrs through __init__
605 for readonly_key in Task.read_only_fields:
606 t = Task(self.tw, description='test task')
607 self.assertRaises(RuntimeError,
608 lambda: t.__setitem__(readonly_key, 'value'))
610 def test_saving_unmodified_task(self):
611 t = Task(self.tw, description="test task")
615 def test_adding_tag_by_appending(self):
616 t = Task(self.tw, description="test task", tags=['test1'])
618 t['tags'].append('test2')
620 self.assertEqual(t['tags'], ['test1', 'test2'])
622 def test_adding_tag_by_appending_empty(self):
623 t = Task(self.tw, description="test task")
625 t['tags'].append('test')
627 self.assertEqual(t['tags'], ['test'])
629 def test_serializers_returning_empty_string_for_none(self):
630 # Test that any serializer returns '' when passed None
632 serializers = [getattr(t, serializer_name) for serializer_name in
633 filter(lambda x: x.startswith('serialize_'), dir(t))]
634 for serializer in serializers:
635 self.assertEqual(serializer(None), '')
637 def test_deserializer_returning_empty_value_for_empty_string(self):
638 # Test that any deserializer returns empty value when passed ''
640 deserializers = [getattr(t, deserializer_name) for deserializer_name in
641 filter(lambda x: x.startswith('deserialize_'), dir(t))]
642 for deserializer in deserializers:
643 self.assertTrue(deserializer('') in (None, [], set()))
645 def test_normalizers_handling_none(self):
646 # Test that any normalizer can handle None as a valid value
649 for key in TASK_STANDARD_ATTRS:
650 t._normalize(key, None)
652 def test_recurrent_task_generation(self):
653 today = datetime.date.today()
654 t = Task(self.tw, description="brush teeth",
655 due=today, recur="daily")
657 self.assertEqual(len(self.tw.tasks.pending()), 2)
659 class TaskFromHookTest(TasklibTest):
661 input_add_data = six.StringIO(
662 '{"description":"Buy some milk",'
663 '"entry":"20141118T050231Z",'
664 '"status":"pending",'
665 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
667 input_modify_data = six.StringIO(input_add_data.getvalue() + '\n' +
668 '{"description":"Buy some milk finally",'
669 '"entry":"20141118T050231Z",'
670 '"status":"completed",'
671 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
673 exported_raw_data = (
675 '"due":"20150101T232323Z",'
676 '"description":"test task"}')
678 def test_setting_up_from_add_hook_input(self):
679 t = Task.from_input(input_file=self.input_add_data, warrior=self.tw)
680 self.assertEqual(t['description'], "Buy some milk")
681 self.assertEqual(t.pending, True)
683 def test_setting_up_from_modified_hook_input(self):
684 t = Task.from_input(input_file=self.input_modify_data, modify=True,
686 self.assertEqual(t['description'], "Buy some milk finally")
687 self.assertEqual(t.pending, False)
688 self.assertEqual(t.completed, True)
690 self.assertEqual(t._original_data['status'], "pending")
691 self.assertEqual(t._original_data['description'], "Buy some milk")
692 self.assertEqual(set(t._modified_fields),
693 set(['status', 'description']))
695 def test_export_data(self):
696 t = Task(self.tw, description="test task",
698 due=pytz.utc.localize(datetime.datetime(2015,1,1,23,23,23)))
700 # Check that the output is a permutation of:
701 # {"project":"Home","description":"test task","due":"20150101232323Z"}
702 allowed_segments = self.exported_raw_data[1:-1].split(',')
704 '{' + ','.join(segments) + '}'
705 for segments in itertools.permutations(allowed_segments)
708 self.assertTrue(any(t.export_data() == expected
709 for expected in allowed_output))
711 class TimezoneAwareDatetimeTest(TasklibTest):
714 super(TimezoneAwareDatetimeTest, self).setUp()
715 self.zone = local_zone
716 self.localdate_naive = datetime.datetime(2015,2,2)
717 self.localtime_naive = datetime.datetime(2015,2,2,0,0,0)
718 self.localtime_aware = self.zone.localize(self.localtime_naive)
719 self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
721 def test_timezone_naive_datetime_setitem(self):
722 t = Task(self.tw, description="test task")
723 t['due'] = self.localtime_naive
724 self.assertEqual(t['due'], self.localtime_aware)
726 def test_timezone_naive_datetime_using_init(self):
727 t = Task(self.tw, description="test task", due=self.localtime_naive)
728 self.assertEqual(t['due'], self.localtime_aware)
730 def test_filter_by_naive_datetime(self):
731 t = Task(self.tw, description="task1", due=self.localtime_naive)
733 matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
734 self.assertEqual(len(matching_tasks), 1)
736 def test_serialize_naive_datetime(self):
737 t = Task(self.tw, description="task1", due=self.localtime_naive)
738 self.assertEqual(json.loads(t.export_data())['due'],
739 self.utctime_aware.strftime(DATE_FORMAT))
741 def test_timezone_naive_date_setitem(self):
742 t = Task(self.tw, description="test task")
743 t['due'] = self.localdate_naive
744 self.assertEqual(t['due'], self.localtime_aware)
746 def test_timezone_naive_date_using_init(self):
747 t = Task(self.tw, description="test task", due=self.localdate_naive)
748 self.assertEqual(t['due'], self.localtime_aware)
750 def test_filter_by_naive_date(self):
751 t = Task(self.tw, description="task1", due=self.localdate_naive)
753 matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
754 self.assertEqual(len(matching_tasks), 1)
756 def test_serialize_naive_date(self):
757 t = Task(self.tw, description="task1", due=self.localdate_naive)
758 self.assertEqual(json.loads(t.export_data())['due'],
759 self.utctime_aware.strftime(DATE_FORMAT))
761 def test_timezone_aware_datetime_setitem(self):
762 t = Task(self.tw, description="test task")
763 t['due'] = self.localtime_aware
764 self.assertEqual(t['due'], self.localtime_aware)
766 def test_timezone_aware_datetime_using_init(self):
767 t = Task(self.tw, description="test task", due=self.localtime_aware)
768 self.assertEqual(t['due'], self.localtime_aware)
770 def test_filter_by_aware_datetime(self):
771 t = Task(self.tw, description="task1", due=self.localtime_aware)
773 matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
774 self.assertEqual(len(matching_tasks), 1)
776 def test_serialize_aware_datetime(self):
777 t = Task(self.tw, description="task1", due=self.localtime_aware)
778 self.assertEqual(json.loads(t.export_data())['due'],
779 self.utctime_aware.strftime(DATE_FORMAT))
781 class AnnotationTest(TasklibTest):
784 super(AnnotationTest, self).setUp()
785 Task(self.tw, description="test task").save()
787 def test_adding_annotation(self):
788 task = self.tw.tasks.get()
789 task.add_annotation('test annotation')
790 self.assertEqual(len(task['annotations']), 1)
791 ann = task['annotations'][0]
792 self.assertEqual(ann['description'], 'test annotation')
794 def test_removing_annotation(self):
795 task = self.tw.tasks.get()
796 task.add_annotation('test annotation')
797 ann = task['annotations'][0]
799 self.assertEqual(len(task['annotations']), 0)
801 def test_removing_annotation_by_description(self):
802 task = self.tw.tasks.get()
803 task.add_annotation('test annotation')
804 task.remove_annotation('test annotation')
805 self.assertEqual(len(task['annotations']), 0)
807 def test_removing_annotation_by_obj(self):
808 task = self.tw.tasks.get()
809 task.add_annotation('test annotation')
810 ann = task['annotations'][0]
811 task.remove_annotation(ann)
812 self.assertEqual(len(task['annotations']), 0)
814 def test_annotation_after_modification(self):
815 task = self.tw.tasks.get()
816 task['project'] = 'test'
817 task.add_annotation('I should really do this task')
818 self.assertEqual(task['project'], 'test')
820 self.assertEqual(task['project'], 'test')
822 def test_serialize_annotations(self):
823 # Test that serializing annotations is possible
824 t = Task(self.tw, description="test")
827 t.add_annotation("annotation1")
828 t.add_annotation("annotation2")
830 data = t._serialize('annotations', t._data['annotations'])
832 self.assertEqual(len(data), 2)
833 self.assertEqual(type(data[0]), dict)
834 self.assertEqual(type(data[1]), dict)
836 self.assertEqual(data[0]['description'], "annotation1")
837 self.assertEqual(data[1]['description'], "annotation2")
840 class UnicodeTest(TasklibTest):
842 def test_unicode_task(self):
843 Task(self.tw, description="†åßk").save()
846 def test_non_unicode_task(self):
847 Task(self.tw, description="test task").save()
850 class ReadOnlyDictViewTest(unittest.TestCase):
853 self.sample = dict(l=[1,2,3], d={'k':'v'})
854 self.original_sample = copy.deepcopy(self.sample)
855 self.view = ReadOnlyDictView(self.sample)
857 def test_readonlydictview_getitem(self):
859 self.assertEqual(l, self.sample['l'])
861 # Assert that modification changed only copied value
863 self.assertNotEqual(l, self.sample['l'])
865 # Assert that viewed dict is not changed
866 self.assertEqual(self.sample, self.original_sample)
868 def test_readonlydictview_contains(self):
869 self.assertEqual('l' in self.view, 'l' in self.sample)
870 self.assertEqual('d' in self.view, 'd' in self.sample)
871 self.assertEqual('k' in self.view, 'k' in self.sample)
873 # Assert that viewed dict is not changed
874 self.assertEqual(self.sample, self.original_sample)
876 def test_readonlydictview_iter(self):
877 self.assertEqual(list(k for k in self.view),
878 list(k for k in self.sample))
880 # Assert the view is correct after modification
881 self.sample['new'] = 'value'
882 self.assertEqual(list(k for k in self.view),
883 list(k for k in self.sample))
885 def test_readonlydictview_len(self):
886 self.assertEqual(len(self.view), len(self.sample))
888 # Assert the view is correct after modification
889 self.sample['new'] = 'value'
890 self.assertEqual(len(self.view), len(self.sample))
892 def test_readonlydictview_get(self):
893 l = self.view.get('l')
894 self.assertEqual(l, self.sample.get('l'))
896 # Assert that modification changed only copied value
898 self.assertNotEqual(l, self.sample.get('l'))
900 # Assert that viewed dict is not changed
901 self.assertEqual(self.sample, self.original_sample)
903 def test_readonlydict_items(self):
904 view_items = self.view.items()
905 sample_items = list(self.sample.items())
906 self.assertEqual(view_items, sample_items)
908 view_items.append('newkey')
909 self.assertNotEqual(view_items, sample_items)
910 self.assertEqual(self.sample, self.original_sample)
912 def test_readonlydict_values(self):
913 view_values = self.view.values()
914 sample_values = list(self.sample.values())
915 self.assertEqual(view_values, sample_values)
917 view_list_item = list(filter(lambda x: type(x) is list,
919 view_list_item.append(4)
920 self.assertNotEqual(view_values, sample_values)
921 self.assertEqual(self.sample, self.original_sample)