Files
markitect-main/tests/test_modular_theme_system.py
tegwick d1e129c9b8 feat: implement modular theme system with file-based theme organization
Transform theme system from large inline dictionaries to maintainable YAML files:

**Architecture:**
- File-based themes organized by scope: mode/, ui/, document/, branding/
- Dynamic theme loading with automatic discovery
- Hybrid system maintaining 100% backward compatibility
- Rich metadata support with theme documentation

**Implementation:**
- Created markitect/themes/ directory with organized structure
- Added ThemeRegistry for dynamic YAML theme loading
- Extracted ChatGPT and Substack themes to separate files
- Added mode themes (light.yaml, dark.yaml) as examples
- Integrated with existing LAYERED_THEMES system seamlessly

**Benefits:**
- Improved maintainability: each theme is a separate file
- Better collaboration: multiple contributors can work simultaneously
- Enhanced discoverability: clear organization shows available themes
- Rich documentation: each theme file includes design notes and metadata
- Schema validation potential with YAML format

**Quality Assurance:**
- Comprehensive 12-test suite for modular system (12/12 passing)
- Backward compatibility verified with existing 15 theme tests (15/15 passing)
- CLI integration tested and working with file-based themes
- Theme combination and scoping functionality preserved

**Files Created:**
- markitect/themes/__init__.py - Theme registry and dynamic loader
- markitect/themes/README.md - Complete documentation and usage guide
- markitect/themes/document/{chatgpt,substack}.yaml - Modular theme files
- markitect/themes/mode/{light,dark}.yaml - Mode theme examples
- tests/test_modular_theme_system.py - Comprehensive test coverage

Addresses maintainability concerns while preserving all existing functionality.
No breaking changes - all existing code, CLI commands, and API calls work unchanged.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 11:43:25 +01:00

222 lines
8.0 KiB
Python

