-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
169 additions
and
84 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |