Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 22 additions & 23 deletions questionary/prompts/confirm.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,25 @@

def confirm(
message: str,
default: bool = True,
default: Optional[bool] = True,
qmark: str = DEFAULT_QUESTION_PREFIX,
style: Optional[Style] = None,
auto_enter: bool = True,
instruction: Optional[str] = None,
mandatory: bool = True,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would strongly suggest defaulting mandatory to False to preserve the default behaviour and avoid breaking people's code.

That will also resolve the issue with deletion of test cases. When mandatory defaults to false all existing test cases can remain the same and you only have to add new ones for mandatory=True case

**kwargs: Any,
) -> Question:
"""A yes or no question. The user can either confirm or deny.

This question type can be used to prompt the user for a confirmation
of a yes-or-no question. If the user just hits enter, the default
value will be returned.

Example:
>>> import questionary
>>> questionary.confirm("Are you amazed?").ask()
? Are you amazed? Yes
True

.. image:: ../images/confirm.gif

This is just a really basic example, the prompt can be customised using the
parameters.

value will be returned unless mandatory=True.

Args:
message: Question text.

default: Default value will be returned if the user just hits
enter.
default: Default value will be returned if the user just hits enter,
unless mandatory=True.

qmark: Question prefix displayed in front of the question.
By default this is a ``?``.
Expand All @@ -61,6 +50,10 @@ def confirm(

instruction: A message describing how to proceed through the
confirmation prompt.

mandatory: If set to `True`, the user must type either 'y' or 'n';
pressing Enter without input is not allowed.

Returns:
:class:`Question`: Question instance, ready to be prompted (using `.ask()`).
"""
Expand All @@ -72,13 +65,16 @@ def get_prompt_tokens():
tokens = []

tokens.append(("class:qmark", qmark))
tokens.append(("class:question", " {} ".format(message)))
tokens.append(("class:question", f" {message} "))

if instruction is not None:
tokens.append(("class:instruction", instruction))
elif not status["complete"]:
_instruction = YES_OR_NO if default else NO_OR_YES
tokens.append(("class:instruction", "{} ".format(_instruction)))
if mandatory:
tokens.append(("class:instruction", "(Type 'y' or 'n') "))
else:
_instruction = YES_OR_NO if default else NO_OR_YES
tokens.append(("class:instruction", f"{_instruction} "))

if status["answer"] is not None:
answer = YES if status["answer"] else NO
Expand Down Expand Up @@ -117,10 +113,13 @@ def key_backspace(event):

@bindings.add(Keys.ControlM, eager=True)
def set_answer(event):
if status["answer"] is None:
status["answer"] = default

exit_with_result(event)
if mandatory and status["answer"] is None:
# Prevent submission if answer is None and mandatory=True
event.app.invalidate()
else:
if status["answer"] is None:
status["answer"] = default
exit_with_result(event)

@bindings.add(Keys.Any)
def other(event):
Expand Down
75 changes: 48 additions & 27 deletions tests/prompts/test_confirm.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,8 @@
from tests.utils import feed_cli_with_input


def test_confirm_enter_default_yes():
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason you removed these tests? The default behaviour should stay the same

message = "Foo message"
text = KeyInputs.ENTER + "\r"

result, cli = feed_cli_with_input("confirm", message, text)
assert result is True


def test_confirm_enter_default_no():
message = "Foo message"
text = KeyInputs.ENTER + "\r"

result, cli = feed_cli_with_input("confirm", message, text, default=False)
assert result is False


def test_confirm_yes():
"""Test standard confirmation with 'y'."""
message = "Foo message"
text = "y" + "\r"

Expand All @@ -30,6 +15,7 @@ def test_confirm_yes():


def test_confirm_no():
"""Test standard confirmation with 'n'."""
message = "Foo message"
text = "n" + "\r"

Expand All @@ -38,6 +24,7 @@ def test_confirm_no():


def test_confirm_big_yes():
"""Test standard confirmation with 'Y'."""
message = "Foo message"
text = "Y" + "\r"

Expand All @@ -46,22 +33,16 @@ def test_confirm_big_yes():


def test_confirm_big_no():
"""Test standard confirmation with 'N'."""
message = "Foo message"
text = "N" + "\r"

result, cli = feed_cli_with_input("confirm", message, text)
assert result is False


def test_confirm_random_input():
message = "Foo message"
text = "my stuff" + KeyInputs.ENTER + "\r"

result, cli = feed_cli_with_input("confirm", message, text)
assert result is True


def test_confirm_ctr_c():
"""Test handling of Ctrl+C interruption."""
message = "Foo message"
text = KeyInputs.CONTROLC

Expand All @@ -70,6 +51,7 @@ def test_confirm_ctr_c():


def test_confirm_not_autoenter_yes():
"""Test confirm prompt without auto-enter when user types 'y'."""
message = "Foo message"
text = "n" + "y" + KeyInputs.ENTER + "\r"

Expand All @@ -78,26 +60,65 @@ def test_confirm_not_autoenter_yes():


def test_confirm_not_autoenter_no():
"""Test confirm prompt without auto-enter when user types 'n'."""
message = "Foo message"
text = "n" + "y" + KeyInputs.ENTER + "\r"
text = "n" + KeyInputs.ENTER + "\r"

result, cli = feed_cli_with_input("confirm", message, text, auto_enter=False)
assert result is True
assert result is False


def test_confirm_not_autoenter_backspace():
"""Test confirm prompt where user backspaces to empty input and retypes."""
message = "Foo message"
text = "n" + KeyInputs.BACK + KeyInputs.ENTER + "\r"
text = "n" + KeyInputs.BACK + "y" + KeyInputs.ENTER + "\r"

result, cli = feed_cli_with_input("confirm", message, text, auto_enter=False)
assert result is True


def test_confirm_instruction():
"""Test confirm prompt with a custom instruction."""
message = "Foo message"
text = "Y" + "\r"

result, cli = feed_cli_with_input(
"confirm", message, text, instruction="Foo instruction"
)
assert result is True


def test_confirm_mandatory_yes():
"""Test mandatory confirm prompt when user explicitly types 'yes'."""
message = "Foo message"
text = "y" + "\r"

result, cli = feed_cli_with_input("confirm", message, text, mandatory=True)
assert result is True


def test_confirm_mandatory_no():
"""Test mandatory confirm prompt when user explicitly types 'no'."""
message = "Foo message"
text = "n" + "\r"

result, cli = feed_cli_with_input("confirm", message, text, mandatory=True)
assert result is False


def test_confirm_mandatory_reject_empty():
"""Test mandatory confirm prompt rejects empty input."""
message = "Foo message"
text = KeyInputs.ENTER + "y" + "\r"

result, cli = feed_cli_with_input("confirm", message, text, mandatory=True)
assert result is True


def test_confirm_mandatory_reject_invalid_input():
"""Test mandatory confirm prompt rejects invalid input."""
message = "Foo message"
text = "x" + KeyInputs.ENTER + "y" + "\r"

result, cli = feed_cli_with_input("confirm", message, text, mandatory=True)
assert result is True