860 lines
32 KiB
Python
860 lines
32 KiB
Python
"""
|
|
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
|
|
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.assertIsNone(word.previous)
|
|
self.assertIsNone(word.next)
|
|
self.assertEqual(len(word.possible_hyphenation()), 0)
|
|
|
|
def test_word_hyphenation(self):
|
|
"""Test word creation with minimal parameters."""
|
|
word = Word("amsterdam", self.font)
|
|
|
|
self.assertEqual(word.text, "amsterdam")
|
|
self.assertEqual(word.style, self.font)
|
|
self.assertIsNone(word.previous)
|
|
self.assertIsNone(word.next)
|
|
self.assertEqual(len(word.possible_hyphenation()), 3)
|
|
|
|
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.assertEqual(word1.next, word2)
|
|
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)
|
|
|
|
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)
|
|
|
|
# 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)
|
|
|
|
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_param(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])
|
|
|
|
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()
|