#!/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.")