Files
markitect-main/tests/test_clean_architecture.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

247 lines
9.9 KiB
Python

"""
Test suite for the new clean architecture implementation
Tests the JSON configuration interface and separation of concerns
"""
import pytest
import tempfile
import json
from pathlib import Path
from markitect.clean_document_manager import CleanDocumentManager
class TestCleanArchitecture:
"""Test suite for clean JavaScript-Python separation"""
def setup_method(self):
"""Setup for each test"""
self.manager = CleanDocumentManager()
def test_clean_edit_mode_json_configuration(self):
"""Test that edit mode uses clean JSON configuration interface"""
test_markdown = '''# Test Document
## Section with Problematic Content
```python
script = f"""
function test() {
console.log("Hello {name}");
}
"""
```
This content has quotes that previously broke 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:
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()
# Test 1: Check for clean template usage
assert 'markitect-config' in html_content
assert 'type="application/json"' in html_content
# Test 2: Extract and validate JSON configuration
config_json = self.extract_config_json(html_content)
assert config_json is not None, "Configuration JSON not found"
config = json.loads(config_json)
# Test 3: Validate configuration structure
required_fields = ['markdownContent', 'mode', 'theme', 'originalFilename']
for field in required_fields:
assert field in config, f"Required field '{field}' missing from configuration"
# Test 4: Check that problematic content is properly escaped
assert 'script = f"""' in config['markdownContent'] # Should be in JSON
assert '"""' not in html_content.split('markitect-config')[1].split('</script>')[0], "Unescaped quotes in HTML"
def test_clean_architecture_no_python_js_mixing(self):
"""Test that no Python code generates JavaScript strings"""
test_markdown = "# Simple Test\n\nBasic content."
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 1: No direct JavaScript variable assignments from Python
problematic_patterns = [
'const markdownContent = "', # Old way
'const markdownContentWithDogtag = "', # Old way
'var markdownContent = "',
'let markdownContent = "'
]
for pattern in problematic_patterns:
assert pattern not in html_content, f"Found problematic pattern: {pattern}"
# Test 2: Configuration should be in JSON script tag only
config_sections = html_content.count('markitect-config')
assert config_sections >= 2, f"Expected at least 2 config references (opening and closing), found {config_sections}"
# Test 3: JavaScript files should be embedded inline (no external src attributes)
js_components = [
'config-loader',
'section-manager',
'dom-renderer'
]
for component in js_components:
# Check that the component JavaScript is embedded, not referenced externally
assert f'src="js/' not in html_content, "Found external JavaScript references - should be embedded"
# Check that components are embedded inline
assert '{js_config_loader}' not in html_content, "Template placeholder not replaced"
assert 'class MarkitectConfig' in html_content, "Config loader not embedded"
assert 'class SectionManager' in html_content, "Section manager not embedded"
def test_configuration_interface_completeness(self):
"""Test that all required data is passed through the configuration interface"""
test_markdown = "# Config Test\n\nTesting configuration completeness."
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,
editor_theme='dark',
keyboard_shortcuts=False
)
assert result['success'] is True
html_content = Path(html_file.name).read_text()
config_json = self.extract_config_json(html_content)
config = json.loads(config_json)
# Test configuration completeness
expected_config = {
'markdownContent': test_markdown,
'mode': 'edit',
'theme': 'dark',
'keyboardShortcuts': False,
'autosave': False,
'sections': True,
'base64References': {}
}
for key, expected_value in expected_config.items():
assert key in config, f"Configuration missing key: {key}"
if key == 'markdownContent':
assert config[key] == expected_value, f"Configuration {key} value mismatch"
def test_insert_mode_configuration(self):
"""Test insert mode specific configuration"""
test_markdown = "# Insert Mode 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,
insert_mode=True
)
assert result['success'] is True
html_content = Path(html_file.name).read_text()
# Check body class
assert 'class="markitect-insert-mode"' in html_content
# Check configuration
config_json = self.extract_config_json(html_content)
config = json.loads(config_json)
assert config['mode'] == 'insert'
assert 'restrictedHeadingLevels' in config
assert config['restrictedHeadingLevels'] == [1, 2, 3]
def test_static_vs_edit_mode_separation(self):
"""Test that static mode and edit mode use different templates"""
test_markdown = "# Mode Test\n\nTesting template separation."
# Test static mode
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 static_file:
static_result = self.manager.render_file(
input_file=md_file.name,
output_file=static_file.name,
edit_mode=False
)
static_content = Path(static_file.name).read_text()
# Static mode should NOT have configuration interface
assert 'markitect-config' not in static_content
assert 'application/json' not in static_content
# Test edit mode
with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False) as edit_file:
edit_result = self.manager.render_file(
input_file=md_file.name,
output_file=edit_file.name,
edit_mode=True
)
edit_content = Path(edit_file.name).read_text()
# Edit mode should HAVE configuration interface
assert 'markitect-config' in edit_content
assert 'application/json' in edit_content
# Helper methods
def extract_config_json(self, html_content):
"""Extract JSON configuration from HTML"""
try:
# Find the config script tag
start_marker = 'id="markitect-config" type="application/json">'
end_marker = '</script>'
start_pos = html_content.find(start_marker)
if start_pos == -1:
return None
start_pos += len(start_marker)
end_pos = html_content.find(end_marker, start_pos)
if end_pos == -1:
return None
config_json = html_content[start_pos:end_pos].strip()
return config_json
except Exception as e:
print(f"Failed to extract config JSON: {e}")
return None