remove multiline
This commit is contained in:
@@ -1,180 +1,47 @@
|
||||
"""
|
||||
Unit tests for version-aware text processing behavior.
|
||||
Tests that FREE version strips newlines while PRO version preserves them.
|
||||
Unit tests for text content normalization.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import patch
|
||||
|
||||
class TestProcessTextContent:
|
||||
"""Verify newline and whitespace handling for text processing."""
|
||||
|
||||
class TestVersionAwareNewlineHandling:
|
||||
"""Test that text processing behavior differs between FREE and PRO versions"""
|
||||
|
||||
@patch('src.core.text_processor.VERSION_TYPE', 'free')
|
||||
def test_process_text_strips_newlines_in_free_version(self):
|
||||
"""FREE version should strip newlines and replace with spaces"""
|
||||
def test_collapses_single_newlines(self):
|
||||
from src.core.text_processor import process_text_content
|
||||
|
||||
text = "Line 1\nLine 2\nLine 3"
|
||||
|
||||
text = "Line 1\nLine 2"
|
||||
result = process_text_content(text)
|
||||
|
||||
assert result == "Line 1 Line 2 Line 3", \
|
||||
f"FREE version must strip newlines, got: {repr(result)}"
|
||||
|
||||
@patch('src.core.text_processor.VERSION_TYPE', 'full')
|
||||
def test_process_text_preserves_newlines_in_pro_version(self):
|
||||
"""PRO version should preserve newlines for multiline rendering"""
|
||||
from src.core.text_processor import process_text_content
|
||||
|
||||
text = "Line 1\nLine 2\nLine 3"
|
||||
result = process_text_content(text)
|
||||
|
||||
assert result == "Line 1\nLine 2\nLine 3", \
|
||||
f"PRO version must preserve newlines, got: {repr(result)}"
|
||||
assert result == "Line 1 Line 2"
|
||||
|
||||
@patch('src.core.text_processor.VERSION_TYPE', 'free')
|
||||
def test_process_text_strips_multiple_consecutive_newlines_in_free(self):
|
||||
"""FREE version should collapse multiple newlines to single space"""
|
||||
def test_collapses_multiple_newlines(self):
|
||||
from src.core.text_processor import process_text_content
|
||||
|
||||
|
||||
text = "Line 1\n\n\nLine 2"
|
||||
result = process_text_content(text)
|
||||
|
||||
assert result == "Line 1 Line 2", \
|
||||
f"FREE version must collapse multiple newlines, got: {repr(result)}"
|
||||
|
||||
@patch('src.core.text_processor.VERSION_TYPE', 'free')
|
||||
def test_process_text_handles_mixed_whitespace_in_free(self):
|
||||
"""FREE version should normalize mixed whitespace with newlines"""
|
||||
assert result == "Line 1 Line 2"
|
||||
|
||||
def test_normalizes_mixed_whitespace(self):
|
||||
from src.core.text_processor import process_text_content
|
||||
|
||||
text = "Line 1\n \nLine 2"
|
||||
|
||||
text = "Line 1\n \t\nLine 2"
|
||||
result = process_text_content(text)
|
||||
|
||||
# Should have no newlines and normalized spacing
|
||||
assert "\n" not in result, \
|
||||
f"FREE version must remove all newlines, got: {repr(result)}"
|
||||
assert "Line 1" in result and "Line 2" in result, \
|
||||
f"Must preserve actual text content, got: {repr(result)}"
|
||||
|
||||
@patch('src.core.text_processor.VERSION_TYPE', 'full')
|
||||
def test_process_text_preserves_multiple_newlines_in_pro(self):
|
||||
"""PRO version should preserve multiple consecutive newlines"""
|
||||
from src.core.text_processor import process_text_content
|
||||
|
||||
text = "Line 1\n\n\nLine 2"
|
||||
result = process_text_content(text)
|
||||
|
||||
assert result == "Line 1\n\n\nLine 2", \
|
||||
f"PRO version must preserve all newlines, got: {repr(result)}"
|
||||
assert "\n" not in result
|
||||
assert result == "Line 1 Line 2"
|
||||
|
||||
@patch('src.core.text_processor.VERSION_TYPE', 'free')
|
||||
def test_process_empty_text_in_free(self):
|
||||
"""FREE version should handle empty text gracefully"""
|
||||
def test_handles_empty_string(self):
|
||||
from src.core.text_processor import process_text_content
|
||||
|
||||
|
||||
result = process_text_content("")
|
||||
|
||||
assert result == "", \
|
||||
f"Empty text should remain empty, got: {repr(result)}"
|
||||
|
||||
@patch('src.core.text_processor.VERSION_TYPE', 'free')
|
||||
def test_process_text_without_newlines_in_free(self):
|
||||
"""FREE version should pass through text without newlines unchanged"""
|
||||
assert result == ""
|
||||
|
||||
def test_preserves_text_without_newlines(self):
|
||||
from src.core.text_processor import process_text_content
|
||||
|
||||
|
||||
text = "Single line text"
|
||||
result = process_text_content(text)
|
||||
|
||||
assert result == "Single line text", \
|
||||
f"Text without newlines should be unchanged, got: {repr(result)}"
|
||||
"""
|
||||
Unit tests for version-aware text processing behavior.
|
||||
Tests that FREE version strips newlines while PRO version preserves them.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
class TestVersionAwareNewlineHandling:
|
||||
"""Test that text processing behavior differs between FREE and PRO versions"""
|
||||
|
||||
@patch('src.utils.constants.VERSION_TYPE', 'free')
|
||||
def test_process_text_strips_newlines_in_free_version(self):
|
||||
"""FREE version should strip newlines and replace with spaces"""
|
||||
from src.core.text_processor import process_text_content
|
||||
|
||||
text = "Line 1\nLine 2\nLine 3"
|
||||
result = process_text_content(text)
|
||||
|
||||
assert result == "Line 1 Line 2 Line 3", \
|
||||
f"FREE version must strip newlines, got: {repr(result)}"
|
||||
|
||||
@patch('src.utils.constants.VERSION_TYPE', 'full')
|
||||
def test_process_text_preserves_newlines_in_pro_version(self):
|
||||
"""PRO version should preserve newlines for multiline rendering"""
|
||||
from src.core.text_processor import process_text_content
|
||||
|
||||
text = "Line 1\nLine 2\nLine 3"
|
||||
result = process_text_content(text)
|
||||
|
||||
assert result == "Line 1\nLine 2\nLine 3", \
|
||||
f"PRO version must preserve newlines, got: {repr(result)}"
|
||||
|
||||
@patch('src.utils.constants.VERSION_TYPE', 'free')
|
||||
def test_process_text_strips_multiple_consecutive_newlines_in_free(self):
|
||||
"""FREE version should collapse multiple newlines to single space"""
|
||||
from src.core.text_processor import process_text_content
|
||||
|
||||
text = "Line 1\n\n\nLine 2"
|
||||
result = process_text_content(text)
|
||||
|
||||
assert result == "Line 1 Line 2", \
|
||||
f"FREE version must collapse multiple newlines, got: {repr(result)}"
|
||||
|
||||
@patch('src.utils.constants.VERSION_TYPE', 'free')
|
||||
def test_process_text_handles_mixed_whitespace_in_free(self):
|
||||
"""FREE version should normalize mixed whitespace with newlines"""
|
||||
from src.core.text_processor import process_text_content
|
||||
|
||||
text = "Line 1\n \nLine 2"
|
||||
result = process_text_content(text)
|
||||
|
||||
# Should have no newlines and normalized spacing
|
||||
assert "\n" not in result, \
|
||||
f"FREE version must remove all newlines, got: {repr(result)}"
|
||||
assert "Line 1" in result and "Line 2" in result, \
|
||||
f"Must preserve actual text content, got: {repr(result)}"
|
||||
|
||||
@patch('src.utils.constants.VERSION_TYPE', 'full')
|
||||
def test_process_text_preserves_multiple_newlines_in_pro(self):
|
||||
"""PRO version should preserve multiple consecutive newlines"""
|
||||
from src.core.text_processor import process_text_content
|
||||
|
||||
text = "Line 1\n\n\nLine 2"
|
||||
result = process_text_content(text)
|
||||
|
||||
assert result == "Line 1\n\n\nLine 2", \
|
||||
f"PRO version must preserve all newlines, got: {repr(result)}"
|
||||
|
||||
@patch('src.utils.constants.VERSION_TYPE', 'free')
|
||||
def test_process_empty_text_in_free(self):
|
||||
"""FREE version should handle empty text gracefully"""
|
||||
from src.core.text_processor import process_text_content
|
||||
|
||||
result = process_text_content("")
|
||||
|
||||
assert result == "", \
|
||||
f"Empty text should remain empty, got: {repr(result)}"
|
||||
|
||||
@patch('src.utils.constants.VERSION_TYPE', 'free')
|
||||
def test_process_text_without_newlines_in_free(self):
|
||||
"""FREE version should pass through text without newlines unchanged"""
|
||||
from src.core.text_processor import process_text_content
|
||||
|
||||
text = "Single line text"
|
||||
result = process_text_content(text)
|
||||
|
||||
assert result == "Single line text", \
|
||||
f"Text without newlines should be unchanged, got: {repr(result)}"
|
||||
assert result == text
|
||||
|
||||
@@ -28,22 +28,22 @@ class TestModalTextEditorOperator:
|
||||
assert hasattr(TEXT_TEXTURE_OT_edit_text, 'draw')
|
||||
|
||||
def test_poll_denies_access_in_free_version(self):
|
||||
"""poll() must return False when has_text_editor() is False (FREE version)."""
|
||||
"""poll() must return False when running in the free version."""
|
||||
from src.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_text
|
||||
|
||||
mock_context = Mock()
|
||||
|
||||
with patch('src.operators.text_editor_ops.has_text_editor', return_value=False):
|
||||
with patch('src.operators.text_editor_ops.is_full_version', return_value=False):
|
||||
result = TEXT_TEXTURE_OT_edit_text.poll(mock_context)
|
||||
assert result is False, "FREE version should not have access to text editor"
|
||||
|
||||
def test_poll_allows_access_in_pro_version(self):
|
||||
"""poll() must return True when has_text_editor() is True (PRO version)."""
|
||||
"""poll() must return True when running in the pro version."""
|
||||
from src.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_text
|
||||
|
||||
mock_context = Mock()
|
||||
|
||||
with patch('src.operators.text_editor_ops.has_text_editor', return_value=True):
|
||||
with patch('src.operators.text_editor_ops.is_full_version', return_value=True):
|
||||
result = TEXT_TEXTURE_OT_edit_text.poll(mock_context)
|
||||
assert result is True, "PRO version should have access to text editor"
|
||||
|
||||
@@ -125,4 +125,4 @@ class TestModalTextEditorOperator:
|
||||
from src.operators import text_editor_ops
|
||||
|
||||
assert hasattr(text_editor_ops, 'unregister')
|
||||
assert callable(text_editor_ops.unregister)
|
||||
assert callable(text_editor_ops.unregister)
|
||||
|
||||
@@ -9,28 +9,16 @@ src_path = Path(__file__).parent.parent.parent / "src"
|
||||
sys.path.insert(0, str(src_path))
|
||||
|
||||
|
||||
def test_has_multiline_text_function_complete():
|
||||
"""Test that has_multiline_text() is a complete function with body."""
|
||||
from utils.constants import has_multiline_text
|
||||
|
||||
# Should be callable
|
||||
assert callable(has_multiline_text)
|
||||
|
||||
# Should return a boolean (not crash with incomplete body)
|
||||
result = has_multiline_text()
|
||||
assert isinstance(result, bool)
|
||||
def test_has_multiline_text_not_importable():
|
||||
"""Ensure has_multiline_text was fully removed."""
|
||||
with pytest.raises(ImportError, match="cannot import name 'has_multiline_text'"):
|
||||
from utils.constants import has_multiline_text
|
||||
|
||||
|
||||
def test_has_text_editor_imports_from_constants():
|
||||
"""Test that has_text_editor can be imported from utils.constants."""
|
||||
from utils.constants import has_text_editor
|
||||
|
||||
# Verify it's callable (should be a function)
|
||||
assert callable(has_text_editor)
|
||||
|
||||
# Should return a boolean without crashing
|
||||
result = has_text_editor()
|
||||
assert isinstance(result, bool)
|
||||
def test_has_text_editor_not_importable():
|
||||
"""Ensure has_text_editor helper was removed with the multiline feature."""
|
||||
with pytest.raises(ImportError, match="cannot import name 'has_text_editor'"):
|
||||
from utils.constants import has_text_editor
|
||||
|
||||
|
||||
def test_new_operator_name_imports_from_operators():
|
||||
@@ -45,4 +33,4 @@ def test_new_operator_name_imports_from_operators():
|
||||
def test_old_operator_name_not_importable():
|
||||
"""Test that TEXT_TEXTURE_OT_edit_multiline_text (OLD name) is NOT importable."""
|
||||
with pytest.raises(ImportError, match="cannot import name 'TEXT_TEXTURE_OT_edit_multiline_text'"):
|
||||
from src.operators import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
from src.operators import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
@@ -4,7 +4,7 @@ Tests the core text processing logic in isolation.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
from unittest.mock import Mock, patch
|
||||
import sys
|
||||
import os
|
||||
|
||||
@@ -13,7 +13,6 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../src'))
|
||||
|
||||
from fixtures.sample_data import *
|
||||
from src.core.text_processor import (
|
||||
process_multiline_text,
|
||||
wrap_text_to_width,
|
||||
render_text_with_stroke,
|
||||
calculate_text_metrics,
|
||||
@@ -97,68 +96,6 @@ class TestWrapTextToWidth:
|
||||
assert "SuperLongWordThatCannotFit" in result
|
||||
|
||||
|
||||
class TestProcessMultilineText:
|
||||
"""Test the process_multiline_text function"""
|
||||
|
||||
@pytest.mark.unit
|
||||
@patch('src.core.text_processor.wrap_text_to_width')
|
||||
def test_process_single_line_text(self, mock_wrap, sample_font):
|
||||
"""Test processing single line text"""
|
||||
mock_wrap.return_value = [SHORT_TEXT]
|
||||
|
||||
with patch('PIL.Image.new'), patch('PIL.ImageDraw.Draw') as mock_draw_class:
|
||||
mock_draw = Mock()
|
||||
mock_draw_class.return_value = mock_draw
|
||||
mock_draw.textbbox.return_value = (0, 0, 100, 20)
|
||||
|
||||
result = process_multiline_text(SHORT_TEXT, sample_font, 200, 100)
|
||||
|
||||
assert result['line_count'] == 1
|
||||
assert result['lines'] == [SHORT_TEXT]
|
||||
assert result['total_height'] == 20
|
||||
|
||||
@pytest.mark.unit
|
||||
@patch('src.core.text_processor.wrap_text_to_width')
|
||||
def test_process_multiline_text_basic(self, mock_wrap, sample_font):
|
||||
"""Test processing actual multiline text"""
|
||||
mock_wrap.side_effect = [['Line 1'], ['Line 2'], ['Line 3'], ['Line 4']]
|
||||
|
||||
with patch('PIL.Image.new'), patch('PIL.ImageDraw.Draw') as mock_draw_class:
|
||||
mock_draw = Mock()
|
||||
mock_draw_class.return_value = mock_draw
|
||||
mock_draw.textbbox.return_value = (0, 0, 80, 20)
|
||||
|
||||
result = process_multiline_text(MULTILINE_TEXT, sample_font, 200, 200)
|
||||
|
||||
assert result['line_count'] == 4
|
||||
assert len(result['lines']) == 4
|
||||
assert result['total_height'] == 80 # 4 lines * 20 height
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_process_empty_text(self, sample_font):
|
||||
"""Test processing empty text"""
|
||||
result = process_multiline_text("", sample_font, 200, 100)
|
||||
|
||||
assert result['line_count'] == 1
|
||||
assert result['lines'] == [""]
|
||||
|
||||
@pytest.mark.unit
|
||||
@patch('src.core.text_processor.wrap_text_to_width')
|
||||
def test_process_height_limit_exceeded(self, mock_wrap, sample_font):
|
||||
"""Test text processing when height limit is exceeded"""
|
||||
mock_wrap.side_effect = [['Line 1'], ['Line 2'], ['Line 3'], ['Line 4']]
|
||||
|
||||
with patch('PIL.Image.new'), patch('PIL.ImageDraw.Draw') as mock_draw_class:
|
||||
mock_draw = Mock()
|
||||
mock_draw_class.return_value = mock_draw
|
||||
mock_draw.textbbox.return_value = (0, 0, 80, 30) # Each line is 30px high
|
||||
|
||||
# With max_height=50, only 1 line should fit
|
||||
result = process_multiline_text(MULTILINE_TEXT, sample_font, 200, 50)
|
||||
|
||||
assert result['line_count'] <= 2 # Should stop when height limit reached
|
||||
assert result['total_height'] <= 60 # Should not exceed much
|
||||
|
||||
|
||||
class TestCalculateTextMetrics:
|
||||
"""Test the calculate_text_metrics function"""
|
||||
@@ -416,4 +353,4 @@ class TestOptimizeTextLayout:
|
||||
"""Test optimization with empty text blocks list"""
|
||||
result = optimize_text_layout([], 500, 400)
|
||||
|
||||
assert result == []
|
||||
assert result == []
|
||||
|
||||
@@ -129,26 +129,6 @@ class TestVersionDetection:
|
||||
patch('src.utils.constants.ENABLED_FEATURES', ['basic', 'preview', 'text_fitting']):
|
||||
assert has_text_fitting() is True
|
||||
|
||||
def test_has_multiline_text_returns_false_in_free_version(self):
|
||||
"""Multiline text is not available in free version."""
|
||||
from src.utils.constants import has_multiline_text
|
||||
from unittest.mock import patch
|
||||
|
||||
# Test free version - need to patch ENABLED_FEATURES since it's computed at import
|
||||
with patch('src.utils.constants.VERSION_TYPE', 'free'), \
|
||||
patch('src.utils.constants.ENABLED_FEATURES', ['basic', 'preview']):
|
||||
assert has_multiline_text() is False
|
||||
|
||||
def test_has_multiline_text_returns_true_in_pro_version(self):
|
||||
"""Multiline text is available in PRO version."""
|
||||
from src.utils.constants import has_multiline_text
|
||||
from unittest.mock import patch
|
||||
|
||||
# Test full version - need to patch ENABLED_FEATURES since it's computed at import
|
||||
with patch('src.utils.constants.VERSION_TYPE', 'full'), \
|
||||
patch('src.utils.constants.ENABLED_FEATURES', ['basic', 'preview', 'multiline_text']):
|
||||
assert has_multiline_text() is True
|
||||
|
||||
|
||||
|
||||
class TestRuntimeImportDetection:
|
||||
@@ -317,4 +297,4 @@ class TestVersionDifferentiationScenarios:
|
||||
# Generous technical limits
|
||||
limits = get_version_limits()
|
||||
assert limits["max_texture_size"] == 4096, "Should have 4096px texture limit"
|
||||
assert limits["max_concurrent_operations"] == 4, "Should have 4 concurrent operations limit"
|
||||
assert limits["max_concurrent_operations"] == 4, "Should have 4 concurrent operations limit"
|
||||
|
||||
Reference in New Issue
Block a user