Added line overflow test
All checks were successful
Python CI / test (push) Successful in 5m16s

This commit is contained in:
Duncan Tourolle 2025-06-21 10:59:41 +02:00
parent 210358913a
commit ff5f840646

174
tests/test_line_overflow.py Normal file
View File

@ -0,0 +1,174 @@
"""
Test for line overflow behavior in pyWebLayout/concrete/text.py
This test demonstrates how line overflow is triggered when words cannot fit
in progressively shorter lines. Uses a simple sentence with non-hyphenatable words.
"""
import unittest
import numpy as np
from PIL import ImageFont
from pyWebLayout.concrete.text import Line, Text
from pyWebLayout.style import Font, FontStyle, FontWeight
from pyWebLayout.style.layout import Alignment
class TestLineOverflow(unittest.TestCase):
"""Test line overflow behavior with progressively shorter lines."""
def setUp(self):
"""Set up test fixtures with a basic font and simple sentence."""
# Create a simple font for testing
self.font = Font()
# Simple sentence with short, non-hyphenatable words
self.simple_words = ["cat", "dog", "pig", "rat", "ox", "bee", "fly", "ant"]
def test_line_overflow_progression(self):
"""Test that line overflow is triggered as line width decreases."""
# Start with a reasonably wide line that can fit all words
initial_width = 800
line_height = 50
spacing = (5, 20) # min_spacing, max_spacing
# Test results storage
results = []
# Test with progressively shorter lines
for width in range(initial_width, 50, -50): # Decrease by 50px each time
# Create a new line with current width
origin = np.array([0, 0])
size = (width, line_height)
line = Line(spacing, origin, size, self.font, halign=Alignment.LEFT)
# Try to add words until overflow occurs
words_added = []
overflow_word = None
for word in self.simple_words:
result = line.add_word(word, self.font)
if result is None:
# Word fit in line
words_added.append(word)
else:
# Overflow occurred
overflow_word = word
break
# Record the test result
test_result = {
'width': width,
'words_added': words_added.copy(),
'overflow_word': overflow_word,
'total_words_fit': len(words_added)
}
results.append(test_result)
# Print progress for visibility
print(f"Width {width}px: Fit {len(words_added)} words: {' '.join(words_added)}")
if overflow_word:
print(f" -> Overflow on word: '{overflow_word}'")
# Assertions to verify expected behavior
self.assertTrue(len(results) > 0, "Should have test results")
# First (widest) line should fit more words than later lines
first_result = results[0]
last_result = results[-1]
self.assertGreaterEqual(
first_result['total_words_fit'],
last_result['total_words_fit'],
"Wider lines should fit at least as many words as narrower lines"
)
# At some point, overflow should occur (not all words fit)
overflow_occurred = any(r['overflow_word'] is not None for r in results)
self.assertTrue(overflow_occurred, "Line overflow should occur with narrow lines")
# Very narrow lines should fit fewer words
narrow_results = [r for r in results if r['width'] < 200]
if narrow_results:
# At least one narrow line should have triggered overflow
narrow_overflow = any(r['overflow_word'] is not None for r in narrow_results)
self.assertTrue(narrow_overflow, "Narrow lines should trigger overflow")
def test_single_word_overflow(self):
"""Test overflow behavior when even a single word doesn't fit."""
# Create a very narrow line
narrow_width = 30
line_height = 50
spacing = (5, 20)
origin = np.array([0, 0])
size = (narrow_width, line_height)
line = Line(spacing, origin, size, self.font, halign=Alignment.LEFT)
# Try to add a word that likely won't fit
test_word = "elephant" # Longer word that should overflow
result = line.add_word(test_word, self.font)
# The implementation may partially fit the word and return the remaining part
# Some overflow occurred - either full word or remaining part
self.assertIsInstance(result, str, "Should return remaining text as string")
self.assertGreater(len(result), 0, "Remaining text should not be empty")
# Check that some part was fitted
self.assertGreater(len(line.text_objects), 0, "Should have fitted at least some characters")
# The fitted part + remaining part should equal the original word
fitted_text = line.text_objects[0].text if line.text_objects else ""
self.assertEqual(fitted_text + result, test_word,
f"Fitted part '{fitted_text}' + remaining '{result}' should equal '{test_word}'")
print(f"Word '{test_word}' partially fit: '{fitted_text}' fitted, '{result}' remaining")
def test_empty_line_behavior(self):
"""Test behavior when adding words to an empty line."""
width = 300
line_height = 50
spacing = (5, 20)
origin = np.array([0, 0])
size = (width, line_height)
line = Line(spacing, origin, size, self.font, halign=Alignment.LEFT)
# Initially empty
self.assertEqual(len(line.text_objects), 0, "Line should start empty")
# Add first word
result = line.add_word("cat", self.font)
self.assertIsNone(result, "First word should fit in reasonable width")
self.assertEqual(len(line.text_objects), 1, "Should have one text object")
self.assertEqual(line.text_objects[0].text, "cat", "Text should match added word")
def test_progressive_overflow_demonstration(self):
"""Demonstrate the exact point where overflow begins."""
# Use a specific set of short words
words = ["a", "bb", "ccc", "dd", "e"]
# Start wide and narrow down until we find the overflow point
for width in range(200, 10, -10):
origin = np.array([0, 0])
size = (width, 50)
line = Line((3, 15), origin, size, self.font, halign=Alignment.LEFT)
words_that_fit = []
for word in words:
result = line.add_word(word, self.font)
if result is None:
words_that_fit.append(word)
else:
# This is where overflow started
print(f"Overflow at width {width}px: '{' '.join(words_that_fit)}' + '{word}' (overflow)")
self.assertGreater(len(words_that_fit), 0, "Should fit at least some words before overflow")
return # Test successful
# If we get here, no overflow occurred even at minimum width
print("No overflow occurred - all words fit even in narrowest line")
if __name__ == '__main__':
unittest.main(verbosity=2)