Newer
Older
navi-1 / tests / clients / test_message_renderers.py
"""Tests for user/assistant message renderers (markdown in Navi's answers)."""

from __future__ import annotations

from rich.console import Console

from clients.terminal.tui.renderers.message import (
    AssistantMessageRenderer,
    UserMessageRenderer,
)
from clients.terminal.tui.themes import set_active_theme


def _render_text(renderable) -> str:
    console = Console(record=True, width=80, force_terminal=True, color_system=None)
    console.print(renderable)
    return console.export_text()


def test_assistant_message_renders_markdown_headings_and_code() -> None:
    """Navi's answer is parsed as markdown, not shown as raw syntax."""
    set_active_theme("gnexus-dark")
    renderer = AssistantMessageRenderer()
    msg = {
        "type": "assistant_message",
        "content": "## Result\n\nThe value is `42`.\n\n```python\nx = 1\n```",
    }
    out = _render_text(renderer.render(msg))
    # Heading renders (not as a literal "## Result" line).
    assert "Result" in out
    assert "##" not in out
    # Inline code content present, fenced code present.
    assert "42" in out
    assert "x = 1" in out
    # Backticks are consumed by the markdown renderer, not echoed literally.
    assert "```" not in out


def test_assistant_message_panel_titled_navi() -> None:
    set_active_theme("gnexus-dark")
    renderer = AssistantMessageRenderer()
    panel = renderer.render({"type": "assistant_message", "content": "hi"})
    assert str(panel.title) == "Navi"


def test_assistant_message_empty_content_renders_without_error() -> None:
    set_active_theme("gnexus-dark")
    renderer = AssistantMessageRenderer()
    out = _render_text(renderer.render({"type": "assistant_message", "content": ""}))
    assert "Navi" in out


def test_assistant_renderer_does_not_accept_user_message() -> None:
    assert not AssistantMessageRenderer().accepts({"type": "user_message", "content": "x"})


def test_user_message_stays_plain_text() -> None:
    """User messages are NOT markdown-rendered — syntax shows literally."""
    set_active_theme("gnexus-dark")
    renderer = UserMessageRenderer()
    out = _render_text(
        renderer.render({"type": "user_message", "content": "## not a heading\n**bold**"})
    )
    # Raw markdown syntax is preserved for user input.
    assert "## not a heading" in out
    assert "**bold**" in out
    assert str(renderer.render({"type": "user_message", "content": "x"}).title) == "You"

# ─── reading mode (render_plain: borderless for raw-terminal copy) ────────────


def test_assistant_render_plain_returns_raw_markdown_without_borders() -> None:
    """In reading mode an assistant answer is raw markdown text, not a Panel.

    Rich Markdown would wrap fenced code blocks in their own Panels, leaking
    borders into a raw-terminal (Shift+drag + Ctrl+Shift+C) copy. render_plain
    returns the raw text so fences stay as ```…``` and nothing borders remain.
    """
    from rich.panel import Panel
    from rich.text import Text

    set_active_theme("gnexus-dark")
    renderer = AssistantMessageRenderer()
    r = renderer.render_plain(
        {"type": "assistant_message", "content": "## Result\n\n```python\nx = 1\n```"}
    )
    assert isinstance(r, Text)
    assert not isinstance(r, Panel)
    assert "## Result" in r.plain
    assert "```python" in r.plain
    assert "x = 1" in r.plain


def test_user_render_plain_strips_panel_to_text() -> None:
    """User message render_plain is the bare text, no bubble border/title."""
    from rich.panel import Panel
    from rich.text import Text

    set_active_theme("gnexus-dark")
    renderer = UserMessageRenderer()
    r = renderer.render_plain({"type": "user_message", "content": "Hello world"})
    assert isinstance(r, Text)
    assert not isinstance(r, Panel)
    assert r.plain == "Hello world"