""" JavaScript Sanity Test Suite Tests for basic JavaScript functionality, syntax validation, and initialization """ import pytest import tempfile import re from pathlib import Path from markitect.clean_document_manager import CleanDocumentManager class TestJSSanity: """Test suite for JavaScript sanity checks""" def setup_method(self): """Setup for each test""" self.manager = CleanDocumentManager() def test_basic_html_generation_no_edit_mode(self): """Test that basic HTML generation works without edit mode""" with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as md_file: md_file.write("# Test Document\n\nThis is a test.") md_file.flush() with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False) as html_file: result = self.manager.render_file( input_file=md_file.name, output_file=html_file.name, edit_mode=False ) assert result['success'] is True # Read generated HTML html_content = Path(html_file.name).read_text() # Basic HTML structure checks assert '' in html_content assert '' in html_content assert '' in html_content assert 'Test Document' in html_content def test_edit_mode_javascript_syntax_validation(self): """Test that edit mode generates syntactically valid JavaScript""" test_markdown = '''# Test Document ## Code Block Test ```python script = f""" function test() { console.log("test"); } """ ``` This contains quotes that could break JavaScript. ''' with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as md_file: md_file.write(test_markdown) md_file.flush() with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False) as html_file: result = self.manager.render_file( input_file=md_file.name, output_file=html_file.name, edit_mode=True ) assert result['success'] is True # Read generated HTML html_content = Path(html_file.name).read_text() # Extract JavaScript content js_content = self.extract_javascript_from_html(html_content) # Test 1: Basic syntax validation syntax_errors = self.check_javascript_syntax(js_content) assert len(syntax_errors) == 0, f"JavaScript syntax errors found: {syntax_errors}" # Test 2: Check for unescaped quotes quote_errors = self.check_for_quote_escaping_issues(js_content) assert len(quote_errors) == 0, f"Quote escaping issues found: {quote_errors}" # Test 3: Check for required constants self.check_required_constants(js_content) def test_edit_mode_component_loading(self): """Test that all required JavaScript components are loaded""" test_markdown = "# Simple Test\n\nBasic content for component loading test." with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as md_file: md_file.write(test_markdown) md_file.flush() with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False) as html_file: result = self.manager.render_file( input_file=md_file.name, output_file=html_file.name, edit_mode=True ) assert result['success'] is True html_content = Path(html_file.name).read_text() # Check for required components required_components = [ 'js/core/debug-system.js', 'js/core/section-manager.js', 'js/components/dom-renderer.js', 'js/controls/control-base.js', 'js/main.js' ] for component in required_components: assert f"// === {component} ===" in html_content, f"Component {component} not loaded" def test_edit_mode_class_definitions(self): """Test that required JavaScript classes are defined""" test_markdown = "# Class Definition Test\n\nTesting class loading." with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as md_file: md_file.write(test_markdown) md_file.flush() with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False) as html_file: result = self.manager.render_file( input_file=md_file.name, output_file=html_file.name, edit_mode=True ) assert result['success'] is True html_content = Path(html_file.name).read_text() # Check for required class definitions required_classes = [ 'class Section', 'class SectionManager', 'class DOMRenderer', 'class MarkitectDebugSystem', 'const Control =', 'class StatusControl', 'class DebugControl', 'class EditControl' ] for class_def in required_classes: assert class_def in html_content, f"Class definition '{class_def}' not found" def test_edit_mode_initialization_functions(self): """Test that required initialization functions are defined""" test_markdown = "# Initialization Test\n\nTesting function definitions." with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as md_file: md_file.write(test_markdown) md_file.flush() with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False) as html_file: result = self.manager.render_file( input_file=md_file.name, output_file=html_file.name, edit_mode=True ) assert result['success'] is True html_content = Path(html_file.name).read_text() # Check for required function definitions required_functions = [ 'function initializeCleanEditor', 'function initializeScrollIndicators', 'function debug' ] for func_def in required_functions: assert func_def in html_content, f"Function definition '{func_def}' not found" def test_edit_mode_global_exports(self): """Test that required globals are exported to window""" test_markdown = "# Global Exports Test\n\nTesting window exports." with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as md_file: md_file.write(test_markdown) md_file.flush() with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False) as html_file: result = self.manager.render_file( input_file=md_file.name, output_file=html_file.name, edit_mode=True ) assert result['success'] is True html_content = Path(html_file.name).read_text() # Check for required window exports required_exports = [ 'window.MarkitectDebugSystem = new MarkitectDebugSystem', 'window.SectionManager = SectionManager', 'window.Control = Control', 'window.StatusControl = StatusControl' ] for export in required_exports: assert export in html_content, f"Window export '{export}' not found" # Helper methods def extract_javascript_from_html(self, html_content): """Extract JavaScript content from HTML""" # Find all script tags and extract their content script_pattern = r']*>(.*?)' scripts = re.findall(script_pattern, html_content, re.DOTALL) return '\n'.join(scripts) def check_javascript_syntax(self, js_content): """Basic JavaScript syntax validation""" errors = [] # Check for common syntax errors # 1. Unmatched quotes single_quotes = js_content.count("'") - js_content.count("\\'") double_quotes = js_content.count('"') - js_content.count('\\"') if single_quotes % 2 != 0: errors.append("Unmatched single quotes detected") if double_quotes % 2 != 0: errors.append("Unmatched double quotes detected") # 2. Unmatched braces open_braces = js_content.count('{') close_braces = js_content.count('}') if open_braces != close_braces: errors.append(f"Unmatched braces: {open_braces} open, {close_braces} close") # 3. Unmatched parentheses open_parens = js_content.count('(') close_parens = js_content.count(')') if open_parens != close_parens: errors.append(f"Unmatched parentheses: {open_parens} open, {close_parens} close") # 4. Check for unterminated string literals # Look for patterns that suggest unterminated strings unterminated_patterns = [ r'[^\\]"[^"]*$', # Double quote not followed by closing quote at line end r'[^\\]\'[^\']*$' # Single quote not followed by closing quote at line end ] for pattern in unterminated_patterns: matches = re.findall(pattern, js_content, re.MULTILINE) if matches: errors.append(f"Potential unterminated string literals: {len(matches)} found") return errors def check_for_quote_escaping_issues(self, js_content): """Check for common quote escaping problems""" errors = [] # Look for problematic patterns # 1. Triple quotes in JSON strings (common Python -> JS issue) if '"""' in js_content and 'const markdownContent' in js_content: errors.append("Triple quotes found in markdownContent - likely escaping issue") # 2. Unescaped newlines in strings problem_patterns = [ r'"[^"]*\n[^"]*"', # Newline in double-quoted string r"'[^']*\n[^']*'" # Newline in single-quoted string ] for pattern in problem_patterns: matches = re.findall(pattern, js_content) if matches: errors.append(f"Unescaped newlines in strings: {len(matches)} found") return errors def check_required_constants(self, js_content): """Check that required constants are defined""" required_constants = [ 'const markdownContent =', 'const MARKITECT_EDIT_MODE =', 'const MARKITECT_EDITOR_CONFIG =', 'const EditState =', 'const SectionType =' ] for constant in required_constants: assert constant in js_content, f"Required constant '{constant}' not found" def check_for_infinite_retry_loop(self, js_content): """Check for patterns that indicate infinite retry loops""" errors = [] # Pattern 1: Retry logic that can loop infinitely if "setTimeout(() => this.initialize(), 50)" in js_content: # Check if there's a proper termination condition if "maxWait" not in js_content and "startTime" not in js_content: errors.append("Found retry setTimeout without timeout protection") # Pattern 2: Configuration loading that retries indefinitely retry_patterns = [ r"setTimeout\([^)]*initialize[^)]*\)", # setTimeout calling initialize r"if\s*\(\s*!.*\.loaded\s*\)\s*{[^}]*setTimeout" # if not loaded, setTimeout ] import re for pattern in retry_patterns: matches = re.findall(pattern, js_content) if matches: # Check if there are proper safeguards if "maxWait" not in js_content or "timeout" not in js_content.lower(): errors.append(f"Found retry pattern without timeout protection: {pattern}") # Pattern 3: Check for MarkitectMain.initialize calling itself recursively if js_content.count("MarkitectMain.initialize") > 2: # Once for definition, once for call if "this.initialized" not in js_content: errors.append("MarkitectMain.initialize may call itself recursively without proper guard") return errors def check_configuration_loading_logic(self, js_content): """Check for proper configuration loading setup""" errors = [] # Check 1: Configuration should be loaded via JSON element if 'markitect-config' not in js_content: errors.append("No markitect-config element found - configuration loading will fail") # Check 2: Configuration loader should wait for DOM if 'DOMContentLoaded' not in js_content and 'document.readyState' not in js_content: errors.append("Configuration loading doesn't wait for DOM ready") # Check 3: Should have proper error handling for missing config element if "getElementById('markitect-config')" in js_content: if "throw new Error" not in js_content and "console.error" not in js_content: errors.append("No error handling for missing configuration element") # Check 4: Check for proper retry logic with timeout if "setTimeout" in js_content and "initialize" in js_content: if "maxWait" not in js_content and "startTime" not in js_content: errors.append("Retry logic present but no timeout mechanism found") return errors def test_comprehensive_edit_mode_validation(self): """Comprehensive test that validates the complete edit mode functionality""" # Use the actual GUARDRAILS.md that was causing issues test_markdown = '''# Development Guardrails ## JavaScript Code Principles ### 1. No Inline JavaScript in Python **NEVER write JavaScript code directly from Python code** ❌ **Wrong:** ```python script = f""" function myFunction() {{ console.log("Hello {name}"); }} """ ``` ✅ **Correct:** ```python # Load from external files only components = [ 'js/core/section-manager.js', 'js/components/debug-panel.js' ] ``` This is the content that was breaking the JavaScript generation. ''' with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as md_file: md_file.write(test_markdown) md_file.flush() with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False) as html_file: # This should not raise an exception result = self.manager.render_file( input_file=md_file.name, output_file=html_file.name, edit_mode=True ) assert result['success'] is True # Read and validate the generated HTML html_content = Path(html_file.name).read_text() js_content = self.extract_javascript_from_html(html_content) # Comprehensive validation syntax_errors = self.check_javascript_syntax(js_content) quote_errors = self.check_for_quote_escaping_issues(js_content) # If these fail, we have the exact same problem as reported assert len(syntax_errors) == 0, f"SYNTAX ERRORS: {syntax_errors}" assert len(quote_errors) == 0, f"QUOTE ESCAPING ERRORS: {quote_errors}" # Verify all required components loaded self.check_required_constants(js_content) # CRITICAL: Test for infinite retry loop retry_errors = self.check_for_infinite_retry_loop(js_content) assert len(retry_errors) == 0, f"INFINITE RETRY LOOP DETECTED: {retry_errors}" def test_configuration_loading_not_stuck_in_loop(self): """Test specifically for infinite configuration loading retry loops""" test_markdown = "# Simple Test\n\nBasic content for testing configuration loading." with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as md_file: md_file.write(test_markdown) md_file.flush() with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False) as html_file: result = self.manager.render_file( input_file=md_file.name, output_file=html_file.name, edit_mode=True ) assert result['success'] is True html_content = Path(html_file.name).read_text() # Test for infinite retry patterns retry_issues = self.check_for_infinite_retry_loop(html_content) assert len(retry_issues) == 0, f"INFINITE RETRY LOOP ISSUES: {retry_issues}" # Test for proper configuration loading setup config_issues = self.check_configuration_loading_logic(html_content) assert len(config_issues) == 0, f"CONFIGURATION LOADING ISSUES: {config_issues}"