Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more unit testing for the Input widget #2737

Merged
merged 11 commits into from
Jun 6, 2023
9 changes: 9 additions & 0 deletions tests/input/test_input_key_modification_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ async def test_delete_left_word_from_home() -> None:
assert input.value == TEST_INPUTS[input.id]


async def test_delete_left_word_from_inside_first_word() -> None:
async with InputTester().run_test() as pilot:
for input in pilot.app.query(Input):
input.cursor_position = 1
input.action_delete_left_word()
assert input.cursor_position == 0
assert input.value == TEST_INPUTS[input.id][1:]


async def test_delete_left_word_from_end() -> None:
"""Deleting word left from end should remove the expected text."""
async with InputTester().run_test() as pilot:
Expand Down
75 changes: 75 additions & 0 deletions tests/input/test_input_messages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from __future__ import annotations

from textual import on
from textual.app import App, ComposeResult
from textual.events import Paste
from textual.widgets import Input


class InputApp(App[None]):
def __init__(self, initial: str | None = None) -> None:
super().__init__()
self.messages: list[str] = []
self._initial = initial

def compose(self) -> ComposeResult:
if self._initial:
yield Input(self._initial)
else:
yield Input()

@on(Input.Changed)
@on(Input.Submitted)
def log_message(self, event: Input.Submitted | Input.Changed) -> None:
assert event.control == event.input
self.messages.append(event.__class__.__name__)


async def test_no_startup_messages():
"""An input with no initial value should have no initial messages."""
async with InputApp().run_test() as pilot:
assert pilot.app.messages == []


async def test_startup_messages_with_initial_value():
"""An input with an initial value should send a changed event."""
async with InputApp("Hello, World!").run_test() as pilot:
assert pilot.app.messages == ["Changed"]


async def test_typing_from_empty_causes_changed():
"""An input with no initial value should send messages when entering text."""
input_text = "Hello, World!"
async with InputApp().run_test() as pilot:
await pilot.press(*input_text)
assert pilot.app.messages == ["Changed"] * len(input_text)


async def test_typing_from_pre_populated_causes_changed():
"""An input with initial value should send messages when entering text after an initial message."""
input_text = "Hello, World!"
async with InputApp(input_text).run_test() as pilot:
await pilot.press(*input_text)
assert pilot.app.messages == ["Changed"] + (["Changed"] * len(input_text))


async def test_submit_empty_input():
"""Pressing enter on an empty input should send a submitted event."""
async with InputApp().run_test() as pilot:
await pilot.press("enter")
assert pilot.app.messages == ["Submitted"]


async def test_submit_pre_populated_input():
"""Pressing enter on a pre-populated input should send a changed then submitted event."""
async with InputApp("The owls are not what they seem").run_test() as pilot:
await pilot.press("enter")
assert pilot.app.messages == ["Changed", "Submitted"]


async def test_paste_event_impact():
"""A paste event should result in a changed event."""
async with InputApp().run_test() as pilot:
await pilot.app._post_message(Paste("Hello, World"))
await pilot.pause()
assert pilot.app.messages == ["Changed"]
45 changes: 45 additions & 0 deletions tests/input/test_input_mouse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from __future__ import annotations

import pytest

from textual.app import App, ComposeResult
from textual.geometry import Offset
from textual.widgets import Input


class InputApp(App[None]):
TEST_TEXT = "That gum you like is going to come back in style"

def compose(self) -> ComposeResult:
yield Input(self.TEST_TEXT)


@pytest.mark.parametrize(
"click_at, should_land",
(
(0, 0),
(1, 1),
(10, 10),
(len(InputApp.TEST_TEXT) - 1, len(InputApp.TEST_TEXT) - 1),
(len(InputApp.TEST_TEXT), len(InputApp.TEST_TEXT)),
(len(InputApp.TEST_TEXT) * 2, len(InputApp.TEST_TEXT)),
),
)
async def test_mouse_clicks_within(click_at, should_land):
"""Mouse clicks should result in the cursor going to the right place."""
async with InputApp().run_test() as pilot:
# Note the offsets to take into account the decoration around an
# Input.
await pilot.click(Input, Offset(click_at + 3, 1))
await pilot.pause()
assert pilot.app.query_one(Input).cursor_position == should_land


async def test_mouse_click_outwith():
"""Mouse clicks outside the input should not affect cursor position."""
async with InputApp().run_test() as pilot:
pilot.app.query_one(Input).cursor_position = 3
assert pilot.app.query_one(Input).cursor_position == 3
await pilot.click(Input, Offset(0, 0))
await pilot.pause()
assert pilot.app.query_one(Input).cursor_position == 3
45 changes: 45 additions & 0 deletions tests/input/test_input_properties.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from __future__ import annotations

from rich.highlighter import JSONHighlighter
from rich.text import Text

from textual.app import App, ComposeResult
from textual.widgets import Input


class InputApp(App[None]):
TEST_TEXT = "Let's rock!"

def compose(self) -> ComposeResult:
yield Input(self.TEST_TEXT)


async def test_internal_value_no_password():
"""The displayed value should be the input value."""
async with InputApp().run_test() as pilot:
assert pilot.app.query_one(Input)._value == Text(pilot.app.TEST_TEXT)


async def test_internal_value_password():
"""The displayed value should be a password text."""
async with InputApp().run_test() as pilot:
pilot.app.query_one(Input).password = True
assert pilot.app.query_one(Input)._value == Text("•" * len(pilot.app.TEST_TEXT))


async def test_internal_value_highlighted():
async with InputApp().run_test() as pilot:
pilot.app.query_one(Input).highlighter = JSONHighlighter()
test_text = f'{{"test": "{pilot.app.TEST_TEXT}"}}'
pilot.app.query_one(Input).value = test_text
assert pilot.app.query_one(Input)._value == JSONHighlighter()(test_text)


async def test_cursor_toggle():
"""Cursor toggling should toggle the cursor."""
async with InputApp().run_test() as pilot:
input_widget = pilot.app.query_one(Input)
input_widget.cursor_blink = False
assert input_widget._cursor_visible is True
input_widget._toggle_cursor()
assert input_widget._cursor_visible is False