""" Unit tests for abstract inline elements. Tests the Word and FormattedSpan classes that handle inline text elements and formatting within documents. """ import unittest from unittest.mock import Mock, patch, MagicMock from pyWebLayout.abstract.inline import Word, FormattedSpan, LineBreak from pyWebLayout.style import Font class TestWord(unittest.TestCase): """Test cases for Word class.""" def setUp(self): """Set up test fixtures.""" self.font = Font() # Note: Font background is a tuple (255, 255, 255, 0) by default # Note: Font language is set via constructor parameter (language - with typo) def test_word_creation_minimal(self): """Test word creation with minimal parameters.""" word = Word("hello", self.font) self.assertEqual(word.text, "hello") self.assertEqual(word.style, self.font) self.assertEqual(word.background, self.font.background) self.assertIsNone(word.previous) self.assertIsNone(word.next) self.assertIsNone(word.hyphenated_parts) def test_word_creation_with_previous(self): """Test word creation with previous word reference.""" word1 = Word("first", self.font) word2 = Word("second", self.font, previous=word1) self.assertEqual(word2.previous, word1) self.assertIsNone(word1.previous) self.assertIsNone(word1.next) self.assertIsNone(word2.next) def test_word_creation_with_background_override(self): """Test word creation with background color override.""" word = Word("test", self.font, background="yellow") self.assertEqual(word.background, "yellow") # Original font background should be unchanged - it's a tuple self.assertEqual(word.style.background, (255, 255, 255, 0)) def test_word_properties(self): """Test word property getters.""" word1 = Word("first", self.font) word2 = Word("second", self.font, background="blue", previous=word1) # Test all properties self.assertEqual(word2.text, "second") self.assertEqual(word2.style, self.font) self.assertEqual(word2.background, "blue") self.assertEqual(word2.previous, word1) self.assertIsNone(word2.next) self.assertIsNone(word2.hyphenated_parts) def test_add_next_word(self): """Test linking words with add_next method.""" word1 = Word("first", self.font) word2 = Word("second", self.font) word3 = Word("third", self.font) # Link words word1.add_next(word2) word2.add_next(word3) # Test forward links self.assertEqual(word1.next, word2) self.assertEqual(word2.next, word3) self.assertIsNone(word3.next) # Backward links should remain as set in constructor self.assertIsNone(word1.previous) self.assertIsNone(word2.previous) self.assertIsNone(word3.previous) def test_word_chain(self): """Test creating a chain of linked words.""" word1 = Word("first", self.font) word2 = Word("second", self.font, previous=word1) word3 = Word("third", self.font, previous=word2) # Add forward links word1.add_next(word2) word2.add_next(word3) # Test complete chain self.assertIsNone(word1.previous) self.assertEqual(word1.next, word2) self.assertEqual(word2.previous, word1) self.assertEqual(word2.next, word3) self.assertEqual(word3.previous, word2) self.assertIsNone(word3.next) @patch('pyWebLayout.abstract.inline.pyphen') def test_can_hyphenate_true(self, mock_pyphen): """Test can_hyphenate method when word can be hyphenated.""" # Mock pyphen behavior mock_dic = Mock() mock_dic.inserted.return_value = "hy-phen-ated" mock_pyphen.Pyphen.return_value = mock_dic word = Word("hyphenated", self.font) result = word.can_hyphenate() self.assertTrue(result) # Font language is set as "en_EN" by default (with typo in constructor param) mock_pyphen.Pyphen.assert_called_once_with(lang="en_EN") mock_dic.inserted.assert_called_once_with("hyphenated", hyphen='-') @patch('pyWebLayout.abstract.inline.pyphen') def test_can_hyphenate_false(self, mock_pyphen): """Test can_hyphenate method when word cannot be hyphenated.""" # Mock pyphen behavior for non-hyphenatable word mock_dic = Mock() mock_dic.inserted.return_value = "cat" # No hyphens added mock_pyphen.Pyphen.return_value = mock_dic word = Word("cat", self.font) result = word.can_hyphenate() self.assertFalse(result) mock_dic.inserted.assert_called_once_with("cat", hyphen='-') @patch('pyWebLayout.abstract.inline.pyphen') def test_can_hyphenate_with_language_override(self, mock_pyphen): """Test can_hyphenate with explicit language parameter.""" mock_dic = Mock() mock_dic.inserted.return_value = "hy-phen" mock_pyphen.Pyphen.return_value = mock_dic word = Word("hyphen", self.font) result = word.can_hyphenate("de_DE") self.assertTrue(result) mock_pyphen.Pyphen.assert_called_once_with(lang="de_DE") @patch('pyWebLayout.abstract.inline.pyphen') def test_hyphenate_success(self, mock_pyphen): """Test successful word hyphenation.""" # Mock pyphen behavior mock_dic = Mock() mock_dic.inserted.return_value = "hy-phen-ation" mock_pyphen.Pyphen.return_value = mock_dic word = Word("hyphenation", self.font) result = word.hyphenate() self.assertTrue(result) self.assertEqual(word.hyphenated_parts, ["hy-", "phen-", "ation"]) mock_pyphen.Pyphen.assert_called_once_with(lang="en_EN") @patch('pyWebLayout.abstract.inline.pyphen') def test_hyphenate_failure(self, mock_pyphen): """Test word hyphenation when word cannot be hyphenated.""" # Mock pyphen behavior for non-hyphenatable word mock_dic = Mock() mock_dic.inserted.return_value = "cat" # No hyphens mock_pyphen.Pyphen.return_value = mock_dic word = Word("cat", self.font) result = word.hyphenate() self.assertFalse(result) self.assertIsNone(word.hyphenated_parts) @patch('pyWebLayout.abstract.inline.pyphen') def test_hyphenate_with_language_override(self, mock_pyphen): """Test hyphenation with explicit language parameter.""" mock_dic = Mock() mock_dic.inserted.return_value = "Wort-teil" mock_pyphen.Pyphen.return_value = mock_dic word = Word("Wortteil", self.font) result = word.hyphenate("de_DE") self.assertTrue(result) self.assertEqual(word.hyphenated_parts, ["Wort-", "teil"]) mock_pyphen.Pyphen.assert_called_once_with(lang="de_DE") def test_dehyphenate(self): """Test removing hyphenation from word.""" word = Word("test", self.font) # Simulate hyphenated state word._hyphenated_parts = ["test-", "ing"] word.dehyphenate() self.assertIsNone(word.hyphenated_parts) def test_get_hyphenated_part(self): """Test getting specific hyphenated parts.""" word = Word("testing", self.font) # Simulate hyphenated state word._hyphenated_parts = ["test-", "ing"] # Test valid indices self.assertEqual(word.get_hyphenated_part(0), "test-") self.assertEqual(word.get_hyphenated_part(1), "ing") # Test invalid index with self.assertRaises(IndexError): word.get_hyphenated_part(2) def test_get_hyphenated_part_not_hyphenated(self): """Test getting hyphenated part from non-hyphenated word.""" word = Word("test", self.font) with self.assertRaises(IndexError) as context: word.get_hyphenated_part(0) self.assertIn("Word has not been hyphenated", str(context.exception)) def test_get_hyphenated_part_count(self): """Test getting hyphenated part count.""" word = Word("test", self.font) # Test non-hyphenated word self.assertEqual(word.get_hyphenated_part_count(), 0) # Test hyphenated word word._hyphenated_parts = ["hy-", "phen-", "ated"] self.assertEqual(word.get_hyphenated_part_count(), 3) @patch('pyWebLayout.abstract.inline.pyphen') def test_complex_hyphenation_scenario(self, mock_pyphen): """Test complex hyphenation with multiple syllables.""" # Mock pyphen for a complex word mock_dic = Mock() mock_dic.inserted.return_value = "un-der-stand-ing" mock_pyphen.Pyphen.return_value = mock_dic word = Word("understanding", self.font) result = word.hyphenate() self.assertTrue(result) expected_parts = ["un-", "der-", "stand-", "ing"] self.assertEqual(word.hyphenated_parts, expected_parts) self.assertEqual(word.get_hyphenated_part_count(), 4) # Test getting individual parts for i, expected_part in enumerate(expected_parts): self.assertEqual(word.get_hyphenated_part(i), expected_part) def test_word_create_and_add_to_with_container_style(self): """Test Word.create_and_add_to with container that has style property.""" # Create mock container with style and add_word method mock_container = Mock() mock_container.style = self.font mock_container.add_word = Mock() # Ensure _words and background don't interfere del mock_container._words del mock_container.background # Remove background so it inherits from font # Create and add word word = Word.create_and_add_to("hello", mock_container) # Test that word was created with correct properties self.assertIsInstance(word, Word) self.assertEqual(word.text, "hello") self.assertEqual(word.style, self.font) self.assertEqual(word.background, self.font.background) # Test that add_word was called mock_container.add_word.assert_called_once_with(word) def test_word_create_and_add_to_with_style_override(self): """Test Word.create_and_add_to with explicit style parameter.""" # Create alternate font alt_font = Font() # Create mock container mock_container = Mock() mock_container.style = self.font mock_container.add_word = Mock() # Ensure _words doesn't interfere del mock_container._words # Create word with style override word = Word.create_and_add_to("test", mock_container, style=alt_font) # Test that word uses the override style, not container style self.assertEqual(word.style, alt_font) self.assertNotEqual(word.style, self.font) def test_word_create_and_add_to_with_background_override(self): """Test Word.create_and_add_to with explicit background parameter.""" # Create mock container mock_container = Mock() mock_container.style = self.font mock_container.background = "container_bg" mock_container.add_word = Mock() # Ensure _words doesn't interfere del mock_container._words # Create word with background override word = Word.create_and_add_to("test", mock_container, background="override_bg") # Test that word uses the override background self.assertEqual(word.background, "override_bg") def test_word_create_and_add_to_inherit_container_background(self): """Test Word.create_and_add_to inheriting background from container.""" # Create mock container with background mock_container = Mock() mock_container.style = self.font mock_container.background = "container_bg" mock_container.add_word = Mock() # Ensure _words doesn't interfere del mock_container._words # Create word without background override word = Word.create_and_add_to("test", mock_container) # Test that word inherits container background self.assertEqual(word.background, "container_bg") def test_word_create_and_add_to_with_words_list_previous(self): """Test Word.create_and_add_to linking with previous word from _words list.""" # Create mock container with _words list mock_container = Mock() mock_container.style = self.font mock_container.add_word = Mock() # Create existing word and add to container's _words list existing_word = Word("previous", self.font) mock_container._words = [existing_word] # Create new word word = Word.create_and_add_to("current", mock_container) # Test that words are linked self.assertEqual(word.previous, existing_word) self.assertEqual(existing_word.next, word) def test_word_create_and_add_to_with_words_method_previous(self): """Test Word.create_and_add_to linking with previous word from words() method.""" # Create a simple container that implements words() method class SimpleContainer: def __init__(self, font): self.style = font self.existing_word = Word("previous", font) def words(self): yield ("span1", self.existing_word) def add_word(self, word): pass # Simple implementation container = SimpleContainer(self.font) # Create new word word = Word.create_and_add_to("current", container) # Test that words are linked self.assertEqual(word.previous, container.existing_word) self.assertEqual(container.existing_word.next, word) def test_word_create_and_add_to_no_style_error(self): """Test Word.create_and_add_to raises error when container has no style.""" # Create container without style class BadContainer: def add_word(self, word): pass container = BadContainer() # Test that AttributeError is raised with self.assertRaises(AttributeError) as context: Word.create_and_add_to("test", container) self.assertIn("must have a 'style' property", str(context.exception)) def test_word_create_and_add_to_no_add_word_error(self): """Test Word.create_and_add_to raises error when container has no add_word method.""" # Create container without add_word class BadContainer: def __init__(self, font): self.style = font container = BadContainer(self.font) # Test that AttributeError is raised with self.assertRaises(AttributeError) as context: Word.create_and_add_to("test", container) self.assertIn("must have an 'add_word' method", str(context.exception)) def test_word_create_and_add_to_parameter_inspection_word_object(self): """Test Word.create_and_add_to with add_word method that expects Word object.""" # Create container with add_word method that has 'word' parameter name class WordObjectContainer: def __init__(self, font): self.style = font self.added_words = [] def add_word(self, word): # Parameter named 'word' indicates it expects Word object self.added_words.append(word) container = WordObjectContainer(self.font) # Create and add word word = Word.create_and_add_to("test", container) # Test that the Word object was passed to add_word self.assertEqual(len(container.added_words), 1) self.assertEqual(container.added_words[0], word) self.assertIsInstance(container.added_words[0], Word) def test_word_create_and_add_to_parameter_inspection_word_obj(self): """Test Word.create_and_add_to with add_word method that has 'word_obj' parameter.""" class WordObjContainer: def __init__(self, font): self.style = font self.added_words = [] def add_word(self, word_obj): # Parameter named 'word_obj' indicates it expects Word object self.added_words.append(word_obj) container = WordObjContainer(self.font) word = Word.create_and_add_to("test", container) self.assertEqual(len(container.added_words), 1) self.assertEqual(container.added_words[0], word) def test_word_create_and_add_to_parameter_inspection_word_object(self): """Test Word.create_and_add_to with add_word method that has 'word_object' parameter.""" class WordObjectContainer: def __init__(self, font): self.style = font self.added_words = [] def add_word(self, word_object): # Parameter named 'word_object' indicates it expects Word object self.added_words.append(word_object) container = WordObjectContainer(self.font) word = Word.create_and_add_to("test", container) self.assertEqual(len(container.added_words), 1) self.assertEqual(container.added_words[0], word) def test_word_create_and_add_to_parameter_inspection_text_fallback_with_words_list(self): """Test Word.create_and_add_to with add_word that expects text but container has _words list.""" class TextExpectingContainer: def __init__(self, font): self.style = font self._words = [] # Has _words list self.add_word_calls = [] def add_word(self, text): # Parameter named 'text' suggests it expects string # This simulates FormattedSpan.add_word behavior self.add_word_calls.append(text) # In real FormattedSpan, this would create a Word internally container = TextExpectingContainer(self.font) word = Word.create_and_add_to("test", container) # Word should be added to _words list directly, not via add_word self.assertEqual(len(container._words), 1) self.assertEqual(container._words[0], word) # add_word should not have been called since it expects text self.assertEqual(len(container.add_word_calls), 0) def test_word_create_and_add_to_parameter_inspection_fallback_without_words_list(self): """Test Word.create_and_add_to fallback when container doesn't have _words list.""" class TextExpectingContainer: def __init__(self, font): self.style = font # No _words list self.added_words = [] def add_word(self, text): # Parameter suggests text but we'll pass Word as fallback self.added_words.append(text) container = TextExpectingContainer(self.font) word = Word.create_and_add_to("test", container) # Should fallback to calling add_word with Word object self.assertEqual(len(container.added_words), 1) self.assertEqual(container.added_words[0], word) def test_word_create_and_add_to_no_parameters_edge_case(self): """Test Word.create_and_add_to with add_word method that has no parameters.""" class NoParamsContainer: def __init__(self, font): self.style = font self.add_word_called = False def add_word(self): # No parameters - edge case self.add_word_called = True container = NoParamsContainer(self.font) # The current implementation will fail when calling add_word(word) with a no-parameter method with self.assertRaises(TypeError) as context: Word.create_and_add_to("test", container) self.assertIn("takes 1 positional argument but 2 were given", str(context.exception)) def test_word_create_and_add_to_linking_behavior_with_existing_words(self): """Test Word.create_and_add_to properly links with existing words in container.""" # Create container with existing words class ContainerWithWords: def __init__(self, font): self.style = font self._words = [] # Add an existing word existing_word = Word("existing", font) self._words.append(existing_word) def add_word(self, word): self._words.append(word) container = ContainerWithWords(self.font) existing_word = container._words[0] # Create new word new_word = Word.create_and_add_to("new", container) # Test linking self.assertEqual(new_word.previous, existing_word) self.assertEqual(existing_word.next, new_word) self.assertEqual(len(container._words), 2) self.assertEqual(container._words[1], new_word) def test_word_create_and_add_to_linking_behavior_with_words_method(self): """Test Word.create_and_add_to properly links with words from container.words() method.""" class ContainerWithWordsMethod: def __init__(self, font): self.style = font self.existing_word1 = Word("first", font) self.existing_word2 = Word("second", font) self.existing_word1.add_next(self.existing_word2) self.added_words = [] def words(self): yield ("span1", self.existing_word1) yield ("span2", self.existing_word2) def add_word(self, word): self.added_words.append(word) container = ContainerWithWordsMethod(self.font) # Create new word new_word = Word.create_and_add_to("third", container) # Should link to the last word returned by words() method self.assertEqual(new_word.previous, container.existing_word2) self.assertEqual(container.existing_word2.next, new_word) def test_word_create_and_add_to_linking_behavior_empty_words_method(self): """Test Word.create_and_add_to with empty words() method.""" class EmptyWordsContainer: def __init__(self, font): self.style = font def words(self): # Empty iterator return iter([]) def add_word(self, word): pass container = EmptyWordsContainer(self.font) # Create word word = Word.create_and_add_to("test", container) # Should have no previous word self.assertIsNone(word.previous) def test_word_create_and_add_to_linking_behavior_words_method_exception(self): """Test Word.create_and_add_to with words() method that raises exception.""" class ExceptionWordsContainer: def __init__(self, font): self.style = font def words(self): raise TypeError("Error in words method") def add_word(self, word): pass container = ExceptionWordsContainer(self.font) # Create word - should handle exception gracefully word = Word.create_and_add_to("test", container) # Should have no previous word due to exception self.assertIsNone(word.previous) class TestFormattedSpan(unittest.TestCase): """Test cases for FormattedSpan class.""" def setUp(self): """Set up test fixtures.""" self.font = Font() # Font background is a tuple, not a string def test_formatted_span_creation_minimal(self): """Test formatted span creation with minimal parameters.""" span = FormattedSpan(self.font) self.assertEqual(span.style, self.font) self.assertEqual(span.background, self.font.background) self.assertEqual(len(span.words), 0) def test_formatted_span_creation_with_background(self): """Test formatted span creation with background override.""" span = FormattedSpan(self.font, background="yellow") self.assertEqual(span.style, self.font) self.assertEqual(span.background, "yellow") self.assertNotEqual(span.background, self.font.background) def test_formatted_span_properties(self): """Test formatted span property getters.""" span = FormattedSpan(self.font, background="blue") self.assertEqual(span.style, self.font) self.assertEqual(span.background, "blue") self.assertIsInstance(span.words, list) self.assertEqual(len(span.words), 0) def test_add_single_word(self): """Test adding a single word to formatted span.""" span = FormattedSpan(self.font) word = span.add_word("hello") # Test returned word self.assertIsInstance(word, Word) self.assertEqual(word.text, "hello") self.assertEqual(word.style, self.font) self.assertEqual(word.background, self.font.background) self.assertIsNone(word.previous) # Test span state self.assertEqual(len(span.words), 1) self.assertEqual(span.words[0], word) def test_add_multiple_words(self): """Test adding multiple words to formatted span.""" span = FormattedSpan(self.font) word1 = span.add_word("first") word2 = span.add_word("second") word3 = span.add_word("third") # Test span contains all words self.assertEqual(len(span.words), 3) self.assertEqual(span.words[0], word1) self.assertEqual(span.words[1], word2) self.assertEqual(span.words[2], word3) # Test word linking self.assertIsNone(word1.previous) self.assertEqual(word1.next, word2) self.assertEqual(word2.previous, word1) self.assertEqual(word2.next, word3) self.assertEqual(word3.previous, word2) self.assertIsNone(word3.next) def test_add_word_with_background_override(self): """Test that added words inherit span background.""" span = FormattedSpan(self.font, background="red") word = span.add_word("test") # Word should inherit span's background self.assertEqual(word.background, "red") self.assertEqual(word.style, self.font) def test_word_style_consistency(self): """Test that all words in span have consistent style.""" span = FormattedSpan(self.font, background="green") words = [] for text in ["this", "is", "a", "test"]: words.append(span.add_word(text)) # All words should have same style and background for word in words: self.assertEqual(word.style, self.font) self.assertEqual(word.background, "green") def test_word_chain_integrity(self): """Test that word chain is properly maintained.""" span = FormattedSpan(self.font) words = [] for i in range(5): words.append(span.add_word(f"word{i}")) # Test complete chain for i in range(5): word = words[i] # Test previous link if i == 0: self.assertIsNone(word.previous) else: self.assertEqual(word.previous, words[i-1]) # Test next link if i == 4: self.assertIsNone(word.next) else: self.assertEqual(word.next, words[i+1]) def test_empty_span_operations(self): """Test operations on empty formatted span.""" span = FormattedSpan(self.font) # Test empty state self.assertEqual(len(span.words), 0) self.assertEqual(span.words, []) # Add first word first_word = span.add_word("first") self.assertIsNone(first_word.previous) self.assertIsNone(first_word.next) def test_formatted_span_create_and_add_to_with_container_style(self): """Test FormattedSpan.create_and_add_to with container that has style property.""" # Create mock container with style and add_span method mock_container = Mock() mock_container.style = self.font mock_container.add_span = Mock() # Remove background so it inherits from font del mock_container.background # Create and add span span = FormattedSpan.create_and_add_to(mock_container) # Test that span was created with correct properties self.assertIsInstance(span, FormattedSpan) self.assertEqual(span.style, self.font) self.assertEqual(span.background, self.font.background) # Test that add_span was called mock_container.add_span.assert_called_once_with(span) def test_formatted_span_create_and_add_to_with_style_override(self): """Test FormattedSpan.create_and_add_to with explicit style parameter.""" # Create alternate font alt_font = Font() # Create mock container mock_container = Mock() mock_container.style = self.font mock_container.add_span = Mock() # Create span with style override span = FormattedSpan.create_and_add_to(mock_container, style=alt_font) # Test that span uses the override style, not container style self.assertEqual(span.style, alt_font) self.assertNotEqual(span.style, self.font) def test_formatted_span_create_and_add_to_with_background_override(self): """Test FormattedSpan.create_and_add_to with explicit background parameter.""" # Create mock container mock_container = Mock() mock_container.style = self.font mock_container.background = "container_bg" mock_container.add_span = Mock() # Create span with background override span = FormattedSpan.create_and_add_to(mock_container, background="override_bg") # Test that span uses the override background self.assertEqual(span.background, "override_bg") def test_formatted_span_create_and_add_to_inherit_container_background(self): """Test FormattedSpan.create_and_add_to inheriting background from container.""" # Create mock container with background mock_container = Mock() mock_container.style = self.font mock_container.background = "container_bg" mock_container.add_span = Mock() # Create span without background override span = FormattedSpan.create_and_add_to(mock_container) # Test that span inherits container background self.assertEqual(span.background, "container_bg") def test_formatted_span_create_and_add_to_no_style_error(self): """Test FormattedSpan.create_and_add_to raises error when container has no style.""" # Create mock container without style mock_container = Mock() del mock_container.style mock_container.add_span = Mock() # Test that AttributeError is raised with self.assertRaises(AttributeError) as context: FormattedSpan.create_and_add_to(mock_container) self.assertIn("must have a 'style' property", str(context.exception)) def test_formatted_span_create_and_add_to_no_add_span_error(self): """Test FormattedSpan.create_and_add_to raises error when container has no add_span method.""" # Create mock container without add_span mock_container = Mock() mock_container.style = self.font del mock_container.add_span # Test that AttributeError is raised with self.assertRaises(AttributeError) as context: FormattedSpan.create_and_add_to(mock_container) self.assertIn("must have an 'add_span' method", str(context.exception)) class TestWordFormattedSpanIntegration(unittest.TestCase): """Integration tests between Word and FormattedSpan classes.""" def setUp(self): """Set up test fixtures.""" self.font = Font() # Font background is a tuple, not a string def test_sentence_creation(self): """Test creating a complete sentence with formatted span.""" span = FormattedSpan(self.font) sentence_words = ["The", "quick", "brown", "fox", "jumps"] words = [] for word_text in sentence_words: words.append(span.add_word(word_text)) # Test sentence structure self.assertEqual(len(span.words), 5) # Test word content for i, expected_text in enumerate(sentence_words): self.assertEqual(words[i].text, expected_text) # Test linking for i in range(5): if i > 0: self.assertEqual(words[i].previous, words[i-1]) if i < 4: self.assertEqual(words[i].next, words[i+1]) @patch('pyWebLayout.abstract.inline.pyphen') def test_span_with_hyphenated_words(self, mock_pyphen): """Test formatted span containing hyphenated words.""" # Mock pyphen mock_dic = Mock() mock_pyphen.Pyphen.return_value = mock_dic def mock_inserted(word, hyphen='-'): if word == "understanding": return "un-der-stand-ing" elif word == "hyphenation": return "hy-phen-ation" else: return word # No hyphenation mock_dic.inserted.side_effect = mock_inserted span = FormattedSpan(self.font) # Add words, some of which can be hyphenated word1 = span.add_word("The") word2 = span.add_word("understanding") word3 = span.add_word("of") word4 = span.add_word("hyphenation") # Test hyphenation self.assertTrue(word2.can_hyphenate()) self.assertTrue(word2.hyphenate()) self.assertFalse(word1.can_hyphenate()) self.assertTrue(word4.can_hyphenate()) self.assertTrue(word4.hyphenate()) # Test hyphenated parts self.assertEqual(word2.hyphenated_parts, ["un-", "der-", "stand-", "ing"]) self.assertEqual(word4.hyphenated_parts, ["hy-", "phen-", "ation"]) self.assertIsNone(word1.hyphenated_parts) self.assertIsNone(word3.hyphenated_parts) def test_multiple_spans_same_style(self): """Test creating multiple spans with the same style.""" font = Font() span1 = FormattedSpan(font) span2 = FormattedSpan(font) # Add words to both spans span1_words = [span1.add_word("First"), span1.add_word("span")] span2_words = [span2.add_word("Second"), span2.add_word("span")] # Test that spans are independent self.assertEqual(len(span1.words), 2) self.assertEqual(len(span2.words), 2) # Test that words in different spans are not linked self.assertIsNone(span1_words[1].next) self.assertIsNone(span2_words[0].previous) # But words within spans are linked self.assertEqual(span1_words[0].next, span1_words[1]) self.assertEqual(span2_words[1].previous, span2_words[0]) def test_span_style_inheritance(self): """Test that words properly inherit span styling.""" font = Font() # Font background is a tuple (255, 255, 255, 0) # Test with span background override span = FormattedSpan(font, background="lightgreen") word1 = span.add_word("styled") word2 = span.add_word("text") # Both words should have span's background, not font's self.assertEqual(word1.background, "lightgreen") self.assertEqual(word2.background, "lightgreen") # But they should have font's other properties self.assertEqual(word1.style, font) self.assertEqual(word2.style, font) def test_word_modification_after_creation(self): """Test modifying words after they've been added to span.""" span = FormattedSpan(self.font) word = span.add_word("original") # Verify initial state self.assertEqual(word.text, "original") self.assertEqual(len(span.words), 1) # Words are immutable by design (no setter for text property) # But we can test that the reference is maintained self.assertEqual(span.words[0], word) self.assertEqual(span.words[0].text, "original") class TestLineBreak(unittest.TestCase): """Test cases for LineBreak class.""" def test_line_break_creation(self): """Test line break creation.""" line_break = LineBreak() # Test initial state self.assertIsNotNone(line_break.block_type) self.assertIsNone(line_break.parent) def test_line_break_block_type(self): """Test line break block type property.""" line_break = LineBreak() # Import BlockType to verify the correct type from pyWebLayout.abstract.block import BlockType self.assertEqual(line_break.block_type, BlockType.LINE_BREAK) def test_line_break_parent_property(self): """Test line break parent property getter and setter.""" line_break = LineBreak() # Test initial state self.assertIsNone(line_break.parent) # Test setter mock_parent = Mock() line_break.parent = mock_parent self.assertEqual(line_break.parent, mock_parent) # Test setting to None line_break.parent = None self.assertIsNone(line_break.parent) def test_line_break_create_and_add_to_with_add_line_break(self): """Test LineBreak.create_and_add_to with container that has add_line_break method.""" # Create mock container with add_line_break method mock_container = Mock() mock_container.add_line_break = Mock() # Create and add line break line_break = LineBreak.create_and_add_to(mock_container) # Test that line break was created self.assertIsInstance(line_break, LineBreak) # Test that add_line_break was called mock_container.add_line_break.assert_called_once_with(line_break) def test_line_break_create_and_add_to_with_add_element(self): """Test LineBreak.create_and_add_to with container that has add_element method.""" # Create mock container without add_line_break but with add_element mock_container = Mock() del mock_container.add_line_break # Ensure no add_line_break method mock_container.add_element = Mock() # Create and add line break line_break = LineBreak.create_and_add_to(mock_container) # Test that line break was created self.assertIsInstance(line_break, LineBreak) # Test that add_element was called mock_container.add_element.assert_called_once_with(line_break) def test_line_break_create_and_add_to_with_add_word(self): """Test LineBreak.create_and_add_to with container that has add_word method.""" # Create mock container with only add_word method mock_container = Mock() del mock_container.add_line_break # Ensure no add_line_break method del mock_container.add_element # Ensure no add_element method mock_container.add_word = Mock() # Create and add line break line_break = LineBreak.create_and_add_to(mock_container) # Test that line break was created self.assertIsInstance(line_break, LineBreak) # Test that add_word was called mock_container.add_word.assert_called_once_with(line_break) def test_line_break_create_and_add_to_fallback(self): """Test LineBreak.create_and_add_to fallback behavior when no add methods available.""" # Create mock container without any add methods mock_container = Mock() del mock_container.add_line_break del mock_container.add_element del mock_container.add_word # Create and add line break line_break = LineBreak.create_and_add_to(mock_container) # Test that line break was created self.assertIsInstance(line_break, LineBreak) # Test that parent was set manually self.assertEqual(line_break.parent, mock_container) if __name__ == '__main__': unittest.main()