Skip to content

Commit

Permalink
chore: update testing.
Browse files Browse the repository at this point in the history
  • Loading branch information
zlj-zz committed May 14, 2024
1 parent c146580 commit 1398846
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 84 deletions.
12 changes: 7 additions & 5 deletions pigit/tui/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ def __init__(
children: Optional[Dict[str, "Component"]] = None,
parent: Optional["Component"] = None,
) -> None:
assert self.NAME, "The name attribute cannot be empty."
assert self.NAME, "The `NAME` attribute cannot be empty."
assert (
self.NAME not in _Namespace
), f"The name attribute must be unique: '{self.NAME}'."
), f"The `NAME` attribute must be unique: '{self.NAME}'."
_Namespace.add(self.NAME)

self._activated = False # component whether activated state.
Expand Down Expand Up @@ -159,8 +159,10 @@ def resize(self, size: Tuple[int, int]):
def accept(self, action: str, **data):
# sourcery skip: remove-unnecessary-else, swap-if-else-branches
if action == "goto" and (name := data.get("target")) is not None:
child = self.switch_child(name)
child.update(action, **data)
if child := self.switch_child(name): # switch and fetch next child.
child.update(action, **data)
else:
_Log.warning(f"Not found child: {name}.")
else:
raise ComponentError("Not support action of ~Container.")

Expand All @@ -184,7 +186,7 @@ def _handle_event(self, key: str):
name = self.switch_handle(key)
self.switch_child(name)

def switch_child(self, name: str) -> "Component":
def switch_child(self, name: str) -> Optional["Component"]:
"""Choice which child should be activated."""
child = None

Expand Down
74 changes: 74 additions & 0 deletions tests/test_tui_components.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from typing import Callable, Dict, Tuple
import pytest
from pigit.tui.components import Container, Component, ComponentError, _Namespace


# Mock Component to use in tests
class MockComponent(Component):
NAME = "mock-comp"

def __init__(self, name):
self.NAME = name
super().__init__()

def _render(self):
pass

def resize(self, size):
pass

def _handle_event(self, key):
pass


class TestContainer(Container):
NAME = "test-container"

def update(self, action: str, **data):
pass


@pytest.mark.parametrize(
"start_name, switch_key, expected_active",
[
("main", None, "main"), # Happy path: default start_name
("secondary", None, "secondary"), # Happy path: specified start_name
("main", "secondary", "secondary"), # Edge case: switch child after init
],
ids=["default-start", "specified-start", "switch-after-init"],
)
def test_container_init_and_switch(start_name, switch_key, expected_active):
# Arrange
_Namespace.clear()
children = {"main": MockComponent("main"), "secondary": MockComponent("secondary")}
switch_handle = lambda key: switch_key or start_name

# Act
container = TestContainer(
children=children, start_name=start_name, switch_handle=switch_handle
)
if switch_key:
container._handle_event(switch_key)

# Assert
assert children[
expected_active
].is_activated(), f"{expected_active} should be activated"


@pytest.mark.parametrize(
"action, data, expected_exception",
[
("unsupported", {}, ComponentError), # Error case: unsupported action
],
ids=["unsupported-action"],
)
def test_container_accept_errors(action, data, expected_exception):
# Arrange
_Namespace.clear()
children = {"main": MockComponent("main")}
container = TestContainer(children=children)

# Act / Assert
with pytest.raises(expected_exception):
container.accept(action, **data)
167 changes: 88 additions & 79 deletions tests/test_tui_eventloop.py
Original file line number Diff line number Diff line change
@@ -1,119 +1,128 @@
import pytest
from unittest.mock import Mock, patch
from pigit.tui.event_loop import EventLoop, ExitEventLoop
from pigit.tui.input import InputTerminal, PosixInput


@pytest.mark.parametrize(
"alt,expected",
[(True, 1), (False, 0)],
ids=["alt-true", "alt-false"],
)
def test_start(alt, expected):
class ComponentMock:
def resize(self, size):
pass

def _render(self):
pass

def _handle_event(self, event):
pass


@pytest.mark.parametrize("real_time, alt, expected_start_calls", [
(True, True, "to_alt_screen"), # ID: real_time-true_alt-true
(False, False, "to_normal_screen"), # ID: real_time-false_alt-false
(True, False, None), # ID: real_time-true_alt-false
(False, True, "to_alt_screen"), # ID: real_time-false_alt-true
])
def test_start_stop(real_time, alt, expected_start_calls):
# Arrange
mock_child = Mock()
event_loop = EventLoop(mock_child, alt=alt)
event_loop.to_alt_screen = Mock()
event_loop.resize = Mock()
component = ComponentMock()
event_loop = EventLoop(component, real_time=real_time, alt=alt)
with patch.object(EventLoop, 'to_alt_screen') as mock_to_alt_screen, \
patch.object(EventLoop, 'to_normal_screen') as mock_to_normal_screen:

