This commit is contained in:
2025-10-13 11:15:21 +02:00
parent 9a6543c530
commit 2519b55f08
24 changed files with 498 additions and 169 deletions

View File

@@ -48,6 +48,7 @@ def setup_pil_mocks(mock_pil_image_module, mock_pil_imagedraw_module,
# Setup ImageFont mock based on font_type
if font_type == 'default':
mock_font = MagicMock()
mock_pil_imagefont_module.truetype.return_value = mock_font
mock_pil_imagefont_module.load_default.return_value = mock_font
elif font_type == 'custom':
mock_font = MagicMock()
@@ -195,7 +196,47 @@ class TestGenerateTextureImage:
call_args = mock_pil_image_module.new.call_args
assert call_args[0][0] == "RGBA"
assert call_args[0][2] == (0, 0, 0, 0)
def test_default_font_uses_scalable_size(self):
"""Ensure default font configuration respects requested font size."""
with patch('PIL.Image') as mock_pil_image_module, \
patch('PIL.ImageDraw') as mock_pil_imagedraw_module, \
patch('PIL.ImageFont') as mock_pil_imagefont_module, \
patch('src.core.generation_engine.bpy') as mock_bpy, \
patch('src.core.generation_engine.find_default_truetype_font', return_value="/fonts/fallback.ttf", create=True):
setup_pil_mocks(
mock_pil_image_module,
mock_pil_imagedraw_module,
mock_pil_imagefont_module,
DEFAULT_WIDTH,
DEFAULT_HEIGHT,
font_type='default'
)
setup_bpy_mocks(mock_bpy, DEFAULT_WIDTH, DEFAULT_HEIGHT)
fallback_font = MagicMock()
def truetype_side_effect(font_path_arg, size_arg):
if font_path_arg is None:
raise TypeError("file must be a path, not None")
return fallback_font
mock_pil_imagefont_module.truetype.side_effect = truetype_side_effect
mock_pil_imagefont_module.load_default.return_value = MagicMock()
props = create_mock_props(
text="Size Probe",
font_path='default',
use_custom_font=False,
font_size=42
)
generate_texture_image(props, DEFAULT_WIDTH, DEFAULT_HEIGHT)
mock_pil_imagefont_module.truetype.assert_called_with("/fonts/fallback.ttf", 42)
mock_pil_imagefont_module.load_default.assert_not_called()
def test_generate_colored_background(self):
"""Test generation with custom background color."""
with patch('PIL.Image') as mock_pil_image_module, \

View File

@@ -0,0 +1,89 @@
"""Tests for property group update hooks."""
from types import ModuleType, SimpleNamespace
import importlib.util
import pathlib
import sys
import pytest
@pytest.fixture
def property_groups_module():
"""Import property_groups with minimal bpy stubs."""
module_name = "src.properties.property_groups"
original_modules = {
name: sys.modules.get(name)
for name in (module_name, "bpy", "bpy.props", "bpy.types", "src", "src.properties")
}
class TypesStub(SimpleNamespace):
def __getattr__(self, name):
value = type(name, (), {})
setattr(self, name, value)
return value
bpy_stub = SimpleNamespace(
props=SimpleNamespace(
StringProperty=lambda **kwargs: None,
IntProperty=lambda **kwargs: None,
FloatVectorProperty=lambda **kwargs: None,
FloatProperty=lambda **kwargs: None,
BoolProperty=lambda **kwargs: None,
EnumProperty=lambda **kwargs: None,
CollectionProperty=lambda **kwargs: None,
PointerProperty=lambda **kwargs: None,
),
types=TypesStub(PropertyGroup=object),
)
sys.modules["bpy"] = bpy_stub
sys.modules["bpy.props"] = bpy_stub.props
sys.modules["bpy.types"] = bpy_stub.types
src_module = sys.modules.get("src")
if src_module is None:
src_module = ModuleType("src")
src_module.__path__ = [str(pathlib.Path(__file__).resolve().parents[3] / "src")]
sys.modules["src"] = src_module
props_package = sys.modules.get("src.properties")
if props_package is None:
props_package = ModuleType("src.properties")
props_package.__path__ = [str(pathlib.Path(__file__).resolve().parents[3] / "src" / "properties")]
sys.modules["src.properties"] = props_package
spec = importlib.util.spec_from_file_location(
module_name,
pathlib.Path(__file__).resolve().parents[3] / "src" / "properties" / "property_groups.py",
)
module = importlib.util.module_from_spec(spec)
assert spec.loader is not None
sys.modules[module_name] = module
spec.loader.exec_module(module)
try:
yield module
finally:
for name, value in original_modules.items():
if value is None:
sys.modules.pop(name, None)
else:
sys.modules[name] = value
def test_update_live_preview_no_scene_is_noop(property_groups_module):
"""Function should safely return when context has no scene."""
result = property_groups_module.update_live_preview(SimpleNamespace(), SimpleNamespace())
assert result is None
def test_update_live_preview_with_scene_noop(property_groups_module):
"""Even with props present, function should not modify state."""
props = SimpleNamespace(is_updating=False)
context = SimpleNamespace(scene=SimpleNamespace(text_texture_props=props))
result = property_groups_module.update_live_preview(SimpleNamespace(), context)
assert result is None
assert props.is_updating is False

View File

@@ -0,0 +1,45 @@
"""Unit tests for font sizing UI helpers."""
from types import SimpleNamespace
import importlib
import pytest
@pytest.fixture
def font_helpers_module():
"""Import the font helper utilities."""
return importlib.import_module("src.ui.font_helpers")
def test_font_size_ui_state_manual_when_fitting_disabled(monkeypatch, font_helpers_module):
"""Manual font size should remain enabled when fitting is off."""
monkeypatch.setattr(font_helpers_module, "has_text_fitting", lambda: True)
props = SimpleNamespace(enable_text_fitting=False, text_fitting_mode="FIT_CONTAIN")
enabled, message = font_helpers_module.font_size_ui_state(props)
assert enabled is True
assert message == ""
def test_font_size_ui_state_auto_when_fitting_enabled(monkeypatch, font_helpers_module):
"""Font size control shows auto-sizing message when fitting is active."""
monkeypatch.setattr(font_helpers_module, "has_text_fitting", lambda: True)
props = SimpleNamespace(enable_text_fitting=True, text_fitting_mode="FIT_WIDTH")
enabled, message = font_helpers_module.font_size_ui_state(props)
assert enabled is False
assert "Fit Width" in message
def test_font_size_ui_state_without_text_fitting_feature(monkeypatch, font_helpers_module):
"""Without text fitting support, manual font sizing stays enabled."""
monkeypatch.setattr(font_helpers_module, "has_text_fitting", lambda: False)
props = SimpleNamespace(enable_text_fitting=True, text_fitting_mode="FIT_WIDTH")
enabled, message = font_helpers_module.font_size_ui_state(props)
assert enabled is True
assert message == ""

View File

@@ -0,0 +1,77 @@
"""Tests for system font utilities."""
from types import ModuleType
import importlib.util
import pathlib
import sys
import pytest
@pytest.fixture
def system_module():
"""Load the system utilities module without importing the addon __init__."""
module_name = "src.utils.system"
original = sys.modules.pop(module_name, None)
spec = importlib.util.spec_from_file_location(
module_name,
pathlib.Path(__file__).resolve().parents[3] / "src" / "utils" / "system.py",
)
module = importlib.util.module_from_spec(spec)
assert spec.loader is not None
sys.modules[module_name] = module
spec.loader.exec_module(module)
try:
yield module
finally:
if original is None:
sys.modules.pop(module_name, None)
else:
sys.modules[module_name] = original
def test_find_default_truetype_font_skips_fonts_without_latin(monkeypatch, system_module):
"""Ensure selection skips fonts that produce unusable glyph metrics."""
try:
from PIL import ImageFont
except ImportError:
pytest.skip("Pillow not available in test environment")
def fake_system():
return "Darwin"
monkeypatch.setattr(system_module.platform, "system", fake_system)
def fake_exists(path):
return path in {"/bad/font.ttf", "/good/font.ttf"}
monkeypatch.setattr(system_module.os.path, "exists", fake_exists)
monkeypatch.setattr(
system_module,
"get_system_fonts",
lambda: {"Bad": "/bad/font.ttf", "Good": "/good/font.ttf"},
)
class FontStub:
def __init__(self, path):
self.path = path
def getbbox(self, text):
if self.path == "/bad/font.ttf":
if text == "H":
return (0, 10, 40, 50)
return (0, 0, 100, 200)
if text == "H":
return (0, 12, 36, 52)
return (0, 20, 30, 48)
def fake_truetype(path, size):
return FontStub(path)
monkeypatch.setattr(ImageFont, "truetype", fake_truetype)
system_module._DEFAULT_FONT_CACHE = None
selected = system_module.find_default_truetype_font()
assert selected == "/good/font.ttf"