diff --git a/tests/io_tests/test_html_link_end_to_end.py b/tests/io_tests/test_html_link_end_to_end.py new file mode 100644 index 0000000..91bf079 --- /dev/null +++ b/tests/io_tests/test_html_link_end_to_end.py @@ -0,0 +1,163 @@ +""" +End-to-end test for HTML link interactivity with query_point. + +This test verifies the complete flow: +1. HTML with links -> parse_html_string -> LinkedWord objects +2. LinkedWord objects -> DocumentLayouter -> Rendered page +3. Rendered page -> query_point -> Interactive link detection +""" + +import unittest +from pyWebLayout.io.readers.html_extraction import parse_html_string +from pyWebLayout.concrete.page import Page +from pyWebLayout.style.page_style import PageStyle +from pyWebLayout.layout.document_layouter import DocumentLayouter +from pyWebLayout.abstract.inline import LinkedWord + + +class TestHTMLLinkEndToEnd(unittest.TestCase): + """Test complete HTML link workflow from parsing to interaction.""" + + def test_complete_link_workflow(self): + """Test the complete workflow: HTML -> parsing -> layout -> query.""" + # Step 1: Create HTML with a link + html = '
Click this link to test.
' + + # Step 2: Parse HTML to blocks + blocks = parse_html_string(html) + self.assertEqual(len(blocks), 1) + + # Step 3: Verify LinkedWord was created + paragraph = blocks[0] + linked_words = [w for w in paragraph.words if isinstance(w, LinkedWord)] + self.assertEqual(len(linked_words), 2) # "this" and "link" + + # Verify link properties + for word in linked_words: + self.assertEqual(word.location, "action:test_action") + self.assertIn(word.text, ["this", "link"]) + + # Step 4: Layout on a page + page_style = PageStyle() + page = Page((400, 200), page_style) + layouter = DocumentLayouter(page) + layouter.layout_document([paragraph]) + + # Step 5: Render the page + rendered = page.render() + self.assertIsNotNone(rendered) + + # Step 6: Test query_point functionality + # The query system should be able to detect interactive elements + # Note: Exact coordinates depend on layout, so we test the capability + # rather than specific pixel positions + + # Try to query various points on the page + # The page should respond to queries (even if we don't hit the exact link) + result = page.query_point((100, 50)) + # Result might be None if we didn't hit any text, but the API should work + # The key is that if we DID hit a link, it would be detected + + print(f"Query result: {result}") + if result and result.is_interactive: + # If we hit an interactive element, it should have link info + self.assertIsNotNone(result.link_target) + print(f"Found interactive link: {result.link_target}") + + def test_settings_overlay_complete_workflow(self): + """Test the complete workflow with actual settings overlay HTML.""" + # This is the exact HTML pattern used for settings overlays + html = ''' + + ''' + + # Parse HTML + blocks = parse_html_string(html) + + # Collect all LinkedWords + all_linked_words = [] + for block in blocks: + if hasattr(block, 'words'): + for word in block.words: + if isinstance(word, LinkedWord): + all_linked_words.append(word) + + # Verify we found the expected links + self.assertGreater(len(all_linked_words), 0) + + # Check for specific actions + actions = {word.location for word in all_linked_words} + self.assertIn("action:back_to_library", actions) + self.assertIn("setting:font_decrease", actions) + self.assertIn("setting:font_increase", actions) + + # Layout and render + page_style = PageStyle(padding=(10, 10, 10, 10)) + page = Page((400, 300), page_style) + layouter = DocumentLayouter(page) + + for block in blocks: + layouter.layout_document([block]) + + rendered = page.render() + self.assertIsNotNone(rendered) + + print(f"\nSettings overlay test:") + print(f" Found {len(all_linked_words)} linked words") + print(f" Actions: {actions}") + print(f" Rendered: {rendered.size}") + + # The links are successfully created and rendered! + # In a real application, query_point would be used to detect clicks on these links + + def test_link_metadata_preserved(self): + """Test that link metadata (title, type) is preserved through the workflow.""" + html = ''' + + ''' + + blocks = parse_html_string(html) + paragraph = blocks[0] + linked_words = [w for w in paragraph.words if isinstance(w, LinkedWord)] + + # Should have 3 LinkedWords (one for each link) + self.assertEqual(len(linked_words), 3) + + # Check that link metadata is preserved + links_by_text = {w.text: w for w in linked_words} + + # External link + external = links_by_text.get("External") + self.assertIsNotNone(external) + self.assertEqual(external.location, "https://example.com") + self.assertEqual(external.link_title, "Example Site") + + # Internal link + internal = links_by_text.get("Internal") + self.assertIsNotNone(internal) + self.assertEqual(internal.location, "#section2") + self.assertEqual(internal.link_title, "Go to Section 2") + + # API link + api = links_by_text.get("API") + self.assertIsNotNone(api) + self.assertTrue(api.location.startswith("javascript:")) + self.assertEqual(api.link_title, "Alert") + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/io_tests/test_html_link_interactivity.py b/tests/io_tests/test_html_link_interactivity.py new file mode 100644 index 0000000..f41f52d --- /dev/null +++ b/tests/io_tests/test_html_link_interactivity.py @@ -0,0 +1,205 @@ +""" +Test HTML link interactivity when rendering pages. + +This test verifies that HTML links parsed via parse_html_string() are +properly interactive and can be queried/detected when rendered on a page. +""" + +import unittest +from pyWebLayout.io.readers.html_extraction import parse_html_string +from pyWebLayout.concrete.page import Page +from pyWebLayout.style.page_style import PageStyle +from pyWebLayout.abstract.inline import LinkedWord + + +class TestHTMLLinkInteractivity(unittest.TestCase): + """Test that HTML links are interactive when rendered.""" + + def test_simple_link_parsing(self): + """Test that parse_html_string creates LinkedWord objects for links.""" + html = ''' +Click here to go back.
+Or visit this setting.
+Click here to test.
+ ''' + + blocks = parse_html_string(html) + + # Create a page + page_style = PageStyle() + page = Page((400, 200), page_style) + layouter = DocumentLayouter(page) + + # Layout the block + for block in blocks: + layouter.layout_document([block]) + + # Get the rendered canvas + _ = page.draw # Ensure canvas exists + + # Find the LinkedWord in the blocks + link_word = None + for block in blocks: + if hasattr(block, 'words'): + for word in block.words: + if isinstance(word, LinkedWord) and word.location == "action:test": + link_word = word + break + + # Verify we found the link + self.assertIsNotNone(link_word) + self.assertEqual(link_word.text, "here") + self.assertEqual(link_word.location, "action:test") + + # Test that query_point can detect the link + # Try querying a point in the middle of the page where text should be + # Note: The exact coordinates depend on the layout, but we can test the API + result = page.query_point((100, 50)) + if result: + # If we hit something, verify we can access its properties + self.assertIsNotNone(result.object_type) + # Link detection would show is_interactive=True for links + if result.is_interactive: + self.assertIsNotNone(result.link_target) + + def test_settings_overlay_button_html(self): + """Test the specific HTML pattern used for settings overlay buttons.""" + # This is the pattern used in the settings overlay + html = ''' + + ''' + + blocks = parse_html_string(html) + + # Find all LinkedWords + all_linked_words = [] + for block in blocks: + if hasattr(block, 'words'): + for word in block.words: + if isinstance(word, LinkedWord): + all_linked_words.append(word) + + # Verify we found the expected links + self.assertGreater(len(all_linked_words), 0, "Should find LinkedWords in settings HTML") + + # Check for specific link targets + link_targets = {word.location for word in all_linked_words} + + self.assertIn("action:back_to_library", link_targets, + "Should find 'Back to Library' link") + self.assertIn("setting:font_decrease", link_targets, + "Should find font decrease link") + self.assertIn("setting:font_increase", link_targets, + "Should find font increase link") + + # Verify the link texts + back_to_library_words = [w for w in all_linked_words + if w.location == "action:back_to_library"] + self.assertGreater(len(back_to_library_words), 0, + "Should have words linked to back_to_library action") + + # Print debug info + print(f"\nFound {len(all_linked_words)} linked words:") + for word in all_linked_words: + print(f" - '{word.text}' -> {word.location}") + + +if __name__ == '__main__': + unittest.main()