130 lines
4.5 KiB
Python
130 lines
4.5 KiB
Python
"""
|
|
Tests for pointer interaction on EreaderLayoutManager (R7).
|
|
|
|
concrete/interaction_handler.py was 310 lines reachable only from
|
|
examples/07_pressed_state_demo.py - no library code, no tests. These cover the
|
|
wiring; the press/hover state on the elements themselves lives in
|
|
tests/concrete/.
|
|
"""
|
|
|
|
import pytest
|
|
from PIL import Image
|
|
|
|
from pyWebLayout.io.readers.html_extraction import parse_html_string
|
|
from pyWebLayout.layout.ereader_manager import EreaderLayoutManager
|
|
|
|
|
|
@pytest.fixture
|
|
def manager(tmp_path):
|
|
blocks = parse_html_string(
|
|
'<p>Tap <a href="action:go">this link</a> please.</p>'
|
|
'<p>' + " ".join(f"w{i}" for i in range(400)) + '</p>')
|
|
manager = EreaderLayoutManager(blocks, page_size=(400, 600),
|
|
document_id="interaction",
|
|
bookmarks_dir=str(tmp_path))
|
|
yield manager
|
|
manager.shutdown()
|
|
|
|
|
|
@pytest.fixture
|
|
def link_point(manager):
|
|
"""A page coordinate that lands on the interactive link."""
|
|
page = manager.get_current_page()
|
|
page.render()
|
|
for y in range(0, 120, 2):
|
|
for x in range(0, 400, 2):
|
|
result = page.query_point((x, y))
|
|
if result is not None and result.is_interactive:
|
|
return (x, y)
|
|
pytest.fail("fixture document rendered no interactive element")
|
|
|
|
|
|
EMPTY_POINT = (399, 599)
|
|
|
|
|
|
class TestHover:
|
|
def test_hovering_an_element_produces_a_frame(self, manager, link_point):
|
|
assert isinstance(manager.handle_hover(link_point), Image.Image)
|
|
|
|
def test_hovering_the_same_element_again_reports_no_change(self, manager, link_point):
|
|
manager.handle_hover(link_point)
|
|
|
|
assert manager.handle_hover(link_point) is None, \
|
|
"an unchanged hover should not force the caller to redraw"
|
|
|
|
def test_moving_off_the_element_clears_the_hover(self, manager, link_point):
|
|
manager.handle_hover(link_point)
|
|
|
|
assert isinstance(manager.handle_hover(EMPTY_POINT), Image.Image)
|
|
|
|
|
|
class TestPress:
|
|
def test_pressing_an_element_produces_a_frame(self, manager, link_point):
|
|
assert isinstance(manager.handle_touch_down(link_point), Image.Image)
|
|
|
|
def test_pressing_empty_space_does_nothing(self, manager):
|
|
manager.get_current_page().render()
|
|
|
|
assert manager.handle_touch_down(EMPTY_POINT) is None
|
|
|
|
def test_release_runs_the_link_action(self, manager, link_point):
|
|
manager.handle_touch_down(link_point)
|
|
|
|
frame, result = manager.handle_touch_up(link_point)
|
|
|
|
assert isinstance(frame, Image.Image)
|
|
assert result == "action:go"
|
|
|
|
def test_release_without_a_press_is_a_no_op(self, manager):
|
|
manager.get_current_page().render()
|
|
|
|
assert manager.handle_touch_up(EMPTY_POINT) == (None, None)
|
|
|
|
def test_a_full_press_release_cycle_leaves_no_state(self, manager, link_point):
|
|
manager.handle_touch_down(link_point)
|
|
manager.handle_touch_up(link_point)
|
|
|
|
assert manager.handle_touch_up(link_point) == (None, None)
|
|
|
|
|
|
class TestStateFollowsTheDisplayedPage:
|
|
def test_navigating_rebinds_the_state_machine(self, manager, link_point):
|
|
before = manager._interaction_state()
|
|
|
|
manager.next_page()
|
|
|
|
assert manager._interaction_state() is not before, \
|
|
"press state belongs to one rendered page"
|
|
|
|
def test_state_survives_repeated_access_on_one_page(self, manager, link_point):
|
|
assert manager._interaction_state() is manager._interaction_state()
|
|
|
|
def test_reset_is_safe_before_any_interaction(self, manager):
|
|
manager.reset_interaction_state() # must not raise
|
|
|
|
def test_reset_clears_a_pending_press(self, manager, link_point):
|
|
manager.handle_touch_down(link_point)
|
|
|
|
manager.reset_interaction_state()
|
|
|
|
assert manager.handle_touch_up(link_point) == (None, None)
|
|
|
|
|
|
class TestPressedRenderingRegression:
|
|
"""
|
|
LinkText.render passed [origin, origin + size] - two numpy arrays - to
|
|
PIL's draw.rectangle, which needs a flat four-scalar box. Rendering any
|
|
hovered or pressed link raised TypeError. Nothing caught it because the
|
|
only caller was an example.
|
|
"""
|
|
|
|
def test_rendering_a_hovered_link_does_not_raise(self, manager, link_point):
|
|
manager.handle_hover(link_point)
|
|
|
|
assert isinstance(manager.get_current_page().render(), Image.Image)
|
|
|
|
def test_rendering_a_pressed_link_does_not_raise(self, manager, link_point):
|
|
manager.handle_touch_down(link_point)
|
|
|
|
assert isinstance(manager.get_current_page().render(), Image.Image)
|