4 Commits

Author SHA1 Message Date
3a53e0aa58 fix: resolve md-render --edit functionality and add enhanced version tracking
Some checks failed
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / code-quality (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
This commit fixes the critical md-render --edit regression that was causing
"blue box, no content" issues and adds comprehensive version tracking.

Key fixes:
- Fixed JavaScript newline escaping in f-string templates (\\n\\n not \\\\n\\\\n)
- Restored proper content rendering with marked.js CDN and graceful fallback
- Removed problematic validation logic that was blocking content display
- Cleaned up html-inject-editing command and related experimental code

Enhancements:
- Added version display in edit mode header with git commit and timestamp
- Enhanced version tracking to show local uncommitted changes with timestamps
- Added comprehensive regression tests to prevent future breakage
- Improved error handling and recovery mechanisms

The md-render --edit functionality now works reliably with full version visibility.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 18:16:25 +02:00
64d1606740 feat: add comprehensive testing and error tracking for edit mode
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>
2025-10-25 16:53:09 +02:00
1022e2597f fix: resolve critical JavaScript syntax errors in md-render --edit
Fix broken edit mode that prevented content rendering due to:
- Unescaped newline literals causing JavaScript string syntax errors
- Inconsistent brace escaping in f-string templates
- Template literal syntax issues with variable interpolation

The edit mode now properly renders content AND provides editing capabilities.

Also added html-inject-editing command for standalone HTML enhancement
with graceful fallback options.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 16:46:47 +02:00
50170f75df fix: resolve md-ingest Path object conversion error
Fix critical runtime error where md-ingest command failed with
'str' object has no attribute 'exists'. The DocumentManager.ingest_file()
method expects a Path object but was receiving a string from the CLI.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 05:59:25 +02:00
5 changed files with 800 additions and 30 deletions

View File

@@ -83,6 +83,7 @@ help:
@echo " status - Show git status for repo and submodules" @echo " status - Show git status for repo and submodules"
@echo " clean - Clean build artifacts" @echo " clean - Clean build artifacts"
@echo " check-deps - Check dependency status" @echo " check-deps - Check dependency status"
@echo " validate-js - Validate JavaScript syntax in templates"
@echo "" @echo ""
@echo "Documentation:" @echo "Documentation:"
@echo " update-digest - Update ProjectStatusDigest.md (requires Claude Code)" @echo " update-digest - Update ProjectStatusDigest.md (requires Claude Code)"
@@ -1340,3 +1341,13 @@ cost-help:
@echo "💰 Currency: Costs calculated in USD and EUR" @echo "💰 Currency: Costs calculated in USD and EUR"
@echo "🤖 Model: Default claude-sonnet-4 pricing" @echo "🤖 Model: Default claude-sonnet-4 pricing"
# JavaScript validation for edit mode templates
validate-js: $(VENV)/bin/activate
@echo "🔍 Validating JavaScript syntax in templates..."
@if command -v node >/dev/null 2>&1; then \
$(PYTHON) tools/validate_js_syntax.py; \
else \
echo "⚠️ Node.js not available - skipping JavaScript validation"; \
echo " Install Node.js to enable JavaScript syntax checking"; \
fi

View File

@@ -574,35 +574,35 @@ class DocumentManager:
const sections = content.querySelectorAll('.markitect-section-editable'); const sections = content.querySelectorAll('.markitect-section-editable');
let reconstructed = ''; let reconstructed = '';
sections.forEach(section => { sections.forEach(section => {{
const tagName = section.tagName.toLowerCase(); const tagName = section.tagName.toLowerCase();
const text = section.textContent.trim(); const text = section.textContent.trim();
if (tagName.startsWith('h')) { if (tagName.startsWith('h')) {{
const level = parseInt(tagName.charAt(1)); const level = parseInt(tagName.charAt(1));
reconstructed += '#'.repeat(level) + ' ' + text + '\n\n'; reconstructed += '#'.repeat(level) + ' ' + text + '\\n\\n';
} else if (tagName === 'p') { }} else if (tagName === 'p') {{
reconstructed += text + '\n\n'; reconstructed += text + '\\n\\n';
} else if (tagName === 'blockquote') { }} else if (tagName === 'blockquote') {{
reconstructed += '> ' + text + '\n\n'; reconstructed += '> ' + text + '\\n\\n';
} else if (tagName === 'pre') { }} else if (tagName === 'pre') {{
reconstructed += '```\n' + text + '\n```\n\n'; reconstructed += '```\\n' + text + '\\n```\\n\\n';
} else if (tagName === 'ul') { }} else if (tagName === 'ul') {{
const items = section.querySelectorAll('li'); const items = section.querySelectorAll('li');
items.forEach(item => { items.forEach(item => {{
reconstructed += '- ' + item.textContent.trim() + '\n'; reconstructed += '- ' + item.textContent.trim() + '\\n';
}); }});
reconstructed += '\n'; reconstructed += '\\n';
} else if (tagName === 'ol') { }} else if (tagName === 'ol') {{
const items = section.querySelectorAll('li'); const items = section.querySelectorAll('li');
items.forEach((item, index) => { items.forEach((item, index) => {{
reconstructed += `${index + 1}. ` + item.textContent.trim() + '\n'; reconstructed += (index + 1) + '. ' + item.textContent.trim() + '\\n';
}); }});
reconstructed += '\n'; reconstructed += '\\n';
} else { }} else {{
reconstructed += text + '\n\n'; reconstructed += text + '\\n\\n';
} }}
}); }});
return reconstructed.trim(); return reconstructed.trim();
} }
@@ -617,9 +617,82 @@ class DocumentManager:
# Edit mode status and error reporting section # Edit mode status and error reporting section
edit_mode_html = "" edit_mode_html = ""
if edit_mode: if edit_mode:
# Get version info for header
try:
import markitect
from pathlib import Path
import subprocess
# Get base version
version = "0.3.0" # fallback
try:
from importlib.metadata import version as get_version
version = get_version('markitect')
except:
pass
# Get git commit with timestamp and local changes info
git_info = ""
try:
repo_path = Path(__file__).parent.parent
# Get commit hash and timestamp
result = subprocess.run(['git', 'rev-parse', '--short', 'HEAD'],
capture_output=True, text=True, cwd=repo_path)
if result.returncode == 0:
commit_hash = result.stdout.strip()
# Get commit timestamp
timestamp_result = subprocess.run(['git', 'show', '-s', '--format=%ci', 'HEAD'],
capture_output=True, text=True, cwd=repo_path)
commit_time = ""
if timestamp_result.returncode == 0:
from datetime import datetime
# Parse git timestamp and format it nicely
git_time = timestamp_result.stdout.strip()
try:
dt = datetime.fromisoformat(git_time.replace(' +', '+'))
commit_time = f" ({dt.strftime('%Y-%m-%d %H:%M')})"
except:
pass
git_info = f"+{commit_hash}{commit_time}"
# Check for uncommitted changes
status_result = subprocess.run(['git', 'status', '--porcelain'],
capture_output=True, text=True, cwd=repo_path)
if status_result.returncode == 0 and status_result.stdout.strip():
# Get timestamp of most recent uncommitted change
import os
import glob
latest_change = 0
for line in status_result.stdout.strip().split('\n'):
if line.strip():
# Extract filename (skip first 3 chars which are status indicators)
filename = line[3:].strip()
try:
file_path = repo_path / filename
if file_path.exists():
mtime = os.path.getmtime(file_path)
latest_change = max(latest_change, mtime)
except:
pass
if latest_change > 0:
change_dt = datetime.fromtimestamp(latest_change)
git_info += f" including local changes until {change_dt.strftime('%Y-%m-%d %H:%M')}"
except:
pass
version_info = f"{version}{git_info}"
except:
version_info = "0.3.0"
edit_mode_html = f""" edit_mode_html = f"""
<div id="markitect-status" style="background: #e3f2fd; border-left: 4px solid #2196f3; padding: 12px; margin-bottom: 20px; font-family: monospace; font-size: 14px;"> <div id="markitect-status" style="background: #e3f2fd; border-left: 4px solid #2196f3; padding: 12px; margin-bottom: 20px; font-family: monospace; font-size: 14px;">
<div style="font-weight: bold; color: #1976d2;">📝 Markitect Edit Mode</div> <div style="font-weight: bold; color: #1976d2;">📝 Markitect Edit Mode <span style="font-weight: normal; color: #666;">v{version_info}</span></div>
<div id="status-message" style="margin-top: 8px;">Loading edit capabilities...</div> <div id="status-message" style="margin-top: 8px;">Loading edit capabilities...</div>
<div id="error-details" style="display: none; background: #ffebee; border: 1px solid #f44336; padding: 8px; margin-top: 8px; border-radius: 4px;"> <div id="error-details" style="display: none; background: #ffebee; border: 1px solid #f44336; padding: 8px; margin-top: 8px; border-radius: 4px;">
<div style="font-weight: bold; color: #c62828;">❌ Edit Mode Failed</div> <div style="font-weight: bold; color: #c62828;">❌ Edit Mode Failed</div>
@@ -659,20 +732,56 @@ class DocumentManager:
// Define editor class first (if in edit mode) // Define editor class first (if in edit mode)
{editor_scripts if edit_mode else ''} {editor_scripts if edit_mode else ''}
// Error reporting utility // Enhanced error reporting utility
function reportEditModeError(errorMsg, technicalDetails) {{ function reportEditModeError(errorMsg, technicalDetails, errorType = 'error') {{
const statusDiv = document.getElementById('markitect-status'); const statusDiv = document.getElementById('markitect-status');
const errorDiv = document.getElementById('error-details'); const errorDiv = document.getElementById('error-details');
const errorText = document.getElementById('error-text'); const errorText = document.getElementById('error-text');
const statusMsg = document.getElementById('status-message'); const statusMsg = document.getElementById('status-message');
const browserInfo = document.getElementById('browser-info'); const browserInfo = document.getElementById('browser-info');
if (statusMsg) statusMsg.textContent = 'Edit mode unavailable - content displayed in read-only mode'; // Log to console for debugging
console.error('[MarkiTect Edit Mode Error]', errorMsg, technicalDetails);
// Create error report object
const errorReport = {{
timestamp: new Date().toISOString(),
error: errorMsg,
details: technicalDetails,
type: errorType,
userAgent: navigator.userAgent,
url: window.location.href,
markdownContent: typeof markdownContent !== 'undefined' ? markdownContent.length + ' chars' : 'unavailable'
}};
// Store error for potential reporting
if (!window.markitectErrors) window.markitectErrors = [];
window.markitectErrors.push(errorReport);
// Update UI
if (statusMsg) {{
const statusText = errorType === 'warning'
? 'Edit mode partially available - some features may not work'
: 'Edit mode unavailable - content displayed in read-only mode';
statusMsg.textContent = statusText;
}}
if (errorDiv) errorDiv.style.display = 'block'; if (errorDiv) errorDiv.style.display = 'block';
if (errorText) errorText.textContent = errorMsg + (technicalDetails ? ' (' + technicalDetails + ')' : ''); if (errorText) {{
const fullError = errorMsg + (technicalDetails ? ' (' + technicalDetails + ')' : '');
errorText.textContent = fullError;
}}
if (browserInfo) browserInfo.textContent = navigator.userAgent.split(' ').slice(-2).join(' '); if (browserInfo) browserInfo.textContent = navigator.userAgent.split(' ').slice(-2).join(' ');
// Auto-hide warnings after 10 seconds
if (errorType === 'warning' && errorDiv) {{
setTimeout(() => {{
errorDiv.style.display = 'none';
}}, 10000);
}}
}} }}
// Status update utility // Status update utility
function updateStatus(message, isError = false) {{ function updateStatus(message, isError = false) {{
const statusMsg = document.getElementById('status-message'); const statusMsg = document.getElementById('status-message');

View File

@@ -1533,7 +1533,7 @@ def md_ingest_command(ctx, file_path):
doc_manager = DocumentManager(config.get('db_manager')) doc_manager = DocumentManager(config.get('db_manager'))
# Process the file # Process the file
result = doc_manager.ingest_file(file_path) result = doc_manager.ingest_file(Path(file_path))
if config.get('verbose', False): if config.get('verbose', False):
click.echo(f"Processing results:") click.echo(f"Processing results:")
@@ -2980,4 +2980,6 @@ class FilenameDecoder:
def decode_batch(self, filenames): def decode_batch(self, filenames):
"""Process multiple filenames in batch.""" """Process multiple filenames in batch."""
return [self.decode(filename) for filename in filenames] return [self.decode(filename) for filename in filenames]

View File

@@ -0,0 +1,365 @@
"""
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
# Create a mock DocumentManager to avoid database dependency
class MockDatabaseManager:
pass
doc_manager = DocumentManager.__new__(DocumentManager)
doc_manager.database_manager = MockDatabaseManager()
# 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
# Create a mock DocumentManager to avoid database dependency
class MockDatabaseManager:
pass
doc_manager = DocumentManager.__new__(DocumentManager)
doc_manager.database_manager = MockDatabaseManager()
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
# Create a mock DocumentManager to avoid database dependency
class MockDatabaseManager:
pass
doc_manager = DocumentManager.__new__(DocumentManager)
doc_manager.database_manager = MockDatabaseManager()
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
# Create a mock DocumentManager to avoid database dependency
class MockDatabaseManager:
pass
doc_manager = DocumentManager.__new__(DocumentManager)
doc_manager.database_manager = MockDatabaseManager()
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.*{{', # Single brace followed by double (incorrect)
r'}} else if.*}(?!})', # Double brace followed by single closing (incorrect)
]
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
# Create a mock DocumentManager to avoid database dependency
class MockDatabaseManager:
pass
doc_manager = DocumentManager.__new__(DocumentManager)
doc_manager.database_manager = MockDatabaseManager()
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
# Create a mock DocumentManager to avoid database dependency
class MockDatabaseManager:
pass
doc_manager = DocumentManager.__new__(DocumentManager)
doc_manager.database_manager = MockDatabaseManager()
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
# Create a mock DocumentManager to avoid database dependency
class MockDatabaseManager:
pass
doc_manager = DocumentManager.__new__(DocumentManager)
doc_manager.database_manager = MockDatabaseManager()
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
# Create a mock DocumentManager to avoid database dependency
class MockDatabaseManager:
pass
doc_manager = DocumentManager.__new__(DocumentManager)
doc_manager.database_manager = MockDatabaseManager()
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
# Create a mock DocumentManager to avoid database dependency
class MockDatabaseManager:
pass
doc_manager = DocumentManager.__new__(DocumentManager)
doc_manager.database_manager = MockDatabaseManager()
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
# Create a mock DocumentManager to avoid database dependency
class MockDatabaseManager:
pass
doc_manager = DocumentManager.__new__(DocumentManager)
doc_manager.database_manager = MockDatabaseManager()
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' in the JavaScript
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_save_functionality_javascript_presence(self):
"""Test that the save functionality JavaScript is properly included."""
from markitect.document_manager import DocumentManager
# Create a mock DocumentManager to avoid database dependency
class MockDatabaseManager:
pass
doc_manager = DocumentManager.__new__(DocumentManager)
doc_manager.database_manager = MockDatabaseManager()
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"])

283
tools/validate_js_syntax.py Executable file
View File

@@ -0,0 +1,283 @@
#!/usr/bin/env python3
"""
JavaScript syntax validation tool for MarkiTect build process.
This tool validates that all generated JavaScript in edit mode is syntactically correct,
preventing the regression that caused edit mode to fail completely.
"""
import subprocess
import tempfile
import sys
import re
from pathlib import Path
from typing import List, Tuple, Optional
class JavaScriptValidator:
"""Validates JavaScript syntax in MarkiTect templates."""
def __init__(self):
self.errors = []
self.warnings = []
def validate_document_manager_templates(self) -> bool:
"""Validate JavaScript templates in DocumentManager."""
try:
# Import the template generation method directly to avoid database dependency
from markitect.document_manager import DocumentManager
# Create a mock DocumentManager to access the template method
class MockDatabaseManager:
pass
doc_manager = DocumentManager.__new__(DocumentManager)
doc_manager.database_manager = MockDatabaseManager()
# Test various configurations
test_cases = [
{
'title': 'Basic Edit Mode',
'markdown': '# Test\n\nBasic content.',
'edit_mode': True,
'theme': 'github'
},
{
'title': 'Complex Content',
'markdown': '''# Header 1
## Header 2
Paragraph with **bold** and *italic* text.
- List item 1
- List item 2
1. Numbered item
2. Another item
> Blockquote text
```python
code block
```
Final paragraph.''',
'edit_mode': True,
'theme': 'dark'
},
{
'title': 'Special Characters',
'markdown': "# Test 'quotes' and \"double quotes\"\n\nContent with $pecial ch@racters & symbols!",
'edit_mode': True,
'theme': 'github'
}
]
all_valid = True
for test_case in test_cases:
print(f"Validating: {test_case['title']}")
html_content = doc_manager._generate_html_template(
title=test_case['title'],
markdown_content=test_case['markdown'],
edit_mode=test_case['edit_mode'],
editor_theme=test_case.get('theme', 'github')
)
is_valid, errors = self._validate_html_javascript(html_content, test_case['title'])
if not is_valid:
all_valid = False
self.errors.extend(errors)
else:
print(f"{test_case['title']} - JavaScript syntax valid")
return all_valid
except Exception as e:
self.errors.append(f"Failed to validate document manager templates: {e}")
return False
def _validate_html_javascript(self, html_content: str, context: str) -> Tuple[bool, List[str]]:
"""Extract and validate JavaScript from HTML content."""
errors = []
# Extract all JavaScript blocks
js_blocks = re.findall(r'<script[^>]*>(.*?)</script>', html_content, re.DOTALL)
if not js_blocks:
errors.append(f"{context}: No JavaScript blocks found")
return False, errors
for i, js_content in enumerate(js_blocks):
# Skip empty blocks or blocks with only external src
if not js_content.strip() or 'src=' in js_content:
continue
is_valid, js_errors = self._validate_javascript_syntax(js_content, f"{context} block {i+1}")
if not is_valid:
errors.extend(js_errors)
return len(errors) == 0, errors
def _validate_javascript_syntax(self, js_content: str, context: str) -> Tuple[bool, List[str]]:
"""Validate JavaScript syntax using Node.js."""
errors = []
try:
with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False) as f:
f.write(js_content)
temp_js_path = f.name
# Use Node.js to check syntax
result = subprocess.run(
['node', '-c', temp_js_path],
capture_output=True,
text=True,
timeout=10
)
if result.returncode != 0:
errors.append(f"{context}: JavaScript syntax error - {result.stderr.strip()}")
# Add helpful debugging info
lines = js_content.split('\n')
for line_num, line in enumerate(lines[:20], 1): # Show first 20 lines
if line.strip():
print(f" Line {line_num}: {line[:100]}") # First 100 chars
Path(temp_js_path).unlink(missing_ok=True)
except subprocess.TimeoutExpired:
errors.append(f"{context}: JavaScript validation timeout")
except FileNotFoundError:
errors.append(f"{context}: Node.js not available for validation")
except Exception as e:
errors.append(f"{context}: Validation error - {e}")
return len(errors) == 0, errors
def check_common_issues(self) -> bool:
"""Check for common JavaScript issues that have caused problems."""
try:
from markitect.document_manager import DocumentManager
# Create a mock DocumentManager to access the template method
class MockDatabaseManager:
pass
doc_manager = DocumentManager.__new__(DocumentManager)
doc_manager.database_manager = MockDatabaseManager()
html_content = doc_manager._generate_html_template(
title="Issue Check",
markdown_content="# Test\n\nTest content.",
edit_mode=True
)
js_match = re.search(r'<script>(.*?)</script>', html_content, re.DOTALL)
if not js_match:
self.errors.append("No JavaScript found for common issues check")
return False
js_content = js_match.group(1)
issues_found = False
# Check for specific issues that have caused problems
issue_patterns = [
(r"'\s*\n\s*'", "Broken string literal across lines"),
(r'"\s*\n\s*"', "Broken string literal across lines"),
(r'} else if.*{{.*}} else if.*{[^{]', "Inconsistent brace escaping"),
(r'\$\{\{.*?\}\}', "Double-escaped template literals"),
(r'reconstructed \+= .*\'\n', "Unescaped newline in string concatenation"),
]
for pattern, description in issue_patterns:
matches = re.findall(pattern, js_content)
if matches:
self.errors.append(f"Found {description}: {matches[:3]}...") # Show first 3 matches
issues_found = True
# Check for required elements
required_elements = [
'MarkitectEditor',
'updateStatus',
'makeContentEditable',
'DOMContentLoaded'
]
for element in required_elements:
if element not in js_content:
self.errors.append(f"Missing required element: {element}")
issues_found = True
return not issues_found
except Exception as e:
self.errors.append(f"Failed to check common issues: {e}")
return False
def validate_all(self) -> bool:
"""Run all validation checks."""
print("🔍 Validating JavaScript syntax in MarkiTect templates...")
all_checks_passed = True
# Run all validation checks
checks = [
("Document Manager Templates", self.validate_document_manager_templates),
("Common Issues", self.check_common_issues),
]
for check_name, check_func in checks:
print(f"\n📋 Running {check_name} check...")
try:
if not check_func():
all_checks_passed = False
print(f"{check_name} check failed")
else:
print(f"{check_name} check passed")
except Exception as e:
all_checks_passed = False
self.errors.append(f"{check_name} check failed with exception: {e}")
print(f"{check_name} check failed with exception")
return all_checks_passed
def report_results(self) -> None:
"""Print validation results."""
print("\n" + "="*60)
print("JavaScript Validation Results")
print("="*60)
if self.errors:
print(f"\n{len(self.errors)} Error(s) Found:")
for i, error in enumerate(self.errors, 1):
print(f" {i}. {error}")
if self.warnings:
print(f"\n⚠️ {len(self.warnings)} Warning(s):")
for i, warning in enumerate(self.warnings, 1):
print(f" {i}. {warning}")
if not self.errors and not self.warnings:
print("\n✅ All JavaScript validation checks passed!")
print("\n" + "="*60)
def main():
"""Main validation function."""
validator = JavaScriptValidator()
success = validator.validate_all()
validator.report_results()
return 0 if success else 1
if __name__ == "__main__":
exit_code = main()
sys.exit(exit_code)