260 lines
7.1 KiB
Python
260 lines
7.1 KiB
Python
"""
|
|
Pytest configuration and fixtures for Text Texture Generator tests.
|
|
Provides Blender mocking and common test utilities.
|
|
"""
|
|
|
|
import pytest
|
|
import sys
|
|
from unittest.mock import Mock, MagicMock, patch
|
|
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()
|
|
|
|
|
|
class MockBpy:
|
|
"""Mock for the bpy module"""
|
|
def __init__(self):
|
|
self.data = MockBlenderData()
|
|
|
|
|
|
@pytest.fixture(scope="session", autouse=True)
|
|
def mock_bpy():
|
|
"""Mock the bpy module for all tests"""
|
|
# Mock bpy and related Blender modules BEFORE any imports
|
|
mock_bpy_module = MockBpy()
|
|
mock_bmesh = Mock()
|
|
mock_bpy_types = Mock()
|
|
mock_bpy_props = Mock()
|
|
mock_bpy_utils = Mock()
|
|
mock_bpy_app = Mock()
|
|
mock_bpy_context = Mock()
|
|
|
|
# Set up mock attributes
|
|
mock_bpy_types.Operator = Mock
|
|
mock_bpy_types.Panel = Mock
|
|
mock_bpy_types.PropertyGroup = Mock
|
|
mock_bpy_types.UIList = Mock
|
|
mock_bpy_types.Scene = Mock()
|
|
|
|
mock_bpy_props.StringProperty = Mock
|
|
mock_bpy_props.IntProperty = Mock
|
|
mock_bpy_props.FloatVectorProperty = Mock
|
|
mock_bpy_props.FloatProperty = Mock
|
|
mock_bpy_props.BoolProperty = Mock
|
|
mock_bpy_props.EnumProperty = Mock
|
|
mock_bpy_props.CollectionProperty = Mock
|
|
mock_bpy_props.PointerProperty = Mock
|
|
|
|
mock_bpy_utils.register_class = Mock()
|
|
mock_bpy_utils.unregister_class = Mock()
|
|
|
|
mock_bpy_app.handlers = Mock()
|
|
mock_bpy_app.handlers.load_post = []
|
|
mock_bpy_app.handlers.persistent = lambda f: f
|
|
|
|
mock_bpy_context.window_manager = Mock()
|
|
mock_bpy_context.scene = Mock()
|
|
|
|
mock_bpy_module.types = mock_bpy_types
|
|
mock_bpy_module.props = mock_bpy_props
|
|
mock_bpy_module.utils = mock_bpy_utils
|
|
mock_bpy_module.app = mock_bpy_app
|
|
mock_bpy_module.context = mock_bpy_context
|
|
|
|
mocked_modules = {
|
|
'bpy': mock_bpy_module,
|
|
'bmesh': mock_bmesh,
|
|
'bpy.types': mock_bpy_types,
|
|
'bpy.props': mock_bpy_props,
|
|
'bpy.utils': mock_bpy_utils,
|
|
'bpy.app': mock_bpy_app,
|
|
'bpy.context': mock_bpy_context
|
|
}
|
|
|
|
with patch.dict('sys.modules', mocked_modules):
|
|
yield mock_bpy_module
|
|
|
|
|
|
@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"""
|
|
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"""
|
|
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 |