font
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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, \
|
||||
|
||||
89
tests/unit/properties/test_property_group_updates.py
Normal file
89
tests/unit/properties/test_property_group_updates.py
Normal 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
|
||||
45
tests/unit/ui/test_font_ui_helpers.py
Normal file
45
tests/unit/ui/test_font_ui_helpers.py
Normal 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 == ""
|
||||
77
tests/unit/utils/test_system_fonts.py
Normal file
77
tests/unit/utils/test_system_fonts.py
Normal 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"
|
||||
Reference in New Issue
Block a user