Repository: pimutils/todoman
Pull request #207 (2017-03-23 02:07:35)

More tests


Patch:
diff --git a/.travis.yml b/.travis.yml
index 8a1d887..b94902d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -24,13 +24,6 @@ matrix:
       env: BUILD=flake8
     - python: 3.6
       env: BUILD=docs
-  exclude:
-    # Sphinx requires python 3.4 or above
-    - python: 3.3
-      env: BUILD=docs
-    # Ditto
-    - python: pypy3.3-5.2-alpha1
-      env: BUILD=docs
 
 addons:
   apt:
diff --git a/setup.cfg b/setup.cfg
index 5ac84e5..27ba598 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -3,4 +3,4 @@ test=pytest
 
 [tool:pytest]
 testpaths = tests
-addopts = --cov=todoman
+addopts = --cov=todoman --cov-report=term-missing
diff --git a/todoman/cli.py b/todoman/cli.py
index b2e74b6..e28159b 100644
--- a/todoman/cli.py
+++ b/todoman/cli.py
@@ -152,10 +152,6 @@ class AppContext:
             self.config['main']['dt_separator']
         )
 
-    @cached_property
-    def porcelain_formatter(self):
-        return formatters.PorcelainFormatter()
-
     @cached_property
     def formatter(self):
         return self.formatter_class(
@@ -338,11 +334,6 @@ def done(ctx, ids):
         click.echo(ctx.formatter.detailed(todo))
 
 
-def _abort_if_false(ctx, param, value):
-    if not value:
-        ctx.abort()
-
-
 @cli.command()
 @pass_ctx
 @click.confirmation_option(
diff --git a/todoman/configuration.py b/todoman/configuration.py
index 82349f6..e7c052b 100644
--- a/todoman/configuration.py
+++ b/todoman/configuration.py
@@ -3,7 +3,7 @@ from os.path import exists, join
 
 import xdg.BaseDirectory
 from configobj import ConfigObj, flatten_errors
-from validate import is_option, Validator, VdtValueError
+from validate import Validator, VdtValueError
 
 from todoman import __documentation__
 
@@ -18,8 +18,6 @@ class ConfigurationException(Exception):
 
 def expand_path(path):
     """expands `~` as well as variable names"""
-    if path is None:
-        return False
     return os.path.expanduser(os.path.expandvars(path))
 
 
@@ -51,15 +49,6 @@ def validate_time_format(fmt):
     return fmt
 
 
-def validate_color(color):
-    color = is_option(color)
-    if color == 'always':
-        return True
-    elif color == 'never':
-        return False
-    return None
-
-
 def find_config():
     custom_path = os.environ.get('TODOMAN_CONFIG')
     if custom_path:
diff --git a/todoman/interactive.py b/todoman/interactive.py
index b0a66ab..bd6fbb3 100644
--- a/todoman/interactive.py
+++ b/todoman/interactive.py
@@ -77,16 +77,18 @@ class TodoEditor:
                         in widgets.ExtendedEdit.HELP)
         )
 
-        list_selector = []
+        self.list_selector = []
         for _list in self.lists:
             urwid.RadioButton(
-                list_selector,
+                self.list_selector,
                 _list.name,
                 state=_list == self.current_list,
                 on_state_change=self._change_current_list,
                 user_data=_list,
             )
-        right_column = urwid.ListBox([urwid.Text('List:\n')] + list_selector)
+        right_column = urwid.ListBox(
+            [urwid.Text('List:\n')] + self.list_selector
+        )
 
         self._ui = urwid.Columns([left_column, right_column])
 


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
index ff53054..19210ca 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -141,7 +141,7 @@ def test_default_command(tmpdir, runner, create):
     assert 'harhar' in result.output
 
 