"""
Tests for the Modular Theme System
This module tests the new file-based theme loading system that allows themes
to be defined in separate YAML files for better maintainability.
"""
import pytest
import tempfile
import os
from pathlib import Path
from unittest.mock import patch, MagicMock
import yaml
# Add project root to path for imports
import sys
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
class TestModularThemeSystem:
"""Test the modular theme file loading system."""
def test_theme_loader_imports_successfully(self):
"""Test that the theme loader can be imported."""
from markitect.themes import get_layered_themes, get_theme, list_themes
# Should be able to import without errors
assert callable(get_layered_themes)
assert callable(get_theme)
assert callable(list_themes)
def test_themes_loaded_from_yaml_files(self):
"""Test that themes are loaded from YAML files."""
from markitect.themes import get_layered_themes
themes = get_layered_themes()
# Should have loaded our test themes
assert 'chatgpt' in themes
assert 'substack' in themes
assert 'light' in themes
assert 'dark' in themes
def test_theme_structure_validation(self):
"""Test that loaded themes have correct structure."""
from markitect.themes import get_theme
chatgpt_theme = get_theme('chatgpt')
# Should have correct structure
assert 'scope' in chatgpt_theme
assert 'properties' in chatgpt_theme
assert 'metadata' in chatgpt_theme
# Should have correct values
assert chatgpt_theme['scope'] == 'document'
assert 'font_family' in chatgpt_theme['properties']
assert 'Inter' in chatgpt_theme['properties']['font_family']
def test_backward_compatibility_with_layered_themes(self):
"""Test that modular themes work with existing LAYERED_THEMES system."""
from markitect.plugins.builtin.markdown_commands import LAYERED_THEMES
# Modular themes should be merged into LAYERED_THEMES
assert 'chatgpt' in LAYERED_THEMES
assert 'substack' in LAYERED_THEMES
# Should have expected structure
chatgpt = LAYERED_THEMES['chatgpt']
assert chatgpt['scope'] == 'document'
assert 'Inter' in chatgpt['properties']['font_family']
def test_theme_scoping_system(self):
"""Test that themes are properly organized by scope."""
from markitect.themes import theme_registry
# Load all themes
all_themes = theme_registry.load_themes()
# Check scope organization
document_themes = [name for name, data in all_themes.items()
if data.get('scope') == 'document']
mode_themes = [name for name, data in all_themes.items()
if data.get('scope') == 'mode']
assert 'chatgpt' in document_themes
assert 'substack' in document_themes
assert 'light' in mode_themes
assert 'dark' in mode_themes
def test_theme_listing_functionality(self):
"""Test theme listing and filtering capabilities."""
from markitect.themes import list_themes
# Should list all themes
all_themes = list_themes()
assert 'chatgpt' in all_themes
assert 'substack' in all_themes
assert 'light' in all_themes
# Should filter by scope
document_themes = list_themes(scope='document')
mode_themes = list_themes(scope='mode')
assert 'chatgpt' in document_themes
assert 'substack' in document_themes
assert 'chatgpt' not in mode_themes
assert 'light' in mode_themes
assert 'light' not in document_themes
def test_theme_metadata_preservation(self):
"""Test that theme metadata is properly preserved."""
from markitect.themes import get_theme
chatgpt_theme = get_theme('chatgpt')
metadata = chatgpt_theme['metadata']
# Should have metadata
assert 'name' in metadata
assert 'description' in metadata
assert 'author' in metadata
assert 'version' in metadata
assert 'file' in metadata
# Should have correct values
assert metadata['name'] == 'chatgpt'
assert 'ChatGPT' in metadata['description']
assert metadata['author'] == 'Claude Code'
assert metadata['version'] == '1.0.0'
def test_cli_integration_with_modular_themes(self):
"""Test CLI integration works with file-based themes."""
from markitect.cli import cli
from click.testing import CliRunner
# Create test content
test_content = "# Modular Theme Test\n\nTesting file-based theme loading."
with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f:
f.write(test_content)
input_file = f.name
try:
output_file = input_file.replace('.md', '.html')
runner = CliRunner()
result = runner.invoke(cli, [
'md-render',
input_file,
'--output', output_file,
'--theme', 'chatgpt'
])
assert result.exit_code == 0
# Verify output file exists and contains theme styling
with open(output_file, 'r') as f:
html_content = f.read()
assert 'Inter' in html_content # ChatGPT font
assert '#10a37f' in html_content # ChatGPT accent color
finally:
# Clean up
for file_path in [input_file, output_file]:
if os.path.exists(file_path):
os.unlink(file_path)
def test_theme_combination_still_works(self):
"""Test that theme combinations work with modular themes."""
from markitect.plugins.builtin.markdown_commands import parse_theme_string, combine_theme_properties
# Test combining themes that include file-based ones
theme_list = parse_theme_string("light,standard,chatgpt")
combined_styles = combine_theme_properties(theme_list)
# Should include properties from all themes
assert 'body_background' in combined_styles # From light
assert 'font_family' in combined_styles # From chatgpt
assert 'Inter' in combined_styles['font_family'] # ChatGPT font
assert combined_styles['accent_color'] == '#10a37f' # ChatGPT green
def test_fallback_to_inline_themes(self):
"""Test that system falls back gracefully if file loading fails."""
from markitect.plugins.builtin.markdown_commands import LAYERED_THEMES
# Even if some themes are in files and some inline, should have both
# We know 'standard' is still inline, 'chatgpt' is in files
assert 'standard' in LAYERED_THEMES # Inline theme
assert 'chatgpt' in LAYERED_THEMES # File theme
# Both should have proper structure
assert 'scope' in LAYERED_THEMES['standard']
assert 'scope' in LAYERED_THEMES['chatgpt']
def test_theme_reload_functionality(self):
"""Test that themes can be reloaded during runtime."""
from markitect.themes import reload_themes, get_theme
# Get initial theme
initial_theme = get_theme('chatgpt')
assert initial_theme is not None
# Reload themes
reload_themes()
# Should still be able to get theme after reload
reloaded_theme = get_theme('chatgpt')
assert reloaded_theme is not None
assert reloaded_theme['scope'] == 'document'
def test_yaml_error_handling(self):
"""Test that YAML parsing errors are handled gracefully."""
from markitect.themes import theme_registry
# Should not crash even if there are YAML errors
# (This tests the try/except blocks in the theme loader)
themes = theme_registry.load_themes()
# Should still load valid themes even if some have errors
assert isinstance(themes, dict)
assert len(themes) > 0