Files
Text-Texture-Generator-for-…/test_positioning_system.py
2025-08-11 12:48:35 +07:00

558 lines
22 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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.")