169 lines
6.0 KiB
Python
169 lines
6.0 KiB
Python
"""
|
|
Tests for the highlight API on EreaderLayoutManager (R7).
|
|
|
|
core/highlight.py was fully implemented and tested but unreachable: the manager
|
|
had no highlight API, so highlighting could not be used through the library's
|
|
own interface. These tests cover the wiring, not the dataclass - that is
|
|
tests/core/test_highlight.py.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from pyWebLayout.core.highlight import Highlight, HighlightColor
|
|
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>" + " ".join(f"word{i}" for i in range(300)) + "</p>")
|
|
manager = EreaderLayoutManager(blocks, page_size=(400, 600),
|
|
document_id="highlights",
|
|
bookmarks_dir=str(tmp_path))
|
|
yield manager
|
|
manager.shutdown()
|
|
|
|
|
|
def text_points(page, limit=None):
|
|
"""Points on the rendered page that land on a text object."""
|
|
found = []
|
|
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.object_type == "text" and result.text:
|
|
found.append((x, y))
|
|
if limit and len(found) >= limit:
|
|
return found
|
|
return found
|
|
|
|
|
|
@pytest.fixture
|
|
def point_on_text(manager):
|
|
page = manager.get_current_page()
|
|
page.render()
|
|
return text_points(page, limit=1)[0]
|
|
|
|
|
|
class TestHighlightPoint:
|
|
def test_highlighting_a_word_returns_a_stored_highlight(self, manager, point_on_text):
|
|
highlight = manager.highlight_point(point_on_text)
|
|
|
|
assert isinstance(highlight, Highlight)
|
|
assert highlight.text
|
|
assert manager.list_highlights() == [highlight]
|
|
|
|
def test_colour_note_and_tags_are_kept(self, manager, point_on_text):
|
|
highlight = manager.highlight_point(
|
|
point_on_text, color=HighlightColor.GREEN.value,
|
|
note="a note", tags=["review"])
|
|
|
|
assert highlight.color == HighlightColor.GREEN.value
|
|
assert highlight.note == "a note"
|
|
assert highlight.tags == ["review"]
|
|
|
|
def test_highlighting_empty_space_returns_none(self, manager):
|
|
manager.get_current_page().render()
|
|
|
|
assert manager.highlight_point((399, 599)) is None
|
|
assert manager.list_highlights() == []
|
|
|
|
def test_the_originating_position_is_recorded(self, manager, point_on_text):
|
|
highlight = manager.highlight_point(point_on_text)
|
|
|
|
assert highlight.position == manager.current_position.to_dict()
|
|
|
|
|
|
class TestHighlightRange:
|
|
def test_a_selection_spans_multiple_words(self, manager):
|
|
page = manager.get_current_page()
|
|
page.render()
|
|
points = text_points(page)
|
|
|
|
highlight = manager.highlight_range(points[0], points[-1])
|
|
|
|
assert highlight is not None
|
|
assert len(highlight.text.split()) > 1
|
|
assert len(highlight.bounds) > 1
|
|
|
|
def test_a_selection_hitting_no_text_returns_none(self, manager):
|
|
manager.get_current_page().render()
|
|
|
|
assert manager.highlight_range((398, 596), (399, 599)) is None
|
|
|
|
|
|
class TestHighlightsAreScopedToTheirPage:
|
|
def test_current_page_highlights_do_not_leak_across_pages(self, manager, point_on_text):
|
|
manager.highlight_point(point_on_text)
|
|
assert len(manager.get_highlights_for_current_page()) == 1
|
|
|
|
manager.next_page()
|
|
|
|
assert manager.get_highlights_for_current_page() == []
|
|
assert len(manager.list_highlights()) == 1, "still in the document, just not here"
|
|
|
|
def test_returning_to_the_page_finds_it_again(self, manager, point_on_text):
|
|
highlight = manager.highlight_point(point_on_text)
|
|
manager.next_page()
|
|
manager.previous_page()
|
|
|
|
assert manager.get_highlights_for_current_page() == [highlight]
|
|
|
|
|
|
class TestPersistence:
|
|
def test_highlights_survive_a_restart(self, manager, point_on_text, tmp_path):
|
|
highlight = manager.highlight_point(point_on_text, note="kept")
|
|
manager.shutdown()
|
|
|
|
reopened = EreaderLayoutManager(
|
|
manager.blocks, page_size=(400, 600), document_id="highlights",
|
|
bookmarks_dir=str(tmp_path))
|
|
try:
|
|
restored = reopened.list_highlights()
|
|
assert len(restored) == 1
|
|
assert restored[0].id == highlight.id
|
|
assert restored[0].note == "kept"
|
|
assert restored[0].position == highlight.position
|
|
finally:
|
|
reopened.shutdown()
|
|
|
|
def test_highlights_share_the_bookmarks_directory_by_default(self, manager,
|
|
point_on_text, tmp_path):
|
|
manager.highlight_point(point_on_text)
|
|
|
|
assert (tmp_path / "highlights_highlights.json").exists()
|
|
|
|
def test_removing_a_highlight_persists(self, manager, point_on_text, tmp_path):
|
|
highlight = manager.highlight_point(point_on_text)
|
|
|
|
assert manager.remove_highlight(highlight.id) is True
|
|
assert manager.remove_highlight(highlight.id) is False
|
|
|
|
reopened = EreaderLayoutManager(
|
|
manager.blocks, page_size=(400, 600), document_id="highlights",
|
|
bookmarks_dir=str(tmp_path))
|
|
try:
|
|
assert reopened.list_highlights() == []
|
|
finally:
|
|
reopened.shutdown()
|
|
|
|
def test_clear_removes_everything(self, manager, point_on_text):
|
|
manager.highlight_point(point_on_text)
|
|
|
|
manager.clear_highlights()
|
|
|
|
assert manager.list_highlights() == []
|
|
|
|
def test_a_corrupt_store_does_not_stop_the_book_opening(self, tmp_path):
|
|
(tmp_path / "broken_highlights.json").write_text("{not json")
|
|
blocks = parse_html_string("<p>hello world</p>")
|
|
|
|
manager = EreaderLayoutManager(blocks, page_size=(400, 600),
|
|
document_id="broken",
|
|
bookmarks_dir=str(tmp_path))
|
|
try:
|
|
assert manager.list_highlights() == []
|
|
assert manager.get_current_page() is not None
|
|
finally:
|
|
manager.shutdown()
|