#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Test file for: tile_panels.py
import os
import sys
from pathlib import Path
import pytest
# Add scripts/python to path for imports
ROOT_DIR = Path(__file__).resolve().parent.parent.parent
sys.path.insert(0, str(ROOT_DIR / "scripts" / "python"))
pytest.importorskip("PIL")
# calculate_layout is pure logic - no dependencies needed
from tile_panels import calculate_layout, detect_panels # noqa: E402
# Check for PIL dependency
try:
from PIL import Image
from tile_panels import tile_images
HAS_PIL = True
except ImportError:
HAS_PIL = False
# Tests for calculate_layout (pure logic - always testable)
def test_calculate_layout_1_panel():
"""Test layout for 1 panel."""
assert calculate_layout(1) == (1, 1)
def test_calculate_layout_2_panels():
"""Test layout for 2 panels."""
assert calculate_layout(2) == (1, 2)
def test_calculate_layout_3_panels():
"""Test layout for 3 panels."""
assert calculate_layout(3) == (1, 3)
def test_calculate_layout_4_panels():
"""Test layout for 4 panels."""
assert calculate_layout(4) == (2, 2)
def test_calculate_layout_5_panels():
"""Test layout for 5 panels."""
assert calculate_layout(5) == (2, 3)
def test_calculate_layout_6_panels():
"""Test layout for 6 panels."""
assert calculate_layout(6) == (2, 3)
def test_calculate_layout_7_panels():
"""Test layout for 7 panels."""
assert calculate_layout(7) == (3, 3)
def test_calculate_layout_9_panels():
"""Test layout for 9 panels."""
assert calculate_layout(9) == (3, 3)
def test_calculate_layout_10_panels():
"""Test layout for 10 panels."""
rows, cols = calculate_layout(10)
assert rows * cols >= 10
assert isinstance(rows, int)
assert isinstance(cols, int)
# Tests for detect_panels (file system only)
def test_detect_panels_finds_files(tmp_path):
"""Test that detect_panels finds panel files."""
# Create test panel files following naming convention
(tmp_path / "01a_figure.jpg").touch()
(tmp_path / "01b_figure.jpg").touch()
(tmp_path / "01c_figure.jpg").touch()
panels = detect_panels("01_demographic_data", str(tmp_path))
assert len(panels) == 3
assert "A" in panels
assert "B" in panels
assert "C" in panels
def test_detect_panels_empty_dir(tmp_path):
"""Test that empty directory returns empty dict."""
panels = detect_panels("01_figure", str(tmp_path))
assert panels == {}
def test_detect_panels_wrong_prefix(tmp_path):
"""Test that panels with wrong prefix are not detected."""
(tmp_path / "02a_figure.jpg").touch()
(tmp_path / "02b_figure.jpg").touch()
panels = detect_panels("01_figure", str(tmp_path))
assert panels == {}
def test_detect_panels_sorted_order(tmp_path):
"""Test that panels are returned in sorted order."""
(tmp_path / "01c_figure.jpg").touch()
(tmp_path / "01a_figure.jpg").touch()
(tmp_path / "01b_figure.jpg").touch()
panels = detect_panels("01_figure", str(tmp_path))
keys = list(panels.keys())
assert keys == ["A", "B", "C"]
def test_detect_panels_mixed_case(tmp_path):
"""Test that lowercase panel letters are uppercase in output."""
(tmp_path / "01a_figure.jpg").touch()
panels = detect_panels("01_figure", str(tmp_path))
assert "A" in panels
assert "a" not in panels
# Tests for tile_images (requires PIL)
@pytest.mark.skipif(not HAS_PIL, reason="PIL (Pillow) not available")
def test_tile_images_empty_panels():
"""Test that tile_images returns False for empty panels."""
result = tile_images({}, "output.jpg")
assert result is False
@pytest.mark.skipif(not HAS_PIL, reason="PIL (Pillow) not available")
def test_tile_images_creates_output(tmp_path):
"""Test that tile_images creates output file."""
# Create small test images
img_a = Image.new("RGB", (100, 100), color="red")
img_b = Image.new("RGB", (100, 100), color="blue")
img_a_path = tmp_path / "01a_test.jpg"
img_b_path = tmp_path / "01b_test.jpg"
img_a.save(str(img_a_path))
img_b.save(str(img_b_path))
panels = {"A": str(img_a_path), "B": str(img_b_path)}
output_path = tmp_path / "tiled.jpg"
result = tile_images(panels, str(output_path), spacing=10, dpi=100)
assert result is True
assert output_path.exists()
@pytest.mark.skipif(not HAS_PIL, reason="PIL (Pillow) not available")
def test_tile_images_correct_dimensions(tmp_path):
"""Test that tiled image has correct dimensions."""
# Create test images
img_a = Image.new("RGB", (200, 150), color="red")
img_b = Image.new("RGB", (200, 150), color="blue")
img_a_path = tmp_path / "01a_test.jpg"
img_b_path = tmp_path / "01b_test.jpg"
img_a.save(str(img_a_path))
img_b.save(str(img_b_path))
panels = {"A": str(img_a_path), "B": str(img_b_path)}
output_path = tmp_path / "tiled.jpg"
spacing = 20
tile_images(panels, str(output_path), spacing=spacing)
# Open result and check dimensions
result_img = Image.open(str(output_path))
# 2 panels in 1 row, 2 cols layout
expected_width = 2 * 200 + (2 - 1) * spacing
assert result_img.width == expected_width
@pytest.mark.skipif(not HAS_PIL, reason="PIL (Pillow) not available")
def test_tile_images_invalid_path_returns_false(tmp_path):
"""Test that tile_images returns False for invalid image path."""
panels = {"A": "/nonexistent/image.jpg"}
output_path = tmp_path / "output.jpg"
result = tile_images(panels, str(output_path))
assert result is False
@pytest.mark.skipif(not HAS_PIL, reason="PIL (Pillow) not available")
def test_tile_images_4_panels_layout(tmp_path):
"""Test that 4 panels use 2x2 layout."""
# Create 4 test images
panels = {}
for letter in ["A", "B", "C", "D"]:
img = Image.new("RGB", (100, 100), color="white")
img_path = tmp_path / f"01{letter.lower()}_test.jpg"
img.save(str(img_path))
panels[letter] = str(img_path)
output_path = tmp_path / "tiled.jpg"
tile_images(panels, str(output_path), spacing=0)
result_img = Image.open(str(output_path))
# 2x2 layout
assert result_img.width == 200 # 2 * 100
assert result_img.height == 200 # 2 * 100
if __name__ == "__main__":
import pytest
pytest.main([os.path.abspath(__file__), "-v"])