""" Unit tests for Phase 4: HTML Rendering Mode components. Tests cover: - SpaceRenderer base class - RenderConfig and ThemeConfig - MarkdownToHTMLRenderer - SpaceRenderingService - Cache integration """ import pytest from unittest.mock import Mock, MagicMock, patch from pathlib import Path from markitect.spaces.rendering import ( SpaceRenderer, RenderConfig, RenderResult, RenderFormat, ThemeConfig, CompositeRenderer, MarkdownToHTMLRenderer, HTMLRendererFactory, THEME_PROPERTIES, combine_theme_properties, SpaceRenderingService, SpaceRenderingServiceBuilder, ) from markitect.spaces.transclusion import ( RenderCache, ReferenceGraph, ) from markitect.spaces.events import EventBus, SpaceEventType class TestRenderFormat: """Tests for RenderFormat enum.""" def test_html_format(self): """Test HTML format value.""" assert RenderFormat.HTML.value == "html" def test_format_members(self): """Test all format members exist.""" assert RenderFormat.HTML assert RenderFormat.PDF assert RenderFormat.DOCX assert RenderFormat.LATEX class TestThemeConfig: """Tests for ThemeConfig dataclass.""" def test_default_values(self): """Test default configuration.""" config = ThemeConfig() assert config.name == "default" assert config.layers == ["basic"] assert config.custom_css is None assert config.custom_properties == {} def test_custom_config(self): """Test custom configuration.""" config = ThemeConfig( name="custom", layers=["github", "dark"], custom_css="body { color: red; }", custom_properties={"max_width": "1000px"}, ) assert config.name == "custom" assert config.layers == ["github", "dark"] assert "color: red" in config.custom_css assert config.custom_properties["max_width"] == "1000px" class TestRenderConfig: """Tests for RenderConfig dataclass.""" def test_default_values(self): """Test default configuration.""" config = RenderConfig() assert config.format == RenderFormat.HTML assert config.include_toc is False assert config.highlight_code is True assert config.sanitize_html is True assert config.link_target_blank is True def test_custom_config(self): """Test custom configuration.""" theme = ThemeConfig(name="github") config = RenderConfig( format=RenderFormat.HTML, theme=theme, include_toc=True, image_max_width="800px", ) assert config.theme.name == "github" assert config.include_toc is True assert config.image_max_width == "800px" class TestRenderResult: """Tests for RenderResult dataclass.""" def test_creation(self): """Test result creation.""" result = RenderResult( content="

Test

", format=RenderFormat.HTML, content_hash="abc123", source_hash="def456", document_id="doc-1", space_id="space-1", ) assert result.content == "

Test

" assert result.format == RenderFormat.HTML assert result.document_id == "doc-1" assert result.space_id == "space-1" assert result.dependencies == set() def test_with_dependencies(self): """Test result with dependencies.""" result = RenderResult( content="

Test

", format=RenderFormat.HTML, content_hash="abc", source_hash="def", document_id="doc-1", space_id="space-1", dependencies={"doc-2", "doc-3"}, ) assert len(result.dependencies) == 2 assert "doc-2" in result.dependencies def test_compute_hash(self): """Test hash computation.""" hash1 = RenderResult.compute_hash("test content") hash2 = RenderResult.compute_hash("test content") hash3 = RenderResult.compute_hash("different content") assert hash1 == hash2 assert hash1 != hash3 assert len(hash1) == 16 # SHA256 truncated to 16 chars class TestCombineThemeProperties: """Tests for theme property combination.""" def test_single_layer(self): """Test single theme layer.""" props = combine_theme_properties(["default"]) assert "font_family" in props assert "body_color" in props def test_multiple_layers(self): """Test multiple theme layers.""" props = combine_theme_properties(["default", "github"]) # GitHub layer should override default assert props["max_width"] == "980px" def test_unknown_layer(self): """Test with unknown layer.""" props = combine_theme_properties(["nonexistent"]) assert props == {} def test_empty_layers(self): """Test with no layers.""" props = combine_theme_properties([]) assert props == {} class TestMarkdownToHTMLRenderer: """Tests for MarkdownToHTMLRenderer.""" def test_supported_formats(self): """Test supported formats.""" renderer = MarkdownToHTMLRenderer() assert RenderFormat.HTML in renderer.supported_formats assert len(renderer.supported_formats) == 1 def test_simple_render(self): """Test simple markdown rendering.""" renderer = MarkdownToHTMLRenderer() result = renderer.render( content="# Hello World\n\nThis is a paragraph.", document_id="doc-1", space_id="space-1", ) assert result.format == RenderFormat.HTML assert "" in result.content assert result.document_id == "doc-1" assert result.space_id == "space-1" def test_render_with_code(self): """Test rendering code blocks.""" renderer = MarkdownToHTMLRenderer() markdown = """ # Code Example ```python def hello(): print("Hello") ``` """ result = renderer.render( content=markdown, document_id="doc-1", space_id="space-1", ) assert "
" in result.content or "" in result.content

    def test_render_with_links(self):
        """Test link rendering with target blank."""
        renderer = MarkdownToHTMLRenderer()
        result = renderer.render(
            content="Visit [Google](https://google.com)",
            document_id="doc-1",
            space_id="space-1",
        )

        assert "https://google.com" in result.content
        assert 'target="_blank"' in result.content

    def test_render_with_toc(self):
        """Test table of contents generation."""
        config = RenderConfig(include_toc=True)
        renderer = MarkdownToHTMLRenderer(config)

        markdown = """
# Main Title

## Section 1

Content here.

## Section 2

More content.
"""
        result = renderer.render(
            content=markdown,
            document_id="doc-1",
            space_id="space-1",
        )

        assert 'class="toc"' in result.content
        assert "Contents" in result.content

    def test_render_with_dependencies(self):
        """Test dependencies are tracked."""
        renderer = MarkdownToHTMLRenderer()
        result = renderer.render(
            content="# Test",
            document_id="doc-1",
            space_id="space-1",
            dependencies={"dep-1", "dep-2"},
        )

        assert len(result.dependencies) == 2
        assert "dep-1" in result.dependencies

    def test_content_hash_computation(self):
        """Test that hashes are computed."""
        renderer = MarkdownToHTMLRenderer()
        result = renderer.render(
            content="# Test",
            document_id="doc-1",
            space_id="space-1",
        )

        assert result.content_hash
        assert result.source_hash
        assert len(result.content_hash) == 16

    def test_different_themes(self):
        """Test different theme configurations."""
        github_config = RenderConfig(theme=ThemeConfig(name="github", layers=["github"]))
        dark_config = RenderConfig(theme=ThemeConfig(name="dark", layers=["dark"]))

        github_renderer = MarkdownToHTMLRenderer(github_config)
        dark_renderer = MarkdownToHTMLRenderer(dark_config)

        github_result = github_renderer.render("# Test", "doc", "space")
        dark_result = dark_renderer.render("# Test", "doc", "space")

        # Results should be different due to different themes
        assert github_result.content != dark_result.content
        assert "#24292e" in github_result.content  # GitHub body color
        assert "#c9d1d9" in dark_result.content  # Dark mode text color


class TestHTMLRendererFactory:
    """Tests for HTMLRendererFactory."""

    def test_create_default(self):
        """Test default renderer creation."""
        renderer = HTMLRendererFactory.create_default()
        assert isinstance(renderer, MarkdownToHTMLRenderer)

    def test_create_github_style(self):
        """Test GitHub-style renderer."""
        renderer = HTMLRendererFactory.create_github_style()
        assert renderer.config.theme.name == "github"

    def test_create_academic_style(self):
        """Test academic-style renderer."""
        renderer = HTMLRendererFactory.create_academic_style()
        assert renderer.config.theme.name == "academic"
        assert renderer.config.include_toc is True

    def test_create_minimal_style(self):
        """Test minimal-style renderer."""
        renderer = HTMLRendererFactory.create_minimal_style()
        assert renderer.config.theme.name == "minimal"

    def test_create_dark_mode(self):
        """Test dark mode renderer."""
        renderer = HTMLRendererFactory.create_dark_mode()
        assert renderer.config.theme.name == "dark"


class TestCompositeRenderer:
    """Tests for CompositeRenderer."""

    def test_register_renderer(self):
        """Test registering a renderer."""
        composite = CompositeRenderer()
        html_renderer = MarkdownToHTMLRenderer()

        composite.register(html_renderer)

        assert composite.get_renderer(RenderFormat.HTML) is html_renderer

    def test_supported_formats(self):
        """Test listing supported formats."""
        composite = CompositeRenderer()
        html_renderer = MarkdownToHTMLRenderer()

        composite.register(html_renderer)

        formats = composite.supported_formats()
        assert RenderFormat.HTML in formats

    def test_render_via_composite(self):
        """Test rendering through composite."""
        composite = CompositeRenderer()
        composite.register(MarkdownToHTMLRenderer())

        result = composite.render(
            content="# Test",
            document_id="doc-1",
            space_id="space-1",
            format=RenderFormat.HTML,
        )

        assert result.format == RenderFormat.HTML
        assert "bold" in result.content or "bold" in result.content

    def test_code_blocks(self):
        """Test code block parsing."""
        renderer = MarkdownToHTMLRenderer()
        markdown = "```python\nprint('hello')\n```"
        result = renderer.render(markdown, "doc", "space")

        assert "" in result.content or "Item" in result.content

    def test_blockquotes(self):
        """Test blockquote parsing."""
        renderer = MarkdownToHTMLRenderer()
        markdown = "> This is a quote"
        result = renderer.render(markdown, "doc", "space")

        assert "