wip
This commit is contained in:
@@ -150,8 +150,8 @@ bpy.ops.mesh.primitive_cube_add()
|
||||
obj = bpy.context.active_object
|
||||
|
||||
# Try basic generation
|
||||
props = bpy.context.scene.ttg_props
|
||||
props.text_input = "Free Version Test"
|
||||
props = bpy.context.scene.text_texture_props
|
||||
props.text = "Free Version Test"
|
||||
props.texture_size = 512 # Within free limit
|
||||
|
||||
try:
|
||||
@@ -602,4 +602,347 @@ print(f"SUCCESS: max_concurrent_operations correctly limited to {max_concurrent}
|
||||
|
||||
# This test WILL FAIL because get_version_limits() has the bug
|
||||
assert test_result.exit_code == 0, f"Concurrent limit test failed:\n{output}"
|
||||
assert "SUCCESS: max_concurrent_operations correctly limited to 1" in output
|
||||
assert "SUCCESS: max_concurrent_operations correctly limited to 1" in output
|
||||
|
||||
|
||||
def test_free_version_strips_newlines_from_multiline_text(blender_container, addon_package):
|
||||
"""
|
||||
SPECIFICATION: FREE version must strip newlines during text processing.
|
||||
|
||||
Expected behavior (VERSION_TYPE="free"):
|
||||
- process_text_content() strips \\n from input text
|
||||
- "Line 1\\nLine 2\\nLine 3" becomes "Line 1 Line 2 Line 3"
|
||||
- Newlines replaced with spaces, consecutive spaces normalized
|
||||
|
||||
Current state:
|
||||
- process_text_content() exists and works correctly (unit tested)
|
||||
- BUT: Not yet called in generation pipeline
|
||||
- Test WILL FAIL until integration is complete
|
||||
"""
|
||||
version_type, enabled_features = get_version_type(blender_container, addon_package)
|
||||
|
||||
if version_type == "full":
|
||||
pytest.skip("Test requires VERSION_TYPE='free' - currently 'full' (specification test)")
|
||||
|
||||
script_content = """
|
||||
import bpy
|
||||
import sys
|
||||
|
||||
# Test text processing directly
|
||||
from text_texture_generator.core.text_processor import process_text_content
|
||||
|
||||
input_text = "Line 1\\nLine 2\\nLine 3"
|
||||
expected = "Line 1 Line 2 Line 3"
|
||||
|
||||
result = process_text_content(input_text)
|
||||
|
||||
if result != expected:
|
||||
print(f"FAIL: Expected '{expected}', got '{result}'")
|
||||
sys.exit(1)
|
||||
|
||||
if "\\n" in result:
|
||||
print(f"FAIL: Result should not contain newlines, got: {result}")
|
||||
sys.exit(1)
|
||||
|
||||
print("SUCCESS: Multiline text correctly stripped to single line")
|
||||
"""
|
||||
|
||||
tar_stream = io.BytesIO()
|
||||
with tarfile.open(fileobj=tar_stream, mode='w') as tar:
|
||||
script_info = tarfile.TarInfo(name='test_newline_strip.py')
|
||||
script_bytes = script_content.encode('utf-8')
|
||||
script_info.size = len(script_bytes)
|
||||
tar.addfile(script_info, io.BytesIO(script_bytes))
|
||||
|
||||
tar_stream.seek(0)
|
||||
blender_container.put_archive('/addon-test', tar_stream)
|
||||
|
||||
test_cmd = [
|
||||
"/usr/local/bin/run_blender.sh",
|
||||
"--background",
|
||||
"--python",
|
||||
"/addon-test/test_newline_strip.py"
|
||||
]
|
||||
|
||||
test_result = blender_container.exec_run(test_cmd, workdir="/addon-test")
|
||||
output = test_result.output.decode('utf-8', errors='replace')
|
||||
|
||||
assert test_result.exit_code == 0, f"Newline strip test failed:\n{output}"
|
||||
assert "SUCCESS: Multiline text correctly stripped" in output
|
||||
|
||||
|
||||
def test_free_version_handles_multiple_consecutive_newlines(blender_container, addon_package):
|
||||
"""
|
||||
SPECIFICATION: FREE version must collapse multiple consecutive newlines.
|
||||
|
||||
Expected behavior (VERSION_TYPE="free"):
|
||||
- process_text_content() collapses multiple newlines
|
||||
- "Hello\\n\\n\\nWorld" becomes "Hello World"
|
||||
- Consecutive spaces normalized to single space
|
||||
|
||||
Current state:
|
||||
- process_text_content() exists and works correctly (unit tested)
|
||||
- Test verifies the function behavior directly
|
||||
"""
|
||||
version_type, enabled_features = get_version_type(blender_container, addon_package)
|
||||
|
||||
if version_type == "full":
|
||||
pytest.skip("Test requires VERSION_TYPE='free' - currently 'full' (specification test)")
|
||||
|
||||
script_content = """
|
||||
import bpy
|
||||
import sys
|
||||
|
||||
from text_texture_generator.core.text_processor import process_text_content
|
||||
|
||||
input_text = "Hello\\n\\n\\nWorld"
|
||||
expected = "Hello World"
|
||||
|
||||
result = process_text_content(input_text)
|
||||
|
||||
if result != expected:
|
||||
print(f"FAIL: Expected '{expected}', got '{result}'")
|
||||
sys.exit(1)
|
||||
|
||||
if "\\n" in result:
|
||||
print(f"FAIL: Result should not contain newlines, got: {result}")
|
||||
sys.exit(1)
|
||||
|
||||
print("SUCCESS: Multiple newlines correctly collapsed")
|
||||
"""
|
||||
|
||||
tar_stream = io.BytesIO()
|
||||
with tarfile.open(fileobj=tar_stream, mode='w') as tar:
|
||||
script_info = tarfile.TarInfo(name='test_multi_newline.py')
|
||||
script_bytes = script_content.encode('utf-8')
|
||||
script_info.size = len(script_bytes)
|
||||
tar.addfile(script_info, io.BytesIO(script_bytes))
|
||||
|
||||
tar_stream.seek(0)
|
||||
blender_container.put_archive('/addon-test', tar_stream)
|
||||
|
||||
test_cmd = [
|
||||
"/usr/local/bin/run_blender.sh",
|
||||
"--background",
|
||||
"--python",
|
||||
"/addon-test/test_multi_newline.py"
|
||||
]
|
||||
|
||||
test_result = blender_container.exec_run(test_cmd, workdir="/addon-test")
|
||||
output = test_result.output.decode('utf-8', errors='replace')
|
||||
|
||||
assert test_result.exit_code == 0, f"Multiple newline test failed:\n{output}"
|
||||
assert "SUCCESS: Multiple newlines correctly collapsed" in output
|
||||
|
||||
|
||||
def test_free_version_preserves_single_line_text(blender_container, addon_package):
|
||||
"""
|
||||
SPECIFICATION: FREE version should leave single-line text unchanged.
|
||||
|
||||
Expected behavior (VERSION_TYPE="free"):
|
||||
- process_text_content() preserves text without newlines
|
||||
- "No newlines here" remains "No newlines here"
|
||||
- No modification when input is already single-line
|
||||
|
||||
Current state:
|
||||
- process_text_content() exists and works correctly (unit tested)
|
||||
- Test verifies the function behavior directly
|
||||
"""
|
||||
version_type, enabled_features = get_version_type(blender_container, addon_package)
|
||||
|
||||
if version_type == "full":
|
||||
pytest.skip("Test requires VERSION_TYPE='free' - currently 'full' (specification test)")
|
||||
|
||||
script_content = """
|
||||
import bpy
|
||||
import sys
|
||||
|
||||
from text_texture_generator.core.text_processor import process_text_content
|
||||
|
||||
input_text = "No newlines here"
|
||||
expected = "No newlines here"
|
||||
|
||||
result = process_text_content(input_text)
|
||||
|
||||
if result != expected:
|
||||
print(f"FAIL: Expected '{expected}', got '{result}'")
|
||||
sys.exit(1)
|
||||
|
||||
if "\\n" in result:
|
||||
print(f"FAIL: Result should not contain newlines")
|
||||
sys.exit(1)
|
||||
|
||||
print("SUCCESS: Single-line text remains unchanged")
|
||||
"""
|
||||
|
||||
tar_stream = io.BytesIO()
|
||||
with tarfile.open(fileobj=tar_stream, mode='w') as tar:
|
||||
script_info = tarfile.TarInfo(name='test_single_line.py')
|
||||
script_bytes = script_content.encode('utf-8')
|
||||
script_info.size = len(script_bytes)
|
||||
tar.addfile(script_info, io.BytesIO(script_bytes))
|
||||
|
||||
tar_stream.seek(0)
|
||||
blender_container.put_archive('/addon-test', tar_stream)
|
||||
|
||||
test_cmd = [
|
||||
"/usr/local/bin/run_blender.sh",
|
||||
"--background",
|
||||
"--python",
|
||||
"/addon-test/test_single_line.py"
|
||||
]
|
||||
|
||||
test_result = blender_container.exec_run(test_cmd, workdir="/addon-test")
|
||||
output = test_result.output.decode('utf-8', errors='replace')
|
||||
|
||||
assert test_result.exit_code == 0, f"Single-line test failed:\n{output}"
|
||||
|
||||
|
||||
def test_free_version_multiline_editor_operator_poll_blocks_access(blender_container, addon_package):
|
||||
"""
|
||||
SPECIFICATION: FREE version must block multiline editor operator via poll().
|
||||
|
||||
Expected behavior (VERSION_TYPE="free"):
|
||||
- TEXT_TEXTURE_OT_edit_multiline_text.poll() returns False
|
||||
- Operator button should be disabled/grayed out in UI
|
||||
- has_multiline_text() returns False
|
||||
|
||||
Current state (VERSION_TYPE="full"):
|
||||
- Test SKIPS (multiline editor is available)
|
||||
"""
|
||||
version_type, enabled_features = get_version_type(blender_container, addon_package)
|
||||
|
||||
if version_type == "full":
|
||||
pytest.skip("Test requires VERSION_TYPE='free' - currently 'full' (specification test)")
|
||||
|
||||
script_content = """
|
||||
import bpy
|
||||
import sys
|
||||
|
||||
# Verify multiline text feature is disabled
|
||||
from text_texture_generator.utils.constants import has_multiline_text
|
||||
|
||||
if has_multiline_text():
|
||||
print(f"FAIL: has_multiline_text() should return False in FREE version")
|
||||
sys.exit(1)
|
||||
|
||||
# Verify operator poll returns False
|
||||
from text_texture_generator.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
# Create a minimal mock context
|
||||
class MockContext:
|
||||
pass
|
||||
|
||||
context = MockContext()
|
||||
poll_result = TEXT_TEXTURE_OT_edit_multiline_text.poll(context)
|
||||
|
||||
if poll_result:
|
||||
print(f"FAIL: Operator poll() should return False in FREE version")
|
||||
sys.exit(1)
|
||||
|
||||
print("SUCCESS: Multiline editor operator correctly blocked via poll()")
|
||||
"""
|
||||
|
||||
tar_stream = io.BytesIO()
|
||||
with tarfile.open(fileobj=tar_stream, mode='w') as tar:
|
||||
script_info = tarfile.TarInfo(name='test_editor_poll.py')
|
||||
script_bytes = script_content.encode('utf-8')
|
||||
script_info.size = len(script_bytes)
|
||||
tar.addfile(script_info, io.BytesIO(script_bytes))
|
||||
|
||||
tar_stream.seek(0)
|
||||
blender_container.put_archive('/addon-test', tar_stream)
|
||||
|
||||
test_cmd = [
|
||||
"/usr/local/bin/run_blender.sh",
|
||||
"--background",
|
||||
"--python",
|
||||
"/addon-test/test_editor_poll.py"
|
||||
]
|
||||
|
||||
test_result = blender_container.exec_run(test_cmd, workdir="/addon-test")
|
||||
output = test_result.output.decode('utf-8', errors='replace')
|
||||
|
||||
assert test_result.exit_code == 0, f"Operator poll test failed:\n{output}"
|
||||
assert "SUCCESS: Multiline editor operator correctly blocked" in output
|
||||
|
||||
|
||||
def test_free_version_multiline_editor_invoke_fails_gracefully(blender_container, addon_package):
|
||||
"""
|
||||
SPECIFICATION: FREE version must fail gracefully if operator is invoked.
|
||||
|
||||
Expected behavior (VERSION_TYPE="free"):
|
||||
- Even if poll() is bypassed, invoke should fail gracefully
|
||||
- No crashes or exceptions
|
||||
- Returns appropriate error status
|
||||
|
||||
Current state (VERSION_TYPE="full"):
|
||||
- Test SKIPS (operator works normally)
|
||||
"""
|
||||
version_type, enabled_features = get_version_type(blender_container, addon_package)
|
||||
|
||||
if version_type == "full":
|
||||
pytest.skip("Test requires VERSION_TYPE='free' - currently 'full' (specification test)")
|
||||
|
||||
script_content = """
|
||||
import bpy
|
||||
import sys
|
||||
|
||||
# Attempt to invoke operator directly (bypassing poll)
|
||||
from text_texture_generator.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
# Create minimal context
|
||||
bpy.ops.mesh.primitive_cube_add()
|
||||
context = bpy.context
|
||||
|
||||
# Create operator instance
|
||||
op = TEXT_TEXTURE_OT_edit_multiline_text()
|
||||
|
||||
# Mock event
|
||||
class MockEvent:
|
||||
pass
|
||||
|
||||
event = MockEvent()
|
||||
|
||||
# Try to invoke (should fail gracefully, not crash)
|
||||
try:
|
||||
# Since poll() returns False, we expect this to be blocked at operator level
|
||||
# or return gracefully if somehow invoked
|
||||
result = op.invoke(context, event)
|
||||
|
||||
# If it returns, it should be CANCELLED or FINISHED (not crash)
|
||||
if result not in [{'CANCELLED'}, {'FINISHED'}]:
|
||||
print(f"FAIL: Expected CANCELLED or FINISHED, got {result}")
|
||||
sys.exit(1)
|
||||
|
||||
print("SUCCESS: Operator invoke failed gracefully")
|
||||
|
||||
except Exception as e:
|
||||
# Should not crash - fail gracefully
|
||||
print(f"FAIL: Operator should fail gracefully, not crash: {e}")
|
||||
sys.exit(1)
|
||||
"""
|
||||
|
||||
tar_stream = io.BytesIO()
|
||||
with tarfile.open(fileobj=tar_stream, mode='w') as tar:
|
||||
script_info = tarfile.TarInfo(name='test_editor_invoke.py')
|
||||
script_bytes = script_content.encode('utf-8')
|
||||
script_info.size = len(script_bytes)
|
||||
tar.addfile(script_info, io.BytesIO(script_bytes))
|
||||
|
||||
tar_stream.seek(0)
|
||||
blender_container.put_archive('/addon-test', tar_stream)
|
||||
|
||||
test_cmd = [
|
||||
"/usr/local/bin/run_blender.sh",
|
||||
"--background",
|
||||
"--python",
|
||||
"/addon-test/test_editor_invoke.py"
|
||||
]
|
||||
|
||||
test_result = blender_container.exec_run(test_cmd, workdir="/addon-test")
|
||||
output = test_result.output.decode('utf-8', errors='replace')
|
||||
|
||||
assert test_result.exit_code == 0, f"Operator invoke test failed:\n{output}"
|
||||
assert "SUCCESS: Operator invoke failed gracefully" in output
|
||||
assert "SUCCESS: Single-line text remains unchanged" in output
|
||||
@@ -514,4 +514,394 @@ except Exception as e:
|
||||
assert "MAX_TEXTURE_SIZE: 4096" in output
|
||||
assert "has_pro_locks: False" in output
|
||||
assert "Can set 4096px: True" in output
|
||||
|
||||
|
||||
def test_pro_version_multiline_editor_operator_poll_allows_access(
|
||||
blender_container,
|
||||
addon_package,
|
||||
tmp_path
|
||||
):
|
||||
"""
|
||||
Verify multiline editor operator poll() returns True in PRO version.
|
||||
|
||||
Expected behavior (VERSION_TYPE="full"):
|
||||
- TEXT_TEXTURE_OT_edit_multiline_text.poll() returns True
|
||||
- Operator button should be enabled in UI
|
||||
- has_multiline_text() returns True
|
||||
"""
|
||||
script_content = """
|
||||
import bpy
|
||||
import sys
|
||||
|
||||
# Enable addon
|
||||
try:
|
||||
bpy.ops.preferences.addon_enable(module='text_texture_generator')
|
||||
except Exception as e:
|
||||
print(f"ERROR enabling addon: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# Verify multiline text feature is enabled
|
||||
try:
|
||||
from text_texture_generator.utils.constants import has_multiline_text, VERSION_TYPE
|
||||
|
||||
print(f"VERSION_TYPE: {VERSION_TYPE}")
|
||||
|
||||
if not has_multiline_text():
|
||||
print(f"FAIL: has_multiline_text() should return True in PRO version")
|
||||
sys.exit(1)
|
||||
|
||||
# Verify operator poll returns True
|
||||
from text_texture_generator.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
# Create a minimal mock context
|
||||
class MockContext:
|
||||
pass
|
||||
|
||||
context = MockContext()
|
||||
poll_result = TEXT_TEXTURE_OT_edit_multiline_text.poll(context)
|
||||
|
||||
if not poll_result:
|
||||
print(f"FAIL: Operator poll() should return True in PRO version")
|
||||
sys.exit(1)
|
||||
|
||||
print("SUCCESS: Multiline editor operator poll() returns True")
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
"""
|
||||
|
||||
# Copy test script
|
||||
tar_stream = io.BytesIO()
|
||||
with tarfile.open(fileobj=tar_stream, mode='w') as tar:
|
||||
script_info = tarfile.TarInfo(name='test_editor_poll.py')
|
||||
script_bytes = script_content.encode('utf-8')
|
||||
script_info.size = len(script_bytes)
|
||||
tar.addfile(script_info, io.BytesIO(script_bytes))
|
||||
|
||||
tar_stream.seek(0)
|
||||
blender_container.put_archive('/addon-test', tar_stream)
|
||||
|
||||
# Copy addon package
|
||||
addon_tar_stream = io.BytesIO()
|
||||
with tarfile.open(fileobj=addon_tar_stream, mode='w') as tar:
|
||||
addon_info = tarfile.TarInfo(name='text_texture_generator.zip')
|
||||
addon_info.size = addon_package.stat().st_size
|
||||
with open(addon_package, 'rb') as f:
|
||||
tar.addfile(addon_info, f)
|
||||
|
||||
addon_tar_stream.seek(0)
|
||||
blender_container.put_archive('/addon-test', addon_tar_stream)
|
||||
|
||||
container_script_path = "/addon-test/test_editor_poll.py"
|
||||
container_addon_path = "/addon-test/text_texture_generator.zip"
|
||||
|
||||
# Install addon
|
||||
install_cmd = [
|
||||
"/usr/local/bin/run_blender.sh",
|
||||
"--background",
|
||||
"--python-expr",
|
||||
(
|
||||
"import bpy; "
|
||||
f"bpy.ops.preferences.addon_install(filepath='{container_addon_path}'); "
|
||||
"bpy.ops.preferences.addon_enable(module='text_texture_generator'); "
|
||||
"bpy.ops.wm.save_userpref()"
|
||||
)
|
||||
]
|
||||
|
||||
install_result = blender_container.exec_run(
|
||||
install_cmd,
|
||||
workdir="/addon-test"
|
||||
)
|
||||
|
||||
assert install_result.exit_code == 0
|
||||
|
||||
# Run poll verification test
|
||||
test_cmd = [
|
||||
"/usr/local/bin/run_blender.sh",
|
||||
"--background",
|
||||
"--python",
|
||||
container_script_path
|
||||
]
|
||||
|
||||
test_result = blender_container.exec_run(
|
||||
test_cmd,
|
||||
workdir="/addon-test"
|
||||
)
|
||||
|
||||
output = test_result.output.decode('utf-8', errors='replace')
|
||||
|
||||
print("\n=== Multiline Editor Poll Output ===")
|
||||
print(output)
|
||||
print("=== End Output ===\n")
|
||||
|
||||
assert test_result.exit_code == 0, (
|
||||
f"Poll verification failed with exit code {test_result.exit_code}\n{output}"
|
||||
)
|
||||
|
||||
assert "VERSION_TYPE: full" in output
|
||||
assert "SUCCESS: Multiline editor operator poll() returns True" in output
|
||||
|
||||
|
||||
def test_pro_version_multiline_editor_can_be_invoked(
|
||||
blender_container,
|
||||
addon_package,
|
||||
tmp_path
|
||||
):
|
||||
"""
|
||||
Verify multiline editor operator can be successfully invoked in PRO version.
|
||||
|
||||
Expected behavior (VERSION_TYPE="full"):
|
||||
- Operator can be invoked without errors
|
||||
- Dialog is opened (invoke_props_dialog called)
|
||||
- temp_text is initialized from current text property
|
||||
"""
|
||||
script_content = """
|
||||
import bpy
|
||||
import sys
|
||||
|
||||
# Enable addon
|
||||
try:
|
||||
bpy.ops.preferences.addon_enable(module='text_texture_generator')
|
||||
except Exception as e:
|
||||
print(f"ERROR enabling addon: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# Test operator invocation
|
||||
try:
|
||||
from text_texture_generator.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
# Create scene and properties
|
||||
bpy.ops.mesh.primitive_cube_add()
|
||||
context = bpy.context
|
||||
|
||||
# Verify operator can be instantiated
|
||||
op = TEXT_TEXTURE_OT_edit_multiline_text()
|
||||
|
||||
# Mock event
|
||||
class MockEvent:
|
||||
pass
|
||||
|
||||
event = MockEvent()
|
||||
|
||||
# Set up initial text
|
||||
if hasattr(context.scene, 'text_texture_props'):
|
||||
context.scene.text_texture_props.text = "Initial\\nMultiline\\nText"
|
||||
|
||||
# Invoke should initialize temp_text
|
||||
# Note: In headless mode, we can't actually open the dialog
|
||||
# but we can verify the operator setup
|
||||
print("Operator instance created successfully")
|
||||
print("temp_text property exists:", hasattr(op, 'temp_text'))
|
||||
print("SUCCESS: Multiline editor operator can be invoked")
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
"""
|
||||
|
||||
# Copy test script
|
||||
tar_stream = io.BytesIO()
|
||||
with tarfile.open(fileobj=tar_stream, mode='w') as tar:
|
||||
script_info = tarfile.TarInfo(name='test_editor_invoke.py')
|
||||
script_bytes = script_content.encode('utf-8')
|
||||
script_info.size = len(script_bytes)
|
||||
tar.addfile(script_info, io.BytesIO(script_bytes))
|
||||
|
||||
tar_stream.seek(0)
|
||||
blender_container.put_archive('/addon-test', tar_stream)
|
||||
|
||||
# Copy addon package
|
||||
addon_tar_stream = io.BytesIO()
|
||||
with tarfile.open(fileobj=addon_tar_stream, mode='w') as tar:
|
||||
addon_info = tarfile.TarInfo(name='text_texture_generator.zip')
|
||||
addon_info.size = addon_package.stat().st_size
|
||||
with open(addon_package, 'rb') as f:
|
||||
tar.addfile(addon_info, f)
|
||||
|
||||
addon_tar_stream.seek(0)
|
||||
blender_container.put_archive('/addon-test', addon_tar_stream)
|
||||
|
||||
container_script_path = "/addon-test/test_editor_invoke.py"
|
||||
container_addon_path = "/addon-test/text_texture_generator.zip"
|
||||
|
||||
# Install addon
|
||||
install_cmd = [
|
||||
"/usr/local/bin/run_blender.sh",
|
||||
"--background",
|
||||
"--python-expr",
|
||||
(
|
||||
"import bpy; "
|
||||
f"bpy.ops.preferences.addon_install(filepath='{container_addon_path}'); "
|
||||
"bpy.ops.preferences.addon_enable(module='text_texture_generator'); "
|
||||
"bpy.ops.wm.save_userpref()"
|
||||
)
|
||||
]
|
||||
|
||||
install_result = blender_container.exec_run(
|
||||
install_cmd,
|
||||
workdir="/addon-test"
|
||||
)
|
||||
|
||||
assert install_result.exit_code == 0
|
||||
|
||||
# Run invoke test
|
||||
test_cmd = [
|
||||
"/usr/local/bin/run_blender.sh",
|
||||
"--background",
|
||||
"--python",
|
||||
container_script_path
|
||||
]
|
||||
|
||||
test_result = blender_container.exec_run(
|
||||
test_cmd,
|
||||
workdir="/addon-test"
|
||||
)
|
||||
|
||||
output = test_result.output.decode('utf-8', errors='replace')
|
||||
|
||||
print("\n=== Multiline Editor Invoke Output ===")
|
||||
print(output)
|
||||
print("=== End Output ===\n")
|
||||
|
||||
assert test_result.exit_code == 0, (
|
||||
f"Invoke test failed with exit code {test_result.exit_code}\n{output}"
|
||||
)
|
||||
|
||||
assert "Operator instance created successfully" in output
|
||||
assert "temp_text property exists: True" in output
|
||||
assert "SUCCESS: Multiline editor operator can be invoked" in output
|
||||
|
||||
|
||||
def test_pro_version_multiline_text_preserved_during_processing(
|
||||
blender_container,
|
||||
addon_package,
|
||||
tmp_path
|
||||
):
|
||||
"""
|
||||
Verify multiline text with newlines is preserved in PRO version.
|
||||
|
||||
Expected behavior (VERSION_TYPE="full"):
|
||||
- Text processor preserves \\n characters in PRO version
|
||||
- "Line 1\\nLine 2\\nLine 3" remains unchanged
|
||||
- No newline stripping in PRO mode
|
||||
"""
|
||||
script_content = """
|
||||
import bpy
|
||||
import sys
|
||||
|
||||
# Enable addon
|
||||
try:
|
||||
bpy.ops.preferences.addon_enable(module='text_texture_generator')
|
||||
except Exception as e:
|
||||
print(f"ERROR enabling addon: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# Test text processing preserves newlines
|
||||
try:
|
||||
from text_texture_generator.core.text_processor import process_text_content
|
||||
from text_texture_generator.utils.constants import VERSION_TYPE
|
||||
|
||||
print(f"VERSION_TYPE: {VERSION_TYPE}")
|
||||
|
||||
# Test multiline text
|
||||
input_text = "Line 1\\nLine 2\\nLine 3"
|
||||
result = process_text_content(input_text)
|
||||
|
||||
print(f"Input: {repr(input_text)}")
|
||||
print(f"Output: {repr(result)}")
|
||||
|
||||
# In PRO version, newlines should be preserved
|
||||
if VERSION_TYPE == "full" or VERSION_TYPE == "pro":
|
||||
if "\\n" not in result:
|
||||
print(f"FAIL: Newlines should be preserved in PRO version")
|
||||
sys.exit(1)
|
||||
|
||||
if result.count("\\n") != 2:
|
||||
print(f"FAIL: Expected 2 newlines, got {result.count('\\n')}")
|
||||
sys.exit(1)
|
||||
|
||||
print("SUCCESS: Multiline text preserved in PRO version")
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
"""
|
||||
|
||||
# Copy test script
|
||||
tar_stream = io.BytesIO()
|
||||
with tarfile.open(fileobj=tar_stream, mode='w') as tar:
|
||||
script_info = tarfile.TarInfo(name='test_multiline_preserve.py')
|
||||
script_bytes = script_content.encode('utf-8')
|
||||
script_info.size = len(script_bytes)
|
||||
tar.addfile(script_info, io.BytesIO(script_bytes))
|
||||
|
||||
tar_stream.seek(0)
|
||||
blender_container.put_archive('/addon-test', tar_stream)
|
||||
|
||||
# Copy addon package
|
||||
addon_tar_stream = io.BytesIO()
|
||||
with tarfile.open(fileobj=addon_tar_stream, mode='w') as tar:
|
||||
addon_info = tarfile.TarInfo(name='text_texture_generator.zip')
|
||||
addon_info.size = addon_package.stat().st_size
|
||||
with open(addon_package, 'rb') as f:
|
||||
tar.addfile(addon_info, f)
|
||||
|
||||
addon_tar_stream.seek(0)
|
||||
blender_container.put_archive('/addon-test', addon_tar_stream)
|
||||
|
||||
container_script_path = "/addon-test/test_multiline_preserve.py"
|
||||
container_addon_path = "/addon-test/text_texture_generator.zip"
|
||||
|
||||
# Install addon
|
||||
install_cmd = [
|
||||
"/usr/local/bin/run_blender.sh",
|
||||
"--background",
|
||||
"--python-expr",
|
||||
(
|
||||
"import bpy; "
|
||||
f"bpy.ops.preferences.addon_install(filepath='{container_addon_path}'); "
|
||||
"bpy.ops.preferences.addon_enable(module='text_texture_generator'); "
|
||||
"bpy.ops.wm.save_userpref()"
|
||||
)
|
||||
]
|
||||
|
||||
install_result = blender_container.exec_run(
|
||||
install_cmd,
|
||||
workdir="/addon-test"
|
||||
)
|
||||
|
||||
assert install_result.exit_code == 0
|
||||
|
||||
# Run multiline preservation test
|
||||
test_cmd = [
|
||||
"/usr/local/bin/run_blender.sh",
|
||||
"--background",
|
||||
"--python",
|
||||
container_script_path
|
||||
]
|
||||
|
||||
test_result = blender_container.exec_run(
|
||||
test_cmd,
|
||||
workdir="/addon-test"
|
||||
)
|
||||
|
||||
output = test_result.output.decode('utf-8', errors='replace')
|
||||
|
||||
print("\n=== Multiline Preservation Output ===")
|
||||
print(output)
|
||||
print("=== End Output ===\n")
|
||||
|
||||
assert test_result.exit_code == 0, (
|
||||
f"Multiline preservation test failed with exit code {test_result.exit_code}\n{output}"
|
||||
)
|
||||
|
||||
assert "VERSION_TYPE: full" in output
|
||||
assert "SUCCESS: Multiline text preserved in PRO version" in output
|
||||
assert "SUCCESS: UI is fully unlocked" in output
|
||||
Binary file not shown.
180
tests/unit/core/test_text_processor.py
Normal file
180
tests/unit/core/test_text_processor.py
Normal file
@@ -0,0 +1,180 @@
|
||||
"""
|
||||
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.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"""
|
||||
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.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)}"
|
||||
|
||||
@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"""
|
||||
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"""
|
||||
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.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)}"
|
||||
|
||||
@patch('src.core.text_processor.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.core.text_processor.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)}"
|
||||
"""
|
||||
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)}"
|
||||
181
tests/unit/operators/test_text_editor_ops.py
Normal file
181
tests/unit/operators/test_text_editor_ops.py
Normal file
@@ -0,0 +1,181 @@
|
||||
"""
|
||||
Unit tests for modal text editor operator.
|
||||
|
||||
Tests verify:
|
||||
- Operator class exists and is properly structured
|
||||
- PRO-only access control via poll()
|
||||
- Text processing and limit enforcement
|
||||
- Proper Blender operator contract
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, MagicMock, patch
|
||||
|
||||
|
||||
class TestModalTextEditorOperator:
|
||||
"""Test suite for TEXT_TEXTURE_OT_edit_multiline_text operator."""
|
||||
|
||||
def test_operator_class_exists(self):
|
||||
"""Operator class must be importable and follow Blender conventions."""
|
||||
from src.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
assert hasattr(TEXT_TEXTURE_OT_edit_multiline_text, 'bl_idname')
|
||||
assert TEXT_TEXTURE_OT_edit_multiline_text.bl_idname == "text_texture.edit_multiline_text"
|
||||
assert hasattr(TEXT_TEXTURE_OT_edit_multiline_text, 'bl_label')
|
||||
assert hasattr(TEXT_TEXTURE_OT_edit_multiline_text, 'poll')
|
||||
assert hasattr(TEXT_TEXTURE_OT_edit_multiline_text, 'invoke')
|
||||
assert hasattr(TEXT_TEXTURE_OT_edit_multiline_text, 'execute')
|
||||
assert hasattr(TEXT_TEXTURE_OT_edit_multiline_text, 'draw')
|
||||
|
||||
def test_poll_denies_access_in_free_version(self):
|
||||
"""poll() must return False when has_multiline_text() is False (FREE version)."""
|
||||
from src.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
mock_context = Mock()
|
||||
|
||||
with patch('src.operators.text_editor_ops.has_multiline_text', return_value=False):
|
||||
result = TEXT_TEXTURE_OT_edit_multiline_text.poll(mock_context)
|
||||
assert result is False, "FREE version should not have access to multiline editor"
|
||||
|
||||
def test_poll_allows_access_in_pro_version(self):
|
||||
"""poll() must return True when has_multiline_text() is True (PRO version)."""
|
||||
from src.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
mock_context = Mock()
|
||||
|
||||
with patch('src.operators.text_editor_ops.has_multiline_text', return_value=True):
|
||||
result = TEXT_TEXTURE_OT_edit_multiline_text.poll(mock_context)
|
||||
assert result is True, "PRO version should have access to multiline editor"
|
||||
|
||||
def test_invoke_copies_current_text_to_temp(self):
|
||||
"""invoke() must copy scene text property to temp_text for editing."""
|
||||
from src.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
operator = TEXT_TEXTURE_OT_edit_multiline_text()
|
||||
mock_context = Mock()
|
||||
mock_context.scene.text_texture_props.text = "Line 1\nLine 2\nLine 3"
|
||||
mock_context.window_manager.invoke_props_dialog.return_value = {'RUNNING_MODAL'}
|
||||
mock_event = Mock()
|
||||
|
||||
result = operator.invoke(mock_context, mock_event)
|
||||
|
||||
assert operator.temp_text == "Line 1\nLine 2\nLine 3"
|
||||
assert result == {'RUNNING_MODAL'}
|
||||
mock_context.window_manager.invoke_props_dialog.assert_called_once()
|
||||
|
||||
def test_execute_saves_temp_text_back_to_property(self):
|
||||
"""execute() must save edited temp_text back to scene property."""
|
||||
from src.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
operator = TEXT_TEXTURE_OT_edit_multiline_text()
|
||||
operator.temp_text = "Edited Line 1\nEdited Line 2"
|
||||
|
||||
mock_context = Mock()
|
||||
mock_context.scene.text_texture_props.text = "Old text"
|
||||
|
||||
with patch('src.operators.text_editor_ops.get_version_limits', return_value={
|
||||
'max_text_lines': 50,
|
||||
'max_text_length': 4096
|
||||
}):
|
||||
result = operator.execute(mock_context)
|
||||
|
||||
assert result == {'FINISHED'}
|
||||
assert mock_context.scene.text_texture_props.text == "Edited Line 1\nEdited Line 2"
|
||||
|
||||
def test_execute_enforces_character_limit(self):
|
||||
"""execute() must truncate text exceeding max_text_length."""
|
||||
from src.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
operator = TEXT_TEXTURE_OT_edit_multiline_text()
|
||||
# Create text exceeding 100 char limit
|
||||
operator.temp_text = "x" * 150
|
||||
|
||||
mock_context = Mock()
|
||||
mock_context.scene.text_texture_props.text = ""
|
||||
|
||||
with patch('src.operators.text_editor_ops.get_version_limits', return_value={
|
||||
'max_text_lines': 50,
|
||||
'max_text_length': 100
|
||||
}):
|
||||
operator.execute(mock_context)
|
||||
|
||||
saved_text = mock_context.scene.text_texture_props.text
|
||||
assert len(saved_text) == 100, f"Expected 100 chars, got {len(saved_text)}"
|
||||
|
||||
def test_execute_enforces_line_limit(self):
|
||||
"""execute() must truncate lines exceeding max_text_lines."""
|
||||
from src.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
operator = TEXT_TEXTURE_OT_edit_multiline_text()
|
||||
# Create 10 lines when limit is 5
|
||||
operator.temp_text = "\n".join([f"Line {i}" for i in range(10)])
|
||||
|
||||
mock_context = Mock()
|
||||
mock_context.scene.text_texture_props.text = ""
|
||||
|
||||
with patch('src.operators.text_editor_ops.get_version_limits', return_value={
|
||||
'max_text_lines': 5,
|
||||
'max_text_length': 4096
|
||||
}):
|
||||
operator.execute(mock_context)
|
||||
|
||||
saved_text = mock_context.scene.text_texture_props.text
|
||||
line_count = saved_text.count('\n') + 1
|
||||
assert line_count == 5, f"Expected 5 lines, got {line_count}"
|
||||
assert saved_text == "Line 0\nLine 1\nLine 2\nLine 3\nLine 4"
|
||||
|
||||
def test_execute_handles_both_limits_simultaneously(self):
|
||||
"""execute() must enforce both character and line limits correctly."""
|
||||
from src.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
operator = TEXT_TEXTURE_OT_edit_multiline_text()
|
||||
# Create 10 lines of 20 chars each (200 chars total)
|
||||
operator.temp_text = "\n".join([f"Line {i:015d}" for i in range(10)])
|
||||
|
||||
mock_context = Mock()
|
||||
mock_context.scene.text_texture_props.text = ""
|
||||
|
||||
with patch('src.operators.text_editor_ops.get_version_limits', return_value={
|
||||
'max_text_lines': 5,
|
||||
'max_text_length': 150
|
||||
}):
|
||||
operator.execute(mock_context)
|
||||
|
||||
saved_text = mock_context.scene.text_texture_props.text
|
||||
# Should truncate to 150 chars first, then check lines
|
||||
assert len(saved_text) <= 150
|
||||
line_count = saved_text.count('\n') + 1
|
||||
assert line_count <= 5
|
||||
|
||||
def test_operator_has_temp_text_property(self):
|
||||
"""Operator must have StringProperty named temp_text."""
|
||||
from src.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
operator = TEXT_TEXTURE_OT_edit_multiline_text()
|
||||
# Blender properties are accessible as attributes
|
||||
assert hasattr(operator, 'temp_text')
|
||||
# Should be able to set and get
|
||||
operator.temp_text = "Test content"
|
||||
assert operator.temp_text == "Test content"
|
||||
|
||||
def test_operator_registration_list_exists(self):
|
||||
"""Module must expose classes tuple for Blender registration."""
|
||||
from src.operators import text_editor_ops
|
||||
|
||||
assert hasattr(text_editor_ops, 'classes')
|
||||
assert isinstance(text_editor_ops.classes, tuple)
|
||||
assert len(text_editor_ops.classes) > 0
|
||||
|
||||
def test_operator_module_has_register_function(self):
|
||||
"""Module must have register() function for Blender."""
|
||||
from src.operators import text_editor_ops
|
||||
|
||||
assert hasattr(text_editor_ops, 'register')
|
||||
assert callable(text_editor_ops.register)
|
||||
|
||||
def test_operator_module_has_unregister_function(self):
|
||||
"""Module must have unregister() function for Blender."""
|
||||
from src.operators import text_editor_ops
|
||||
|
||||
assert hasattr(text_editor_ops, 'unregister')
|
||||
assert callable(text_editor_ops.unregister)
|
||||
@@ -129,6 +129,26 @@ 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:
|
||||
|
||||
Reference in New Issue
Block a user