Files
Text-Texture-Generator-for-…/tests/conftest.py
2025-10-09 23:36:10 +02:00

262 lines
7.1 KiB
Python

# Mock bpy BEFORE test collection can import src modules
import sys
from unittest.mock import Mock, MagicMock
# Create comprehensive bpy mock
bpy_mock = MagicMock()
bpy_mock.types = MagicMock()
bpy_mock.props = MagicMock()
bpy_mock.utils = MagicMock()
bpy_mock.ops = MagicMock()
bpy_mock.context = MagicMock()
bpy_mock.data = MagicMock()
bpy_mock.app = MagicMock()
# Set up mock attributes that src code needs
bpy_mock.types.Operator = Mock
bpy_mock.types.Panel = Mock
bpy_mock.types.PropertyGroup = Mock
bpy_mock.types.UIList = Mock
bpy_mock.types.Scene = Mock()
bpy_mock.types.NODE_MT_add = Mock()
bpy_mock.types.VIEW3D_MT_add = Mock()
bpy_mock.types.VIEW3D_MT_mesh_add = Mock()
bpy_mock.types.NODE_MT_node = Mock()
bpy_mock.types.IMAGE_MT_image = Mock()
bpy_mock.props.StringProperty = Mock
bpy_mock.props.IntProperty = Mock
bpy_mock.props.FloatVectorProperty = Mock
bpy_mock.props.FloatProperty = Mock
bpy_mock.props.BoolProperty = Mock
bpy_mock.props.EnumProperty = Mock
bpy_mock.props.CollectionProperty = Mock
bpy_mock.props.PointerProperty = Mock
bpy_mock.utils.register_class = Mock()
bpy_mock.utils.unregister_class = Mock()
bpy_mock.app.handlers = Mock()
bpy_mock.app.handlers.load_post = []
bpy_mock.app.handlers.persistent = lambda f: f
bpy_mock.context.window_manager = Mock()
bpy_mock.context.scene = Mock()
# Mock ALL Blender modules at import time
sys.modules['bpy'] = bpy_mock
sys.modules['bmesh'] = MagicMock()
sys.modules['mathutils'] = MagicMock()
sys.modules['bpy.types'] = bpy_mock.types
sys.modules['bpy.props'] = bpy_mock.props
sys.modules['bpy.utils'] = bpy_mock.utils
sys.modules['bpy.ops'] = bpy_mock.ops
sys.modules['bpy.app'] = bpy_mock.app
sys.modules['bpy.context'] = bpy_mock.context
"""
Pytest configuration and fixtures for Text Texture Generator tests.
Provides Blender mocking and common test utilities.
"""
import pytest
from PIL import Image, ImageDraw, ImageFont
class MockBlenderData:
"""Mock for bpy.data"""
def __init__(self):
self.images = MockImageCollection()
class MockImageCollection:
"""Mock for bpy.data.images collection"""
def __init__(self):
self._images = {}
def new(self, name, width, height, alpha=True):
"""Create a new mock Blender image"""
mock_image = MockBlenderImage(name, width, height, alpha)
self._images[name] = mock_image
return mock_image
def remove(self, image):
"""Remove image from collection"""
if hasattr(image, 'name') and image.name in self._images:
del self._images[image.name]
def __contains__(self, name):
return name in self._images
def __getitem__(self, name):
return self._images[name]
class MockBlenderImage:
"""Mock for Blender image objects"""
def __init__(self, name, width, height, alpha=True):
self.name = name
self.width = width
self.height = height
self.alpha = alpha
self.pixels = [0.0] * (width * height * 4) # RGBA pixels
self.packed_file = None
def pack(self):
"""Mock pack method"""
self.packed_file = Mock()
# Attach mock data to bpy_mock
bpy_mock.data = MockBlenderData()
@pytest.fixture(autouse=True)
def mock_bpy_fixture():
"""Provide bpy mock to tests (already mocked at import time)."""
yield bpy_mock
@pytest.fixture
def sample_font():
"""Provide a sample font for testing"""
try:
# Try to use default font
return ImageFont.load_default()
except Exception:
# Create a minimal mock font if default fails
mock_font = Mock()
mock_font.getmetrics.return_value = (10, 2) # ascent, descent
return mock_font
@pytest.fixture
def sample_text_short():
"""Short sample text for testing"""
return "Hello World"
@pytest.fixture
def sample_text_long():
"""Long sample text for testing text wrapping"""
return "This is a much longer piece of text that should wrap across multiple lines when constrained by width limits."
@pytest.fixture
def sample_text_multiline():
"""Multiline sample text for testing"""
return "Line 1\nLine 2\nLine 3\nLine 4"
@pytest.fixture
def standard_dimensions():
"""Standard width/height dimensions for testing"""
return {"width": 512, "height": 512}
@pytest.fixture
def mock_pil_image():
"""Mock PIL Image for testing"""
from unittest.mock import patch
with patch('PIL.Image.new') as mock_new:
mock_img = Mock()
mock_img.size = (512, 512)
mock_img.width = 512
mock_img.height = 512
mock_img.getdata.return_value = [(255, 255, 255, 255)] * (512 * 512)
mock_img.transpose.return_value = mock_img
mock_new.return_value = mock_img
yield mock_img
@pytest.fixture
def mock_image_draw():
"""Mock PIL ImageDraw for testing"""
from unittest.mock import patch
with patch('PIL.ImageDraw.Draw') as mock_draw_class:
mock_draw = Mock()
mock_draw.textbbox.return_value = (0, 0, 100, 20) # x1, y1, x2, y2
mock_draw.text.return_value = None
mock_draw_class.return_value = mock_draw
yield mock_draw
@pytest.fixture
def available_area_small():
"""Small available area for testing"""
return {
'x': 10,
'y': 10,
'width': 100,
'height': 50,
'area': 5000
}
@pytest.fixture
def available_area_large():
"""Large available area for testing"""
return {
'x': 0,
'y': 0,
'width': 500,
'height': 400,
'area': 200000
}
@pytest.fixture
def text_positions_no_overlap():
"""Text positions that don't overlap"""
return [
{'x': 0, 'y': 0, 'width': 100, 'height': 50},
{'x': 150, 'y': 0, 'width': 100, 'height': 50},
{'x': 0, 'y': 100, 'width': 100, 'height': 50}
]
@pytest.fixture
def text_positions_with_overlap():
"""Text positions that overlap"""
return [
{'x': 0, 'y': 0, 'width': 100, 'height': 50},
{'x': 50, 'y': 25, 'width': 100, 'height': 50}, # Overlaps first
{'x': 200, 'y': 0, 'width': 100, 'height': 50}
]
@pytest.fixture
def overlays_list():
"""Sample overlays for area calculation"""
return [
{'x': 50, 'y': 50, 'width': 100, 'height': 100},
{'x': 200, 'y': 200, 'width': 80, 'height': 80}
]
@pytest.fixture
def mock_props():
"""Mock properties object for generation engine tests"""
props = Mock()
props.text = "Test Text"
props.prepend_text = ""
props.append_text = ""
props.background_transparent = False
props.background_color = (1.0, 1.0, 1.0, 1.0) # White
props.text_color = (0.0, 0.0, 0.0, 1.0) # Black
props.font_size = 24
props.font_path = "default"
props.use_custom_font = False
props.custom_font_path = ""
props.enable_text_fitting = False
props.min_font_size = 8
props.max_font_size = 200
props.text_fitting_margin = 10.0
props.prepend_append_layout = 'HORIZONTAL'
props.prepend_margin = 10
props.append_margin = 10
props.generate_normal_map = False
props.normal_map_strength = 1.0
props.normal_map_blur_radius = 0
props.normal_map_invert = False
return props