""" 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( "
" + " ".join(f"word{i}" for i in range(300)) + "
") 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("hello world
") 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()