558 lines
22 KiB
Python
558 lines
22 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Comprehensive test suite for the Text Texture Generator's enhanced positioning system.
|
||
This script validates the positioning calculations and identifies potential issues
|
||
without requiring Blender to be installed.
|
||
"""
|
||
|
||
import sys
|
||
import traceback
|
||
from typing import Dict, Tuple, List, Any
|
||
|
||
# Mock Blender properties for testing
|
||
class MockProps:
|
||
"""Mock properties class to simulate Blender addon properties"""
|
||
def __init__(self):
|
||
# Enhanced positioning properties
|
||
self.anchor_point = 'MIDDLE_CENTER'
|
||
self.position_mode = 'PRESET'
|
||
self.margin_x = 0.0
|
||
self.margin_y = 0.0
|
||
self.margins_linked = False
|
||
self.offset_x = 0
|
||
self.offset_y = 0
|
||
self.manual_position_x = 50.0
|
||
self.manual_position_y = 50.0
|
||
self.manual_position_unit = 'PERCENTAGE'
|
||
self.constrain_to_canvas = True
|
||
|
||
# Legacy properties for backward compatibility testing
|
||
self.text_align = 'center'
|
||
self.padding = 20
|
||
|
||
# Copy of the enhanced positioning function from the addon
|
||
def calculate_text_position(props, canvas_width, canvas_height, text_width, text_height):
|
||
"""Calculate text position based on enhanced positioning properties"""
|
||
|
||
if props.position_mode == 'MANUAL':
|
||
# Manual positioning mode
|
||
if props.manual_position_unit == 'PERCENTAGE':
|
||
x = int((props.manual_position_x / 100.0) * canvas_width)
|
||
y = int((props.manual_position_y / 100.0) * canvas_height)
|
||
else: # PIXELS
|
||
x = int(props.manual_position_x)
|
||
y = int(props.manual_position_y)
|
||
else:
|
||
# Preset anchor-based positioning
|
||
margin_x_px = int((props.margin_x / 100.0) * canvas_width)
|
||
margin_y_px = int((props.margin_y / 100.0) * canvas_height)
|
||
|
||
# Calculate base position based on anchor point
|
||
if 'LEFT' in props.anchor_point:
|
||
base_x = margin_x_px
|
||
elif 'RIGHT' in props.anchor_point:
|
||
base_x = canvas_width - text_width - margin_x_px
|
||
else: # CENTER
|
||
base_x = (canvas_width - text_width) // 2
|
||
|
||
if 'TOP' in props.anchor_point:
|
||
base_y = margin_y_px
|
||
elif 'BOTTOM' in props.anchor_point:
|
||
base_y = canvas_height - text_height - margin_y_px
|
||
else: # MIDDLE
|
||
base_y = (canvas_height - text_height) // 2
|
||
|
||
# Apply fine-tuning offsets
|
||
x = base_x + props.offset_x
|
||
y = base_y + props.offset_y
|
||
|
||
# Apply canvas constraints if enabled
|
||
if props.constrain_to_canvas:
|
||
x = max(0, min(x, canvas_width - text_width))
|
||
y = max(0, min(y, canvas_height - text_height))
|
||
|
||
return x, y
|
||
|
||
def sync_margin_values(props, changed_margin):
|
||
"""Synchronize margin values when linked"""
|
||
if props.margins_linked:
|
||
if changed_margin == 'x':
|
||
props.margin_y = props.margin_x
|
||
elif changed_margin == 'y':
|
||
props.margin_x = props.margin_y
|
||
|
||
def get_anchor_point_matrix():
|
||
"""Return 3x3 matrix of anchor point identifiers for UI layout"""
|
||
return [
|
||
['TOP_LEFT', 'TOP_CENTER', 'TOP_RIGHT'],
|
||
['MIDDLE_LEFT', 'MIDDLE_CENTER', 'MIDDLE_RIGHT'],
|
||
['BOTTOM_LEFT', 'BOTTOM_CENTER', 'BOTTOM_RIGHT']
|
||
]
|
||
|
||
class PositioningTestSuite:
|
||
"""Comprehensive test suite for positioning system"""
|
||
|
||
def __init__(self):
|
||
self.test_results = []
|
||
self.issues_found = []
|
||
self.canvas_width = 1024
|
||
self.canvas_height = 1024
|
||
self.text_width = 200
|
||
self.text_height = 50
|
||
|
||
def log_test(self, test_name: str, passed: bool, details: str = "", expected=None, actual=None):
|
||
"""Log test result"""
|
||
result = {
|
||
'test': test_name,
|
||
'passed': passed,
|
||
'details': details,
|
||
'expected': expected,
|
||
'actual': actual
|
||
}
|
||
self.test_results.append(result)
|
||
|
||
if not passed:
|
||
self.issues_found.append(f"❌ {test_name}: {details}")
|
||
if expected is not None and actual is not None:
|
||
self.issues_found.append(f" Expected: {expected}, Got: {actual}")
|
||
else:
|
||
print(f"✅ {test_name}")
|
||
|
||
def test_anchor_point_calculations(self):
|
||
"""Test all 9 anchor point calculations"""
|
||
print("\n=== Testing Anchor Point Calculations ===")
|
||
|
||
anchor_matrix = get_anchor_point_matrix()
|
||
expected_positions = {
|
||
'TOP_LEFT': (0, 0),
|
||
'TOP_CENTER': (412, 0), # (1024-200)/2 = 412
|
||
'TOP_RIGHT': (824, 0), # 1024-200 = 824
|
||
'MIDDLE_LEFT': (0, 487), # (1024-50)/2 = 487
|
||
'MIDDLE_CENTER': (412, 487),
|
||
'MIDDLE_RIGHT': (824, 487),
|
||
'BOTTOM_LEFT': (0, 974), # 1024-50 = 974
|
||
'BOTTOM_CENTER': (412, 974),
|
||
'BOTTOM_RIGHT': (824, 974)
|
||
}
|
||
|
||
for row in anchor_matrix:
|
||
for anchor in row:
|
||
props = MockProps()
|
||
props.anchor_point = anchor
|
||
props.position_mode = 'PRESET'
|
||
props.margin_x = 0.0
|
||
props.margin_y = 0.0
|
||
props.offset_x = 0
|
||
props.offset_y = 0
|
||
|
||
try:
|
||
x, y = calculate_text_position(props, self.canvas_width, self.canvas_height,
|
||
self.text_width, self.text_height)
|
||
expected = expected_positions[anchor]
|
||
|
||
if (x, y) == expected:
|
||
self.log_test(f"Anchor {anchor}", True, f"Position: ({x}, {y})")
|
||
else:
|
||
self.log_test(f"Anchor {anchor}", False,
|
||
f"Incorrect position calculation", expected, (x, y))
|
||
|
||
except Exception as e:
|
||
self.log_test(f"Anchor {anchor}", False, f"Exception: {str(e)}")
|
||
|
||
def test_margin_calculations(self):
|
||
"""Test margin calculations with various percentages"""
|
||
print("\n=== Testing Margin Calculations ===")
|
||
|
||
test_cases = [
|
||
(10.0, 10.0), # 10% margins
|
||
(25.0, 25.0), # 25% margins
|
||
(50.0, 50.0), # 50% margins (maximum)
|
||
(0.0, 0.0), # No margins
|
||
]
|
||
|
||
for margin_x, margin_y in test_cases:
|
||
props = MockProps()
|
||
props.anchor_point = 'TOP_LEFT'
|
||
props.position_mode = 'PRESET'
|
||
props.margin_x = margin_x
|
||
props.margin_y = margin_y
|
||
props.offset_x = 0
|
||
props.offset_y = 0
|
||
|
||
expected_x = int((margin_x / 100.0) * self.canvas_width)
|
||
expected_y = int((margin_y / 100.0) * self.canvas_height)
|
||
|
||
try:
|
||
x, y = calculate_text_position(props, self.canvas_width, self.canvas_height,
|
||
self.text_width, self.text_height)
|
||
|
||
if x == expected_x and y == expected_y:
|
||
self.log_test(f"Margins {margin_x}%/{margin_y}%", True, f"Position: ({x}, {y})")
|
||
else:
|
||
self.log_test(f"Margins {margin_x}%/{margin_y}%", False,
|
||
f"Incorrect margin calculation", (expected_x, expected_y), (x, y))
|
||
|
||
except Exception as e:
|
||
self.log_test(f"Margins {margin_x}%/{margin_y}%", False, f"Exception: {str(e)}")
|
||
|
||
def test_manual_positioning(self):
|
||
"""Test manual positioning mode with percentage and pixel units"""
|
||
print("\n=== Testing Manual Positioning ===")
|
||
|
||
# Test percentage mode
|
||
props = MockProps()
|
||
props.position_mode = 'MANUAL'
|
||
props.manual_position_unit = 'PERCENTAGE'
|
||
props.manual_position_x = 25.0 # 25%
|
||
props.manual_position_y = 75.0 # 75%
|
||
|
||
expected_x = int((25.0 / 100.0) * self.canvas_width) # 256
|
||
expected_y = int((75.0 / 100.0) * self.canvas_height) # 768
|
||
|
||
try:
|
||
x, y = calculate_text_position(props, self.canvas_width, self.canvas_height,
|
||
self.text_width, self.text_height)
|
||
|
||
if x == expected_x and y == expected_y:
|
||
self.log_test("Manual Percentage Mode", True, f"Position: ({x}, {y})")
|
||
else:
|
||
self.log_test("Manual Percentage Mode", False,
|
||
f"Incorrect calculation", (expected_x, expected_y), (x, y))
|
||
|
||
except Exception as e:
|
||
self.log_test("Manual Percentage Mode", False, f"Exception: {str(e)}")
|
||
|
||
# Test pixel mode
|
||
props.manual_position_unit = 'PIXELS'
|
||
props.manual_position_x = 100.0
|
||
props.manual_position_y = 200.0
|
||
|
||
expected_x = 100
|
||
expected_y = 200
|
||
|
||
try:
|
||
x, y = calculate_text_position(props, self.canvas_width, self.canvas_height,
|
||
self.text_width, self.text_height)
|
||
|
||
if x == expected_x and y == expected_y:
|
||
self.log_test("Manual Pixel Mode", True, f"Position: ({x}, {y})")
|
||
else:
|
||
self.log_test("Manual Pixel Mode", False,
|
||
f"Incorrect calculation", (expected_x, expected_y), (x, y))
|
||
|
||
except Exception as e:
|
||
self.log_test("Manual Pixel Mode", False, f"Exception: {str(e)}")
|
||
|
||
def test_canvas_constraints(self):
|
||
"""Test canvas constraint functionality"""
|
||
print("\n=== Testing Canvas Constraints ===")
|
||
|
||
# Test position that would go out of bounds
|
||
props = MockProps()
|
||
props.position_mode = 'MANUAL'
|
||
props.manual_position_unit = 'PIXELS'
|
||
props.manual_position_x = 1000.0 # Would put text partially outside (text_width=200)
|
||
props.manual_position_y = 1000.0 # Would put text partially outside (text_height=50)
|
||
props.constrain_to_canvas = True
|
||
|
||
expected_x = self.canvas_width - self.text_width # 824
|
||
expected_y = self.canvas_height - self.text_height # 974
|
||
|
||
try:
|
||
x, y = calculate_text_position(props, self.canvas_width, self.canvas_height,
|
||
self.text_width, self.text_height)
|
||
|
||
if x == expected_x and y == expected_y:
|
||
self.log_test("Canvas Constraints (constrained)", True, f"Position: ({x}, {y})")
|
||
else:
|
||
self.log_test("Canvas Constraints (constrained)", False,
|
||
f"Constraint failed", (expected_x, expected_y), (x, y))
|
||
|
||
except Exception as e:
|
||
self.log_test("Canvas Constraints (constrained)", False, f"Exception: {str(e)}")
|
||
|
||
# Test with constraints disabled
|
||
props.constrain_to_canvas = False
|
||
expected_x = 1000 # Should not be constrained
|
||
expected_y = 1000
|
||
|
||
try:
|
||
x, y = calculate_text_position(props, self.canvas_width, self.canvas_height,
|
||
self.text_width, self.text_height)
|
||
|
||
if x == expected_x and y == expected_y:
|
||
self.log_test("Canvas Constraints (unconstrained)", True, f"Position: ({x}, {y})")
|
||
else:
|
||
self.log_test("Canvas Constraints (unconstrained)", False,
|
||
f"Should not be constrained", (expected_x, expected_y), (x, y))
|
||
|
||
except Exception as e:
|
||
self.log_test("Canvas Constraints (unconstrained)", False, f"Exception: {str(e)}")
|
||
|
||
def test_offset_application(self):
|
||
"""Test pixel offset fine-tuning"""
|
||
print("\n=== Testing Pixel Offset Application ===")
|
||
|
||
props = MockProps()
|
||
props.anchor_point = 'MIDDLE_CENTER'
|
||
props.position_mode = 'PRESET'
|
||
props.margin_x = 0.0
|
||
props.margin_y = 0.0
|
||
props.offset_x = 50 # 50px right
|
||
props.offset_y = -25 # 25px up
|
||
|
||
base_x = (self.canvas_width - self.text_width) // 2 # 412
|
||
base_y = (self.canvas_height - self.text_height) // 2 # 487
|
||
expected_x = base_x + props.offset_x # 462
|
||
expected_y = base_y + props.offset_y # 462
|
||
|
||
try:
|
||
x, y = calculate_text_position(props, self.canvas_width, self.canvas_height,
|
||
self.text_width, self.text_height)
|
||
|
||
if x == expected_x and y == expected_y:
|
||
self.log_test("Pixel Offset Application", True, f"Position: ({x}, {y})")
|
||
else:
|
||
self.log_test("Pixel Offset Application", False,
|
||
f"Offset not applied correctly", (expected_x, expected_y), (x, y))
|
||
|
||
except Exception as e:
|
||
self.log_test("Pixel Offset Application", False, f"Exception: {str(e)}")
|
||
|
||
def test_margin_synchronization(self):
|
||
"""Test margin link functionality"""
|
||
print("\n=== Testing Margin Synchronization ===")
|
||
|
||
props = MockProps()
|
||
props.margins_linked = True
|
||
props.margin_x = 15.0
|
||
props.margin_y = 10.0
|
||
|
||
# Test X margin sync
|
||
sync_margin_values(props, 'x')
|
||
if props.margin_y == props.margin_x:
|
||
self.log_test("Margin Sync (X to Y)", True, f"Y margin updated to {props.margin_y}")
|
||
else:
|
||
self.log_test("Margin Sync (X to Y)", False,
|
||
f"Y margin not synced", props.margin_x, props.margin_y)
|
||
|
||
# Reset and test Y margin sync
|
||
props.margin_x = 20.0
|
||
props.margin_y = 25.0
|
||
sync_margin_values(props, 'y')
|
||
if props.margin_x == props.margin_y:
|
||
self.log_test("Margin Sync (Y to X)", True, f"X margin updated to {props.margin_x}")
|
||
else:
|
||
self.log_test("Margin Sync (Y to X)", False,
|
||
f"X margin not synced", props.margin_y, props.margin_x)
|
||
|
||
# Test unlinked margins
|
||
props.margins_linked = False
|
||
original_x = props.margin_x
|
||
original_y = props.margin_y
|
||
sync_margin_values(props, 'x')
|
||
|
||
if props.margin_x == original_x and props.margin_y == original_y:
|
||
self.log_test("Margin Sync (Unlinked)", True, "Margins unchanged when unlinked")
|
||
else:
|
||
self.log_test("Margin Sync (Unlinked)", False,
|
||
"Margins changed when they shouldn't")
|
||
|
||
def test_edge_cases(self):
|
||
"""Test edge cases and boundary conditions"""
|
||
print("\n=== Testing Edge Cases ===")
|
||
|
||
# Test zero canvas dimensions
|
||
props = MockProps()
|
||
try:
|
||
x, y = calculate_text_position(props, 0, 0, self.text_width, self.text_height)
|
||
self.log_test("Zero Canvas Dimensions", True, f"Handled gracefully: ({x}, {y})")
|
||
except Exception as e:
|
||
self.log_test("Zero Canvas Dimensions", False, f"Exception: {str(e)}")
|
||
|
||
# Test text larger than canvas
|
||
props = MockProps()
|
||
props.anchor_point = 'TOP_LEFT'
|
||
props.constrain_to_canvas = True
|
||
large_text_width = 2000
|
||
large_text_height = 2000
|
||
|
||
try:
|
||
x, y = calculate_text_position(props, self.canvas_width, self.canvas_height,
|
||
large_text_width, large_text_height)
|
||
|
||
if x <= 0 and y <= 0:
|
||
self.log_test("Text Larger Than Canvas", True, f"Constrained to: ({x}, {y})")
|
||
else:
|
||
self.log_test("Text Larger Than Canvas", False,
|
||
f"Not properly constrained: ({x}, {y})")
|
||
except Exception as e:
|
||
self.log_test("Text Larger Than Canvas", False, f"Exception: {str(e)}")
|
||
|
||
# Test maximum margin values (50%)
|
||
props = MockProps()
|
||
props.anchor_point = 'TOP_LEFT'
|
||
props.margin_x = 50.0
|
||
props.margin_y = 50.0
|
||
|
||
try:
|
||
x, y = calculate_text_position(props, self.canvas_width, self.canvas_height,
|
||
self.text_width, self.text_height)
|
||
|
||
expected_x = int(0.5 * self.canvas_width) # 512
|
||
expected_y = int(0.5 * self.canvas_height) # 512
|
||
|
||
if x == expected_x and y == expected_y:
|
||
self.log_test("Maximum Margins (50%)", True, f"Position: ({x}, {y})")
|
||
else:
|
||
self.log_test("Maximum Margins (50%)", False,
|
||
f"Incorrect calculation", (expected_x, expected_y), (x, y))
|
||
except Exception as e:
|
||
self.log_test("Maximum Margins (50%)", False, f"Exception: {str(e)}")
|
||
|
||
def analyze_ui_integration(self):
|
||
"""Analyze UI integration potential issues"""
|
||
print("\n=== Analyzing UI Integration ===")
|
||
|
||
issues = []
|
||
|
||
# Check anchor point matrix structure
|
||
matrix = get_anchor_point_matrix()
|
||
if len(matrix) != 3 or any(len(row) != 3 for row in matrix):
|
||
issues.append("❌ Anchor point matrix is not 3x3")
|
||
else:
|
||
print("✅ Anchor point matrix structure is correct")
|
||
|
||
# Check all anchor points are defined
|
||
all_anchors = [anchor for row in matrix for anchor in row]
|
||
expected_anchors = [
|
||
'TOP_LEFT', 'TOP_CENTER', 'TOP_RIGHT',
|
||
'MIDDLE_LEFT', 'MIDDLE_CENTER', 'MIDDLE_RIGHT',
|
||
'BOTTOM_LEFT', 'BOTTOM_CENTER', 'BOTTOM_RIGHT'
|
||
]
|
||
|
||
missing_anchors = set(expected_anchors) - set(all_anchors)
|
||
if missing_anchors:
|
||
issues.append(f"❌ Missing anchor points: {missing_anchors}")
|
||
else:
|
||
print("✅ All required anchor points are defined")
|
||
|
||
# Check for property type consistency
|
||
props = MockProps()
|
||
|
||
# Test property ranges
|
||
if hasattr(props, 'margin_x') and hasattr(props, 'margin_y'):
|
||
print("✅ Margin properties exist")
|
||
else:
|
||
issues.append("❌ Margin properties missing")
|
||
|
||
if hasattr(props, 'offset_x') and hasattr(props, 'offset_y'):
|
||
print("✅ Offset properties exist")
|
||
else:
|
||
issues.append("❌ Offset properties missing")
|
||
|
||
self.issues_found.extend(issues)
|
||
|
||
def run_all_tests(self):
|
||
"""Run all tests and generate comprehensive report"""
|
||
print("🔍 ENHANCED POSITIONING SYSTEM TEST SUITE")
|
||
print("=" * 50)
|
||
|
||
try:
|
||
self.test_anchor_point_calculations()
|
||
self.test_margin_calculations()
|
||
self.test_manual_positioning()
|
||
self.test_canvas_constraints()
|
||
self.test_offset_application()
|
||
self.test_margin_synchronization()
|
||
self.test_edge_cases()
|
||
self.analyze_ui_integration()
|
||
|
||
except Exception as e:
|
||
print(f"❌ Critical error during testing: {e}")
|
||
traceback.print_exc()
|
||
|
||
self.generate_report()
|
||
|
||
def generate_report(self):
|
||
"""Generate comprehensive test report"""
|
||
print("\n" + "=" * 60)
|
||
print("📊 COMPREHENSIVE TEST REPORT")
|
||
print("=" * 60)
|
||
|
||
total_tests = len(self.test_results)
|
||
passed_tests = sum(1 for result in self.test_results if result['passed'])
|
||
failed_tests = total_tests - passed_tests
|
||
|
||
print(f"📈 SUMMARY:")
|
||
print(f" Total Tests: {total_tests}")
|
||
print(f" ✅ Passed: {passed_tests}")
|
||
print(f" ❌ Failed: {failed_tests}")
|
||
print(f" Success Rate: {(passed_tests/total_tests*100):.1f}%" if total_tests > 0 else "No tests run")
|
||
|
||
if self.issues_found:
|
||
print(f"\n🐛 ISSUES IDENTIFIED ({len(self.issues_found)}):")
|
||
for issue in self.issues_found:
|
||
print(f" {issue}")
|
||
|
||
# Categorize issues by severity
|
||
critical_issues = []
|
||
major_issues = []
|
||
minor_issues = []
|
||
|
||
for result in self.test_results:
|
||
if not result['passed']:
|
||
if 'exception' in result['details'].lower():
|
||
critical_issues.append(result)
|
||
elif 'constraint' in result['test'].lower() or 'calculation' in result['details'].lower():
|
||
major_issues.append(result)
|
||
else:
|
||
minor_issues.append(result)
|
||
|
||
if critical_issues:
|
||
print(f"\n🚨 CRITICAL ISSUES ({len(critical_issues)}):")
|
||
for issue in critical_issues:
|
||
print(f" • {issue['test']}: {issue['details']}")
|
||
|
||
if major_issues:
|
||
print(f"\n⚠️ MAJOR ISSUES ({len(major_issues)}):")
|
||
for issue in major_issues:
|
||
print(f" • {issue['test']}: {issue['details']}")
|
||
|
||
if minor_issues:
|
||
print(f"\n ℹ️ MINOR ISSUES ({len(minor_issues)}):")
|
||
for issue in minor_issues:
|
||
print(f" • {issue['test']}: {issue['details']}")
|
||
|
||
# Recommendations
|
||
print(f"\n🎯 RECOMMENDATIONS:")
|
||
|
||
if critical_issues:
|
||
print(" 1. Fix critical exceptions before deployment")
|
||
print(" 2. Add proper error handling for edge cases")
|
||
|
||
if major_issues:
|
||
print(" 3. Review positioning calculation logic")
|
||
print(" 4. Test constraint functionality thoroughly")
|
||
|
||
print(" 5. Add comprehensive unit tests to the addon")
|
||
print(" 6. Implement validation for property ranges")
|
||
print(" 7. Add user feedback for invalid input combinations")
|
||
|
||
return {
|
||
'total_tests': total_tests,
|
||
'passed': passed_tests,
|
||
'failed': failed_tests,
|
||
'issues': self.issues_found,
|
||
'critical_issues': len(critical_issues),
|
||
'major_issues': len(major_issues),
|
||
'minor_issues': len(minor_issues)
|
||
}
|
||
|
||
if __name__ == "__main__":
|
||
print("🚀 Starting Enhanced Positioning System Test Suite...")
|
||
test_suite = PositioningTestSuite()
|
||
results = test_suite.run_all_tests()
|
||
|
||
print(f"\n✨ Testing completed!")
|
||
print(f"Check the detailed report above for analysis and recommendations.") |