# Act
event_loop.start()
# Act
event_loop.start()
event_loop.stop()

# Assert
assert event_loop.to_alt_screen.call_count == expected
assert event_loop.resize.call_count == 1
# Assert
if expected_start_calls == "to_alt_screen":
mock_to_alt_screen.assert_called_once()
mock_to_normal_screen.assert_called_once()
else:
mock_to_alt_screen.assert_not_called()
mock_to_normal_screen.assert_not_called()


@pytest.mark.parametrize(
"alt,expected",
[(True, 1), (False, 0)],
ids=["alt-true", "alt-false"],
"input_handle, expected_instance",
[
(None, PosixInput), # ID: input_handle-none
(Mock(spec=InputTerminal), InputTerminal), # ID: input_handle-mock
],
)
def test_stop(alt, expected):
def test_init_input_handle(input_handle, expected_instance):
# Arrange
mock_child = Mock()
event_loop = EventLoop(mock_child, alt=alt)
event_loop.to_normal_screen = Mock()
component = ComponentMock()

# Act
event_loop.stop()
event_loop = EventLoop(component, input_handle=input_handle, alt=False)

# Assert
assert event_loop.to_normal_screen.call_count == expected
assert isinstance(event_loop._input_handle, expected_instance)


def test_resize():
@pytest.mark.parametrize(
"exception, expected_stop_calls",
[
(ExitEventLoop, 1), # ID: exception-ExitEventLoop
(KeyboardInterrupt, 1), # ID: exception-KeyboardInterrupt
(EOFError, 1), # ID: exception-EOFError
(Exception, 1), # ID: exception-Other
],
)
def test_run_exception_handling(exception, expected_stop_calls):
# Arrange
mock_child = Mock()
event_loop = EventLoop(mock_child)
event_loop.clear_screen = Mock()
event_loop._child._render = Mock()
print("eeeexcepiton:", exception)
component = ComponentMock()
event_loop = EventLoop(component, alt=False)
event_loop._loop = Mock(side_effect=exception())
event_loop.start = Mock()
event_loop.stop = Mock()

# Act
event_loop.resize()
with pytest.raises(exception):
event_loop._loop()

# Assert
mock_child.resize.assert_called_once()
event_loop.clear_screen.assert_called_once()
event_loop._child._render.assert_called_once()
event_loop.run()
assert event_loop.stop.call_count == expected_stop_calls


@pytest.mark.parametrize(
"input_key,expected",
[([["window resize", ""]], 1), ([["other", ""]], 0)],
ids=["resize-event", "other-event"],
"size, expected_resize_calls",
[
((80, 24), 1), # ID: size-80x24
((100, 40), 1), # ID: size-100x40
],
)
def test_loop(input_key, expected):
def test_resize(size, expected_resize_calls):
# Arrange
mock_child = Mock()
event_loop = EventLoop(mock_child)
event_loop._input_handle.get_input = Mock(return_value=input_key)
event_loop.resize = Mock()
component = ComponentMock()
event_loop = EventLoop(component, alt=False)
event_loop.get_term_size = Mock(return_value=size)
event_loop._child.resize = Mock()

# Act
event_loop._loop()
event_loop.resize()

# Assert
assert event_loop.resize.call_count == expected
event_loop._child.resize.assert_called_once_with(size)


def test_with():
mock_child = Mock()
event_loop = EventLoop(mock_child)
event_loop.start = Mock()
event_loop.stop = Mock()

with event_loop as el:
pass

event_loop.start.assert_called_once()
event_loop.stop.assert_called_once()


def test_run():
@pytest.mark.parametrize(
"timeout, expected_set_timeout_calls",
[
(0.1, 1), # ID: timeout-0.1
(1.0, 1), # ID: timeout-1.0
(5.0, 1), # ID: timeout-5.0
],
)
def test_set_input_timeouts(timeout, expected_set_timeout_calls):
# Arrange
mock_child = Mock()
event_loop = EventLoop(mock_child)
event_loop._input_handle.start = Mock()
event_loop._input_handle.stop = Mock()
event_loop.start = Mock()
event_loop.stop = Mock()
event_loop._loop = Mock(side_effect=[None, KeyboardInterrupt])
component = ComponentMock()
event_loop = EventLoop(component, alt=False)
event_loop._input_handle.set_input_timeouts = Mock()

# Act
event_loop.run()
event_loop.set_input_timeouts(timeout)

# Assert
event_loop._input_handle.start.assert_called_once()
event_loop._input_handle.stop.assert_called_once()
event_loop.start.assert_called_once()
event_loop.stop.assert_called_once()


def test_quit():
# Arrange
mock_child = Mock()
event_loop = EventLoop(mock_child)

# Act & Assert
with pytest.raises(ExitEventLoop):
event_loop.quit()
event_loop._input_handle.set_input_timeouts.assert_called_once_with(timeout)

0 comments on commit 1398846

Please sign in to comment.