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 def test_modify_number_of_tasks_at_once(self):
665 for i in range(1, 100):
666 Task(self.tw, description="test task %d" % i, tags=['test']).save()
668 self.tw.execute_command(['+test', 'mod', 'unified', 'description'])
671 class TaskFromHookTest(TasklibTest):
673 input_add_data = six.StringIO(
674 '{"description":"Buy some milk",'
675 '"entry":"20141118T050231Z",'
676 '"status":"pending",'
677 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
679 input_modify_data = six.StringIO(input_add_data.getvalue() + '\n' +
680 '{"description":"Buy some milk finally",'
681 '"entry":"20141118T050231Z",'
682 '"status":"completed",'
683 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
685 exported_raw_data = (
687 '"due":"20150101T232323Z",'
688 '"description":"test task"}')
690 def test_setting_up_from_add_hook_input(self):
691 t = Task.from_input(input_file=self.input_add_data, warrior=self.tw)
692 self.assertEqual(t['description'], "Buy some milk")
693 self.assertEqual(t.pending, True)
695 def test_setting_up_from_modified_hook_input(self):
696 t = Task.from_input(input_file=self.input_modify_data, modify=True,
698 self.assertEqual(t['description'], "Buy some milk finally")
699 self.assertEqual(t.pending, False)
700 self.assertEqual(t.completed, True)
702 self.assertEqual(t._original_data['status'], "pending")
703 self.assertEqual(t._original_data['description'], "Buy some milk")
704 self.assertEqual(set(t._modified_fields),
705 set(['status', 'description']))
707 def test_export_data(self):
708 t = Task(self.tw, description="test task",
710 due=pytz.utc.localize(datetime.datetime(2015,1,1,23,23,23)))
712 # Check that the output is a permutation of:
713 # {"project":"Home","description":"test task","due":"20150101232323Z"}
714 allowed_segments = self.exported_raw_data[1:-1].split(',')
716 '{' + ','.join(segments) + '}'
717 for segments in itertools.permutations(allowed_segments)
720 self.assertTrue(any(t.export_data() == expected
721 for expected in allowed_output))
723 class TimezoneAwareDatetimeTest(TasklibTest):
726 super(TimezoneAwareDatetimeTest, self).setUp()
727 self.zone = local_zone
728 self.localdate_naive = datetime.datetime(2015,2,2)
729 self.localtime_naive = datetime.datetime(2015,2,2,0,0,0)
730 self.localtime_aware = self.zone.localize(self.localtime_naive)
731 self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
733 def test_timezone_naive_datetime_setitem(self):
734 t = Task(self.tw, description="test task")
735 t['due'] = self.localtime_naive
736 self.assertEqual(t['due'], self.localtime_aware)
738 def test_timezone_naive_datetime_using_init(self):
739 t = Task(self.tw, description="test task", due=self.localtime_naive)
740 self.assertEqual(t['due'], self.localtime_aware)
742 def test_filter_by_naive_datetime(self):
743 t = Task(self.tw, description="task1", due=self.localtime_naive)
745 matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
746 self.assertEqual(len(matching_tasks), 1)
748 def test_serialize_naive_datetime(self):
749 t = Task(self.tw, description="task1", due=self.localtime_naive)
750 self.assertEqual(json.loads(t.export_data())['due'],
751 self.utctime_aware.strftime(DATE_FORMAT))
753 def test_timezone_naive_date_setitem(self):
754 t = Task(self.tw, description="test task")
755 t['due'] = self.localdate_naive
756 self.assertEqual(t['due'], self.localtime_aware)
758 def test_timezone_naive_date_using_init(self):
759 t = Task(self.tw, description="test task", due=self.localdate_naive)
760 self.assertEqual(t['due'], self.localtime_aware)
762 def test_filter_by_naive_date(self):
763 t = Task(self.tw, description="task1", due=self.localdate_naive)
765 matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
766 self.assertEqual(len(matching_tasks), 1)
768 def test_serialize_naive_date(self):
769 t = Task(self.tw, description="task1", due=self.localdate_naive)
770 self.assertEqual(json.loads(t.export_data())['due'],
771 self.utctime_aware.strftime(DATE_FORMAT))
773 def test_timezone_aware_datetime_setitem(self):
774 t = Task(self.tw, description="test task")
775 t['due'] = self.localtime_aware
776 self.assertEqual(t['due'], self.localtime_aware)
778 def test_timezone_aware_datetime_using_init(self):
779 t = Task(self.tw, description="test task", due=self.localtime_aware)
780 self.assertEqual(t['due'], self.localtime_aware)
782 def test_filter_by_aware_datetime(self):
783 t = Task(self.tw, description="task1", due=self.localtime_aware)
785 matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
786 self.assertEqual(len(matching_tasks), 1)
788 def test_serialize_aware_datetime(self):
789 t = Task(self.tw, description="task1", due=self.localtime_aware)
790 self.assertEqual(json.loads(t.export_data())['due'],
791 self.utctime_aware.strftime(DATE_FORMAT))
793 class DatetimeStringTest(TasklibTest):
795 def test_simple_now_conversion(self):
796 if self.tw.version < six.text_type('2.4.0'):
797 # Python2.6 does not support SkipTest. As a workaround
798 # mark the test as passed by exiting.
799 if getattr(unittest, 'SkipTest', None) is not None:
800 raise unittest.SkipTest()
804 t = Task(self.tw, description="test task", due="now")
805 now = local_zone.localize(datetime.datetime.now())
807 # Assert that both times are not more than 5 seconds apart
808 self.assertTrue((now - t['due']).total_seconds() < 5)
809 self.assertTrue((t['due'] - now).total_seconds() < 5)
811 def test_simple_eoy_conversion(self):
812 if self.tw.version < six.text_type('2.4.0'):
813 # Python2.6 does not support SkipTest. As a workaround
814 # mark the test as passed by exiting.
815 if getattr(unittest, 'SkipTest', None) is not None:
816 raise unittest.SkipTest()
820 t = Task(self.tw, description="test task", due="eoy")
821 now = local_zone.localize(datetime.datetime.now())
822 eoy = local_zone.localize(datetime.datetime(
830 self.assertEqual(eoy, t['due'])
832 def test_complex_eoy_conversion(self):
833 if self.tw.version < six.text_type('2.4.0'):
834 # Python2.6 does not support SkipTest. As a workaround
835 # mark the test as passed by exiting.
836 if getattr(unittest, 'SkipTest', None) is not None:
837 raise unittest.SkipTest()
841 t = Task(self.tw, description="test task", due="eoy - 4 months")
842 now = local_zone.localize(datetime.datetime.now())
843 due_date = local_zone.localize(datetime.datetime(
850 )) - datetime.timedelta(0,4 * 30 * 86400)
851 self.assertEqual(due_date, t['due'])
853 def test_filtering_with_string_datetime(self):
854 if self.tw.version < six.text_type('2.4.0'):
855 # Python2.6 does not support SkipTest. As a workaround
856 # mark the test as passed by exiting.
857 if getattr(unittest, 'SkipTest', None) is not None:
858 raise unittest.SkipTest()
862 t = Task(self.tw, description="test task",
863 due=datetime.datetime.now() - datetime.timedelta(0,2))
865 self.assertEqual(len(self.tw.tasks.filter(due__before="now")), 1)
867 class AnnotationTest(TasklibTest):
870 super(AnnotationTest, self).setUp()
871 Task(self.tw, description="test task").save()
873 def test_adding_annotation(self):
874 task = self.tw.tasks.get()
875 task.add_annotation('test annotation')
876 self.assertEqual(len(task['annotations']), 1)
877 ann = task['annotations'][0]
878 self.assertEqual(ann['description'], 'test annotation')
880 def test_removing_annotation(self):
881 task = self.tw.tasks.get()
882 task.add_annotation('test annotation')
883 ann = task['annotations'][0]
885 self.assertEqual(len(task['annotations']), 0)
887 def test_removing_annotation_by_description(self):
888 task = self.tw.tasks.get()
889 task.add_annotation('test annotation')
890 task.remove_annotation('test annotation')
891 self.assertEqual(len(task['annotations']), 0)
893 def test_removing_annotation_by_obj(self):
894 task = self.tw.tasks.get()
895 task.add_annotation('test annotation')
896 ann = task['annotations'][0]
897 task.remove_annotation(ann)
898 self.assertEqual(len(task['annotations']), 0)
900 def test_annotation_after_modification(self):
901 task = self.tw.tasks.get()
902 task['project'] = 'test'
903 task.add_annotation('I should really do this task')
904 self.assertEqual(task['project'], 'test')
906 self.assertEqual(task['project'], 'test')
908 def test_serialize_annotations(self):
909 # Test that serializing annotations is possible
910 t = Task(self.tw, description="test")
913 t.add_annotation("annotation1")
914 t.add_annotation("annotation2")
916 data = t._serialize('annotations', t._data['annotations'])
918 self.assertEqual(len(data), 2)
919 self.assertEqual(type(data[0]), dict)
920 self.assertEqual(type(data[1]), dict)
922 self.assertEqual(data[0]['description'], "annotation1")
923 self.assertEqual(data[1]['description'], "annotation2")
926 class UnicodeTest(TasklibTest):
928 def test_unicode_task(self):
929 Task(self.tw, description="†åßk").save()
932 def test_non_unicode_task(self):
933 Task(self.tw, description="test task").save()
936 class ReadOnlyDictViewTest(unittest.TestCase):
939 self.sample = dict(l=[1,2,3], d={'k':'v'})
940 self.original_sample = copy.deepcopy(self.sample)
941 self.view = ReadOnlyDictView(self.sample)
943 def test_readonlydictview_getitem(self):
945 self.assertEqual(l, self.sample['l'])
947 # Assert that modification changed only copied value
949 self.assertNotEqual(l, self.sample['l'])
951 # Assert that viewed dict is not changed
952 self.assertEqual(self.sample, self.original_sample)
954 def test_readonlydictview_contains(self):
955 self.assertEqual('l' in self.view, 'l' in self.sample)
956 self.assertEqual('d' in self.view, 'd' in self.sample)
957 self.assertEqual('k' in self.view, 'k' in self.sample)
959 # Assert that viewed dict is not changed
960 self.assertEqual(self.sample, self.original_sample)
962 def test_readonlydictview_iter(self):
963 self.assertEqual(list(k for k in self.view),
964 list(k for k in self.sample))
966 # Assert the view is correct after modification
967 self.sample['new'] = 'value'
968 self.assertEqual(list(k for k in self.view),
969 list(k for k in self.sample))
971 def test_readonlydictview_len(self):
972 self.assertEqual(len(self.view), len(self.sample))
974 # Assert the view is correct after modification
975 self.sample['new'] = 'value'
976 self.assertEqual(len(self.view), len(self.sample))
978 def test_readonlydictview_get(self):
979 l = self.view.get('l')
980 self.assertEqual(l, self.sample.get('l'))
982 # Assert that modification changed only copied value
984 self.assertNotEqual(l, self.sample.get('l'))
986 # Assert that viewed dict is not changed
987 self.assertEqual(self.sample, self.original_sample)
989 def test_readonlydict_items(self):
990 view_items = self.view.items()
991 sample_items = list(self.sample.items())
992 self.assertEqual(view_items, sample_items)
994 view_items.append('newkey')
995 self.assertNotEqual(view_items, sample_items)
996 self.assertEqual(self.sample, self.original_sample)
998 def test_readonlydict_values(self):
999 view_values = self.view.values()
1000 sample_values = list(self.sample.values())
1001 self.assertEqual(view_values, sample_values)
1003 view_list_item = list(filter(lambda x: type(x) is list,
1005 view_list_item.append(4)
1006 self.assertNotEqual(view_values, sample_values)
1007 self.assertEqual(self.sample, self.original_sample)