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 .task import TaskWarrior, Task, ReadOnlyDictView, local_zone, DATE_FORMAT
16 # http://taskwarrior.org/docs/design/task.html , Section: The Attributes
17 TASK_STANDARD_ATTRS = (
40 total_seconds_2_6 = lambda x: x.microseconds / 1e6 + x.seconds + x.days * 24 * 3600
43 class TasklibTest(unittest.TestCase):
46 self.tmp = tempfile.mkdtemp(dir='.')
47 self.tw = TaskWarrior(data_location=self.tmp, taskrc_location='/')
50 shutil.rmtree(self.tmp)
53 class TaskFilterTest(TasklibTest):
55 def test_all_empty(self):
56 self.assertEqual(len(self.tw.tasks.all()), 0)
58 def test_all_non_empty(self):
59 Task(self.tw, description="test task").save()
60 self.assertEqual(len(self.tw.tasks.all()), 1)
61 self.assertEqual(self.tw.tasks.all()[0]['description'], 'test task')
62 self.assertEqual(self.tw.tasks.all()[0]['status'], 'pending')
64 def test_pending_non_empty(self):
65 Task(self.tw, description="test task").save()
66 self.assertEqual(len(self.tw.tasks.pending()), 1)
67 self.assertEqual(self.tw.tasks.pending()[0]['description'],
69 self.assertEqual(self.tw.tasks.pending()[0]['status'], 'pending')
71 def test_completed_empty(self):
72 Task(self.tw, description="test task").save()
73 self.assertEqual(len(self.tw.tasks.completed()), 0)
75 def test_completed_non_empty(self):
76 Task(self.tw, description="test task").save()
77 self.assertEqual(len(self.tw.tasks.completed()), 0)
78 self.tw.tasks.all()[0].done()
79 self.assertEqual(len(self.tw.tasks.completed()), 1)
81 def test_filtering_by_attribute(self):
82 Task(self.tw, description="no priority task").save()
83 Task(self.tw, priority="H", description="high priority task").save()
84 self.assertEqual(len(self.tw.tasks.all()), 2)
86 # Assert that the correct number of tasks is returned
87 self.assertEqual(len(self.tw.tasks.filter(priority="H")), 1)
89 # Assert that the correct tasks are returned
90 high_priority_task = self.tw.tasks.get(priority="H")
91 self.assertEqual(high_priority_task['description'], "high priority task")
93 def test_filtering_by_empty_attribute(self):
94 Task(self.tw, description="no priority task").save()
95 Task(self.tw, priority="H", description="high priority task").save()
96 self.assertEqual(len(self.tw.tasks.all()), 2)
98 # Assert that the correct number of tasks is returned
99 self.assertEqual(len(self.tw.tasks.filter(priority=None)), 1)
101 # Assert that the correct tasks are returned
102 no_priority_task = self.tw.tasks.get(priority=None)
103 self.assertEqual(no_priority_task['description'], "no priority task")
105 def test_filter_for_task_with_space_in_descripition(self):
106 task = Task(self.tw, description="test task")
109 filtered_task = self.tw.tasks.get(description="test task")
110 self.assertEqual(filtered_task['description'], "test task")
112 def test_filter_for_task_without_space_in_descripition(self):
113 task = Task(self.tw, description="test")
116 filtered_task = self.tw.tasks.get(description="test")
117 self.assertEqual(filtered_task['description'], "test")
119 def test_filter_for_task_with_space_in_project(self):
120 task = Task(self.tw, description="test", project="random project")
123 filtered_task = self.tw.tasks.get(project="random project")
124 self.assertEqual(filtered_task['project'], "random project")
126 def test_filter_for_task_without_space_in_project(self):
127 task = Task(self.tw, description="test", project="random")
130 filtered_task = self.tw.tasks.get(project="random")
131 self.assertEqual(filtered_task['project'], "random")
133 def test_filter_with_empty_uuid(self):
134 self.assertRaises(ValueError, lambda: self.tw.tasks.get(uuid=''))
136 def test_filter_dummy_by_status(self):
137 t = Task(self.tw, description="test")
140 tasks = self.tw.tasks.filter(status=t['status'])
141 self.assertEqual(list(tasks), [t])
143 def test_filter_dummy_by_uuid(self):
144 t = Task(self.tw, description="test")
147 tasks = self.tw.tasks.filter(uuid=t['uuid'])
148 self.assertEqual(list(tasks), [t])
150 def test_filter_dummy_by_entry(self):
151 t = Task(self.tw, description="test")
154 tasks = self.tw.tasks.filter(entry=t['entry'])
155 self.assertEqual(list(tasks), [t])
157 def test_filter_dummy_by_description(self):
158 t = Task(self.tw, description="test")
161 tasks = self.tw.tasks.filter(description=t['description'])
162 self.assertEqual(list(tasks), [t])
164 def test_filter_dummy_by_start(self):
165 t = Task(self.tw, description="test")
169 tasks = self.tw.tasks.filter(start=t['start'])
170 self.assertEqual(list(tasks), [t])
172 def test_filter_dummy_by_end(self):
173 t = Task(self.tw, description="test")
177 tasks = self.tw.tasks.filter(end=t['end'])
178 self.assertEqual(list(tasks), [t])
180 def test_filter_dummy_by_due(self):
181 t = Task(self.tw, description="test", due=datetime.datetime.now())
184 tasks = self.tw.tasks.filter(due=t['due'])
185 self.assertEqual(list(tasks), [t])
187 def test_filter_dummy_by_until(self):
188 t = Task(self.tw, description="test")
191 tasks = self.tw.tasks.filter(until=t['until'])
192 self.assertEqual(list(tasks), [t])
194 def test_filter_dummy_by_modified(self):
195 # Older TW version does not support bumping modified
197 if self.tw.version < six.text_type('2.2.0'):
198 # Python2.6 does not support SkipTest. As a workaround
199 # mark the test as passed by exiting.
200 if getattr(unittest, 'SkipTest', None) is not None:
201 raise unittest.SkipTest()
205 t = Task(self.tw, description="test")
208 tasks = self.tw.tasks.filter(modified=t['modified'])
209 self.assertEqual(list(tasks), [t])
211 def test_filter_dummy_by_scheduled(self):
212 t = Task(self.tw, description="test")
215 tasks = self.tw.tasks.filter(scheduled=t['scheduled'])
216 self.assertEqual(list(tasks), [t])
218 def test_filter_dummy_by_tags(self):
219 t = Task(self.tw, description="test", tags=["home"])
222 tasks = self.tw.tasks.filter(tags=t['tags'])
223 self.assertEqual(list(tasks), [t])
225 def test_filter_dummy_by_projects(self):
226 t = Task(self.tw, description="test", project="random")
229 tasks = self.tw.tasks.filter(project=t['project'])
230 self.assertEqual(list(tasks), [t])
232 def test_filter_by_priority(self):
233 t = Task(self.tw, description="test", priority="H")
236 tasks = self.tw.tasks.filter(priority=t['priority'])
237 self.assertEqual(list(tasks), [t])
240 class TaskTest(TasklibTest):
242 def test_create_unsaved_task(self):
243 # Make sure a new task is not saved unless explicitly called for
244 t = Task(self.tw, description="test task")
245 self.assertEqual(len(self.tw.tasks.all()), 0)
247 # TODO: once python 2.6 compatiblity is over, use context managers here
248 # and in all subsequent tests for assertRaises
250 def test_delete_unsaved_task(self):
251 t = Task(self.tw, description="test task")
252 self.assertRaises(Task.NotSaved, t.delete)
254 def test_complete_unsaved_task(self):
255 t = Task(self.tw, description="test task")
256 self.assertRaises(Task.NotSaved, t.done)
258 def test_refresh_unsaved_task(self):
259 t = Task(self.tw, description="test task")
260 self.assertRaises(Task.NotSaved, t.refresh)
262 def test_start_unsaved_task(self):
263 t = Task(self.tw, description="test task")
264 self.assertRaises(Task.NotSaved, t.start)
266 def test_delete_deleted_task(self):
267 t = Task(self.tw, description="test task")
271 self.assertRaises(Task.DeletedTask, t.delete)
273 def test_complete_completed_task(self):
274 t = Task(self.tw, description="test task")
278 self.assertRaises(Task.CompletedTask, t.done)
280 def test_start_completed_task(self):
281 t = Task(self.tw, description="test task")
285 self.assertRaises(Task.CompletedTask, t.start)
287 def test_add_completed_task(self):
288 t = Task(self.tw, description="test", status="completed",
289 end=datetime.datetime.now())
292 def test_add_multiple_completed_tasks(self):
293 t1 = Task(self.tw, description="test1", status="completed",
294 end=datetime.datetime.now())
295 t2 = Task(self.tw, description="test2", status="completed",
296 end=datetime.datetime.now())
300 def test_complete_deleted_task(self):
301 t = Task(self.tw, description="test task")
305 self.assertRaises(Task.DeletedTask, t.done)
307 def test_starting_task(self):
308 t = Task(self.tw, description="test task")
309 now = t.datetime_normalizer(datetime.datetime.now())
313 self.assertTrue(now.replace(microsecond=0) <= t['start'])
314 self.assertEqual(t['status'], 'pending')
316 def test_completing_task(self):
317 t = Task(self.tw, description="test task")
318 now = t.datetime_normalizer(datetime.datetime.now())
322 self.assertTrue(now.replace(microsecond=0) <= t['end'])
323 self.assertEqual(t['status'], 'completed')
325 def test_deleting_task(self):
326 t = Task(self.tw, description="test task")
327 now = t.datetime_normalizer(datetime.datetime.now())
331 self.assertTrue(now.replace(microsecond=0) <= t['end'])
332 self.assertEqual(t['status'], 'deleted')
334 def test_started_task_active(self):
335 t = Task(self.tw, description="test task")
338 self.assertTrue(t.active)
340 def test_unstarted_task_inactive(self):
341 t = Task(self.tw, description="test task")
342 self.assertFalse(t.active)
344 self.assertFalse(t.active)
346 def test_start_active_task(self):
347 t = Task(self.tw, description="test task")
350 self.assertRaises(Task.ActiveTask, t.start)
352 def test_stop_completed_task(self):
353 t = Task(self.tw, description="test task")
358 self.assertRaises(Task.InactiveTask, t.stop)
360 t = Task(self.tw, description="test task")
364 self.assertRaises(Task.InactiveTask, t.stop)
366 def test_stop_deleted_task(self):
367 t = Task(self.tw, description="test task")
373 def test_stop_inactive_task(self):
374 t = Task(self.tw, description="test task")
377 self.assertRaises(Task.InactiveTask, t.stop)
379 t = Task(self.tw, description="test task")
384 self.assertRaises(Task.InactiveTask, t.stop)
386 def test_stopping_task(self):
387 t = Task(self.tw, description="test task")
388 now = t.datetime_normalizer(datetime.datetime.now())
393 self.assertEqual(t['end'], None)
394 self.assertEqual(t['status'], 'pending')
395 self.assertFalse(t.active)
397 def test_modify_simple_attribute_without_space(self):
398 t = Task(self.tw, description="test")
401 self.assertEquals(t['description'], "test")
403 t['description'] = "test-modified"
406 self.assertEquals(t['description'], "test-modified")
408 def test_modify_simple_attribute_with_space(self):
409 # Space can pose problems with parsing
410 t = Task(self.tw, description="test task")
413 self.assertEquals(t['description'], "test task")
415 t['description'] = "test task modified"
418 self.assertEquals(t['description'], "test task modified")
420 def test_empty_dependency_set_of_unsaved_task(self):
421 t = Task(self.tw, description="test task")
422 self.assertEqual(t['depends'], set())
424 def test_empty_dependency_set_of_saved_task(self):
425 t = Task(self.tw, description="test task")
427 self.assertEqual(t['depends'], set())
429 def test_set_unsaved_task_as_dependency(self):
430 # Adds only one dependency to task with no dependencies
431 t = Task(self.tw, description="test task")
432 dependency = Task(self.tw, description="needs to be done first")
434 # We only save the parent task, dependency task is unsaved
436 t['depends'] = set([dependency])
438 self.assertRaises(Task.NotSaved, t.save)
440 def test_set_simple_dependency_set(self):
441 # Adds only one dependency to task with no dependencies
442 t = Task(self.tw, description="test task")
443 dependency = Task(self.tw, description="needs to be done first")
448 t['depends'] = set([dependency])
450 self.assertEqual(t['depends'], set([dependency]))
452 def test_set_complex_dependency_set(self):
453 # Adds two dependencies to task with no dependencies
454 t = Task(self.tw, description="test task")
455 dependency1 = Task(self.tw, description="needs to be done first")
456 dependency2 = Task(self.tw, description="needs to be done second")
462 t['depends'] = set([dependency1, dependency2])
464 self.assertEqual(t['depends'], set([dependency1, dependency2]))
466 def test_remove_from_dependency_set(self):
467 # Removes dependency from task with two dependencies
468 t = Task(self.tw, description="test task")
469 dependency1 = Task(self.tw, description="needs to be done first")
470 dependency2 = Task(self.tw, description="needs to be done second")
475 t['depends'] = set([dependency1, dependency2])
478 t['depends'].remove(dependency2)
481 self.assertEqual(t['depends'], set([dependency1]))
483 def test_add_to_dependency_set(self):
484 # Adds dependency to task with one dependencies
485 t = Task(self.tw, description="test task")
486 dependency1 = Task(self.tw, description="needs to be done first")
487 dependency2 = Task(self.tw, description="needs to be done second")
492 t['depends'] = set([dependency1])
495 t['depends'].add(dependency2)
498 self.assertEqual(t['depends'], set([dependency1, dependency2]))
500 def test_add_to_empty_dependency_set(self):
501 # Adds dependency to task with one dependencies
502 t = Task(self.tw, description="test task")
503 dependency = Task(self.tw, description="needs to be done first")
507 t['depends'].add(dependency)
510 self.assertEqual(t['depends'], set([dependency]))
512 def test_simple_dependency_set_save_repeatedly(self):
513 # Adds only one dependency to task with no dependencies
514 t = Task(self.tw, description="test task")
515 dependency = Task(self.tw, description="needs to be done first")
518 t['depends'] = set([dependency])
521 # We taint the task, but keep depends intact
522 t['description'] = "test task modified"
525 self.assertEqual(t['depends'], set([dependency]))
527 # We taint the task, but assign the same set to the depends
528 t['depends'] = set([dependency])
529 t['description'] = "test task modified again"
532 self.assertEqual(t['depends'], set([dependency]))
534 def test_compare_different_tasks(self):
535 # Negative: compare two different tasks
536 t1 = Task(self.tw, description="test task")
537 t2 = Task(self.tw, description="test task")
542 self.assertEqual(t1 == t2, False)
544 def test_compare_same_task_object(self):
545 # Compare Task object wit itself
546 t = Task(self.tw, description="test task")
549 self.assertEqual(t == t, True)
551 def test_compare_same_task(self):
552 # Compare the same task using two different objects
553 t1 = Task(self.tw, description="test task")
556 t2 = self.tw.tasks.get(uuid=t1['uuid'])
557 self.assertEqual(t1 == t2, True)
559 def test_compare_unsaved_tasks(self):
560 # t1 and t2 are unsaved tasks, considered to be unequal
561 # despite the content of data
562 t1 = Task(self.tw, description="test task")
563 t2 = Task(self.tw, description="test task")
565 self.assertEqual(t1 == t2, False)
567 def test_hash_unsaved_tasks(self):
568 # Considered equal, it's the same object
569 t1 = Task(self.tw, description="test task")
571 self.assertEqual(hash(t1) == hash(t2), True)
573 def test_hash_same_task(self):
574 # Compare the hash of the task using two different objects
575 t1 = Task(self.tw, description="test task")
578 t2 = self.tw.tasks.get(uuid=t1['uuid'])
579 self.assertEqual(t1.__hash__(), t2.__hash__())
581 def test_adding_task_with_priority(self):
582 t = Task(self.tw, description="test task", priority="M")
585 def test_removing_priority_with_none(self):
586 t = Task(self.tw, description="test task", priority="L")
589 # Remove the priority mark
593 # Assert that priority is not there after saving
594 self.assertEqual(t['priority'], None)
596 def test_adding_task_with_due_time(self):
597 t = Task(self.tw, description="test task", due=datetime.datetime.now())
600 def test_removing_due_time_with_none(self):
601 t = Task(self.tw, description="test task", due=datetime.datetime.now())
604 # Remove the due timestamp
608 # Assert that due timestamp is no longer there
609 self.assertEqual(t['due'], None)
611 def test_modified_fields_new_task(self):
614 # This should be empty with new task
615 self.assertEqual(set(t._modified_fields), set())
618 t['description'] = "test task"
619 self.assertEqual(set(t._modified_fields), set(['description']))
621 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
622 self.assertEqual(set(t._modified_fields), set(['description', 'due']))
624 t['project'] = "test project"
625 self.assertEqual(set(t._modified_fields),
626 set(['description', 'due', 'project']))
628 # List of modified fields should clear out when saved
630 self.assertEqual(set(t._modified_fields), set())
632 # Reassigning the fields with the same values now should not produce
634 t['description'] = "test task"
635 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
636 t['project'] = "test project"
637 self.assertEqual(set(t._modified_fields), set())
639 def test_modified_fields_loaded_task(self):
643 t['description'] = "test task"
644 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
645 t['project'] = "test project"
647 dependency = Task(self.tw, description="dependency")
649 t['depends'] = set([dependency])
651 # List of modified fields should clear out when saved
653 self.assertEqual(set(t._modified_fields), set())
655 # Get the task by using a filter by UUID
656 t2 = self.tw.tasks.get(uuid=t['uuid'])
658 # Reassigning the fields with the same values now should not produce
660 t['description'] = "test task"
661 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
662 t['project'] = "test project"
663 t['depends'] = set([dependency])
664 self.assertEqual(set(t._modified_fields), set())
666 def test_modified_fields_not_affected_by_reading(self):
669 for field in TASK_STANDARD_ATTRS:
672 self.assertEqual(set(t._modified_fields), set())
674 def test_setting_read_only_attrs_through_init(self):
675 # Test that we are unable to set readonly attrs through __init__
676 for readonly_key in Task.read_only_fields:
677 kwargs = {'description': 'test task', readonly_key: 'value'}
678 self.assertRaises(RuntimeError,
679 lambda: Task(self.tw, **kwargs))
681 def test_setting_read_only_attrs_through_setitem(self):
682 # Test that we are unable to set readonly attrs through __init__
683 for readonly_key in Task.read_only_fields:
684 t = Task(self.tw, description='test task')
685 self.assertRaises(RuntimeError,
686 lambda: t.__setitem__(readonly_key, 'value'))
688 def test_saving_unmodified_task(self):
689 t = Task(self.tw, description="test task")
693 def test_adding_tag_by_appending(self):
694 t = Task(self.tw, description="test task", tags=['test1'])
696 t['tags'].append('test2')
698 self.assertEqual(t['tags'], ['test1', 'test2'])
700 def test_adding_tag_by_appending_empty(self):
701 t = Task(self.tw, description="test task")
703 t['tags'].append('test')
705 self.assertEqual(t['tags'], ['test'])
707 def test_serializers_returning_empty_string_for_none(self):
708 # Test that any serializer returns '' when passed None
710 serializers = [getattr(t, serializer_name) for serializer_name in
711 filter(lambda x: x.startswith('serialize_'), dir(t))]
712 for serializer in serializers:
713 self.assertEqual(serializer(None), '')
715 def test_deserializer_returning_empty_value_for_empty_string(self):
716 # Test that any deserializer returns empty value when passed ''
718 deserializers = [getattr(t, deserializer_name) for deserializer_name in
719 filter(lambda x: x.startswith('deserialize_'), dir(t))]
720 for deserializer in deserializers:
721 self.assertTrue(deserializer('') in (None, [], set()))
723 def test_normalizers_handling_none(self):
724 # Test that any normalizer can handle None as a valid value
727 for key in TASK_STANDARD_ATTRS:
728 t._normalize(key, None)
730 def test_recurrent_task_generation(self):
731 today = datetime.date.today()
732 t = Task(self.tw, description="brush teeth",
733 due=today, recur="daily")
735 self.assertEqual(len(self.tw.tasks.pending()), 2)
737 def test_modify_number_of_tasks_at_once(self):
738 for i in range(1, 100):
739 Task(self.tw, description="test task %d" % i, tags=['test']).save()
741 self.tw.execute_command(['+test', 'mod', 'unified', 'description'])
743 def test_return_all_from_executed_command(self):
744 Task(self.tw, description="test task", tags=['test']).save()
745 out, err, rc = self.tw.execute_command(['count'], return_all=True)
746 self.assertEqual(rc, 0)
748 def test_return_all_from_failed_executed_command(self):
749 Task(self.tw, description="test task", tags=['test']).save()
750 out, err, rc = self.tw.execute_command(['countinvalid'],
751 return_all=True, allow_failure=False)
752 self.assertNotEqual(rc, 0)
755 class TaskFromHookTest(TasklibTest):
757 input_add_data = six.StringIO(
758 '{"description":"Buy some milk",'
759 '"entry":"20141118T050231Z",'
760 '"status":"pending",'
761 '"start":"20141119T152233Z",'
762 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
764 input_modify_data = six.StringIO(input_add_data.getvalue() + '\n' +
765 '{"description":"Buy some milk finally",'
766 '"entry":"20141118T050231Z",'
767 '"status":"completed",'
768 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
770 exported_raw_data = (
772 '"due":"20150101T232323Z",'
773 '"description":"test task"}')
775 def test_setting_up_from_add_hook_input(self):
776 t = Task.from_input(input_file=self.input_add_data, warrior=self.tw)
777 self.assertEqual(t['description'], "Buy some milk")
778 self.assertEqual(t.pending, True)
780 def test_setting_up_from_modified_hook_input(self):
781 t = Task.from_input(input_file=self.input_modify_data, modify=True,
783 self.assertEqual(t['description'], "Buy some milk finally")
784 self.assertEqual(t.pending, False)
785 self.assertEqual(t.completed, True)
787 self.assertEqual(t._original_data['status'], "pending")
788 self.assertEqual(t._original_data['description'], "Buy some milk")
789 self.assertEqual(set(t._modified_fields),
790 set(['status', 'description', 'start']))
792 def test_export_data(self):
793 t = Task(self.tw, description="test task",
795 due=pytz.utc.localize(datetime.datetime(2015,1,1,23,23,23)))
797 # Check that the output is a permutation of:
798 # {"project":"Home","description":"test task","due":"20150101232323Z"}
799 allowed_segments = self.exported_raw_data[1:-1].split(',')
801 '{' + ','.join(segments) + '}'
802 for segments in itertools.permutations(allowed_segments)
805 self.assertTrue(any(t.export_data() == expected
806 for expected in allowed_output))
808 class TimezoneAwareDatetimeTest(TasklibTest):
811 super(TimezoneAwareDatetimeTest, self).setUp()
812 self.zone = local_zone
813 self.localdate_naive = datetime.datetime(2015,2,2)
814 self.localtime_naive = datetime.datetime(2015,2,2,0,0,0)
815 self.localtime_aware = self.zone.localize(self.localtime_naive)
816 self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
818 def test_timezone_naive_datetime_setitem(self):
819 t = Task(self.tw, description="test task")
820 t['due'] = self.localtime_naive
821 self.assertEqual(t['due'], self.localtime_aware)
823 def test_timezone_naive_datetime_using_init(self):
824 t = Task(self.tw, description="test task", due=self.localtime_naive)
825 self.assertEqual(t['due'], self.localtime_aware)
827 def test_filter_by_naive_datetime(self):
828 t = Task(self.tw, description="task1", due=self.localtime_naive)
830 matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
831 self.assertEqual(len(matching_tasks), 1)
833 def test_serialize_naive_datetime(self):
834 t = Task(self.tw, description="task1", due=self.localtime_naive)
835 self.assertEqual(json.loads(t.export_data())['due'],
836 self.utctime_aware.strftime(DATE_FORMAT))
838 def test_timezone_naive_date_setitem(self):
839 t = Task(self.tw, description="test task")
840 t['due'] = self.localdate_naive
841 self.assertEqual(t['due'], self.localtime_aware)
843 def test_timezone_naive_date_using_init(self):
844 t = Task(self.tw, description="test task", due=self.localdate_naive)
845 self.assertEqual(t['due'], self.localtime_aware)
847 def test_filter_by_naive_date(self):
848 t = Task(self.tw, description="task1", due=self.localdate_naive)
850 matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
851 self.assertEqual(len(matching_tasks), 1)
853 def test_serialize_naive_date(self):
854 t = Task(self.tw, description="task1", due=self.localdate_naive)
855 self.assertEqual(json.loads(t.export_data())['due'],
856 self.utctime_aware.strftime(DATE_FORMAT))
858 def test_timezone_aware_datetime_setitem(self):
859 t = Task(self.tw, description="test task")
860 t['due'] = self.localtime_aware
861 self.assertEqual(t['due'], self.localtime_aware)
863 def test_timezone_aware_datetime_using_init(self):
864 t = Task(self.tw, description="test task", due=self.localtime_aware)
865 self.assertEqual(t['due'], self.localtime_aware)
867 def test_filter_by_aware_datetime(self):
868 t = Task(self.tw, description="task1", due=self.localtime_aware)
870 matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
871 self.assertEqual(len(matching_tasks), 1)
873 def test_serialize_aware_datetime(self):
874 t = Task(self.tw, description="task1", due=self.localtime_aware)
875 self.assertEqual(json.loads(t.export_data())['due'],
876 self.utctime_aware.strftime(DATE_FORMAT))
878 class DatetimeStringTest(TasklibTest):
880 def test_simple_now_conversion(self):
881 if self.tw.version < six.text_type('2.4.0'):
882 # Python2.6 does not support SkipTest. As a workaround
883 # mark the test as passed by exiting.
884 if getattr(unittest, 'SkipTest', None) is not None:
885 raise unittest.SkipTest()
889 t = Task(self.tw, description="test task", due="now")
890 now = local_zone.localize(datetime.datetime.now())
892 # Assert that both times are not more than 5 seconds apart
893 if sys.version_info < (2,7):
894 self.assertTrue(total_seconds_2_6(now - t['due']) < 5)
895 self.assertTrue(total_seconds_2_6(t['due'] - now) < 5)
897 self.assertTrue((now - t['due']).total_seconds() < 5)
898 self.assertTrue((t['due'] - now).total_seconds() < 5)
900 def test_simple_eoy_conversion(self):
901 if self.tw.version < six.text_type('2.4.0'):
902 # Python2.6 does not support SkipTest. As a workaround
903 # mark the test as passed by exiting.
904 if getattr(unittest, 'SkipTest', None) is not None:
905 raise unittest.SkipTest()
909 t = Task(self.tw, description="test task", due="eoy")
910 now = local_zone.localize(datetime.datetime.now())
911 eoy = local_zone.localize(datetime.datetime(
919 self.assertEqual(eoy, t['due'])
921 def test_complex_eoy_conversion(self):
922 if self.tw.version < six.text_type('2.4.0'):
923 # Python2.6 does not support SkipTest. As a workaround
924 # mark the test as passed by exiting.
925 if getattr(unittest, 'SkipTest', None) is not None:
926 raise unittest.SkipTest()
930 t = Task(self.tw, description="test task", due="eoy - 4 months")
931 now = local_zone.localize(datetime.datetime.now())
932 due_date = local_zone.localize(datetime.datetime(
939 )) - datetime.timedelta(0,4 * 30 * 86400)
940 self.assertEqual(due_date, t['due'])
942 def test_filtering_with_string_datetime(self):
943 if self.tw.version < six.text_type('2.4.0'):
944 # Python2.6 does not support SkipTest. As a workaround
945 # mark the test as passed by exiting.
946 if getattr(unittest, 'SkipTest', None) is not None:
947 raise unittest.SkipTest()
951 t = Task(self.tw, description="test task",
952 due=datetime.datetime.now() - datetime.timedelta(0,2))
954 self.assertEqual(len(self.tw.tasks.filter(due__before="now")), 1)
956 class AnnotationTest(TasklibTest):
959 super(AnnotationTest, self).setUp()
960 Task(self.tw, description="test task").save()
962 def test_adding_annotation(self):
963 task = self.tw.tasks.get()
964 task.add_annotation('test annotation')
965 self.assertEqual(len(task['annotations']), 1)
966 ann = task['annotations'][0]
967 self.assertEqual(ann['description'], 'test annotation')
969 def test_removing_annotation(self):
970 task = self.tw.tasks.get()
971 task.add_annotation('test annotation')
972 ann = task['annotations'][0]
974 self.assertEqual(len(task['annotations']), 0)
976 def test_removing_annotation_by_description(self):
977 task = self.tw.tasks.get()
978 task.add_annotation('test annotation')
979 task.remove_annotation('test annotation')
980 self.assertEqual(len(task['annotations']), 0)
982 def test_removing_annotation_by_obj(self):
983 task = self.tw.tasks.get()
984 task.add_annotation('test annotation')
985 ann = task['annotations'][0]
986 task.remove_annotation(ann)
987 self.assertEqual(len(task['annotations']), 0)
989 def test_annotation_after_modification(self):
990 task = self.tw.tasks.get()
991 task['project'] = 'test'
992 task.add_annotation('I should really do this task')
993 self.assertEqual(task['project'], 'test')
995 self.assertEqual(task['project'], 'test')
997 def test_serialize_annotations(self):
998 # Test that serializing annotations is possible
999 t = Task(self.tw, description="test")
1002 t.add_annotation("annotation1")
1003 t.add_annotation("annotation2")
1005 data = t._serialize('annotations', t._data['annotations'])
1007 self.assertEqual(len(data), 2)
1008 self.assertEqual(type(data[0]), dict)
1009 self.assertEqual(type(data[1]), dict)
1011 self.assertEqual(data[0]['description'], "annotation1")
1012 self.assertEqual(data[1]['description'], "annotation2")
1015 class UnicodeTest(TasklibTest):
1017 def test_unicode_task(self):
1018 Task(self.tw, description=six.u("†åßk")).save()
1021 def test_filter_by_unicode_task(self):
1022 Task(self.tw, description=six.u("†åßk")).save()
1023 tasks = self.tw.tasks.filter(description=six.u("†åßk"))
1024 self.assertEqual(len(tasks), 1)
1026 def test_non_unicode_task(self):
1027 Task(self.tw, description="test task").save()
1030 class ReadOnlyDictViewTest(unittest.TestCase):
1033 self.sample = dict(l=[1,2,3], d={'k':'v'})
1034 self.original_sample = copy.deepcopy(self.sample)
1035 self.view = ReadOnlyDictView(self.sample)
1037 def test_readonlydictview_getitem(self):
1039 self.assertEqual(l, self.sample['l'])
1041 # Assert that modification changed only copied value
1043 self.assertNotEqual(l, self.sample['l'])
1045 # Assert that viewed dict is not changed
1046 self.assertEqual(self.sample, self.original_sample)
1048 def test_readonlydictview_contains(self):
1049 self.assertEqual('l' in self.view, 'l' in self.sample)
1050 self.assertEqual('d' in self.view, 'd' in self.sample)
1051 self.assertEqual('k' in self.view, 'k' in self.sample)
1053 # Assert that viewed dict is not changed
1054 self.assertEqual(self.sample, self.original_sample)
1056 def test_readonlydictview_iter(self):
1057 self.assertEqual(list(k for k in self.view),
1058 list(k for k in self.sample))
1060 # Assert the view is correct after modification
1061 self.sample['new'] = 'value'
1062 self.assertEqual(list(k for k in self.view),
1063 list(k for k in self.sample))
1065 def test_readonlydictview_len(self):
1066 self.assertEqual(len(self.view), len(self.sample))
1068 # Assert the view is correct after modification
1069 self.sample['new'] = 'value'
1070 self.assertEqual(len(self.view), len(self.sample))
1072 def test_readonlydictview_get(self):
1073 l = self.view.get('l')
1074 self.assertEqual(l, self.sample.get('l'))
1076 # Assert that modification changed only copied value
1078 self.assertNotEqual(l, self.sample.get('l'))
1080 # Assert that viewed dict is not changed
1081 self.assertEqual(self.sample, self.original_sample)
1083 def test_readonlydict_items(self):
1084 view_items = self.view.items()
1085 sample_items = list(self.sample.items())
1086 self.assertEqual(view_items, sample_items)
1088 view_items.append('newkey')
1089 self.assertNotEqual(view_items, sample_items)
1090 self.assertEqual(self.sample, self.original_sample)
1092 def test_readonlydict_values(self):
1093 view_values = self.view.values()
1094 sample_values = list(self.sample.values())
1095 self.assertEqual(view_values, sample_values)
1097 view_list_item = list(filter(lambda x: type(x) is list,
1099 view_list_item.append(4)
1100 self.assertNotEqual(view_values, sample_values)
1101 self.assertEqual(self.sample, self.original_sample)