All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
14 from .backends import TaskWarrior
15 from .task import Task, ReadOnlyDictView
16 from .serializing import DATE_FORMAT, local_zone
18 # http://taskwarrior.org/docs/design/task.html , Section: The Attributes
19 TASK_STANDARD_ATTRS = (
42 total_seconds_2_6 = lambda x: x.microseconds / 1e6 + x.seconds + x.days * 24 * 3600
45 class TasklibTest(unittest.TestCase):
48 self.tmp = tempfile.mkdtemp(dir='.')
49 self.tw = TaskWarrior(data_location=self.tmp, taskrc_location='/')
52 shutil.rmtree(self.tmp)
55 class TaskFilterTest(TasklibTest):
57 def test_all_empty(self):
58 self.assertEqual(len(self.tw.tasks.all()), 0)
60 def test_all_non_empty(self):
61 Task(self.tw, description="test task").save()
62 self.assertEqual(len(self.tw.tasks.all()), 1)
63 self.assertEqual(self.tw.tasks.all()[0]['description'], 'test task')
64 self.assertEqual(self.tw.tasks.all()[0]['status'], 'pending')
66 def test_pending_non_empty(self):
67 Task(self.tw, description="test task").save()
68 self.assertEqual(len(self.tw.tasks.pending()), 1)
69 self.assertEqual(self.tw.tasks.pending()[0]['description'],
71 self.assertEqual(self.tw.tasks.pending()[0]['status'], 'pending')
73 def test_completed_empty(self):
74 Task(self.tw, description="test task").save()
75 self.assertEqual(len(self.tw.tasks.completed()), 0)
77 def test_completed_non_empty(self):
78 Task(self.tw, description="test task").save()
79 self.assertEqual(len(self.tw.tasks.completed()), 0)
80 self.tw.tasks.all()[0].done()
81 self.assertEqual(len(self.tw.tasks.completed()), 1)
83 def test_filtering_by_attribute(self):
84 Task(self.tw, description="no priority task").save()
85 Task(self.tw, priority="H", description="high priority task").save()
86 self.assertEqual(len(self.tw.tasks.all()), 2)
88 # Assert that the correct number of tasks is returned
89 self.assertEqual(len(self.tw.tasks.filter(priority="H")), 1)
91 # Assert that the correct tasks are returned
92 high_priority_task = self.tw.tasks.get(priority="H")
93 self.assertEqual(high_priority_task['description'], "high priority task")
95 def test_filtering_by_empty_attribute(self):
96 Task(self.tw, description="no priority task").save()
97 Task(self.tw, priority="H", description="high priority task").save()
98 self.assertEqual(len(self.tw.tasks.all()), 2)
100 # Assert that the correct number of tasks is returned
101 self.assertEqual(len(self.tw.tasks.filter(priority=None)), 1)
103 # Assert that the correct tasks are returned
104 no_priority_task = self.tw.tasks.get(priority=None)
105 self.assertEqual(no_priority_task['description'], "no priority task")
107 def test_filter_for_task_with_space_in_descripition(self):
108 task = Task(self.tw, description="test task")
111 filtered_task = self.tw.tasks.get(description="test task")
112 self.assertEqual(filtered_task['description'], "test task")
114 def test_filter_for_task_without_space_in_descripition(self):
115 task = Task(self.tw, description="test")
118 filtered_task = self.tw.tasks.get(description="test")
119 self.assertEqual(filtered_task['description'], "test")
121 def test_filter_for_task_with_space_in_project(self):
122 task = Task(self.tw, description="test", project="random project")
125 filtered_task = self.tw.tasks.get(project="random project")
126 self.assertEqual(filtered_task['project'], "random project")
128 def test_filter_for_task_without_space_in_project(self):
129 task = Task(self.tw, description="test", project="random")
132 filtered_task = self.tw.tasks.get(project="random")
133 self.assertEqual(filtered_task['project'], "random")
135 def test_filter_with_empty_uuid(self):
136 self.assertRaises(ValueError, lambda: self.tw.tasks.get(uuid=''))
138 def test_filter_dummy_by_status(self):
139 t = Task(self.tw, description="test")
142 tasks = self.tw.tasks.filter(status=t['status'])
143 self.assertEqual(list(tasks), [t])
145 def test_filter_dummy_by_uuid(self):
146 t = Task(self.tw, description="test")
149 tasks = self.tw.tasks.filter(uuid=t['uuid'])
150 self.assertEqual(list(tasks), [t])
152 def test_filter_dummy_by_entry(self):
153 t = Task(self.tw, description="test")
156 tasks = self.tw.tasks.filter(entry=t['entry'])
157 self.assertEqual(list(tasks), [t])
159 def test_filter_dummy_by_description(self):
160 t = Task(self.tw, description="test")
163 tasks = self.tw.tasks.filter(description=t['description'])
164 self.assertEqual(list(tasks), [t])
166 def test_filter_dummy_by_start(self):
167 t = Task(self.tw, description="test")
171 tasks = self.tw.tasks.filter(start=t['start'])
172 self.assertEqual(list(tasks), [t])
174 def test_filter_dummy_by_end(self):
175 t = Task(self.tw, description="test")
179 tasks = self.tw.tasks.filter(end=t['end'])
180 self.assertEqual(list(tasks), [t])
182 def test_filter_dummy_by_due(self):
183 t = Task(self.tw, description="test", due=datetime.datetime.now())
186 tasks = self.tw.tasks.filter(due=t['due'])
187 self.assertEqual(list(tasks), [t])
189 def test_filter_dummy_by_until(self):
190 t = Task(self.tw, description="test")
193 tasks = self.tw.tasks.filter(until=t['until'])
194 self.assertEqual(list(tasks), [t])
196 def test_filter_dummy_by_modified(self):
197 # Older TW version does not support bumping modified
199 if self.tw.version < six.text_type('2.2.0'):
200 # Python2.6 does not support SkipTest. As a workaround
201 # mark the test as passed by exiting.
202 if getattr(unittest, 'SkipTest', None) is not None:
203 raise unittest.SkipTest()
207 t = Task(self.tw, description="test")
210 tasks = self.tw.tasks.filter(modified=t['modified'])
211 self.assertEqual(list(tasks), [t])
213 def test_filter_dummy_by_scheduled(self):
214 t = Task(self.tw, description="test")
217 tasks = self.tw.tasks.filter(scheduled=t['scheduled'])
218 self.assertEqual(list(tasks), [t])
220 def test_filter_dummy_by_tags(self):
221 t = Task(self.tw, description="test", tags=["home"])
224 tasks = self.tw.tasks.filter(tags=t['tags'])
225 self.assertEqual(list(tasks), [t])
227 def test_filter_dummy_by_projects(self):
228 t = Task(self.tw, description="test", project="random")
231 tasks = self.tw.tasks.filter(project=t['project'])
232 self.assertEqual(list(tasks), [t])
234 def test_filter_by_priority(self):
235 t = Task(self.tw, description="test", priority="H")
238 tasks = self.tw.tasks.filter(priority=t['priority'])
239 self.assertEqual(list(tasks), [t])
242 class TaskTest(TasklibTest):
244 def test_create_unsaved_task(self):
245 # Make sure a new task is not saved unless explicitly called for
246 t = Task(self.tw, description="test task")
247 self.assertEqual(len(self.tw.tasks.all()), 0)
249 # TODO: once python 2.6 compatiblity is over, use context managers here
250 # and in all subsequent tests for assertRaises
252 def test_delete_unsaved_task(self):
253 t = Task(self.tw, description="test task")
254 self.assertRaises(Task.NotSaved, t.delete)
256 def test_complete_unsaved_task(self):
257 t = Task(self.tw, description="test task")
258 self.assertRaises(Task.NotSaved, t.done)
260 def test_refresh_unsaved_task(self):
261 t = Task(self.tw, description="test task")
262 self.assertRaises(Task.NotSaved, t.refresh)
264 def test_start_unsaved_task(self):
265 t = Task(self.tw, description="test task")
266 self.assertRaises(Task.NotSaved, t.start)
268 def test_delete_deleted_task(self):
269 t = Task(self.tw, description="test task")
273 self.assertRaises(Task.DeletedTask, t.delete)
275 def test_complete_completed_task(self):
276 t = Task(self.tw, description="test task")
280 self.assertRaises(Task.CompletedTask, t.done)
282 def test_start_completed_task(self):
283 t = Task(self.tw, description="test task")
287 self.assertRaises(Task.CompletedTask, t.start)
289 def test_add_completed_task(self):
290 t = Task(self.tw, description="test", status="completed",
291 end=datetime.datetime.now())
294 def test_add_multiple_completed_tasks(self):
295 t1 = Task(self.tw, description="test1", status="completed",
296 end=datetime.datetime.now())
297 t2 = Task(self.tw, description="test2", status="completed",
298 end=datetime.datetime.now())
302 def test_complete_deleted_task(self):
303 t = Task(self.tw, description="test task")
307 self.assertRaises(Task.DeletedTask, t.done)
309 def test_starting_task(self):
310 t = Task(self.tw, description="test task")
311 now = t.datetime_normalizer(datetime.datetime.now())
315 self.assertTrue(now.replace(microsecond=0) <= t['start'])
316 self.assertEqual(t['status'], 'pending')
318 def test_completing_task(self):
319 t = Task(self.tw, description="test task")
320 now = t.datetime_normalizer(datetime.datetime.now())
324 self.assertTrue(now.replace(microsecond=0) <= t['end'])
325 self.assertEqual(t['status'], 'completed')
327 def test_deleting_task(self):
328 t = Task(self.tw, description="test task")
329 now = t.datetime_normalizer(datetime.datetime.now())
333 self.assertTrue(now.replace(microsecond=0) <= t['end'])
334 self.assertEqual(t['status'], 'deleted')
336 def test_started_task_active(self):
337 t = Task(self.tw, description="test task")
340 self.assertTrue(t.active)
342 def test_unstarted_task_inactive(self):
343 t = Task(self.tw, description="test task")
344 self.assertFalse(t.active)
346 self.assertFalse(t.active)
348 def test_start_active_task(self):
349 t = Task(self.tw, description="test task")
352 self.assertRaises(Task.ActiveTask, t.start)
354 def test_stop_completed_task(self):
355 t = Task(self.tw, description="test task")
360 self.assertRaises(Task.InactiveTask, t.stop)
362 t = Task(self.tw, description="test task")
366 self.assertRaises(Task.InactiveTask, t.stop)
368 def test_stop_deleted_task(self):
369 t = Task(self.tw, description="test task")
375 def test_stop_inactive_task(self):
376 t = Task(self.tw, description="test task")
379 self.assertRaises(Task.InactiveTask, t.stop)
381 t = Task(self.tw, description="test task")
386 self.assertRaises(Task.InactiveTask, t.stop)
388 def test_stopping_task(self):
389 t = Task(self.tw, description="test task")
390 now = t.datetime_normalizer(datetime.datetime.now())
395 self.assertEqual(t['end'], None)
396 self.assertEqual(t['status'], 'pending')
397 self.assertFalse(t.active)
399 def test_modify_simple_attribute_without_space(self):
400 t = Task(self.tw, description="test")
403 self.assertEquals(t['description'], "test")
405 t['description'] = "test-modified"
408 self.assertEquals(t['description'], "test-modified")
410 def test_modify_simple_attribute_with_space(self):
411 # Space can pose problems with parsing
412 t = Task(self.tw, description="test task")
415 self.assertEquals(t['description'], "test task")
417 t['description'] = "test task modified"
420 self.assertEquals(t['description'], "test task modified")
422 def test_empty_dependency_set_of_unsaved_task(self):
423 t = Task(self.tw, description="test task")
424 self.assertEqual(t['depends'], set())
426 def test_empty_dependency_set_of_saved_task(self):
427 t = Task(self.tw, description="test task")
429 self.assertEqual(t['depends'], set())
431 def test_set_unsaved_task_as_dependency(self):
432 # Adds only one dependency to task with no dependencies
433 t = Task(self.tw, description="test task")
434 dependency = Task(self.tw, description="needs to be done first")
436 # We only save the parent task, dependency task is unsaved
438 t['depends'] = set([dependency])
440 self.assertRaises(Task.NotSaved, t.save)
442 def test_set_simple_dependency_set(self):
443 # Adds only one dependency to task with no dependencies
444 t = Task(self.tw, description="test task")
445 dependency = Task(self.tw, description="needs to be done first")
450 t['depends'] = set([dependency])
452 self.assertEqual(t['depends'], set([dependency]))
454 def test_set_complex_dependency_set(self):
455 # Adds two dependencies to task with no dependencies
456 t = Task(self.tw, description="test task")
457 dependency1 = Task(self.tw, description="needs to be done first")
458 dependency2 = Task(self.tw, description="needs to be done second")
464 t['depends'] = set([dependency1, dependency2])
466 self.assertEqual(t['depends'], set([dependency1, dependency2]))
468 def test_remove_from_dependency_set(self):
469 # Removes dependency from task with two dependencies
470 t = Task(self.tw, description="test task")
471 dependency1 = Task(self.tw, description="needs to be done first")
472 dependency2 = Task(self.tw, description="needs to be done second")
477 t['depends'] = set([dependency1, dependency2])
480 t['depends'].remove(dependency2)
483 self.assertEqual(t['depends'], set([dependency1]))
485 def test_add_to_dependency_set(self):
486 # Adds dependency to task with one dependencies
487 t = Task(self.tw, description="test task")
488 dependency1 = Task(self.tw, description="needs to be done first")
489 dependency2 = Task(self.tw, description="needs to be done second")
494 t['depends'] = set([dependency1])
497 t['depends'].add(dependency2)
500 self.assertEqual(t['depends'], set([dependency1, dependency2]))
502 def test_add_to_empty_dependency_set(self):
503 # Adds dependency to task with one dependencies
504 t = Task(self.tw, description="test task")
505 dependency = Task(self.tw, description="needs to be done first")
509 t['depends'].add(dependency)
512 self.assertEqual(t['depends'], set([dependency]))
514 def test_simple_dependency_set_save_repeatedly(self):
515 # Adds only one dependency to task with no dependencies
516 t = Task(self.tw, description="test task")
517 dependency = Task(self.tw, description="needs to be done first")
520 t['depends'] = set([dependency])
523 # We taint the task, but keep depends intact
524 t['description'] = "test task modified"
527 self.assertEqual(t['depends'], set([dependency]))
529 # We taint the task, but assign the same set to the depends
530 t['depends'] = set([dependency])
531 t['description'] = "test task modified again"
534 self.assertEqual(t['depends'], set([dependency]))
536 def test_compare_different_tasks(self):
537 # Negative: compare two different tasks
538 t1 = Task(self.tw, description="test task")
539 t2 = Task(self.tw, description="test task")
544 self.assertEqual(t1 == t2, False)
546 def test_compare_same_task_object(self):
547 # Compare Task object wit itself
548 t = Task(self.tw, description="test task")
551 self.assertEqual(t == t, True)
553 def test_compare_same_task(self):
554 # Compare the same task using two different objects
555 t1 = Task(self.tw, description="test task")
558 t2 = self.tw.tasks.get(uuid=t1['uuid'])
559 self.assertEqual(t1 == t2, True)
561 def test_compare_unsaved_tasks(self):
562 # t1 and t2 are unsaved tasks, considered to be unequal
563 # despite the content of data
564 t1 = Task(self.tw, description="test task")
565 t2 = Task(self.tw, description="test task")
567 self.assertEqual(t1 == t2, False)
569 def test_hash_unsaved_tasks(self):
570 # Considered equal, it's the same object
571 t1 = Task(self.tw, description="test task")
573 self.assertEqual(hash(t1) == hash(t2), True)
575 def test_hash_same_task(self):
576 # Compare the hash of the task using two different objects
577 t1 = Task(self.tw, description="test task")
580 t2 = self.tw.tasks.get(uuid=t1['uuid'])
581 self.assertEqual(t1.__hash__(), t2.__hash__())
583 def test_adding_task_with_priority(self):
584 t = Task(self.tw, description="test task", priority="M")
587 def test_removing_priority_with_none(self):
588 t = Task(self.tw, description="test task", priority="L")
591 # Remove the priority mark
595 # Assert that priority is not there after saving
596 self.assertEqual(t['priority'], None)
598 def test_adding_task_with_due_time(self):
599 t = Task(self.tw, description="test task", due=datetime.datetime.now())
602 def test_removing_due_time_with_none(self):
603 t = Task(self.tw, description="test task", due=datetime.datetime.now())
606 # Remove the due timestamp
610 # Assert that due timestamp is no longer there
611 self.assertEqual(t['due'], None)
613 def test_modified_fields_new_task(self):
616 # This should be empty with new task
617 self.assertEqual(set(t._modified_fields), set())
620 t['description'] = "test task"
621 self.assertEqual(set(t._modified_fields), set(['description']))
623 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
624 self.assertEqual(set(t._modified_fields), set(['description', 'due']))
626 t['project'] = "test project"
627 self.assertEqual(set(t._modified_fields),
628 set(['description', 'due', 'project']))
630 # List of modified fields should clear out when saved
632 self.assertEqual(set(t._modified_fields), set())
634 # Reassigning the fields with the same values now should not produce
636 t['description'] = "test task"
637 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
638 t['project'] = "test project"
639 self.assertEqual(set(t._modified_fields), set())
641 def test_modified_fields_loaded_task(self):
645 t['description'] = "test task"
646 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
647 t['project'] = "test project"
649 dependency = Task(self.tw, description="dependency")
651 t['depends'] = set([dependency])
653 # List of modified fields should clear out when saved
655 self.assertEqual(set(t._modified_fields), set())
657 # Get the task by using a filter by UUID
658 t2 = self.tw.tasks.get(uuid=t['uuid'])
660 # Reassigning the fields with the same values now should not produce
662 t['description'] = "test task"
663 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
664 t['project'] = "test project"
665 t['depends'] = set([dependency])
666 self.assertEqual(set(t._modified_fields), set())
668 def test_modified_fields_not_affected_by_reading(self):
671 for field in TASK_STANDARD_ATTRS:
674 self.assertEqual(set(t._modified_fields), set())
676 def test_setting_read_only_attrs_through_init(self):
677 # Test that we are unable to set readonly attrs through __init__
678 for readonly_key in Task.read_only_fields:
679 kwargs = {'description': 'test task', readonly_key: 'value'}
680 self.assertRaises(RuntimeError,
681 lambda: Task(self.tw, **kwargs))
683 def test_setting_read_only_attrs_through_setitem(self):
684 # Test that we are unable to set readonly attrs through __init__
685 for readonly_key in Task.read_only_fields:
686 t = Task(self.tw, description='test task')
687 self.assertRaises(RuntimeError,
688 lambda: t.__setitem__(readonly_key, 'value'))
690 def test_saving_unmodified_task(self):
691 t = Task(self.tw, description="test task")
695 def test_adding_tag_by_appending(self):
696 t = Task(self.tw, description="test task", tags=['test1'])
698 t['tags'].append('test2')
700 self.assertEqual(t['tags'], ['test1', 'test2'])
702 def test_adding_tag_by_appending_empty(self):
703 t = Task(self.tw, description="test task")
705 t['tags'].append('test')
707 self.assertEqual(t['tags'], ['test'])
709 def test_serializers_returning_empty_string_for_none(self):
710 # Test that any serializer returns '' when passed None
712 serializers = [getattr(t, serializer_name) for serializer_name in
713 filter(lambda x: x.startswith('serialize_'), dir(t))]
714 for serializer in serializers:
715 self.assertEqual(serializer(None), '')
717 def test_deserializer_returning_empty_value_for_empty_string(self):
718 # Test that any deserializer returns empty value when passed ''
720 deserializers = [getattr(t, deserializer_name) for deserializer_name in
721 filter(lambda x: x.startswith('deserialize_'), dir(t))]
722 for deserializer in deserializers:
723 self.assertTrue(deserializer('') in (None, [], set()))
725 def test_normalizers_handling_none(self):
726 # Test that any normalizer can handle None as a valid value
729 for key in TASK_STANDARD_ATTRS:
730 t._normalize(key, None)
732 def test_recurrent_task_generation(self):
733 today = datetime.date.today()
734 t = Task(self.tw, description="brush teeth",
735 due=today, recur="daily")
737 self.assertEqual(len(self.tw.tasks.pending()), 2)
739 def test_modify_number_of_tasks_at_once(self):
740 for i in range(1, 100):
741 Task(self.tw, description="test task %d" % i, tags=['test']).save()
743 self.tw.execute_command(['+test', 'mod', 'unified', 'description'])
745 def test_return_all_from_executed_command(self):
746 Task(self.tw, description="test task", tags=['test']).save()
747 out, err, rc = self.tw.execute_command(['count'], return_all=True)
748 self.assertEqual(rc, 0)
750 def test_return_all_from_failed_executed_command(self):
751 Task(self.tw, description="test task", tags=['test']).save()
752 out, err, rc = self.tw.execute_command(['countinvalid'],
753 return_all=True, allow_failure=False)
754 self.assertNotEqual(rc, 0)
757 class TaskFromHookTest(TasklibTest):
759 input_add_data = six.StringIO(
760 '{"description":"Buy some milk",'
761 '"entry":"20141118T050231Z",'
762 '"status":"pending",'
763 '"start":"20141119T152233Z",'
764 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
766 input_modify_data = six.StringIO(input_add_data.getvalue() + '\n' +
767 '{"description":"Buy some milk finally",'
768 '"entry":"20141118T050231Z",'
769 '"status":"completed",'
770 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
772 exported_raw_data = (
774 '"due":"20150101T232323Z",'
775 '"description":"test task"}')
777 def test_setting_up_from_add_hook_input(self):
778 t = Task.from_input(input_file=self.input_add_data, backend=self.tw)
779 self.assertEqual(t['description'], "Buy some milk")
780 self.assertEqual(t.pending, True)
782 def test_setting_up_from_modified_hook_input(self):
783 t = Task.from_input(input_file=self.input_modify_data, modify=True,
785 self.assertEqual(t['description'], "Buy some milk finally")
786 self.assertEqual(t.pending, False)
787 self.assertEqual(t.completed, True)
789 self.assertEqual(t._original_data['status'], "pending")
790 self.assertEqual(t._original_data['description'], "Buy some milk")
791 self.assertEqual(set(t._modified_fields),
792 set(['status', 'description', 'start']))
794 def test_export_data(self):
795 t = Task(self.tw, description="test task",
797 due=pytz.utc.localize(datetime.datetime(2015,1,1,23,23,23)))
799 # Check that the output is a permutation of:
800 # {"project":"Home","description":"test task","due":"20150101232323Z"}
801 allowed_segments = self.exported_raw_data[1:-1].split(',')
803 '{' + ','.join(segments) + '}'
804 for segments in itertools.permutations(allowed_segments)
807 self.assertTrue(any(t.export_data() == expected
808 for expected in allowed_output))
810 class TimezoneAwareDatetimeTest(TasklibTest):
813 super(TimezoneAwareDatetimeTest, self).setUp()
814 self.zone = local_zone
815 self.localdate_naive = datetime.datetime(2015,2,2)
816 self.localtime_naive = datetime.datetime(2015,2,2,0,0,0)
817 self.localtime_aware = self.zone.localize(self.localtime_naive)
818 self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
820 def test_timezone_naive_datetime_setitem(self):
821 t = Task(self.tw, description="test task")
822 t['due'] = self.localtime_naive
823 self.assertEqual(t['due'], self.localtime_aware)
825 def test_timezone_naive_datetime_using_init(self):
826 t = Task(self.tw, description="test task", due=self.localtime_naive)
827 self.assertEqual(t['due'], self.localtime_aware)
829 def test_filter_by_naive_datetime(self):
830 t = Task(self.tw, description="task1", due=self.localtime_naive)
832 matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
833 self.assertEqual(len(matching_tasks), 1)
835 def test_serialize_naive_datetime(self):
836 t = Task(self.tw, description="task1", due=self.localtime_naive)
837 self.assertEqual(json.loads(t.export_data())['due'],
838 self.utctime_aware.strftime(DATE_FORMAT))
840 def test_timezone_naive_date_setitem(self):
841 t = Task(self.tw, description="test task")
842 t['due'] = self.localdate_naive
843 self.assertEqual(t['due'], self.localtime_aware)
845 def test_timezone_naive_date_using_init(self):
846 t = Task(self.tw, description="test task", due=self.localdate_naive)
847 self.assertEqual(t['due'], self.localtime_aware)
849 def test_filter_by_naive_date(self):
850 t = Task(self.tw, description="task1", due=self.localdate_naive)
852 matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
853 self.assertEqual(len(matching_tasks), 1)
855 def test_serialize_naive_date(self):
856 t = Task(self.tw, description="task1", due=self.localdate_naive)
857 self.assertEqual(json.loads(t.export_data())['due'],
858 self.utctime_aware.strftime(DATE_FORMAT))
860 def test_timezone_aware_datetime_setitem(self):
861 t = Task(self.tw, description="test task")
862 t['due'] = self.localtime_aware
863 self.assertEqual(t['due'], self.localtime_aware)
865 def test_timezone_aware_datetime_using_init(self):
866 t = Task(self.tw, description="test task", due=self.localtime_aware)
867 self.assertEqual(t['due'], self.localtime_aware)
869 def test_filter_by_aware_datetime(self):
870 t = Task(self.tw, description="task1", due=self.localtime_aware)
872 matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
873 self.assertEqual(len(matching_tasks), 1)
875 def test_serialize_aware_datetime(self):
876 t = Task(self.tw, description="task1", due=self.localtime_aware)
877 self.assertEqual(json.loads(t.export_data())['due'],
878 self.utctime_aware.strftime(DATE_FORMAT))
880 class DatetimeStringTest(TasklibTest):
882 def test_simple_now_conversion(self):
883 if self.tw.version < six.text_type('2.4.0'):
884 # Python2.6 does not support SkipTest. As a workaround
885 # mark the test as passed by exiting.
886 if getattr(unittest, 'SkipTest', None) is not None:
887 raise unittest.SkipTest()
891 t = Task(self.tw, description="test task", due="now")
892 now = local_zone.localize(datetime.datetime.now())
894 # Assert that both times are not more than 5 seconds apart
895 if sys.version_info < (2,7):
896 self.assertTrue(total_seconds_2_6(now - t['due']) < 5)
897 self.assertTrue(total_seconds_2_6(t['due'] - now) < 5)
899 self.assertTrue((now - t['due']).total_seconds() < 5)
900 self.assertTrue((t['due'] - now).total_seconds() < 5)
902 def test_simple_eoy_conversion(self):
903 if self.tw.version < six.text_type('2.4.0'):
904 # Python2.6 does not support SkipTest. As a workaround
905 # mark the test as passed by exiting.
906 if getattr(unittest, 'SkipTest', None) is not None:
907 raise unittest.SkipTest()
911 t = Task(self.tw, description="test task", due="eoy")
912 now = local_zone.localize(datetime.datetime.now())
913 eoy = local_zone.localize(datetime.datetime(
921 self.assertEqual(eoy, t['due'])
923 def test_complex_eoy_conversion(self):
924 if self.tw.version < six.text_type('2.4.0'):
925 # Python2.6 does not support SkipTest. As a workaround
926 # mark the test as passed by exiting.
927 if getattr(unittest, 'SkipTest', None) is not None:
928 raise unittest.SkipTest()
932 t = Task(self.tw, description="test task", due="eoy - 4 months")
933 now = local_zone.localize(datetime.datetime.now())
934 due_date = local_zone.localize(datetime.datetime(
941 )) - datetime.timedelta(0,4 * 30 * 86400)
942 self.assertEqual(due_date, t['due'])
944 def test_filtering_with_string_datetime(self):
945 if self.tw.version < six.text_type('2.4.0'):
946 # Python2.6 does not support SkipTest. As a workaround
947 # mark the test as passed by exiting.
948 if getattr(unittest, 'SkipTest', None) is not None:
949 raise unittest.SkipTest()
953 t = Task(self.tw, description="test task",
954 due=datetime.datetime.now() - datetime.timedelta(0,2))
956 self.assertEqual(len(self.tw.tasks.filter(due__before="now")), 1)
958 class AnnotationTest(TasklibTest):
961 super(AnnotationTest, self).setUp()
962 Task(self.tw, description="test task").save()
964 def test_adding_annotation(self):
965 task = self.tw.tasks.get()
966 task.add_annotation('test annotation')
967 self.assertEqual(len(task['annotations']), 1)
968 ann = task['annotations'][0]
969 self.assertEqual(ann['description'], 'test annotation')
971 def test_removing_annotation(self):
972 task = self.tw.tasks.get()
973 task.add_annotation('test annotation')
974 ann = task['annotations'][0]
976 self.assertEqual(len(task['annotations']), 0)
978 def test_removing_annotation_by_description(self):
979 task = self.tw.tasks.get()
980 task.add_annotation('test annotation')
981 task.remove_annotation('test annotation')
982 self.assertEqual(len(task['annotations']), 0)
984 def test_removing_annotation_by_obj(self):
985 task = self.tw.tasks.get()
986 task.add_annotation('test annotation')
987 ann = task['annotations'][0]
988 task.remove_annotation(ann)
989 self.assertEqual(len(task['annotations']), 0)
991 def test_annotation_after_modification(self):
992 task = self.tw.tasks.get()
993 task['project'] = 'test'
994 task.add_annotation('I should really do this task')
995 self.assertEqual(task['project'], 'test')
997 self.assertEqual(task['project'], 'test')
999 def test_serialize_annotations(self):
1000 # Test that serializing annotations is possible
1001 t = Task(self.tw, description="test")
1004 t.add_annotation("annotation1")
1005 t.add_annotation("annotation2")
1007 data = t._serialize('annotations', t._data['annotations'])
1009 self.assertEqual(len(data), 2)
1010 self.assertEqual(type(data[0]), dict)
1011 self.assertEqual(type(data[1]), dict)
1013 self.assertEqual(data[0]['description'], "annotation1")
1014 self.assertEqual(data[1]['description'], "annotation2")
1017 class UnicodeTest(TasklibTest):
1019 def test_unicode_task(self):
1020 Task(self.tw, description=six.u("†åßk")).save()
1023 def test_filter_by_unicode_task(self):
1024 Task(self.tw, description=six.u("†åßk")).save()
1025 tasks = self.tw.tasks.filter(description=six.u("†åßk"))
1026 self.assertEqual(len(tasks), 1)
1028 def test_non_unicode_task(self):
1029 Task(self.tw, description="test task").save()
1032 class ReadOnlyDictViewTest(unittest.TestCase):
1035 self.sample = dict(l=[1,2,3], d={'k':'v'})
1036 self.original_sample = copy.deepcopy(self.sample)
1037 self.view = ReadOnlyDictView(self.sample)
1039 def test_readonlydictview_getitem(self):
1041 self.assertEqual(l, self.sample['l'])
1043 # Assert that modification changed only copied value
1045 self.assertNotEqual(l, self.sample['l'])
1047 # Assert that viewed dict is not changed
1048 self.assertEqual(self.sample, self.original_sample)
1050 def test_readonlydictview_contains(self):
1051 self.assertEqual('l' in self.view, 'l' in self.sample)
1052 self.assertEqual('d' in self.view, 'd' in self.sample)
1053 self.assertEqual('k' in self.view, 'k' in self.sample)
1055 # Assert that viewed dict is not changed
1056 self.assertEqual(self.sample, self.original_sample)
1058 def test_readonlydictview_iter(self):
1059 self.assertEqual(list(k for k in self.view),
1060 list(k for k in self.sample))
1062 # Assert the view is correct after modification
1063 self.sample['new'] = 'value'
1064 self.assertEqual(list(k for k in self.view),
1065 list(k for k in self.sample))
1067 def test_readonlydictview_len(self):
1068 self.assertEqual(len(self.view), len(self.sample))
1070 # Assert the view is correct after modification
1071 self.sample['new'] = 'value'
1072 self.assertEqual(len(self.view), len(self.sample))
1074 def test_readonlydictview_get(self):
1075 l = self.view.get('l')
1076 self.assertEqual(l, self.sample.get('l'))
1078 # Assert that modification changed only copied value
1080 self.assertNotEqual(l, self.sample.get('l'))
1082 # Assert that viewed dict is not changed
1083 self.assertEqual(self.sample, self.original_sample)
1085 def test_readonlydict_items(self):
1086 view_items = self.view.items()
1087 sample_items = list(self.sample.items())
1088 self.assertEqual(view_items, sample_items)
1090 view_items.append('newkey')
1091 self.assertNotEqual(view_items, sample_items)
1092 self.assertEqual(self.sample, self.original_sample)
1094 def test_readonlydict_values(self):
1095 view_values = self.view.values()
1096 sample_values = list(self.sample.values())
1097 self.assertEqual(view_values, sample_values)
1099 view_list_item = list(filter(lambda x: type(x) is list,
1101 view_list_item.append(4)
1102 self.assertNotEqual(view_values, sample_values)
1103 self.assertEqual(self.sample, self.original_sample)