From 5a573c901efdf7b22f45338d65207cb2bf8cbfba Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Mon, 10 Nov 2025 18:31:02 +0100 Subject: [PATCH] fixed word highlighting issue, added test for examples --- examples/word_selection_highlighting.py | 3 +- tests/test_examples.py | 228 ++++++++++++++++++++++++ 2 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 tests/test_examples.py diff --git a/examples/word_selection_highlighting.py b/examples/word_selection_highlighting.py index f525a4f..af6f803 100644 --- a/examples/word_selection_highlighting.py +++ b/examples/word_selection_highlighting.py @@ -18,8 +18,7 @@ This is useful for: from PIL import Image, ImageDraw import numpy as np -from dreader.application import EbookReader -from pyWebLayout.io.gesture import TouchEvent, GestureType +from dreader import EbookReader, TouchEvent, GestureType from pyWebLayout.core.query import QueryResult diff --git a/tests/test_examples.py b/tests/test_examples.py new file mode 100644 index 0000000..fe8e15a --- /dev/null +++ b/tests/test_examples.py @@ -0,0 +1,228 @@ +""" +Unit tests for example scripts. + +This test suite validates that all example scripts: +1. Can be imported without errors (syntax checks, import validation) +2. Have valid import statements +3. Can run their main functions without crashing (when applicable) + +This helps catch issues like: +- Incorrect import paths +- Missing dependencies +- API breakages that affect examples +""" + +import unittest +import importlib.util +import sys +import os +from pathlib import Path + + +class TestExampleImports(unittest.TestCase): + """Test that all example scripts can be imported successfully""" + + def setUp(self): + """Set up test fixtures""" + # Get the project root directory + self.project_root = Path(__file__).parent.parent + self.examples_dir = self.project_root / "examples" + + # Add project root to Python path if not already there + if str(self.project_root) not in sys.path: + sys.path.insert(0, str(self.project_root)) + + def _import_module_from_file(self, file_path: Path): + """ + Import a Python module from a file path. + + Args: + file_path: Path to the Python file + + Returns: + The imported module + + Raises: + Any import errors that occur + """ + spec = importlib.util.spec_from_file_location(file_path.stem, file_path) + if spec is None or spec.loader is None: + raise ImportError(f"Could not load spec for {file_path}") + + module = importlib.util.module_from_spec(spec) + sys.modules[file_path.stem] = module + spec.loader.exec_module(module) + return module + + def test_word_selection_highlighting_imports(self): + """Test word_selection_highlighting.py can be imported""" + example_file = self.examples_dir / "word_selection_highlighting.py" + self.assertTrue(example_file.exists(), f"Example file not found: {example_file}") + + try: + module = self._import_module_from_file(example_file) + + # Verify key components are available + self.assertTrue(hasattr(module, 'draw_highlight')) + self.assertTrue(hasattr(module, 'example_1_single_word_selection')) + self.assertTrue(hasattr(module, 'example_2_range_selection')) + self.assertTrue(hasattr(module, 'example_3_interactive_word_lookup')) + self.assertTrue(hasattr(module, 'example_4_multi_word_annotation')) + self.assertTrue(hasattr(module, 'example_5_link_highlighting')) + + except ImportError as e: + self.fail(f"Failed to import word_selection_highlighting.py: {e}") + + def test_demo_pagination_imports(self): + """Test demo_pagination.py can be imported""" + example_file = self.examples_dir / "demo_pagination.py" + self.assertTrue(example_file.exists(), f"Example file not found: {example_file}") + + try: + module = self._import_module_from_file(example_file) + self.assertTrue(hasattr(module, 'main')) + except ImportError as e: + self.fail(f"Failed to import demo_pagination.py: {e}") + + def test_demo_toc_overlay_imports(self): + """Test demo_toc_overlay.py can be imported""" + example_file = self.examples_dir / "demo_toc_overlay.py" + self.assertTrue(example_file.exists(), f"Example file not found: {example_file}") + + try: + module = self._import_module_from_file(example_file) + self.assertTrue(hasattr(module, 'main')) + except ImportError as e: + self.fail(f"Failed to import demo_toc_overlay.py: {e}") + + def test_demo_settings_overlay_imports(self): + """Test demo_settings_overlay.py can be imported""" + example_file = self.examples_dir / "demo_settings_overlay.py" + self.assertTrue(example_file.exists(), f"Example file not found: {example_file}") + + try: + module = self._import_module_from_file(example_file) + self.assertTrue(hasattr(module, 'main')) + except ImportError as e: + self.fail(f"Failed to import demo_settings_overlay.py: {e}") + + def test_library_reading_integration_imports(self): + """Test library_reading_integration.py can be imported""" + example_file = self.examples_dir / "library_reading_integration.py" + self.assertTrue(example_file.exists(), f"Example file not found: {example_file}") + + try: + module = self._import_module_from_file(example_file) + self.assertTrue(hasattr(module, 'main')) + self.assertTrue(hasattr(module, 'simulate_mode_transition_workflow')) + except ImportError as e: + self.fail(f"Failed to import library_reading_integration.py: {e}") + + def test_all_examples_have_correct_dreader_imports(self): + """ + Verify all example scripts use correct import paths for dreader classes. + + This test specifically checks that examples don't use outdated import paths + like 'from dreader.application import' when they should use 'from dreader import'. + """ + # Get all Python files in examples directory + example_files = list(self.examples_dir.glob("*.py")) + + problematic_imports = [] + + for example_file in example_files: + # Skip __init__.py and other special files + if example_file.name.startswith('_'): + continue + + with open(example_file, 'r') as f: + content = f.read() + + # Check for problematic import patterns + if 'from pyWebLayout.io.gesture import' in content: + problematic_imports.append( + f"{example_file.name}: Uses 'from pyWebLayout.io.gesture import' " + f"(should be 'from dreader import')" + ) + + if 'from dreader.application import EbookReader' in content: + # This is acceptable, but check if TouchEvent/GestureType are also imported correctly + if 'from pyWebLayout.io.gesture import TouchEvent' in content: + problematic_imports.append( + f"{example_file.name}: Mixes dreader.application and pyWebLayout.io.gesture imports" + ) + + if problematic_imports: + self.fail( + "Found problematic imports in example files:\n" + + "\n".join(f" - {issue}" for issue in problematic_imports) + ) + + +class TestExampleFunctions(unittest.TestCase): + """Test key functionality in example scripts""" + + def test_draw_highlight_function(self): + """Test the draw_highlight function from word_selection_highlighting""" + from PIL import Image + + # Import the module + project_root = Path(__file__).parent.parent + example_file = project_root / "examples" / "word_selection_highlighting.py" + + spec = importlib.util.spec_from_file_location("word_selection_highlighting", example_file) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + # Create a test image + test_image = Image.new('RGBA', (100, 100), (255, 255, 255, 255)) + + # Test the draw_highlight function + bounds = (10, 10, 50, 20) + result = module.draw_highlight(test_image, bounds) + + # Verify the result is an image + self.assertIsInstance(result, Image.Image) + self.assertEqual(result.size, (100, 100)) + self.assertEqual(result.mode, 'RGBA') + + +class TestExampleDocumentation(unittest.TestCase): + """Test that examples have proper documentation""" + + def test_all_examples_have_docstrings(self): + """Verify all example scripts have module docstrings""" + project_root = Path(__file__).parent.parent + examples_dir = project_root / "examples" + + example_files = [ + f for f in examples_dir.glob("*.py") + if not f.name.startswith('_') and f.name not in ['__init__.py'] + ] + + missing_docstrings = [] + + for example_file in example_files: + spec = importlib.util.spec_from_file_location(example_file.stem, example_file) + if spec is None or spec.loader is None: + continue + + module = importlib.util.module_from_spec(spec) + try: + spec.loader.exec_module(module) + + if not module.__doc__ or len(module.__doc__.strip()) < 10: + missing_docstrings.append(example_file.name) + except: + # If module can't be loaded, skip docstring check + # (import test will catch the error) + pass + + if missing_docstrings: + self.fail( + f"Examples missing proper docstrings: {', '.join(missing_docstrings)}" + ) + + +if __name__ == '__main__': + unittest.main()