Add robust testing framework to prevent regression of edit mode: - Comprehensive regression tests for JavaScript syntax validation - Build-time JavaScript validation tool with Node.js integration - Enhanced error tracking with detailed logging and recovery - Makefile integration for `make validate-js` command Features: - Validates JavaScript syntax in generated HTML templates - Detects common issues like broken string literals and brace escaping - Enhanced error reporting with timestamps and context - Automatic error recovery for graceful degradation - Build validation to catch syntax errors before deployment 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
348 lines
13 KiB
Python
348 lines
13 KiB
Python
"""
|
|
Test suite for md-render --edit functionality to prevent regression.
|
|
|
|
This test suite specifically targets the critical JavaScript syntax errors
|
|
that were causing edit mode to fail completely, ensuring they never happen again.
|
|
"""
|
|
|
|
import tempfile
|
|
import pytest
|
|
from pathlib import Path
|
|
import re
|
|
import subprocess
|
|
|
|
|
|
class TestEditModeRegression:
|
|
"""Tests to prevent regression of the md-render --edit functionality."""
|
|
|
|
def test_edit_mode_generates_valid_javascript(self):
|
|
"""Test that edit mode generates syntactically valid JavaScript."""
|
|
from markitect.document_manager import DocumentManager
|
|
|
|
doc_manager = DocumentManager()
|
|
|
|
# Test markdown content
|
|
test_content = "# Test Header\n\nThis is a test paragraph.\n\n## Section 2\n\nAnother paragraph."
|
|
|
|
# Generate HTML with edit mode
|
|
html_content = doc_manager._generate_html_template(
|
|
title="Test Document",
|
|
markdown_content=test_content,
|
|
edit_mode=True,
|
|
editor_theme='github',
|
|
keyboard_shortcuts=True
|
|
)
|
|
|
|
# Extract JavaScript from HTML
|
|
js_match = re.search(r'<script>(.*?)</script>', html_content, re.DOTALL)
|
|
assert js_match, "No JavaScript found in edit mode HTML"
|
|
|
|
js_content = js_match.group(1)
|
|
|
|
# Write to temp file and validate syntax with Node.js
|
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False) as f:
|
|
f.write(js_content)
|
|
temp_js_path = f.name
|
|
|
|
try:
|
|
# Use Node.js to check JavaScript syntax
|
|
result = subprocess.run(
|
|
['node', '-c', temp_js_path],
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
assert result.returncode == 0, f"JavaScript syntax error: {result.stderr}"
|
|
|
|
finally:
|
|
Path(temp_js_path).unlink()
|
|
|
|
def test_edit_mode_contains_required_functions(self):
|
|
"""Test that edit mode HTML contains all required JavaScript functions."""
|
|
from markitect.document_manager import DocumentManager
|
|
|
|
doc_manager = DocumentManager()
|
|
|
|
html_content = doc_manager._generate_html_template(
|
|
title="Test",
|
|
markdown_content="# Test",
|
|
edit_mode=True
|
|
)
|
|
|
|
# Check for critical functions that must be present
|
|
required_functions = [
|
|
'MarkitectEditor',
|
|
'updateStatus',
|
|
'reportEditModeError',
|
|
'makeContentEditable',
|
|
'handleSectionClick',
|
|
'editSection'
|
|
]
|
|
|
|
for func_name in required_functions:
|
|
assert func_name in html_content, f"Required function '{func_name}' not found in edit mode HTML"
|
|
|
|
def test_edit_mode_no_broken_string_literals(self):
|
|
"""Test that there are no broken string literals in the generated JavaScript."""
|
|
from markitect.document_manager import DocumentManager
|
|
|
|
doc_manager = DocumentManager()
|
|
|
|
html_content = doc_manager._generate_html_template(
|
|
title="Test",
|
|
markdown_content="# Test",
|
|
edit_mode=True
|
|
)
|
|
|
|
# Extract JavaScript
|
|
js_match = re.search(r'<script>(.*?)</script>', html_content, re.DOTALL)
|
|
js_content = js_match.group(1)
|
|
|
|
# Check for broken string patterns that caused the original bug
|
|
broken_patterns = [
|
|
r"'\s*\n\s*'", # Broken string literal across lines
|
|
r'"\s*\n\s*"', # Broken string literal across lines
|
|
r'reconstructed \+= .*\'\n', # Unescaped newline in string
|
|
]
|
|
|
|
for pattern in broken_patterns:
|
|
matches = re.findall(pattern, js_content)
|
|
assert not matches, f"Found broken string pattern: {pattern} - matches: {matches}"
|
|
|
|
def test_edit_mode_proper_brace_escaping(self):
|
|
"""Test that braces are properly escaped in f-string templates."""
|
|
from markitect.document_manager import DocumentManager
|
|
|
|
doc_manager = DocumentManager()
|
|
|
|
html_content = doc_manager._generate_html_template(
|
|
title="Test",
|
|
markdown_content="# Test",
|
|
edit_mode=True
|
|
)
|
|
|
|
# Extract JavaScript
|
|
js_match = re.search(r'<script>(.*?)</script>', html_content, re.DOTALL)
|
|
js_content = js_match.group(1)
|
|
|
|
# Check for inconsistent brace patterns
|
|
inconsistent_patterns = [
|
|
r'} else if.*{{', # Mixed single and double braces
|
|
r'}} else if.*{[^{]', # Mixed double and single braces
|
|
]
|
|
|
|
for pattern in inconsistent_patterns:
|
|
matches = re.findall(pattern, js_content)
|
|
assert not matches, f"Found inconsistent brace pattern: {pattern}"
|
|
|
|
def test_edit_mode_template_literal_syntax(self):
|
|
"""Test that template literals are properly escaped."""
|
|
from markitect.document_manager import DocumentManager
|
|
|
|
doc_manager = DocumentManager()
|
|
|
|
html_content = doc_manager._generate_html_template(
|
|
title="Test",
|
|
markdown_content="# Test",
|
|
edit_mode=True
|
|
)
|
|
|
|
# Extract JavaScript
|
|
js_match = re.search(r'<script>(.*?)</script>', html_content, re.DOTALL)
|
|
js_content = js_match.group(1)
|
|
|
|
# Check for problematic template literal patterns
|
|
# Should NOT find double-escaped template literals like ${{
|
|
problematic_patterns = [
|
|
r'\$\{\{.*?\}\}', # Double-escaped template literals
|
|
]
|
|
|
|
for pattern in problematic_patterns:
|
|
matches = re.findall(pattern, js_content)
|
|
assert not matches, f"Found problematic template literal: {pattern}"
|
|
|
|
def test_edit_mode_contains_content_div(self):
|
|
"""Test that edit mode HTML contains the markdown-content div."""
|
|
from markitect.document_manager import DocumentManager
|
|
|
|
doc_manager = DocumentManager()
|
|
|
|
html_content = doc_manager._generate_html_template(
|
|
title="Test",
|
|
markdown_content="# Test Content",
|
|
edit_mode=True
|
|
)
|
|
|
|
# Should contain the content container
|
|
assert 'id="markdown-content"' in html_content
|
|
assert 'MARKITECT_EDIT_MODE = true' in html_content
|
|
assert 'markitect-edit-mode' in html_content
|
|
|
|
def test_edit_mode_error_handling_elements(self):
|
|
"""Test that edit mode includes proper error handling UI elements."""
|
|
from markitect.document_manager import DocumentManager
|
|
|
|
doc_manager = DocumentManager()
|
|
|
|
html_content = doc_manager._generate_html_template(
|
|
title="Test",
|
|
markdown_content="# Test",
|
|
edit_mode=True
|
|
)
|
|
|
|
# Should contain error handling elements
|
|
assert 'id="markitect-status"' in html_content
|
|
assert 'id="status-message"' in html_content
|
|
assert 'id="error-details"' in html_content
|
|
assert 'reportEditModeError' in html_content
|
|
|
|
def test_edit_mode_vs_normal_mode_differences(self):
|
|
"""Test that edit mode and normal mode generate different output appropriately."""
|
|
from markitect.document_manager import DocumentManager
|
|
|
|
doc_manager = DocumentManager()
|
|
test_content = "# Test Header\n\nTest content."
|
|
|
|
# Generate both modes
|
|
normal_html = doc_manager._generate_html_template(
|
|
title="Test",
|
|
markdown_content=test_content,
|
|
edit_mode=False
|
|
)
|
|
|
|
edit_html = doc_manager._generate_html_template(
|
|
title="Test",
|
|
markdown_content=test_content,
|
|
edit_mode=True
|
|
)
|
|
|
|
# Edit mode should have additional elements
|
|
assert len(edit_html) > len(normal_html)
|
|
assert 'MarkitectEditor' in edit_html
|
|
assert 'MarkitectEditor' not in normal_html
|
|
assert 'markitect-edit-mode' in edit_html
|
|
assert 'markitect-edit-mode' not in normal_html
|
|
|
|
def test_edit_mode_javascript_execution_flow(self):
|
|
"""Test the logical flow of JavaScript execution in edit mode."""
|
|
from markitect.document_manager import DocumentManager
|
|
|
|
doc_manager = DocumentManager()
|
|
|
|
html_content = doc_manager._generate_html_template(
|
|
title="Test",
|
|
markdown_content="# Test",
|
|
edit_mode=True
|
|
)
|
|
|
|
# Extract JavaScript
|
|
js_match = re.search(r'<script>(.*?)</script>', html_content, re.DOTALL)
|
|
js_content = js_match.group(1)
|
|
|
|
# Check for proper execution flow elements
|
|
flow_elements = [
|
|
'DOMContentLoaded', # Event listener setup
|
|
'MARKITECT_EDIT_MODE', # Mode check
|
|
'new MarkitectEditor', # Editor instantiation
|
|
'makeContentEditable', # Content enhancement
|
|
'handleSectionClick' # Interaction handler
|
|
]
|
|
|
|
for element in flow_elements:
|
|
assert element in js_content, f"Missing execution flow element: {element}"
|
|
|
|
def test_newline_escaping_in_javascript_strings(self):
|
|
"""Test that newlines in JavaScript strings are properly escaped."""
|
|
from markitect.document_manager import DocumentManager
|
|
|
|
doc_manager = DocumentManager()
|
|
|
|
html_content = doc_manager._generate_html_template(
|
|
title="Test",
|
|
markdown_content="# Test\n\nMultiple\nLines",
|
|
edit_mode=True
|
|
)
|
|
|
|
# Extract JavaScript
|
|
js_match = re.search(r'<script>(.*?)</script>', html_content, re.DOTALL)
|
|
js_content = js_match.group(1)
|
|
|
|
# Look for the specific section that was broken
|
|
# Should find properly escaped newlines like '\\n\\n'
|
|
assert '\\\\n\\\\n' in js_content, "Newlines not properly escaped in JavaScript strings"
|
|
|
|
# Should NOT find unescaped newlines in string contexts
|
|
# This regex looks for string concatenation with actual newlines
|
|
broken_newline_pattern = r"'\s*\+\s*text\s*\+\s*'\s*\n"
|
|
matches = re.findall(broken_newline_pattern, js_content)
|
|
assert not matches, f"Found unescaped newlines in string concatenation: {matches}"
|
|
|
|
|
|
class TestEditModeIntegration:
|
|
"""Integration tests for the complete edit mode functionality."""
|
|
|
|
def test_md_render_edit_command_execution(self):
|
|
"""Test that the md-render --edit command executes without errors."""
|
|
import tempfile
|
|
from markitect.plugins.builtin.markdown_commands import md_render_command
|
|
from click.testing import CliRunner
|
|
|
|
runner = CliRunner()
|
|
|
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as md_file:
|
|
md_file.write("# Test Document\n\nThis is a test paragraph.\n\n## Section 2\n\nAnother paragraph.")
|
|
md_file_path = md_file.name
|
|
|
|
with tempfile.NamedTemporaryFile(suffix='.html', delete=False) as html_file:
|
|
html_file_path = html_file.name
|
|
|
|
try:
|
|
# Test the command
|
|
result = runner.invoke(md_render_command, [
|
|
md_file_path,
|
|
'--edit',
|
|
'--output', html_file_path
|
|
])
|
|
|
|
assert result.exit_code == 0, f"Command failed: {result.output}"
|
|
|
|
# Verify the output file exists and contains edit mode elements
|
|
html_content = Path(html_file_path).read_text()
|
|
assert 'MarkitectEditor' in html_content
|
|
assert 'markitect-edit-mode' in html_content
|
|
|
|
# Verify JavaScript syntax
|
|
js_match = re.search(r'<script>(.*?)</script>', html_content, re.DOTALL)
|
|
assert js_match, "No JavaScript found in output"
|
|
|
|
finally:
|
|
Path(md_file_path).unlink(missing_ok=True)
|
|
Path(html_file_path).unlink(missing_ok=True)
|
|
|
|
def test_save_functionality_javascript_presence(self):
|
|
"""Test that the save functionality JavaScript is properly included."""
|
|
from markitect.document_manager import DocumentManager
|
|
|
|
doc_manager = DocumentManager()
|
|
|
|
html_content = doc_manager._generate_html_template(
|
|
title="Test",
|
|
markdown_content="# Test Content",
|
|
edit_mode=True
|
|
)
|
|
|
|
# Check for save-related functionality
|
|
save_elements = [
|
|
'Save & Download', # Button text
|
|
'markitectEditor.save()', # Save function call
|
|
'getMarkdownContent', # Content extraction
|
|
'Blob', # File creation
|
|
'download' # Download attribute
|
|
]
|
|
|
|
for element in save_elements:
|
|
assert element in html_content, f"Save functionality element missing: {element}"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v"]) |