From 3f833b4aeac2c68eb188a6e5b8422f82a20654b2 Mon Sep 17 00:00:00 2001 From: Kevin Guillaumond Date: Sat, 6 Apr 2024 16:27:35 -0700 Subject: [PATCH] Write unit test for TaskView.get_title() I started to look at #1077 and I thought it would be nice to be able to unit test this type of thing. A simple unit test for TaskView would be * create a task * create a view for it * check that the title of the view is the title of the task In the current state of affairs, this is difficult because the code that puts the Task data into the view lives in TaskEditor. But we pass a Task to the constructor of TaskView, so TaskView actually has all the data it needs. So I moved the logic there. I also moved the `is_new()` logic from the TaskEditor down to the Task. I feel like it should be possible to eventually do this in the TaskView constructor but for now, TaskEditor calls `self.textview.set_text_from_task()` from the same place it used to run that code. The test triggers a segmentation fault when running on GitHub Actions, so it's skipped there while I figure out why. --- .github/workflows/unit_tests.yml | 3 +- GTG/core/tasks.py | 6 +++- GTG/gtk/editor/editor.py | 55 +++----------------------------- GTG/gtk/editor/taskview.py | 32 +++++++++++++++++++ tests/core/test_task.py | 16 ++++++++++ tests/core/test_taskview.py | 19 ++++++++++- 6 files changed, 78 insertions(+), 53 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index fcf01f4e4f..884a18539b 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -46,4 +46,5 @@ jobs: - name: Run unit tests with Pytest run: | export PYTHONPATH=${PWD}/inst/lib/python3.9/site-packages - pytest + # Provide a fake display server, for tests that need one + xvfb-run pytest diff --git a/GTG/core/tasks.py b/GTG/core/tasks.py index 735f62f107..d1c1580339 100644 --- a/GTG/core/tasks.py +++ b/GTG/core/tasks.py @@ -295,6 +295,10 @@ def date_modified(self, value: Any) -> None: self._date_modified = Date(value) + def is_new(self) -> bool: + return self.title == DEFAULT_TITLE and not self.content + + @GObject.Property(type=str) def title(self) -> str: return self.raw_title @@ -753,7 +757,7 @@ def duplicate_for_recurrent(self, task: Task) -> Task: return new_task - def new(self, title: str = None, parent: Optional[UUID] = None) -> Task: + def new(self, title: str = '', parent: Optional[UUID] = None) -> Task: """Create a new task and add it to the store.""" tid = uuid4() diff --git a/GTG/gtk/editor/editor.py b/GTG/gtk/editor/editor.py index b283263474..1fd0b651c5 100644 --- a/GTG/gtk/editor/editor.py +++ b/GTG/gtk/editor/editor.py @@ -39,7 +39,7 @@ from GTG.gtk.editor.recurring_menu import RecurringMenu from GTG.gtk.editor.taskview import TaskView from GTG.gtk.colors import rgb_to_hex -from GTG.core.tasks import Task, Status, DEFAULT_TITLE +from GTG.core.tasks import Task, Status log = logging.getLogger(__name__) @@ -148,21 +148,9 @@ def __init__(self, app, task): provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ) - # self.textview.browse_tag_cb = app.select_tag - # self.textview.new_subtask_cb = self.new_subtask - # self.textview.get_subtasks_cb = task.get_children - # self.textview.delete_subtask_cb = self.remove_subtask - # self.textview.rename_subtask_cb = self.rename_subtask - # self.textview.open_subtask_cb = self.open_subtask - # self.textview.save_cb = self.light_save - # self.textview.add_tasktag_cb = self.tag_added - # self.textview.remove_tasktag_cb = self.tag_removed - # self.textview.refresh_cb = self.refresh_editor - # self.textview.get_tagslist_cb = task.get_tags_name - # self.textview.tid = task.id - self.textview.browse_tag_cb = app.select_tag self.textview.new_subtask_cb = self.new_subtask + # self.textview.get_subtasks_cb = task.get_children self.textview.delete_subtask_cb = self.remove_subtask self.textview.rename_subtask_cb = self.rename_subtask self.textview.open_subtask_cb = self.open_subtask @@ -170,6 +158,7 @@ def __init__(self, app, task): self.textview.add_tasktag_cb = self.tag_added self.textview.remove_tasktag_cb = self.tag_removed self.textview.refresh_cb = self.refresh_editor + # self.textview.get_tagslist_cb = task.get_tags_name self.textview.tid = task.id # Voila! it's done @@ -178,40 +167,7 @@ def __init__(self, app, task): textview_focus_controller.connect("leave", self.on_textview_focus_out) self.textview.add_controller(textview_focus_controller) - tags = task.tags - text = self.task.content - title = self.task.title - - # Insert text and tags as a non_undoable action, otherwise - # the user can CTRL+Z even this inserts. - self.textview.buffer.begin_irreversible_action() - self.textview.buffer.set_text(f"{title}\n") - - if text: - self.textview.insert(text) - - # Insert any remaining tags - if tags: - tag_names = [t.name for t in tags] - self.textview.insert_tags(tag_names) - else: - # If not text, we insert tags - if tags: - tag_names = [t.name for t in tags] - self.textview.insert_tags(tag_names) - start = self.textview.buffer.get_end_iter() - self.textview.buffer.insert(start, '\n') - - # Insert subtasks if they weren't inserted in the text - subtasks = task.children - for sub in subtasks: - if sub.id not in self.textview.subtasks['tags']: - self.textview.insert_existing_subtask(sub) - - if self.is_new(): - self.textview.select_title() - - self.textview.buffer.end_irreversible_action() + self.textview.set_text_from_task() # Connect search field to tags popup self.tags_tree.set_search_entry(self.tags_entry) @@ -872,8 +828,7 @@ def on_window_focus_change(self, window, gparam): def is_new(self) -> bool: - return (self.task.title == DEFAULT_TITLE - and self.textview.get_text() == '') + return self.task.is_new() def destruction(self, _=None): diff --git a/GTG/gtk/editor/taskview.py b/GTG/gtk/editor/taskview.py index 9bbf91d1e8..27e36e6a5b 100644 --- a/GTG/gtk/editor/taskview.py +++ b/GTG/gtk/editor/taskview.py @@ -165,6 +165,38 @@ def __init__(self, ds: Datastore, task, clipboard, dark) -> None: press_gesture.connect('begin', self.on_single_begin) self.add_controller(press_gesture) + def set_text_from_task(self) -> None: + """Sets the text of the view, from the text of the task""" + # Insert text and tags as a non_undoable action, otherwise + # the user can CTRL+Z even this inserts. + self.buffer.begin_irreversible_action() + self.buffer.set_text(f"{self.task.title}\n") + + if self.task.content: + self.insert(self.task.content) + + # Insert any remaining tags + if self.task.tags: + tag_names = [t.name for t in self.task.tags] + self.insert_tags(tag_names) + else: + # If not text, we insert tags + if self.task.tags: + tag_names = [t.name for t in self.task.tags] + self.insert_tags(tag_names) + start = self.buffer.get_end_iter() + self.buffer.insert(start, '\n') + + # Insert subtasks if they weren't inserted in the text + subtasks = self.task.children + for sub in subtasks: + if sub.id not in self.subtasks['tags']: + self.insert_existing_subtask(sub) + + if self.task.is_new(): + self.select_title() + + self.buffer.end_irreversible_action() def on_modified(self, buffer: Gtk.TextBuffer) -> None: """Called every time the text buffer changes.""" diff --git a/tests/core/test_task.py b/tests/core/test_task.py index ddf8c46fc0..5db3419315 100644 --- a/tests/core/test_task.py +++ b/tests/core/test_task.py @@ -28,6 +28,22 @@ class TestTask(TestCase): + def test_default_task_from_store_is_new(self): + task = TaskStore().new() + + self.assertTrue(task.is_new()) + + def test_task_with_content_is_not_new(self): + task = TaskStore().new() + task.content = 'foobar' + + self.assertFalse(task.is_new()) + + def test_task_with_title_is_not_new(self): + task = TaskStore().new(title='My new task') + + self.assertFalse(task.is_new()) + def test_title(self): task = Task(id=uuid4(), title='\tMy Title\n') diff --git a/tests/core/test_taskview.py b/tests/core/test_taskview.py index 41611d6130..bf7b4b947f 100644 --- a/tests/core/test_taskview.py +++ b/tests/core/test_taskview.py @@ -16,9 +16,14 @@ # this program. If not, see . # ----------------------------------------------------------------------------- +import os +import pytest import re +from uuid import uuid4 from unittest import TestCase -from GTG.gtk.editor.taskview import TAG_REGEX +from GTG.core.datastore import Datastore +from GTG.core.tasks import Task +from GTG.gtk.editor.taskview import TaskView, TAG_REGEX class TestTaskView(TestCase): @@ -41,3 +46,15 @@ def test_no_detect_tags(self): matches = re.findall(TAG_REGEX, content) self.assertEqual([], matches) + + def test_get_title(self): + task_title = 'Very important task' + task = Task(id = uuid4(), title=task_title) + view = TaskView(Datastore(), task, None, False) + view.refresh_cb = lambda x: x # Refresh CB that does nothing + view.set_text_from_task() + view.detect_title() + + view_title = view.get_title() + + self.assertEqual(view_title, task_title)