521 lines
19 KiB
Python
521 lines
19 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, patch, MagicMock
|
|
from pyWebLayout.abstract.inline import Word, FormattedSpan
|
|
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)
|
|
|
|
|
|
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)
|
|
|
|
|
|
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")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|