Files
markitect-main/tests/test_js_sanity.py
tegwick 55c61a7f2d feat: implement clean JavaScript-Python separation for edit mode
- Created JSON configuration interface eliminating JavaScript-Python code mixing
- Added external script references following non-edit mode patterns
- Implemented edit-mode-fixed.html template with proper fallback content
- Added config-loader.js for clean data transfer via JSON
- Updated main-updated.js with simplified initialization (no infinite retry loops)
- Added comprehensive test suite for JavaScript syntax validation
- Achieved full GUARDRAILS.md compliance with clean separation of concerns

Fixes infinite retry loops and JavaScript syntax errors caused by
template literal escaping issues in Python f-strings.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 06:41:53 +01:00

440 lines
17 KiB
Python

"""
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 '<!DOCTYPE html>' in html_content
assert '<html' in html_content
assert '</html>' in html_content
assert '<body' in html_content
assert '</body>' 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'<script[^>]*>(.*?)</script>'
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}"