Files
Text-Texture-Generator-for-…/tests/conftest.py
2025-09-13 10:38:28 +03:00

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