""" 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