From d8475231c68c9fefe9adadf6b2254ae24ec525e5 Mon Sep 17 00:00:00 2001 From: staticdev Date: Tue, 28 Dec 2021 19:16:42 +0100 Subject: [PATCH] Add missing tests --- codecov.yml | 4 +- pyproject.toml | 2 +- src/inquirer/errors.py | 14 ++---- src/inquirer/prompt.py | 4 +- src/inquirer/questions.py | 2 +- src/inquirer/render/console/__init__.py | 2 +- src/inquirer/shortcuts.py | 4 +- src/inquirer/themes.py | 18 +++---- .../console_render/test_checkbox.py | 46 +++++++++++++---- .../integration/console_render/test_editor.py | 6 +-- tests/integration/console_render/test_list.py | 38 +++++++++++--- .../console_render/test_password.py | 38 ++++++++++++-- tests/unit/test_prompt.py | 49 ++++++++++++------- tests/unit/test_question.py | 16 ++++-- tests/unit/test_shortcuts.py | 31 ++++++++++++ tests/unit/{test_theme.py => test_themes.py} | 21 ++++++++ 16 files changed, 216 insertions(+), 79 deletions(-) create mode 100644 tests/unit/test_shortcuts.py rename tests/unit/{test_theme.py => test_themes.py} (68%) diff --git a/codecov.yml b/codecov.yml index 39c15fc0..284e9599 100644 --- a/codecov.yml +++ b/codecov.yml @@ -3,7 +3,7 @@ coverage: status: project: default: - target: "90" + target: "95" patch: default: - target: "90" + target: "95" diff --git a/pyproject.toml b/pyproject.toml index db86fdb7..c1d2cf07 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ source = ["inquirer", "tests"] [tool.coverage.report] show_missing = true -fail_under = 90 +fail_under = 95 [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/src/inquirer/errors.py b/src/inquirer/errors.py index 09696c93..b9c8224c 100644 --- a/src/inquirer/errors.py +++ b/src/inquirer/errors.py @@ -1,23 +1,15 @@ -class InquirerError(Exception): - pass - - -class ValidationError(InquirerError): +class ValidationError(Exception): def __init__(self, value, reason=None, *args): super().__init__(*args) self.value = value self.reason = reason -class UnknownQuestionTypeError(InquirerError): - pass - - -class Aborted(InquirerError): +class UnknownQuestionTypeError(Exception): pass -class EndOfInput(InquirerError): +class EndOfInput(Exception): def __init__(self, selection, *args): super().__init__(*args) self.selection = selection diff --git a/src/inquirer/prompt.py b/src/inquirer/prompt.py index 4fcc71da..0cfbe7d4 100644 --- a/src/inquirer/prompt.py +++ b/src/inquirer/prompt.py @@ -1,5 +1,5 @@ -from .render.console import ConsoleRender -from . import themes +from inquirer.render.console import ConsoleRender +import inquirer.themes as themes def prompt(questions, render=None, answers=None, theme=themes.Default(), raise_keyboard_interrupt=False): diff --git a/src/inquirer/questions.py b/src/inquirer/questions.py index 923b8f2e..6bd6cea7 100644 --- a/src/inquirer/questions.py +++ b/src/inquirer/questions.py @@ -6,7 +6,7 @@ import errno import sys -from . import errors +import inquirer.errors as errors class TaggedValue: diff --git a/src/inquirer/render/console/__init__.py b/src/inquirer/render/console/__init__.py index a45d08c3..49ed262d 100644 --- a/src/inquirer/render/console/__init__.py +++ b/src/inquirer/render/console/__init__.py @@ -1,10 +1,10 @@ import sys + from blessed import Terminal from inquirer import errors from inquirer import events from inquirer import themes - from ._text import Text from ._editor import Editor from ._password import Password diff --git a/src/inquirer/shortcuts.py b/src/inquirer/shortcuts.py index d3bcfe7c..6d6751ec 100644 --- a/src/inquirer/shortcuts.py +++ b/src/inquirer/shortcuts.py @@ -1,5 +1,5 @@ -from .render.console import ConsoleRender -from . import questions +from inquirer.render.console import ConsoleRender +import inquirer.questions as questions def text(message, render=None, **kwargs): diff --git a/src/inquirer/themes.py b/src/inquirer/themes.py index fcc70fe7..9ab6b78b 100644 --- a/src/inquirer/themes.py +++ b/src/inquirer/themes.py @@ -1,9 +1,9 @@ +import collections import json -from collections import namedtuple from blessed import Terminal -from .errors import ThemeError +import inquirer.errors as errors term = Terminal() @@ -50,7 +50,7 @@ def load_theme_from_dict(dict_theme): t = Default() for question_type, settings in dict_theme.items(): if question_type not in vars(t): - raise ThemeError( + raise errors.ThemeError( "Error while parsing theme. Question type " "`{}` not found or not customizable.".format(question_type) ) @@ -59,7 +59,7 @@ def load_theme_from_dict(dict_theme): for field, value in settings.items(): if field not in question_fields: - raise ThemeError( + raise errors.ThemeError( "Error while parsing theme. Field " "`{}` invalid for question type `{}`".format(field, question_type) ) @@ -70,13 +70,13 @@ def load_theme_from_dict(dict_theme): class Theme: def __init__(self): - self.Question = namedtuple("question", "mark_color brackets_color " "default_color") - self.Editor = namedtuple("editor", "opening_prompt") - self.Checkbox = namedtuple( + self.Question = collections.namedtuple("question", "mark_color brackets_color default_color") + self.Editor = collections.namedtuple("editor", "opening_prompt") + self.Checkbox = collections.namedtuple( "common", - "selection_color selection_icon " "selected_color unselected_color " "selected_icon unselected_icon", + "selection_color selection_icon selected_color unselected_color selected_icon unselected_icon", ) - self.List = namedtuple("List", "selection_color selection_cursor " "unselected_color") + self.List = collections.namedtuple("List", "selection_color selection_cursor unselected_color") class Default(Theme): diff --git a/tests/integration/console_render/test_checkbox.py b/tests/integration/console_render/test_checkbox.py index 7979ff70..4228d4ad 100644 --- a/tests/integration/console_render/test_checkbox.py +++ b/tests/integration/console_render/test_checkbox.py @@ -41,7 +41,7 @@ def test_one_choice(self): result = sut.render(question) self.assertInStdout(message) - self.assertEqual(["foo"], result) + assert result == ["foo"] def test_choose_the_second(self): stdin = helper.event_factory(key.DOWN, key.SPACE, key.ENTER) @@ -55,7 +55,33 @@ def test_choose_the_second(self): result = sut.render(question) self.assertInStdout(message) - self.assertEqual(["bar"], result) + assert result == ["bar"] + + def test_choose_with_long_choices(self): + stdin = helper.event_factory( + key.DOWN, + key.DOWN, + key.DOWN, + key.DOWN, + key.DOWN, + key.DOWN, + key.DOWN, + key.DOWN, + key.SPACE, + key.DOWN, + key.DOWN, + key.ENTER, + ) + message = "Number message" + variable = "Number variable" + choices = list(range(15)) + + question = questions.Checkbox(variable, message, choices=choices) + + sut = ConsoleRender(event_generator=stdin) + result = sut.render(question) + + assert result == [8] def test_can_move(self): stdin = helper.event_factory(key.DOWN, key.DOWN, key.UP, key.SPACE, key.ENTER) @@ -68,7 +94,7 @@ def test_can_move(self): sut = ConsoleRender(event_generator=stdin) result = sut.render(question) - self.assertEqual(["bar"], result) + assert result == ["bar"] def test_cannot_move_beyond_upper_limit(self): stdin = helper.event_factory( @@ -87,7 +113,7 @@ def test_cannot_move_beyond_upper_limit(self): sut = ConsoleRender(event_generator=stdin) result = sut.render(question) - self.assertEqual(["foo"], result) + assert result == ["foo"] def test_cannot_move_beyond_lower_limit(self): stdin = helper.event_factory( @@ -104,7 +130,7 @@ def test_cannot_move_beyond_lower_limit(self): self.printStdout() - self.assertEqual(["bazz"], result) + assert result == ["bazz"] def test_ctrl_c_breaks_execution(self): stdin_array = [key.CTRL_C] @@ -131,7 +157,7 @@ def test_deselection(self): sut = ConsoleRender(event_generator=stdin) result = sut.render(question) - self.assertEqual([], result) + assert result == [] def test_right_cursor_selects_too(self): stdin_array = [key.RIGHT, key.ENTER] @@ -145,7 +171,7 @@ def test_right_cursor_selects_too(self): sut = ConsoleRender(event_generator=stdin) result = sut.render(question) - self.assertEqual(["foo"], result) + assert result == ["foo"] def test_right_cursor_do_not_unselect(self): stdin_array = [key.RIGHT, key.RIGHT, key.ENTER] @@ -159,7 +185,7 @@ def test_right_cursor_do_not_unselect(self): sut = ConsoleRender(event_generator=stdin) result = sut.render(question) - self.assertEqual(["foo"], result) + assert result == ["foo"] def test_left_cursor_unselect(self): stdin_array = [key.SPACE, key.LEFT, key.ENTER] @@ -173,7 +199,7 @@ def test_left_cursor_unselect(self): sut = ConsoleRender(event_generator=stdin) result = sut.render(question) - self.assertEqual([], result) + assert result == [] def test_left_cursor_do_not_select(self): stdin_array = [key.SPACE, key.LEFT, key.LEFT, key.ENTER] @@ -187,4 +213,4 @@ def test_left_cursor_do_not_select(self): sut = ConsoleRender(event_generator=stdin) result = sut.render(question) - self.assertEqual([], result) + assert result == [] diff --git a/tests/integration/console_render/test_editor.py b/tests/integration/console_render/test_editor.py index 78df4795..ddd8e16a 100644 --- a/tests/integration/console_render/test_editor.py +++ b/tests/integration/console_render/test_editor.py @@ -1,9 +1,5 @@ import unittest - -try: - from unittest.mock import patch -except ImportError: - from unittest.mock import patch +from unittest.mock import patch from readchar import key diff --git a/tests/integration/console_render/test_list.py b/tests/integration/console_render/test_list.py index ca0a148e..79625e09 100644 --- a/tests/integration/console_render/test_list.py +++ b/tests/integration/console_render/test_list.py @@ -3,6 +3,7 @@ from . import helper from readchar import key +import pytest from inquirer.render import ConsoleRender @@ -40,7 +41,7 @@ def test_choose_the_first(self): sut = ConsoleRender(event_generator=stdin) result = sut.render(question) - self.assertEqual("foo", result) + assert result == "foo" def test_choose_the_second(self): stdin = helper.event_factory(key.DOWN, key.ENTER) @@ -53,7 +54,32 @@ def test_choose_the_second(self): sut = ConsoleRender(event_generator=stdin) result = sut.render(question) - self.assertEqual("bar", result) + assert result == "bar" + + def test_choose_with_long_choices(self): + stdin = helper.event_factory( + key.DOWN, + key.DOWN, + key.DOWN, + key.DOWN, + key.DOWN, + key.DOWN, + key.DOWN, + key.DOWN, + key.DOWN, + key.DOWN, + key.ENTER, + ) + message = "Number message" + variable = "Number variable" + choices = list(range(15)) + + question = questions.List(variable, message, choices=choices) + + sut = ConsoleRender(event_generator=stdin) + result = sut.render(question) + + assert result == 10 def test_move_up(self): stdin = helper.event_factory(key.DOWN, key.UP, key.ENTER) @@ -66,7 +92,7 @@ def test_move_up(self): sut = ConsoleRender(event_generator=stdin) result = sut.render(question) - self.assertEqual("foo", result) + assert result == "foo" def test_move_down_carousel(self): stdin = helper.event_factory(key.DOWN, key.DOWN, key.DOWN, key.DOWN, key.ENTER) @@ -79,7 +105,7 @@ def test_move_down_carousel(self): sut = ConsoleRender(event_generator=stdin) result = sut.render(question) - self.assertEqual("bar", result) + assert result == "bar" def test_move_up_carousel(self): stdin = helper.event_factory(key.UP, key.ENTER) @@ -92,7 +118,7 @@ def test_move_up_carousel(self): sut = ConsoleRender(event_generator=stdin) result = sut.render(question) - self.assertEqual("bazz", result) + assert result == "bazz" def test_ctrl_c_breaks_execution(self): stdin_array = [key.CTRL_C] @@ -103,5 +129,5 @@ def test_ctrl_c_breaks_execution(self): question = questions.List(variable, message) sut = ConsoleRender(event_generator=stdin) - with self.assertRaises(KeyboardInterrupt): + with pytest.raises(KeyboardInterrupt): sut.render(question) diff --git a/tests/integration/console_render/test_password.py b/tests/integration/console_render/test_password.py index f56f4823..4062121b 100644 --- a/tests/integration/console_render/test_password.py +++ b/tests/integration/console_render/test_password.py @@ -1,9 +1,9 @@ import unittest -import inquirer.questions as questions from . import helper from readchar import key +import inquirer from inquirer.render import ConsoleRender @@ -19,7 +19,7 @@ def test_do_not_show_values(self): message = "Foo message" variable = "Bar variable" - question = questions.Password(variable, message) + question = inquirer.questions.Password(variable, message) sut = ConsoleRender(event_generator=stdin) sut.render(question) @@ -33,7 +33,7 @@ def test_allows_deletion(self): message = "Foo message" variable = "Bar variable" - question = questions.Password(variable, message) + question = inquirer.questions.Password(variable, message) sut = ConsoleRender(event_generator=stdin) result = sut.render(question) @@ -57,7 +57,7 @@ def test_cursor_movement(self): message = "Foo message" variable = "Bar variable" - question = questions.Password(variable, message) + question = inquirer.questions.Password(variable, message) sut = ConsoleRender(event_generator=stdin) result = sut.render(question) @@ -70,8 +70,36 @@ def test_ctrl_c_breaks_execution(self): message = "Foo message" variable = "Bar variable" - question = questions.Password(variable, message) + question = inquirer.questions.Password(variable, message) sut = ConsoleRender(event_generator=stdin) with self.assertRaises(KeyboardInterrupt): sut.render(question) + + def test_validate(self): + stdin = helper.event_factory("p", "a", "s", "s", key.ENTER) + message = "Foo message" + variable = "Bar variable" + + def validate(answers, current): + return False + + question = inquirer.questions.Password(variable, message, validate=validate) + + sut = ConsoleRender(event_generator=stdin) + with self.assertRaises(StopIteration): + sut.render(question) + + def test_handle_validation_error_with_reason(self): + stdin = helper.event_factory("p", "a", "s", "s", key.ENTER) + message = "Foo message" + variable = "Bar variable" + + def validate(answers, current): + raise inquirer.errors.ValidationError("", reason="some reason") + + question = inquirer.questions.Password(variable, message, validate=validate) + + sut = ConsoleRender(event_generator=stdin) + with self.assertRaises(StopIteration): + sut.render(question) diff --git a/tests/unit/test_prompt.py b/tests/unit/test_prompt.py index 60a70c02..b7ae2e36 100644 --- a/tests/unit/test_prompt.py +++ b/tests/unit/test_prompt.py @@ -1,26 +1,37 @@ -import unittest +from unittest.mock import MagicMock, Mock -try: - from unittest.mock import MagicMock, Mock -except ImportError: - from unittest.mock import MagicMock, Mock +import pytest -from inquirer import prompt +import inquirer -class PromptTests(unittest.TestCase): - def test_prompt_returns_a_hash(self): - self.assertEqual({}, prompt([])) +@pytest.fixture() +def render_mock_raise_keyboard(): + render = Mock() + render.render = Mock(side_effect=KeyboardInterrupt) + yield render - def test_prompt_renders_a_questions(self): - question1 = MagicMock() - question1.name = "foo" - result1 = object() - render = Mock() - render.render.return_value = result1 - result = prompt([question1], render=render) +def test_prompt_returns_a_hash(): + answers = inquirer.prompt([]) + assert answers == {} - self.assertEqual({"foo": result1}, result) - self.assertTrue(render.render.called) - render.render.call_args_list[0][0] == result1 + +def test_prompt_renders_a_questions(): + question1 = MagicMock() + question1.name = "foo" + result1 = object() + render = Mock() + render.render.return_value = result1 + + +def test_print(capsys, render_mock_raise_keyboard): + inquirer.prompt([MagicMock()], render=render_mock_raise_keyboard) + out, _ = capsys.readouterr() + + assert "Cancelled by user" in out.rstrip().lstrip() + + +def test_raise_keyboard(render_mock_raise_keyboard): + with pytest.raises(KeyboardInterrupt): + inquirer.prompt([MagicMock()], render=render_mock_raise_keyboard, raise_keyboard_interrupt=True) diff --git a/tests/unit/test_question.py b/tests/unit/test_question.py index 476d2af2..de5bf0ba 100644 --- a/tests/unit/test_question.py +++ b/tests/unit/test_question.py @@ -155,10 +155,7 @@ def compare(x, y): name = "foo" q = questions.Question(name, validate=compare) - try: - q.validate(expected) - except errors.ValidationError: - self.fail("Validation function did not receive the current value") + q.validate(expected) def test_factory_text_type(self): name = "foo" @@ -359,8 +356,17 @@ def test_normalizing_value(self): self.assertEqual(root, q.normalize_value("some/relative/path").split(os.path.sep)[0]) def test_default_value_validation(self): - with self.assertRaises(ValueError): questions.Path("path", default="~/.toggl_log", path_type=questions.Path.DIRECTORY) questions.Path("path", default="~/.toggl_log") + + +def test_tagged_value(): + tv = questions.TaggedValue("label", "value") + + assert tv.__str__() == "label" + assert tv.__repr__() == "value" + assert tv.__eq__(tv) is True + assert tv.__eq__("") is False + assert tv.__ne__(tv) is False diff --git a/tests/unit/test_shortcuts.py b/tests/unit/test_shortcuts.py new file mode 100644 index 00000000..cebc344f --- /dev/null +++ b/tests/unit/test_shortcuts.py @@ -0,0 +1,31 @@ +from unittest.mock import Mock + +import pytest + +import inquirer.shortcuts as shortcuts + + +@pytest.fixture() +def render_mock(): + render = Mock() + render.render = lambda x: x + yield render + + +@pytest.mark.parametrize( + "func,kind,message", + [ + (shortcuts.text, "text", "text message"), + (shortcuts.editor, "editor", "editor message"), + (shortcuts.password, "password", "password message"), + (shortcuts.confirm, "confirm", "confirm message"), + (shortcuts.list_input, "list", "list_input message"), + (shortcuts.checkbox, "checkbox", "checkbox message"), + (shortcuts.path, "path", "path message"), + ], +) +def test_shortcuts(func, kind, message, render_mock): + q = func(message, render=render_mock) + + assert q.kind == kind + assert q.message == message diff --git a/tests/unit/test_theme.py b/tests/unit/test_themes.py similarity index 68% rename from tests/unit/test_theme.py rename to tests/unit/test_themes.py index f6a46d75..f0c7bf8d 100644 --- a/tests/unit/test_theme.py +++ b/tests/unit/test_themes.py @@ -40,3 +40,24 @@ def test_invalid_question(self): with self.assertRaises(errors.ThemeError) as error: themes.load_theme_from_dict(self.theme_dict_wrong_question) assert "questionn" in str(error.exception) + + +def test_themes_green_passion(): + t = themes.GreenPassion() + + assert hasattr(t, "Question") + assert hasattr(t.Question, "mark_color") + assert hasattr(t.Question, "brackets_color") + assert hasattr(t.Question, "default_color") + assert hasattr(t, "Editor") + assert hasattr(t, "Checkbox") + assert hasattr(t.Checkbox, "selection_color") + assert hasattr(t.Checkbox, "selection_icon") + assert hasattr(t.Checkbox, "selected_icon") + assert hasattr(t.Checkbox, "selected_color") + assert hasattr(t.Checkbox, "unselected_icon") + assert hasattr(t.Checkbox, "unselected_color") + assert hasattr(t, "List") + assert hasattr(t.List, "selection_color") + assert hasattr(t.List, "selection_cursor") + assert hasattr(t.List, "unselected_color")