190 lines
7.7 KiB
Python
190 lines
7.7 KiB
Python
"""Visual regression tests for flashcard generation."""
|
|
|
|
import pytest
|
|
import subprocess
|
|
import tempfile
|
|
import imagehash
|
|
from pathlib import Path
|
|
from PIL import Image
|
|
import sys
|
|
|
|
# Import our modules
|
|
from generate import generate_cards_direct
|
|
|
|
class TestVisualRegression:
|
|
"""Visual regression tests using image hashing and comparison."""
|
|
|
|
def test_basic_card_generation(self, temp_dir, sample_config, reference_images_dir):
|
|
"""Test basic card generation produces consistent output."""
|
|
# Generate a single test card (number 7)
|
|
numbers = [7]
|
|
config = {
|
|
**sample_config,
|
|
'transparent': False,
|
|
'card_width': '2in', # Smaller for faster tests
|
|
'card_height': '1.4in'
|
|
}
|
|
|
|
# Generate test output
|
|
test_output = temp_dir / 'test_cards'
|
|
generated_files = generate_cards_direct(
|
|
numbers, config, test_output,
|
|
format='png', dpi=150, # Lower DPI for faster tests
|
|
separate_fronts_backs=True
|
|
)
|
|
|
|
assert len(generated_files) == 2 # Front and back
|
|
|
|
# Check that files were created
|
|
front_file = test_output / 'fronts' / 'card_000.png'
|
|
back_file = test_output / 'backs' / 'card_000.png'
|
|
|
|
assert front_file.exists(), "Front card image should exist"
|
|
assert back_file.exists(), "Back card image should exist"
|
|
|
|
# Basic image validation
|
|
front_img = Image.open(front_file)
|
|
back_img = Image.open(back_file)
|
|
|
|
# Check dimensions are reasonable
|
|
assert front_img.size[0] > 100, "Front image should have reasonable width"
|
|
assert front_img.size[1] > 100, "Front image should have reasonable height"
|
|
assert back_img.size[0] > 100, "Back image should have reasonable width"
|
|
assert back_img.size[1] > 100, "Back image should have reasonable height"
|
|
|
|
# Store as reference if they don't exist
|
|
ref_front = reference_images_dir / 'card_7_front.png'
|
|
ref_back = reference_images_dir / 'card_7_back.png'
|
|
|
|
if not ref_front.exists():
|
|
front_img.save(ref_front)
|
|
print(f"Created reference image: {ref_front}")
|
|
|
|
if not ref_back.exists():
|
|
back_img.save(ref_back)
|
|
print(f"Created reference image: {ref_back}")
|
|
|
|
# If references exist, compare hashes
|
|
if ref_front.exists() and ref_back.exists():
|
|
ref_front_img = Image.open(ref_front)
|
|
ref_back_img = Image.open(ref_back)
|
|
|
|
# Use perceptual hashing for reasonable tolerance
|
|
front_hash = imagehash.phash(front_img)
|
|
ref_front_hash = imagehash.phash(ref_front_img)
|
|
back_hash = imagehash.phash(back_img)
|
|
ref_back_hash = imagehash.phash(ref_back_img)
|
|
|
|
# Allow small differences (hash distance < 5)
|
|
assert front_hash - ref_front_hash < 5, f"Front card changed significantly (hash diff: {front_hash - ref_front_hash})"
|
|
assert back_hash - ref_back_hash < 5, f"Back card changed significantly (hash diff: {back_hash - ref_back_hash})"
|
|
|
|
def test_different_bead_shapes(self, temp_dir, sample_config, reference_images_dir):
|
|
"""Test that different bead shapes produce visually different outputs."""
|
|
numbers = [5] # Simple number for shape testing
|
|
base_config = {
|
|
**sample_config,
|
|
'card_width': '2in',
|
|
'card_height': '1.4in'
|
|
}
|
|
|
|
shapes = ['diamond', 'circle', 'square']
|
|
shape_hashes = {}
|
|
|
|
for shape in shapes:
|
|
config = {**base_config, 'bead_shape': shape}
|
|
|
|
shape_output = temp_dir / f'shape_{shape}'
|
|
generated_files = generate_cards_direct(
|
|
numbers, config, shape_output,
|
|
format='png', dpi=150,
|
|
separate_fronts_backs=True
|
|
)
|
|
|
|
assert len(generated_files) == 2
|
|
|
|
# Get hash of front image (where shape is visible)
|
|
front_file = shape_output / 'fronts' / 'card_000.png'
|
|
front_img = Image.open(front_file)
|
|
shape_hashes[shape] = imagehash.phash(front_img)
|
|
|
|
# Verify shapes produce different images
|
|
assert shape_hashes['diamond'] - shape_hashes['circle'] > 3, "Diamond and circle shapes should be visually different"
|
|
assert shape_hashes['diamond'] - shape_hashes['square'] > 3, "Diamond and square shapes should be visually different"
|
|
assert shape_hashes['circle'] - shape_hashes['square'] > 3, "Circle and square shapes should be visually different"
|
|
|
|
def test_color_schemes(self, temp_dir, sample_config, reference_images_dir):
|
|
"""Test that different color schemes produce different outputs."""
|
|
numbers = [23] # Multi-digit number for color testing
|
|
base_config = {
|
|
**sample_config,
|
|
'card_width': '2in',
|
|
'card_height': '1.4in'
|
|
}
|
|
|
|
schemes = ['monochrome', 'place-value']
|
|
scheme_hashes = {}
|
|
|
|
for scheme in schemes:
|
|
config = {**base_config, 'color_scheme': scheme}
|
|
|
|
scheme_output = temp_dir / f'color_{scheme}'
|
|
generated_files = generate_cards_direct(
|
|
numbers, config, scheme_output,
|
|
format='png', dpi=150,
|
|
separate_fronts_backs=True
|
|
)
|
|
|
|
assert len(generated_files) == 2
|
|
|
|
# Get hash of front image
|
|
front_file = scheme_output / 'fronts' / 'card_000.png'
|
|
front_img = Image.open(front_file)
|
|
scheme_hashes[scheme] = imagehash.phash(front_img)
|
|
|
|
# Color schemes should produce different images
|
|
hash_diff = scheme_hashes['monochrome'] - scheme_hashes['place-value']
|
|
assert hash_diff >= 2, f"Color schemes should be visually different (hash diff: {hash_diff})"
|
|
|
|
@pytest.mark.slow
|
|
def test_pdf_generation_structure(self, temp_dir, sample_config):
|
|
"""Test that PDF generation produces valid PDF files."""
|
|
# This test requires typst to be installed
|
|
try:
|
|
subprocess.run(['typst', '--version'], capture_output=True, check=True)
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
pytest.skip("Typst not installed - skipping PDF tests")
|
|
|
|
from generate import generate_typst_file
|
|
|
|
numbers = [1, 2, 3]
|
|
output_typst = temp_dir / 'test.typ'
|
|
output_pdf = temp_dir / 'test.pdf'
|
|
|
|
# Generate Typst file
|
|
generate_typst_file(numbers, sample_config, output_typst)
|
|
|
|
# Check Typst file was created and has content
|
|
assert output_typst.exists()
|
|
assert output_typst.stat().st_size > 100 # Should have substantial content
|
|
|
|
# Try to compile to PDF (if typst is available)
|
|
try:
|
|
result = subprocess.run([
|
|
'typst', 'compile',
|
|
'--font-path', str(Path(__file__).parent.parent / 'fonts'),
|
|
str(output_typst), str(output_pdf)
|
|
], capture_output=True, text=True, cwd=temp_dir)
|
|
|
|
if result.returncode == 0:
|
|
assert output_pdf.exists(), "PDF should be generated"
|
|
assert output_pdf.stat().st_size > 1000, "PDF should have reasonable size"
|
|
else:
|
|
print(f"PDF compilation failed: {result.stderr}")
|
|
# Don't fail the test - typst might have font issues in test environment
|
|
|
|
except FileNotFoundError:
|
|
pytest.skip("Typst not available for PDF compilation")
|
|
|
|
|