#!/usr/bin/env python3 """ Test to demonstrate and verify fix for the line splitting bug where text is lost at line breaks due to improper hyphenation handling. """ import unittest from unittest.mock import patch, Mock from pyWebLayout.concrete.text import Line from pyWebLayout.abstract.inline import Word from pyWebLayout.style import Font from pyWebLayout.style.layout import Alignment class TestLineSplittingBug(unittest.TestCase): """Test cases for the line splitting bug""" def setUp(self): """Set up test fixtures""" self.font = Font( font_path=None, font_size=12, colour=(0, 0, 0), min_hyphenation_width=20 # Allow hyphenation in narrow spaces for testing ) self.spacing = (5, 10) self.origin = (0, 0) self.size = (100, 20) # Narrow line to force hyphenation @patch('pyWebLayout.abstract.inline.pyphen') def test_hyphenation_preserves_word_boundaries(self, mock_pyphen_module): """Test that hyphenation properly preserves word boundaries""" # Mock pyphen to return a multi-part hyphenated word mock_dic = Mock() mock_pyphen_module.Pyphen.return_value = mock_dic # Simulate hyphenating "supercalifragilisticexpialidocious" # into multiple parts: "super-", "cali-", "fragi-", "listic-", "expiali-", "docious" mock_dic.inserted.return_value = "super-cali-fragi-listic-expiali-docious" line = Line(self.spacing, self.origin, self.size, self.font) # Add the word that will be hyphenated overflow = line.add_word("supercalifragilisticexpialidocious") # The overflow should be the next part only, not all remaining parts joined # In the current buggy implementation, this would return "cali-fragi-listic-expiali-docious" # But it should return "cali-" (the next single part) print(f"Overflow returned: '{overflow}'") # Check that the first part was added to the line self.assertEqual(len(line.text_objects), 1) first_word_text = line.text_objects[0].text self.assertEqual(first_word_text, "super-") # The overflow should be just the next part, not all parts joined # This assertion will fail with the current bug, showing the issue self.assertEqual(overflow, "cali-") # Should be next part only # NOT this (which is what the bug produces): # self.assertEqual(overflow, "cali-fragi-listic-expiali-docious") @patch('pyWebLayout.abstract.inline.pyphen') def test_single_word_overflow_behavior(self, mock_pyphen_module): """Test that overflow returns only the next part, not all remaining parts joined""" # Mock pyphen to return a simple two-part hyphenated word mock_dic = Mock() mock_pyphen_module.Pyphen.return_value = mock_dic mock_dic.inserted.return_value = "very-long" # Create a narrow line that will force hyphenation line = Line(self.spacing, (0, 0), (40, 20), self.font) # Add the word that will be hyphenated overflow = line.add_word("verylong") # Check that the first part was added to the line self.assertEqual(len(line.text_objects), 1) first_word_text = line.text_objects[0].text self.assertEqual(first_word_text, "very-") # The overflow should be just the next part ("long"), not multiple parts joined # This tests the core fix for the line splitting bug self.assertEqual(overflow, "long") print(f"First part in line: '{first_word_text}'") print(f"Overflow returned: '{overflow}'") def test_simple_overflow_case(self): """Test a simple word overflow without hyphenation to verify baseline behavior""" line = Line(self.spacing, self.origin, (50, 20), self.font) # Add a word that fits result1 = line.add_word("short") self.assertIsNone(result1) # Add a word that doesn't fit (should overflow) result2 = line.add_word("verylongword") self.assertEqual(result2, "verylongword") # Only the first word should be in the line self.assertEqual(len(line.text_objects), 1) self.assertEqual(line.text_objects[0].text, "short") def demonstrate_bug(): """Demonstrate the bug with a practical example""" print("=" * 60) print("DEMONSTRATING LINE SPLITTING BUG") print("=" * 60) font = Font(font_path=None, font_size=12, colour=(0, 0, 0)) # Create a very narrow line that will force hyphenation line = Line((3, 6), (0, 0), (80, 20), font) # Try to add a long word that should be hyphenated with patch('pyWebLayout.abstract.inline.pyphen') as mock_pyphen_module: mock_dic = Mock() mock_pyphen_module.Pyphen.return_value = mock_dic mock_dic.inserted.return_value = "hyper-long-example-word" overflow = line.add_word("hyperlongexampleword") print(f"Original word: 'hyperlongexampleword'") print(f"Hyphenated to: 'hyper-long-example-word'") print(f"First part added to line: '{line.text_objects[0].text if line.text_objects else 'None'}'") print(f"Overflow returned: '{overflow}'") print() print("PROBLEM: The overflow should be 'long-' (next part only)") print("but instead it returns 'long-example-word' (all remaining parts joined)") print("This causes word boundary information to be lost!") if __name__ == "__main__": # First demonstrate the bug demonstrate_bug() print("\n" + "=" * 60) print("RUNNING UNIT TESTS") print("=" * 60) # Run unit tests unittest.main()