Files
2026-05-24 20:13:04 +02:00

200 lines
7.2 KiB
Python

"""Tests for grid composition (non-ComfyUI parts)."""
import pytest
import sys
from pathlib import Path
import torch
import numpy as np
# Add parent directory to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent))
from grid import _wrap_text, _wrap_single_line, _measure_text_height
from PIL import ImageFont
class TestWrapText:
"""Test text wrapping utility."""
def test_empty_text(self):
"""Empty text should return empty list."""
font = ImageFont.load_default()
result = _wrap_text("", 100, font)
assert result == []
def test_single_line_fits(self):
"""Single line that fits should be returned."""
font = ImageFont.load_default()
result = _wrap_text("hello", 1000, font)
assert "hello" in result
def test_max_lines_enforced(self):
"""Should not exceed max_lines."""
font = ImageFont.load_default()
text = "one two three four five six seven eight"
result = _wrap_text(text, 50, font, max_lines=2)
assert len(result) <= 2
def test_ellipsis_added(self):
"""Truncated text at max_lines=1 ends with an ellipsis character."""
font = ImageFont.load_default()
text = "one two three four five six seven eight"
result = _wrap_text(text, 1, font, max_lines=1)
assert len(result) == 1
assert result[0].endswith("…")
def test_newline_creates_separate_lines(self):
"""Embedded newlines in the input text produce separate output lines."""
font = ImageFont.load_default()
result = _wrap_text("hello\nworld", 1000, font, max_lines=5)
assert result == ["hello", "world"]
def test_oversized_word_not_dropped(self):
"""A single word wider than max_width is still returned rather than dropped."""
font = ImageFont.load_default()
result = _wrap_text("superlongword", 1, font, max_lines=2)
assert result == ["superlongword"]
def test_newline_overflow_adds_ellipsis(self):
"""\\n-segments that push past max_lines get an ellipsis on the last kept line."""
font = ImageFont.load_default()
result = _wrap_text("a\nb\nc", 1000, font, max_lines=2)
assert len(result) == 2
assert result[-1].endswith("…")
class TestWrapSingleLine:
"""Tests for the _wrap_single_line helper."""
def test_empty_text_returns_empty(self):
font = ImageFont.load_default()
assert _wrap_single_line("", 100, font) == []
def test_single_word_fits(self):
font = ImageFont.load_default()
assert _wrap_single_line("hello", 1000, font) == ["hello"]
def test_multiple_words_fit_on_one_line(self):
font = ImageFont.load_default()
assert _wrap_single_line("hello world", 1000, font) == ["hello world"]
def test_words_wrap_to_separate_lines(self):
font = ImageFont.load_default()
result = _wrap_single_line("one two three", 1, font)
assert result == ["one", "two", "three"]
def test_oversized_word_not_dropped(self):
font = ImageFont.load_default()
assert _wrap_single_line("superlongword", 1, font) == ["superlongword"]
def test_no_newline_handling(self):
"""_wrap_single_line treats \\n as whitespace (split() behaviour), not a line break."""
font = ImageFont.load_default()
result = _wrap_single_line("hello\nworld", 1000, font)
assert result == ["hello world"]
class TestMeasureTextHeight:
"""Test text height measurement."""
def test_empty_lines(self):
"""Empty lines should return 0."""
font = ImageFont.load_default()
height = _measure_text_height([], font)
assert height == 0
def test_single_line_height(self):
"""Single line should return positive height."""
font = ImageFont.load_default()
height = _measure_text_height(["hello"], font)
assert height > 0
def test_multiple_lines_height(self):
"""Multiple lines should scale linearly."""
font = ImageFont.load_default()
single_height = _measure_text_height(["hello"], font)
double_height = _measure_text_height(["hello", "world"], font)
assert double_height == single_height * 2 or double_height > single_height
def test_line_spacing_included(self):
"""Two identical lines include exactly one inter-line spacing gap."""
from grid import _LINE_SPACING
font = ImageFont.load_default()
h1 = _measure_text_height(["hello"], font)
h2 = _measure_text_height(["hello", "hello"], font)
assert h2 == h1 * 2 + _LINE_SPACING
class TestGridComposition:
"""Test grid composition (high-level)."""
def test_single_cell_grid(self):
"""Single cell should compose without error."""
from grid import compose_grid
cell = torch.ones([512, 512, 3], dtype=torch.float32) * 0.5
result = compose_grid([cell])
assert result.shape[2] == 3 # RGB
assert result.dtype == torch.float32
assert result.min() >= 0 and result.max() <= 1
def test_multiple_cells(self):
"""Multiple cells should compose into grid."""
from grid import compose_grid
cells = [torch.ones([256, 256, 3], dtype=torch.float32) * (i / 10) for i in range(4)]
x_labels = ["a", "b"]
y_labels = ["c", "d"]
result = compose_grid(cells, x_labels=x_labels, y_labels=y_labels)
assert result.shape[2] == 3
assert result.dtype == torch.float32
def test_cell_mismatch_error(self):
"""Mismatch between cells and labels should raise error."""
from grid import compose_grid
cells = [torch.ones([256, 256, 3], dtype=torch.float32)]
x_labels = ["a", "b"]
y_labels = ["c", "d"]
with pytest.raises(ValueError):
compose_grid(cells, x_labels=x_labels, y_labels=y_labels)
def test_x_labels_add_top_strip_to_height(self):
"""Output height with x-labels is greater than bare cell height."""
from grid import compose_grid
cells = [torch.zeros([256, 256, 3], dtype=torch.float32) for _ in range(2)]
result = compose_grid(cells, x_labels=["a", "b"], width=256, height=256)
assert result.shape[0] > 256 # top strip adds height beyond one cell row
def test_y_labels_add_left_strip_to_width(self):
"""Output width with y-labels is greater than bare cell width."""
from grid import compose_grid
cells = [torch.zeros([256, 256, 3], dtype=torch.float32) for _ in range(2)]
result = compose_grid(cells, y_labels=["a", "b"], width=256, height=256)
assert result.shape[1] > 256 # left strip adds width beyond one cell column
def test_max_composite_width_respected(self):
"""Composite width does not exceed max_composite_width after rescaling."""
from grid import compose_grid
# Two cells, x-labels only (no y-label strip) so rescaling is exact.
cells = [torch.ones([512, 512, 3], dtype=torch.float32) for _ in range(2)]
x_labels = ["a", "b"]
result = compose_grid(cells, x_labels=x_labels, max_composite_width=512)
assert result.shape[1] <= 512
if __name__ == "__main__":
pytest.main([__file__, "-v"])