-def test_delete(tmpdir, runner, create):
+def test_delete(runner, create):
     create(
         'test.ics',
         'SUMMARY:harhar\n'
@@ -155,6 +155,18 @@ def test_delete(tmpdir, runner, create):
     assert not result.output.strip()
 
 
+def test_delete_prompt(todo_factory, runner, default_database):
+    todo_factory()
+
+    result = runner.invoke(cli, ['delete', '1'], input='yes')
+
+    assert not result.exception
+    assert '[y/N]: yes\nDeleting "YARR!"' in result.output
+
+    default_database.update_cache()
+    assert len(list(default_database.todos())) == 0
+
+
 def test_copy(tmpdir, runner, create):
     tmpdir.mkdir('other_list')
     create(
@@ -394,6 +406,27 @@ def test_color_due_dates(tmpdir, runner, create, hours):
             .format(due_str)
 
 
+def test_color_flag(runner, todo_factory):
+    todo_factory(due=datetime.datetime(2007, 3, 22))
+
+    result = runner.invoke(cli, ['--color', 'always'], color=True)
+    assert(
+        result.output.strip() ==
+        '1  [ ]    \x1b[31m2007-03-22\x1b[0m  YARR! @default\x1b[0m'
+    )
+    result = runner.invoke(cli, color=True)
+    assert(
+        result.output.strip() ==
+        '1  [ ]    \x1b[31m2007-03-22\x1b[0m  YARR! @default\x1b[0m'
+    )
+
+    result = runner.invoke(cli, ['--color', 'never'], color=True)
+    assert(
+        result.output.strip() ==
+        '1  [ ]    2007-03-22  YARR! @default'
+    )
+
+
 def test_flush(tmpdir, runner, create):
     create(
         'test.ics',
@@ -443,6 +476,41 @@ def test_edit(runner, default_database):
     assert todo.summary == 'Eat paint'
 
 
+def test_edit_move(runner, todo_factory, default_database, tmpdir):
+    """
+    Test that editing the list in the UI edits the todo as expected
+
+    The goal of this test is not to test the editor itself, but rather the
+    `edit` command and its slightly-complex moving logic.
+    """
+    tmpdir.mkdir('another_list')
+
+    default_database.paths = [
+        str(tmpdir.join('default')),
+        str(tmpdir.join('another_list')),
+    ]
+    default_database.update_cache()
+
+    todo_factory(summary='Eat some headphones')
+
+    lists = list(default_database.lists())
+    another_list = next(filter(lambda x: x.name == 'another_list', lists))
+
+    def moving_edit(self):
+        self.current_list = another_list
+        self._save_inner()
+
+    with patch('todoman.interactive.TodoEditor.edit', moving_edit):
+        result = runner.invoke(cli, ['edit', '1'])
+
+    assert not result.exception
+
+    default_database.update_cache()
+    todos = list(default_database.todos())
+    assert len(todos) == 1
+    assert todos[0].list.name == 'another_list'
+
+
 def test_empty_list(tmpdir, runner, create):
     for item in tmpdir.listdir():
         if isdir(str(item)):
@@ -623,3 +691,19 @@ def test_bad_start_date(runner):
     result = runner.invoke(cli, ['list', '--start', 'godzilla', '2017-03-22'])
     assert result.exception
     assert ("Format should be '[before|after] [DATE]'" in result.output)
+
+
+def test_done(runner, todo_factory, default_database):
+    todo = todo_factory()
+
+    result = runner.invoke(cli, ['done', '1'])
+    assert not result.exception
+
+    default_database.update_cache()
+    todo = next(default_database.todos(all=True))
+    assert todo.percent_complete == 100
+    assert todo.is_completed is True
+
+    result = runner.invoke(cli, ['done', '17'])
+    assert result.exception
+    assert result.output.strip() == 'No todo with id 17.'
diff --git a/tests/test_config.py b/tests/test_config.py
index d602bf5..3a735c2 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -4,6 +4,7 @@ import pytest
 from click.testing import CliRunner
 
 from todoman.cli import cli
+from todoman.configuration import ConfigurationException, load_config
 
 
 def test_explicit_nonexistant(runner):
@@ -129,3 +130,61 @@ def test_missing_cache_dir(config, runner, tmpdir):
     assert not result.exception
     assert cache_dir.isdir()
     assert cache_file.isfile()
+
+
+def test_date_field_in_time_format(config, runner, tmpdir):
+    config.write(
+        '[main]\n'
+        'path = "/"\n'
+        'time_format = %Y-%m-%d\n'
+    )
+    result = runner.invoke(cli)
+    assert result.exception
+    assert (
+        'Found date component in `time_format`, please use `date_format` for '
+        'that.' in result.output
+    )
+
+
+def test_date_field_in_time(config, runner, tmpdir):
+    config.write(
+        '[main]\n'
+        'path = "/"\n'
+        'date_format = %Y-%d-:%M\n'
+    )
+    result = runner.invoke(cli)
+    assert result.exception
+    assert (
+        'Found time component in `date_format`, please use `time_format` for '
+        'that.' in result.output
+    )
+
+
+def test_colour_validation_auto(config):
+    with patch(
+        'todoman.configuration.find_config',
+        return_value=(str(config)),
+    ):
+        cfg = load_config()
+
+    assert cfg['main']['color'] == 'auto'
+
+
+def test_colour_validation_always(config):
+    config.write("color = 'always'\n", 'a')
+    with patch(
+        'todoman.configuration.find_config',
+        return_value=(str(config)),
+    ):
+        cfg = load_config()
+
+    assert cfg['main']['color'] == 'always'
+
+
+def test_colour_validation_invalid(config):
+    config.write("color = 'on_weekends_only'\n", 'a')
+    with patch(
+        'todoman.configuration.find_config',
+        return_value=(str(config)),
+    ), pytest.raises(ConfigurationException):
+        load_config()
diff --git a/tests/test_ui.py b/tests/test_ui.py
index 45ec9eb..9bfb3a4 100644
--- a/tests/test_ui.py
+++ b/tests/test_ui.py
@@ -23,6 +23,39 @@ def test_todo_editor_priority(default_database, todo_factory,
     assert todo.priority is 0
 
 
+def test_todo_editor_list(default_database, todo_factory, default_formatter,
+                          tmpdir):
+    tmpdir.mkdir('another_list')
+
+    default_database.paths = [
+        str(tmpdir.join('default')),
+        str(tmpdir.join('another_list')),
+    ]
+    default_database.update_cache()
+
+    todo = todo_factory()
+    lists = list(default_database.lists())
+
+    editor = TodoEditor(todo, lists, default_formatter)
+    default_list = next(filter(
+        lambda x: x.label == 'default',
+        editor.list_selector
+    ))
+    another_list = next(filter(
+        lambda x: x.label == 'another_list',
+        editor.list_selector
+    ))
+
+    assert editor.current_list == todo.list
+    assert default_list.label == todo.list.name
+
+    another_list.set_state(True)
+    editor._save_inner()
+
+    assert editor.current_list == todo.list
+    assert another_list.label == todo.list.name
+
+
 def test_todo_editor_summary(default_database, todo_factory,
                              default_formatter):
     todo = todo_factory()
diff --git a/tests/test_widgets.py b/tests/test_widgets.py
index e62f646..bd6c295 100644
--- a/tests/test_widgets.py
+++ b/tests/test_widgets.py
@@ -1,3 +1,5 @@
+from unittest import mock
+
 from todoman.widgets import ExtendedEdit
 
 # We ignore `size` when testing keypresses, because it's not used anywhere.
@@ -131,5 +133,28 @@ def test_extended_edit_delete_next_char():
     assert extended_edit.edit_pos == 24
     assert extended_edit.get_edit_text() == 'The lazy fox bla bla\n@ät'
 
-# TODO: plain ol' keypresses
-# TODO: can we test ctrl o (maybe with some unittest.mock?)
+
+def test_extended_edit_input():
+    """
+    Very basic test to make sure we don't break basic editing
+
+    We don't need to do more testing because that's done upstream. We basically
+    want to test that we properly forward unhandled keypresses.
+    """
+    extended_edit = ExtendedEdit(mock.MagicMock())
+    extended_edit.keypress((10,), 'h')
+    extended_edit.keypress((10,), 'i')
+
+    assert extended_edit.get_edit_text() == 'hi'
+
+
+def test_extended_edit_editor():
+    extended_edit = ExtendedEdit(mock.MagicMock())
+    extended_edit.set_edit_text(BASE_STRING)
+
+    with mock.patch('click.edit', return_value='Sheep!') as edit:
+        extended_edit.keypress(10, 'ctrl o')
+
+    assert edit.call_count == 1
+    assert edit.call_args == mock.call(BASE_STRING)
+    assert extended_edit.get_edit_text() == 'Sheep!'