From b2f869d8a23f891d2c1d52da9d63666fbbdf954d Mon Sep 17 00:00:00 2001 From: Tomas Babej Date: Fri, 19 Dec 2014 08:35:12 +0100 Subject: [PATCH 01/16] tests: Do not use TW directly to create tasks --- tasklib/tests.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tasklib/tests.py b/tasklib/tests.py index 4ff4e03..e8bc0c3 100644 --- a/tasklib/tests.py +++ b/tasklib/tests.py @@ -4,7 +4,7 @@ import shutil import tempfile import unittest -from .task import TaskWarrior +from .task import TaskWarrior, Task class TasklibTest(unittest.TestCase): @@ -23,24 +23,24 @@ class TaskFilterTest(TasklibTest): self.assertEqual(len(self.tw.tasks.all()), 0) def test_all_non_empty(self): - self.tw.execute_command(['add', 'test task']) + Task(self.tw, description="test task").save() self.assertEqual(len(self.tw.tasks.all()), 1) self.assertEqual(self.tw.tasks.all()[0]['description'], 'test task') self.assertEqual(self.tw.tasks.all()[0]['status'], 'pending') def test_pending_non_empty(self): - self.tw.execute_command(['add', 'test task']) + Task(self.tw, description="test task").save() self.assertEqual(len(self.tw.tasks.pending()), 1) self.assertEqual(self.tw.tasks.pending()[0]['description'], 'test task') self.assertEqual(self.tw.tasks.pending()[0]['status'], 'pending') def test_completed_empty(self): - self.tw.execute_command(['add', 'test task']) + Task(self.tw, description="test task").save() self.assertEqual(len(self.tw.tasks.completed()), 0) def test_completed_non_empty(self): - self.tw.execute_command(['add', 'test task']) + Task(self.tw, description="test task").save() self.assertEqual(len(self.tw.tasks.completed()), 0) self.tw.tasks.all()[0].done() self.assertEqual(len(self.tw.tasks.completed()), 1) @@ -50,7 +50,7 @@ class AnnotationTest(TasklibTest): def setUp(self): super(AnnotationTest, self).setUp() - self.tw.execute_command(['add', 'test task']) + Task(self.tw, description="test task").save() def test_adding_annotation(self): task = self.tw.tasks.get() @@ -83,9 +83,9 @@ class AnnotationTest(TasklibTest): class UnicodeTest(TasklibTest): def test_unicode_task(self): - self.tw.execute_command(['add', '†åßk']) + Task(self.tw, description="†åßk").save() self.tw.tasks.get() def test_non_unicode_task(self): - self.tw.execute_command(['add', 'task']) + Task(self.tw, description="test task").save() self.tw.tasks.get() -- 2.39.5 From 98a9ea60e0c22e140650ff5a89dfd544d9c05cb0 Mon Sep 17 00:00:00 2001 From: Tomas Babej Date: Fri, 19 Dec 2014 08:37:43 +0100 Subject: [PATCH 02/16] tests: Add tests to filter by attributes and their empty values --- tasklib/tests.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tasklib/tests.py b/tasklib/tests.py index e8bc0c3..2fa1371 100644 --- a/tasklib/tests.py +++ b/tasklib/tests.py @@ -45,6 +45,30 @@ class TaskFilterTest(TasklibTest): self.tw.tasks.all()[0].done() self.assertEqual(len(self.tw.tasks.completed()), 1) + def test_filtering_by_attribute(self): + Task(self.tw, description="no priority task").save() + Task(self.tw, priority="H", description="high priority task").save() + self.assertEqual(len(self.tw.tasks.all()), 2) + + # Assert that the correct number of tasks is returned + self.assertEqual(len(self.tw.tasks.filter(priority="H")), 1) + + # Assert that the correct tasks are returned + high_priority_task = self.tw.tasks.get(priority="H") + self.assertEqual(high_priority_task['description'], "high priority task") + + def test_filtering_by_empty_attribute(self): + Task(self.tw, description="no priority task").save() + Task(self.tw, priority="H", description="high priority task").save() + self.assertEqual(len(self.tw.tasks.all()), 2) + + # Assert that the correct number of tasks is returned + self.assertEqual(len(self.tw.tasks.filter(priority=None)), 1) + + # Assert that the correct tasks are returned + no_priority_task = self.tw.tasks.get(priority=None) + self.assertEqual(no_priority_task['description'], "no priority task") + class AnnotationTest(TasklibTest): -- 2.39.5 From 7ae5585db6a239a8466b2b55dc5b2053b03e1d3a Mon Sep 17 00:00:00 2001 From: Tomas Babej Date: Fri, 19 Dec 2014 09:00:56 +0100 Subject: [PATCH 03/16] Task: Raise exceptions via Task class reference --- tasklib/task.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tasklib/task.py b/tasklib/task.py index 170fc77..86e98ec 100644 --- a/tasklib/task.py +++ b/tasklib/task.py @@ -148,13 +148,13 @@ class Task(TaskResource): def delete(self): if not self.saved: - raise self.NotSaved("Task needs to be saved before it can be deleted") + raise Task.NotSaved("Task needs to be saved before it can be deleted") # Refresh the status, and raise exception if the task is deleted self.refresh(only_fields=['status']) if self.deleted: - raise self.DeletedTask("Task was already deleted") + raise Task.DeletedTask("Task was already deleted") self.warrior.execute_command([self['uuid'], 'delete'], config_override={ 'confirmation': 'no', @@ -166,15 +166,15 @@ class Task(TaskResource): def done(self): if not self.saved: - raise self.NotSaved("Task needs to be saved before it can be completed") + raise Task.NotSaved("Task needs to be saved before it can be completed") # Refresh, and raise exception if task is already completed/deleted self.refresh(only_fields=['status']) if self.completed: - raise self.CompletedTask("Cannot complete a completed task") + raise Task.CompletedTask("Cannot complete a completed task") elif self.deleted: - raise self.DeletedTask("Deleted task cannot be completed") + raise Task.DeletedTask("Deleted task cannot be completed") self.warrior.execute_command([self['uuid'], 'done']) @@ -204,7 +204,7 @@ class Task(TaskResource): def add_annotation(self, annotation): if not self.saved: - raise self.NotSaved("Task needs to be saved to add annotation") + raise Task.NotSaved("Task needs to be saved to add annotation") args = [self['uuid'], 'annotate', annotation] self.warrior.execute_command(args) @@ -212,7 +212,7 @@ class Task(TaskResource): def remove_annotation(self, annotation): if not self.saved: - raise self.NotSaved("Task needs to be saved to add annotation") + raise Task.NotSaved("Task needs to be saved to add annotation") if isinstance(annotation, TaskAnnotation): annotation = annotation['description'] @@ -239,7 +239,7 @@ class Task(TaskResource): def refresh(self, only_fields=[]): # Raise error when trying to refresh a task that has not been saved if not self.saved: - raise self.NotSaved("Task needs to be saved to be refreshed") + raise Task.NotSaved("Task needs to be saved to be refreshed") # We need to use ID as backup for uuid here for the refreshes # of newly saved tasks. Any other place in the code is fine -- 2.39.5 From 71b09c9d8f0af7799bbe634dfdafccb08e7d9f00 Mon Sep 17 00:00:00 2001 From: Tomas Babej Date: Fri, 19 Dec 2014 09:09:57 +0100 Subject: [PATCH 04/16] tests: Add basic Task tests --- tasklib/tests.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tasklib/tests.py b/tasklib/tests.py index 2fa1371..2af697f 100644 --- a/tasklib/tests.py +++ b/tasklib/tests.py @@ -70,6 +70,53 @@ class TaskFilterTest(TasklibTest): self.assertEqual(no_priority_task['description'], "no priority task") +class TaskTest(TasklibTest): + + def test_create_unsaved_task(self): + # Make sure a new task is not saved unless explicitly called for + t = Task(self.tw, description="test task") + self.assertEqual(len(self.tw.tasks.all()), 0) + + def test_delete_unsaved_task(self): + with self.assertRaises(Task.NotSaved): + t = Task(self.tw, description="test task") + t.delete() + + def test_complete_unsaved_task(self): + with self.assertRaises(Task.NotSaved): + t = Task(self.tw, description="test task") + t.done() + + def test_refresh_unsaved_task(self): + with self.assertRaises(Task.NotSaved): + t = Task(self.tw, description="test task") + t.refresh() + + def test_delete_deleted_task(self): + t = Task(self.tw, description="test task") + t.save() + t.delete() + + with self.assertRaises(Task.DeletedTask): + t.delete() + + def test_complete_completed_task(self): + t = Task(self.tw, description="test task") + t.save() + t.done() + + with self.assertRaises(Task.CompletedTask): + t.done() + + def test_complete_deleted_task(self): + t = Task(self.tw, description="test task") + t.save() + t.delete() + + with self.assertRaises(Task.DeletedTask): + t.done() + + class AnnotationTest(TasklibTest): def setUp(self): -- 2.39.5 From f4936467c1b535b335f6fcf5a5344e72de1437ef Mon Sep 17 00:00:00 2001 From: Tomas Babej Date: Thu, 25 Dec 2014 18:16:36 +0100 Subject: [PATCH 05/16] TaskWarrior: Detect task version --- tasklib/task.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tasklib/task.py b/tasklib/task.py index 86e98ec..9eff14c 100644 --- a/tasklib/task.py +++ b/tasklib/task.py @@ -12,6 +12,11 @@ REPR_OUTPUT_SIZE = 10 PENDING = 'pending' COMPLETED = 'completed' +VERSION_2_1_0 = six.u('2.1.0') +VERSION_2_2_0 = six.u('2.2.0') +VERSION_2_3_0 = six.u('2.3.0') +VERSION_2_4_0 = six.u('2.4.0') + logger = logging.getLogger(__name__) @@ -401,6 +406,7 @@ class TaskWarrior(object): 'data.location': os.path.expanduser(data_location), } self.tasks = TaskQuerySet(self) + self.version = self._get_version() def _get_command_args(self, args, config_override={}): command_args = ['task', 'rc:/'] @@ -411,6 +417,14 @@ class TaskWarrior(object): command_args.extend(map(str, args)) return command_args + def _get_version(self): + p = subprocess.Popen( + ['task', '--version'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = [x.decode('utf-8') for x in p.communicate()] + return stdout.strip('\n') + def execute_command(self, args, config_override={}): command_args = self._get_command_args( args, config_override=config_override) -- 2.39.5 From c7b2d1ec4f3b96ba04b216b1add37f915d1e5399 Mon Sep 17 00:00:00 2001 From: Tomas Babej Date: Thu, 25 Dec 2014 18:40:29 +0100 Subject: [PATCH 06/16] Task: Add workaround for task bug in older versions, which eats up a part of description --- tasklib/task.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tasklib/task.py b/tasklib/task.py index 9eff14c..330491f 100644 --- a/tasklib/task.py +++ b/tasklib/task.py @@ -228,16 +228,24 @@ class Task(TaskResource): def _get_modified_fields_as_args(self): args = [] + def add_field(field): + # Task version older than 2.4.0 ignores first word of the + # task description if description: prefix is used + if self.warrior.version < VERSION_2_4_0 and field == 'description': + args.append(self._data[field]) + else: + args.append('{0}:{1}'.format(field, self._data[field])) + # If we're modifying saved task, simply pass on all modified fields if self.saved: for field in self._modified_fields: - args.append('{0}:{1}'.format(field, self._data[field])) + add_field(field) # For new tasks, pass all fields that make sense else: for field in self._data.keys(): if field in self.read_only_fields: continue - args.append('{0}:{1}'.format(field, self._data[field])) + add_field(field) return args -- 2.39.5 From 34438b38241750f4feace6b6b75b59da2bc2fcfd Mon Sep 17 00:00:00 2001 From: Tomas Babej Date: Thu, 25 Dec 2014 18:51:04 +0100 Subject: [PATCH 07/16] tests: Make tests Python 2.6 compatible, avoid using assertRaises as context manager --- tasklib/tests.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/tasklib/tests.py b/tasklib/tests.py index 2af697f..2103c06 100644 --- a/tasklib/tests.py +++ b/tasklib/tests.py @@ -77,44 +77,41 @@ class TaskTest(TasklibTest): t = Task(self.tw, description="test task") self.assertEqual(len(self.tw.tasks.all()), 0) + # TODO: once python 2.6 compatiblity is over, use context managers here + # and in all subsequent tests for assertRaises + def test_delete_unsaved_task(self): - with self.assertRaises(Task.NotSaved): - t = Task(self.tw, description="test task") - t.delete() + t = Task(self.tw, description="test task") + self.assertRaises(Task.NotSaved, t.delete) def test_complete_unsaved_task(self): - with self.assertRaises(Task.NotSaved): - t = Task(self.tw, description="test task") - t.done() + t = Task(self.tw, description="test task") + self.assertRaises(Task.NotSaved, t.done) def test_refresh_unsaved_task(self): - with self.assertRaises(Task.NotSaved): - t = Task(self.tw, description="test task") - t.refresh() + t = Task(self.tw, description="test task") + self.assertRaises(Task.NotSaved, t.refresh) def test_delete_deleted_task(self): t = Task(self.tw, description="test task") t.save() t.delete() - with self.assertRaises(Task.DeletedTask): - t.delete() + self.assertRaises(Task.DeletedTask, t.delete) def test_complete_completed_task(self): t = Task(self.tw, description="test task") t.save() t.done() - with self.assertRaises(Task.CompletedTask): - t.done() + self.assertRaises(Task.CompletedTask, t.done) def test_complete_deleted_task(self): t = Task(self.tw, description="test task") t.save() t.delete() - with self.assertRaises(Task.DeletedTask): - t.done() + self.assertRaises(Task.DeletedTask, t.done) class AnnotationTest(TasklibTest): -- 2.39.5 From 432b3aa67c40dd92ba2cb0042439ea665b2dcc21 Mon Sep 17 00:00:00 2001 From: Rob Golding Date: Fri, 26 Dec 2014 12:29:24 +0700 Subject: [PATCH 08/16] Don't explicitly require six==1.5.2 to avoid VersionConflict error --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f9b838d..1c98ae3 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( packages=find_packages(), include_package_data=True, test_suite='tasklib.tests', - install_requires=['six==1.5.2'], + install_requires=['six>=1.4'], classifiers=[ 'Development Status :: 4 - Beta', 'Programming Language :: Python', -- 2.39.5 From c9db4bbc93086f404c6d115df17e272989d2c310 Mon Sep 17 00:00:00 2001 From: Rob Golding Date: Fri, 26 Dec 2014 12:30:26 +0700 Subject: [PATCH 09/16] The next version will be 0.6.0 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 1c98ae3..088747a 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import setup, find_packages -version = '0.5.0' +version = '0.6.0' setup( name='tasklib', @@ -15,7 +15,7 @@ setup( packages=find_packages(), include_package_data=True, test_suite='tasklib.tests', - install_requires=['six>=1.4'], + install_requires=['six==1.5.2'], classifiers=[ 'Development Status :: 4 - Beta', 'Programming Language :: Python', -- 2.39.5 From 2fbfb2cbb3473110f7464aa227704535a31cd977 Mon Sep 17 00:00:00 2001 From: Rob Golding Date: Fri, 26 Dec 2014 12:38:07 +0700 Subject: [PATCH 10/16] Add AUTHORS file --- AUTHORS | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 AUTHORS diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..6de540e --- /dev/null +++ b/AUTHORS @@ -0,0 +1,2 @@ +Rob Golding +Tomas Babej -- 2.39.5 From 351a4268660a1d24d7893b2e3ce8daa8f131f966 Mon Sep 17 00:00:00 2001 From: Tomas Babej Date: Fri, 26 Dec 2014 16:34:44 +0100 Subject: [PATCH 11/16] Task: Make two Tasks equal by uuid --- tasklib/task.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tasklib/task.py b/tasklib/task.py index bb3fa53..336c050 100644 --- a/tasklib/task.py +++ b/tasklib/task.py @@ -112,6 +112,12 @@ class Task(TaskResource): def __unicode__(self): return self['description'] + def __eq__(self, other): + return self['uuid'] == other['uuid'] + + def __hash__(self): + return self['uuid'].__hash__() + @property def completed(self): return self['status'] == six.text_type('completed') -- 2.39.5 From 3c4a436d69fb02fca34be8f0189780d33ddbfe85 Mon Sep 17 00:00:00 2001 From: Tomas Babej Date: Fri, 26 Dec 2014 16:39:09 +0100 Subject: [PATCH 12/16] Task: Detect modified fields by actual modifications --- tasklib/task.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tasklib/task.py b/tasklib/task.py index 336c050..3860f8e 100644 --- a/tasklib/task.py +++ b/tasklib/task.py @@ -29,6 +29,7 @@ class TaskResource(object): def _load_data(self, data): self._data = data + self._original_data = data def __getitem__(self, key): hydrate_func = getattr(self, 'deserialize_{0}'.format(key), @@ -41,7 +42,6 @@ class TaskResource(object): dehydrate_func = getattr(self, 'serialize_{0}'.format(key), lambda x: x) self._data[key] = dehydrate_func(value) - self._modified_fields.add(key) def __str__(self): s = six.text_type(self.__unicode__()) @@ -107,7 +107,6 @@ class Task(TaskResource): kwargs.update(data) self._load_data(kwargs) - self._modified_fields = set() def __unicode__(self): return self['description'] @@ -118,6 +117,12 @@ class Task(TaskResource): def __hash__(self): return self['uuid'].__hash__() + @property + def _modified_fields(self): + for key in self._data.keys(): + if self._data.get(key) != self._original_data.get(key): + yield key + @property def completed(self): return self['status'] == six.text_type('completed') @@ -210,7 +215,6 @@ class Task(TaskResource): # Circumvent the ID storage, since ID is considered read-only self._data['id'] = int(id_lines[0].split(' ')[2].rstrip('.')) - self._modified_fields.clear() self.refresh() def add_annotation(self, annotation): @@ -269,8 +273,10 @@ class Task(TaskResource): to_update = dict( [(k, new_data.get(k)) for k in only_fields]) self._data.update(to_update) + self._original_data.update(to_update) else: self._data = new_data + self._original_data = new_data class TaskFilter(object): -- 2.39.5 From 549524410137e15cd2ffcb91937c04c18d996ef7 Mon Sep 17 00:00:00 2001 From: Tomas Babej Date: Sun, 28 Dec 2014 01:53:26 +0100 Subject: [PATCH 13/16] Task: Make a dict.copy() for _original_data --- tasklib/task.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tasklib/task.py b/tasklib/task.py index 3860f8e..44c6965 100644 --- a/tasklib/task.py +++ b/tasklib/task.py @@ -29,7 +29,9 @@ class TaskResource(object): def _load_data(self, data): self._data = data - self._original_data = data + # We need to use a copy for original data, so that changes + # are not propagated + self._original_data = data.copy() def __getitem__(self, key): hydrate_func = getattr(self, 'deserialize_{0}'.format(key), @@ -276,7 +278,10 @@ class Task(TaskResource): self._original_data.update(to_update) else: self._data = new_data - self._original_data = new_data + # We need to create a clone for original_data though + # Shallow copy is alright, since data dict uses only + # primitive data types + self._original_data = new_data.copy() class TaskFilter(object): -- 2.39.5 From bd3efb6ceee683f9575fb40c4ec9cf730e894939 Mon Sep 17 00:00:00 2001 From: Tomas Babej Date: Sun, 28 Dec 2014 01:54:22 +0100 Subject: [PATCH 14/16] Task: Make Task object non-iterable --- tasklib/task.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tasklib/task.py b/tasklib/task.py index 44c6965..096f656 100644 --- a/tasklib/task.py +++ b/tasklib/task.py @@ -34,6 +34,14 @@ class TaskResource(object): self._original_data = data.copy() def __getitem__(self, key): + # This is a workaround to make TaskResource non-iterable + # over simple index-based iteration + try: + int(key) + raise StopIteration + except ValueError: + pass + hydrate_func = getattr(self, 'deserialize_{0}'.format(key), lambda x: x) return hydrate_func(self._data.get(key)) -- 2.39.5 From 22e41afb63e6a1bd1627ee9711a7fd76907739bc Mon Sep 17 00:00:00 2001 From: Tomas Babej Date: Sun, 28 Dec 2014 01:57:02 +0100 Subject: [PATCH 15/16] Task: Represent dependant tasks as a set --- tasklib/task.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tasklib/task.py b/tasklib/task.py index 096f656..8fe1e1b 100644 --- a/tasklib/task.py +++ b/tasklib/task.py @@ -161,6 +161,41 @@ class Task(TaskResource): return None return datetime.datetime.strptime(date_str, DATE_FORMAT) + def serialize_depends(self, cur_dependencies): + # Check that all the tasks are saved + for task in cur_dependencies: + if not task.saved: + raise Task.NotSaved('Task \'%s\' needs to be saved before ' + 'it can be set as dependency.' % task) + + # Return the list of uuids + return ','.join(task['uuid'] for task in cur_dependencies) + + def deserialize_depends(self, raw_uuids): + raw_uuids = raw_uuids or '' # Convert None to empty string + uuids = raw_uuids.split(',') + return set(self.warrior.tasks.get(uuid=uuid) for uuid in uuids if uuid) + + def format_depends(self): + # We need to generate added and removed dependencies list, + # since Taskwarrior does not accept redefining dependencies. + + # This cannot be part of serialize_depends, since we need + # to keep a list of all depedencies in the _data dictionary, + # not just currently added/removed ones + + old_dependencies_raw = self._original_data.get('depends','') + old_dependencies = self.deserialize_depends(old_dependencies_raw) + + added = self['depends'] - old_dependencies + removed = old_dependencies - self['depends'] + + # Removed dependencies need to be prefixed with '-' + return ','.join( + [t['uuid'] for t in added] + + ['-' + t['uuid'] for t in removed] + ) + def deserialize_annotations(self, data): return [TaskAnnotation(self, d) for d in data] if data else [] @@ -253,6 +288,8 @@ class Task(TaskResource): # task description if description: prefix is used if self.warrior.version < VERSION_2_4_0 and field == 'description': args.append(self._data[field]) + elif field == 'depends': + args.append('{0}:{1}'.format(field, self.format_depends())) else: args.append('{0}:{1}'.format(field, self._data[field])) -- 2.39.5 From 32c376913b4cc616cb7fc4e076f0c2ec690229ba Mon Sep 17 00:00:00 2001 From: Tomas Babej Date: Sun, 28 Dec 2014 02:03:00 +0100 Subject: [PATCH 16/16] Tests: Add tests for dependency sets --- tasklib/tests.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/tasklib/tests.py b/tasklib/tests.py index 2103c06..e5b0541 100644 --- a/tasklib/tests.py +++ b/tasklib/tests.py @@ -113,6 +113,86 @@ class TaskTest(TasklibTest): self.assertRaises(Task.DeletedTask, t.done) + def test_empty_dependency_set_of_unsaved_task(self): + t = Task(self.tw, description="test task") + self.assertEqual(t['depends'], set()) + + def test_empty_dependency_set_of_saved_task(self): + t = Task(self.tw, description="test task") + t.save() + self.assertEqual(t['depends'], set()) + + def test_set_unsaved_task_as_dependency(self): + # Adds only one dependency to task with no dependencies + t = Task(self.tw, description="test task") + dependency = Task(self.tw, description="needs to be done first") + + # We only save the parent task, dependency task is unsaved + t.save() + + self.assertRaises(Task.NotSaved, + t.__setitem__, 'depends', set([dependency])) + + def test_set_simple_dependency_set(self): + # Adds only one dependency to task with no dependencies + t = Task(self.tw, description="test task") + dependency = Task(self.tw, description="needs to be done first") + + t.save() + dependency.save() + + t['depends'] = set([dependency]) + + self.assertEqual(t['depends'], set([dependency])) + + def test_set_complex_dependency_set(self): + # Adds two dependencies to task with no dependencies + t = Task(self.tw, description="test task") + dependency1 = Task(self.tw, description="needs to be done first") + dependency2 = Task(self.tw, description="needs to be done second") + + t.save() + dependency1.save() + dependency2.save() + + t['depends'] = set([dependency1, dependency2]) + + self.assertEqual(t['depends'], set([dependency1, dependency2])) + + def test_remove_from_dependency_set(self): + # Removes dependency from task with two dependencies + t = Task(self.tw, description="test task") + dependency1 = Task(self.tw, description="needs to be done first") + dependency2 = Task(self.tw, description="needs to be done second") + + dependency1.save() + dependency2.save() + + t['depends'] = set([dependency1, dependency2]) + t.save() + + t['depends'] = t['depends'] - set([dependency2]) + t.save() + + self.assertEqual(t['depends'], set([dependency1])) + + def test_add_to_dependency_set(self): + # Adds dependency to task with one dependencies + t = Task(self.tw, description="test task") + dependency1 = Task(self.tw, description="needs to be done first") + dependency2 = Task(self.tw, description="needs to be done second") + + dependency1.save() + dependency2.save() + + t['depends'] = set([dependency1]) + t.save() + + t['depends'] = t['depends'] | set([dependency2]) + t.save() + + self.assertEqual(t['depends'], set([dependency1, dependency2])) + class AnnotationTest(TasklibTest): -- 2.39.5