This commit is contained in:
parent
af18b1794a
commit
00314c9b4f
163
tests/io_tests/test_html_link_end_to_end.py
Normal file
163
tests/io_tests/test_html_link_end_to_end.py
Normal file
@ -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 = '<p>Click <a href="action:test_action">this link</a> to test.</p>'
|
||||||
|
|
||||||
|
# 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 = '''
|
||||||
|
<div>
|
||||||
|
<h2>Settings</h2>
|
||||||
|
<p>
|
||||||
|
<a href="action:back_to_library">Back to Library</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Font Size:
|
||||||
|
<a href="setting:font_decrease">[-]</a>
|
||||||
|
<a href="setting:font_increase">[+]</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
'''
|
||||||
|
|
||||||
|
# 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 = '''
|
||||||
|
<p>
|
||||||
|
<a href="https://example.com" title="Example Site">External</a>
|
||||||
|
<a href="#section2" title="Go to Section 2">Internal</a>
|
||||||
|
<a href="javascript:alert()" title="Alert">API</a>
|
||||||
|
</p>
|
||||||
|
'''
|
||||||
|
|
||||||
|
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()
|
||||||
205
tests/io_tests/test_html_link_interactivity.py
Normal file
205
tests/io_tests/test_html_link_interactivity.py
Normal file
@ -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 = '''
|
||||||
|
<div>
|
||||||
|
<p>Click <a href="action:back_to_library">here</a> to go back.</p>
|
||||||
|
<p>Or visit <a href="setting:font_increase">this setting</a>.</p>
|
||||||
|
</div>
|
||||||
|
'''
|
||||||
|
|
||||||
|
blocks = parse_html_string(html)
|
||||||
|
|
||||||
|
# Should have 2 paragraphs
|
||||||
|
self.assertEqual(len(blocks), 2)
|
||||||
|
|
||||||
|
# First paragraph - check for LinkedWord
|
||||||
|
para1_words = list(blocks[0].words)
|
||||||
|
linked_words_1 = [w for w in para1_words if isinstance(w, LinkedWord)]
|
||||||
|
|
||||||
|
# "here" should be a LinkedWord
|
||||||
|
self.assertEqual(len(linked_words_1), 1)
|
||||||
|
self.assertEqual(linked_words_1[0].text, "here")
|
||||||
|
self.assertEqual(linked_words_1[0].location, "action:back_to_library")
|
||||||
|
|
||||||
|
# Second paragraph
|
||||||
|
para2_words = list(blocks[1].words)
|
||||||
|
linked_words_2 = [w for w in para2_words if isinstance(w, LinkedWord)]
|
||||||
|
|
||||||
|
# "this" and "setting" should be LinkedWords
|
||||||
|
self.assertEqual(len(linked_words_2), 2)
|
||||||
|
link_texts = [w.text for w in linked_words_2]
|
||||||
|
self.assertIn("this", link_texts)
|
||||||
|
self.assertIn("setting", link_texts)
|
||||||
|
|
||||||
|
def test_link_rendering_on_page(self):
|
||||||
|
"""Test that links are properly rendered and detectable on a page."""
|
||||||
|
from pyWebLayout.layout.document_layouter import DocumentLayouter
|
||||||
|
|
||||||
|
html = '''
|
||||||
|
<div>
|
||||||
|
<h1>Settings</h1>
|
||||||
|
<p>Font Size: <a href="setting:font_decrease">-</a> | <a href="setting:font_increase">+</a></p>
|
||||||
|
<p><a href="action:back_to_library">Back to Library</a></p>
|
||||||
|
</div>
|
||||||
|
'''
|
||||||
|
|
||||||
|
blocks = parse_html_string(html)
|
||||||
|
|
||||||
|
# Create a page and use DocumentLayouter to properly layout blocks
|
||||||
|
page_style = PageStyle()
|
||||||
|
page = Page((600, 800), page_style)
|
||||||
|
layouter = DocumentLayouter(page)
|
||||||
|
|
||||||
|
# Layout all blocks
|
||||||
|
for block in blocks:
|
||||||
|
layouter.layout_document([block])
|
||||||
|
|
||||||
|
# Render the page
|
||||||
|
rendered_image = page.render()
|
||||||
|
|
||||||
|
# Verify the page rendered
|
||||||
|
self.assertIsNotNone(rendered_image)
|
||||||
|
self.assertEqual(rendered_image.size, (600, 800))
|
||||||
|
|
||||||
|
# Now verify we can detect the links via query_point
|
||||||
|
# We need to find the positions of the linked words
|
||||||
|
found_links = []
|
||||||
|
|
||||||
|
# Scan the blocks for LinkedWords
|
||||||
|
for block in blocks:
|
||||||
|
if hasattr(block, 'words'):
|
||||||
|
for word in block.words:
|
||||||
|
if isinstance(word, LinkedWord):
|
||||||
|
found_links.append({
|
||||||
|
'text': word.text,
|
||||||
|
'location': word.location,
|
||||||
|
'link_type': word.link_type
|
||||||
|
})
|
||||||
|
|
||||||
|
# Verify we found the expected links
|
||||||
|
# -, +, Back, to, Library (5 LinkedWords total)
|
||||||
|
self.assertGreater(len(found_links), 0, "Should find at least some LinkedWords")
|
||||||
|
|
||||||
|
link_locations = [link['location'] for link in found_links]
|
||||||
|
self.assertIn("setting:font_decrease", link_locations)
|
||||||
|
self.assertIn("setting:font_increase", link_locations)
|
||||||
|
self.assertIn("action:back_to_library", link_locations)
|
||||||
|
|
||||||
|
def test_link_query_point_detection(self):
|
||||||
|
"""Test that query_point can detect links on a rendered page."""
|
||||||
|
from pyWebLayout.layout.document_layouter import DocumentLayouter
|
||||||
|
|
||||||
|
html = '''
|
||||||
|
<p>Click <a href="action:test">here</a> to test.</p>
|
||||||
|
'''
|
||||||
|
|
||||||
|
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 = '''
|
||||||
|
<div>
|
||||||
|
<h2 style="text-align: center; font-size: 18px; font-weight: bold; margin: 10px 0;">Settings</h2>
|
||||||
|
<p style="padding: 15px; margin: 5px 0; background-color: #dc3545; text-align: center; border-radius: 5px;">
|
||||||
|
<a href="action:back_to_library" style="text-decoration: none; color: white; font-weight: bold; font-size: 14px;">◄ Back to Library</a>
|
||||||
|
</p>
|
||||||
|
<p style="padding: 10px; margin: 5px 0; background-color: #f8f9fa; border-radius: 5px;">
|
||||||
|
<span style="font-weight: bold;">Font Size: 100%</span><br>
|
||||||
|
<a href="setting:font_decrease" style="text-decoration: none; color: #007bff; margin: 0 10px;">[-]</a>
|
||||||
|
<a href="setting:font_increase" style="text-decoration: none; color: #007bff; margin: 0 10px;">[+]</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
'''
|
||||||
|
|
||||||
|
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()
|
||||||
Loading…
x
Reference in New Issue
Block a user