358 lines
13 KiB
Python
358 lines
13 KiB
Python
"""
|
|
Unit tests for pyWebLayout.concrete.text module.
|
|
Tests the Text and Line classes for text rendering functionality.
|
|
"""
|
|
|
|
import unittest
|
|
import numpy as np
|
|
from PIL import Image, ImageFont
|
|
from unittest.mock import Mock, patch, MagicMock
|
|
|
|
from pyWebLayout.concrete.text import Text, Line
|
|
from pyWebLayout.abstract.inline import Word
|
|
from pyWebLayout.style import Font, FontStyle, FontWeight, TextDecoration
|
|
from pyWebLayout.style.layout import Alignment
|
|
|
|
|
|
class TestText(unittest.TestCase):
|
|
"""Test cases for the Text class"""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures"""
|
|
self.font = Font(
|
|
font_path=None, # Use default font
|
|
font_size=12,
|
|
colour=(0, 0, 0),
|
|
weight=FontWeight.NORMAL,
|
|
style=FontStyle.NORMAL,
|
|
decoration=TextDecoration.NONE
|
|
)
|
|
self.sample_text = "Hello World"
|
|
|
|
def test_text_initialization(self):
|
|
"""Test basic text initialization"""
|
|
text = Text(self.sample_text, self.font)
|
|
|
|
self.assertEqual(text._text, self.sample_text)
|
|
self.assertEqual(text._style, self.font)
|
|
self.assertIsNone(text._line)
|
|
self.assertIsNone(text._previous)
|
|
self.assertIsNone(text._next)
|
|
np.testing.assert_array_equal(text._origin, np.array([0, 0]))
|
|
|
|
def test_text_properties(self):
|
|
"""Test text property accessors"""
|
|
text = Text(self.sample_text, self.font)
|
|
|
|
self.assertEqual(text.text, self.sample_text)
|
|
self.assertEqual(text.style, self.font)
|
|
self.assertIsNone(text.line)
|
|
|
|
# Test size property
|
|
self.assertIsInstance(text.size, tuple)
|
|
self.assertEqual(len(text.size), 2)
|
|
self.assertGreater(text.width, 0)
|
|
self.assertGreater(text.height, 0)
|
|
|
|
def test_set_origin(self):
|
|
"""Test setting text origin"""
|
|
text = Text(self.sample_text, self.font)
|
|
text.set_origin(50, 75)
|
|
|
|
np.testing.assert_array_equal(text._origin, np.array([50, 75]))
|
|
|
|
def test_line_assignment(self):
|
|
"""Test line assignment"""
|
|
text = Text(self.sample_text, self.font)
|
|
mock_line = Mock()
|
|
|
|
text.line = mock_line
|
|
self.assertEqual(text.line, mock_line)
|
|
self.assertEqual(text._line, mock_line)
|
|
|
|
def test_add_to_line(self):
|
|
"""Test adding text to a line"""
|
|
text = Text(self.sample_text, self.font)
|
|
mock_line = Mock()
|
|
|
|
text.add_to_line(mock_line)
|
|
self.assertEqual(text._line, mock_line)
|
|
|
|
@patch('PIL.ImageDraw.Draw')
|
|
def test_render_basic(self, mock_draw_class):
|
|
"""Test basic text rendering"""
|
|
mock_draw = Mock()
|
|
mock_draw_class.return_value = mock_draw
|
|
|
|
text = Text(self.sample_text, self.font)
|
|
result = text.render()
|
|
|
|
self.assertIsInstance(result, Image.Image)
|
|
self.assertEqual(result.mode, 'RGBA')
|
|
mock_draw.text.assert_called_once()
|
|
|
|
@patch('PIL.ImageDraw.Draw')
|
|
def test_render_with_background(self, mock_draw_class):
|
|
"""Test text rendering with background color"""
|
|
mock_draw = Mock()
|
|
mock_draw_class.return_value = mock_draw
|
|
|
|
font_with_bg = Font(
|
|
font_path=None, # Use default font
|
|
font_size=12,
|
|
colour=(0, 0, 0),
|
|
background=(255, 255, 0, 128) # Yellow background with alpha
|
|
)
|
|
|
|
text = Text(self.sample_text, font_with_bg)
|
|
result = text.render()
|
|
|
|
self.assertIsInstance(result, Image.Image)
|
|
mock_draw.rectangle.assert_called_once()
|
|
mock_draw.text.assert_called_once()
|
|
|
|
@patch('PIL.ImageDraw.Draw')
|
|
def test_apply_decoration_underline(self, mock_draw_class):
|
|
"""Test underline decoration"""
|
|
mock_draw = Mock()
|
|
mock_draw_class.return_value = mock_draw
|
|
|
|
font_underlined = Font(
|
|
font_path=None, # Use default font
|
|
font_size=12,
|
|
colour=(0, 0, 0),
|
|
decoration=TextDecoration.UNDERLINE
|
|
)
|
|
|
|
text = Text(self.sample_text, font_underlined)
|
|
text._apply_decoration(mock_draw)
|
|
|
|
mock_draw.line.assert_called_once()
|
|
|
|
@patch('PIL.ImageDraw.Draw')
|
|
def test_apply_decoration_strikethrough(self, mock_draw_class):
|
|
"""Test strikethrough decoration"""
|
|
mock_draw = Mock()
|
|
mock_draw_class.return_value = mock_draw
|
|
|
|
font_strikethrough = Font(
|
|
font_path=None, # Use default font
|
|
font_size=12,
|
|
colour=(0, 0, 0),
|
|
decoration=TextDecoration.STRIKETHROUGH
|
|
)
|
|
|
|
text = Text(self.sample_text, font_strikethrough)
|
|
text._apply_decoration(mock_draw)
|
|
|
|
mock_draw.line.assert_called_once()
|
|
|
|
def test_in_object_point_inside(self):
|
|
"""Test in_object method with point inside text"""
|
|
text = Text(self.sample_text, self.font)
|
|
text.set_origin(10, 20)
|
|
|
|
# Point inside text bounds
|
|
inside_point = np.array([15, 25])
|
|
self.assertTrue(text.in_object(inside_point))
|
|
|
|
def test_in_object_point_outside(self):
|
|
"""Test in_object method with point outside text"""
|
|
text = Text(self.sample_text, self.font)
|
|
text.set_origin(10, 20)
|
|
|
|
# Point outside text bounds
|
|
outside_point = np.array([200, 200])
|
|
self.assertFalse(text.in_object(outside_point))
|
|
|
|
def test_get_size(self):
|
|
"""Test get_size method"""
|
|
text = Text(self.sample_text, self.font)
|
|
size = text.get_size()
|
|
|
|
self.assertIsInstance(size, tuple)
|
|
self.assertEqual(len(size), 2)
|
|
self.assertEqual(size, text.size)
|
|
|
|
|
|
class TestLine(unittest.TestCase):
|
|
"""Test cases for the Line class"""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures"""
|
|
self.font = Font(
|
|
font_path=None, # Use default font
|
|
font_size=12,
|
|
colour=(0, 0, 0)
|
|
)
|
|
self.spacing = (5, 10) # min, max spacing
|
|
self.origin = (0, 0)
|
|
self.size = (200, 20)
|
|
|
|
def test_line_initialization(self):
|
|
"""Test basic line initialization"""
|
|
line = Line(self.spacing, self.origin, self.size, self.font)
|
|
|
|
self.assertEqual(line._spacing, self.spacing)
|
|
self.assertEqual(line._font, self.font)
|
|
self.assertEqual(len(line._text_objects), 0) # Updated to _text_objects
|
|
self.assertEqual(line._current_width, 0)
|
|
self.assertIsNone(line._previous)
|
|
self.assertIsNone(line._next)
|
|
|
|
def test_line_initialization_with_previous(self):
|
|
"""Test line initialization with previous line"""
|
|
previous_line = Mock()
|
|
line = Line(self.spacing, self.origin, self.size, self.font, previous=previous_line)
|
|
|
|
self.assertEqual(line._previous, previous_line)
|
|
|
|
|
|
def test_text_objects_property(self):
|
|
"""Test text_objects property"""
|
|
line = Line(self.spacing, self.origin, self.size, self.font)
|
|
|
|
self.assertIsInstance(line.text_objects, list)
|
|
self.assertEqual(len(line.text_objects), 0)
|
|
|
|
def test_set_next(self):
|
|
"""Test setting next line"""
|
|
line = Line(self.spacing, self.origin, self.size, self.font)
|
|
next_line = Mock()
|
|
|
|
line.set_next(next_line)
|
|
self.assertEqual(line._next, next_line)
|
|
|
|
def test_add_word_fits(self):
|
|
"""Test adding word that fits in line"""
|
|
line = Line(self.spacing, self.origin, self.size, self.font)
|
|
result = line.add_word("short")
|
|
|
|
self.assertIsNone(result) # Word fits, no overflow
|
|
self.assertEqual(len(line._text_objects), 1) # Updated to _text_objects
|
|
self.assertGreater(line._current_width, 0)
|
|
|
|
def test_add_word_overflow(self):
|
|
"""Test adding word that doesn't fit"""
|
|
# Create a narrow line
|
|
narrow_line = Line(self.spacing, self.origin, (50, 20), self.font)
|
|
|
|
# Add a long word that won't fit
|
|
result = narrow_line.add_word("supercalifragilisticexpialidocious")
|
|
|
|
# Should return the word text indicating overflow
|
|
self.assertIsInstance(result, str)
|
|
|
|
@patch.object(Word, 'hyphenate')
|
|
@patch.object(Word, 'get_hyphenated_part')
|
|
@patch.object(Word, 'get_hyphenated_part_count')
|
|
def test_add_word_hyphenated(self, mock_part_count, mock_get_part, mock_hyphenate):
|
|
"""Test adding word that gets hyphenated"""
|
|
# Mock hyphenation behavior
|
|
mock_hyphenate.return_value = True
|
|
mock_get_part.side_effect = lambda i: ["supercalifragilisticexpialidocious-", "remainder"][i]
|
|
mock_part_count.return_value = 2
|
|
|
|
# Use a very narrow line to ensure even the first part doesn't fit
|
|
narrow_line = Line(self.spacing, self.origin, (30, 20), self.font)
|
|
result = narrow_line.add_word("supercalifragilisticexpialidocious")
|
|
|
|
# Should return the original word since even the first part doesn't fit
|
|
self.assertIsInstance(result, str)
|
|
self.assertEqual(result, "supercalifragilisticexpialidocious")
|
|
|
|
def test_add_multiple_words(self):
|
|
"""Test adding multiple words to line"""
|
|
line = Line(self.spacing, self.origin, self.size, self.font)
|
|
|
|
line.add_word("first")
|
|
line.add_word("second")
|
|
line.add_word("third")
|
|
|
|
self.assertEqual(len(line._text_objects), 3) # Updated to _text_objects
|
|
self.assertGreater(line._current_width, 0)
|
|
|
|
def test_render_empty_line(self):
|
|
"""Test rendering empty line"""
|
|
line = Line(self.spacing, self.origin, self.size, self.font)
|
|
result = line.render()
|
|
|
|
self.assertIsInstance(result, Image.Image)
|
|
self.assertEqual(result.size, tuple(self.size))
|
|
|
|
def test_render_with_words_left_aligned(self):
|
|
"""Test rendering line with left alignment"""
|
|
line = Line(self.spacing, self.origin, self.size, self.font, halign=Alignment.LEFT)
|
|
line.add_word("hello")
|
|
line.add_word("world")
|
|
|
|
result = line.render()
|
|
|
|
self.assertIsInstance(result, Image.Image)
|
|
self.assertEqual(result.size, tuple(self.size))
|
|
|
|
def test_render_with_words_right_aligned(self):
|
|
"""Test rendering line with right alignment"""
|
|
line = Line(self.spacing, self.origin, self.size, self.font, halign=Alignment.RIGHT)
|
|
line.add_word("hello")
|
|
line.add_word("world")
|
|
|
|
result = line.render()
|
|
|
|
self.assertIsInstance(result, Image.Image)
|
|
self.assertEqual(result.size, tuple(self.size))
|
|
|
|
def test_render_with_words_centered(self):
|
|
"""Test rendering line with center alignment"""
|
|
line = Line(self.spacing, self.origin, self.size, self.font, halign=Alignment.CENTER)
|
|
line.add_word("hello")
|
|
line.add_word("world")
|
|
|
|
result = line.render()
|
|
|
|
self.assertIsInstance(result, Image.Image)
|
|
self.assertEqual(result.size, tuple(self.size))
|
|
|
|
def test_render_with_words_justified(self):
|
|
"""Test rendering line with justified alignment"""
|
|
line = Line(self.spacing, self.origin, self.size, self.font, halign=Alignment.JUSTIFY)
|
|
line.add_word("hello")
|
|
line.add_word("world")
|
|
line.add_word("test")
|
|
|
|
result = line.render()
|
|
|
|
self.assertIsInstance(result, Image.Image)
|
|
self.assertEqual(result.size, tuple(self.size))
|
|
|
|
def test_render_single_word(self):
|
|
"""Test rendering line with single word"""
|
|
line = Line(self.spacing, self.origin, self.size, self.font)
|
|
line.add_word("single")
|
|
|
|
result = line.render()
|
|
|
|
self.assertIsInstance(result, Image.Image)
|
|
self.assertEqual(result.size, tuple(self.size))
|
|
|
|
def test_text_objects_contain_text_instances(self):
|
|
"""Test that text_objects contain Text instances"""
|
|
line = Line(self.spacing, self.origin, self.size, self.font)
|
|
line.add_word("test")
|
|
|
|
self.assertEqual(len(line.text_objects), 1)
|
|
self.assertIsInstance(line.text_objects[0], Text)
|
|
self.assertEqual(line.text_objects[0].text, "test")
|
|
|
|
def test_text_objects_linked_to_line(self):
|
|
"""Test that Text objects are properly linked to the line"""
|
|
line = Line(self.spacing, self.origin, self.size, self.font)
|
|
line.add_word("test")
|
|
|
|
text_obj = line.text_objects[0]
|
|
self.assertEqual(text_obj.line, line)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|