263 lines
7.1 KiB
Python
263 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():
|
|
"""Sample text containing newlines 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
|