"""Extensive tests for the custom SVG exporter.""" from __future__ import annotations import pytest from webterm.svg_exporter import ( ANSI_COLORS, DEFAULT_BG, DEFAULT_FG, CharData, _color_to_hex, _escape_xml, render_terminal_svg, ) class TestColorToHex: """Tests for _color_to_hex function.""" @pytest.mark.parametrize( ("color", "is_foreground", "expected"), [ ("default", True, DEFAULT_FG), ("default", False, DEFAULT_BG), ("#ff0000", True, "#ff0000"), ("#123456", True, "#123456"), ("#AABBCC", True, "#AABBCC"), ("ff0000", True, "#ff0000"), ("123456", True, "#123456"), ("AABBCC", True, "#AABBCC"), ("ff8700", True, "#ff8700"), ("red", True, ANSI_COLORS["red"]), ("green", True, ANSI_COLORS["green"]), ("blue", True, ANSI_COLORS["blue"]), ("white", True, ANSI_COLORS["white"]), ("black", True, ANSI_COLORS["black"]), ("brightred", True, ANSI_COLORS["brightred"]), ("brightgreen", True, ANSI_COLORS["brightgreen"]), ("brightblue", True, ANSI_COLORS["brightblue"]), ("RED", True, ANSI_COLORS["red"]), ("Green", True, ANSI_COLORS["green"]), ("BRIGHTBLUE", True, ANSI_COLORS["brightblue"]), ("unknowncolor", True, DEFAULT_FG), ("unknowncolor", False, DEFAULT_BG), ("rgb(255,0,0)", True, DEFAULT_FG), ("rgb(0,255,0)", False, DEFAULT_BG), ("gray", True, ANSI_COLORS["gray"]), ("grey", True, ANSI_COLORS["grey"]), ("lightgray", True, ANSI_COLORS["lightgray"]), ("lightgrey", True, ANSI_COLORS["lightgrey"]), ], ) def test_color_to_hex(self, color: str, is_foreground: bool, expected: str) -> None: """Color conversion covers named/hex/default cases.""" assert _color_to_hex(color, is_foreground=is_foreground) == expected def test_color_to_hex_uses_palette_defaults(self) -> None: palette = {"red": "#123456"} assert _color_to_hex( "default", is_foreground=True, palette=palette, default_fg="#111111", default_bg="#222222", ) == "#111111" assert _color_to_hex( "default", is_foreground=False, palette=palette, default_fg="#111111", default_bg="#222222", ) == "#222222" assert _color_to_hex( "red", is_foreground=True, palette=palette, default_fg="#111111", default_bg="#222222", ) == "#123456" class TestEscapeXml: """Tests for XML escaping.""" @pytest.mark.parametrize( ("input_str", "expected"), [ ("hello world", "hello world"), ("<", "<"), ("a < b", "a < b"), (">", ">"), ("a > b", "a > b"), ("&", "&"), ("a & b", "a & b"), ('"', """), ("'", "'"), ('', "<script>"alert"</script>"), ("你好世界", "你好世界"), ("🎉🚀", "🎉🚀"), ], ) def test_escape_xml(self, input_str: str, expected: str) -> None: """Escape XML special chars and preserve unicode.""" assert _escape_xml(input_str) == expected class TestRenderTerminalSvg: """Tests for render_terminal_svg function.""" def _char( self, data: str, fg: str = "default", bg: str = "default", bold: bool = False, italics: bool = False, underscore: bool = False, reverse: bool = False, ) -> CharData: """Helper to create CharData.""" return { "data": data, "fg": fg, "bg": bg, "bold": bold, "italics": italics, "underscore": underscore, "reverse": reverse, } def _make_buffer(self, rows: list[str]) -> list[list[CharData]]: """Create simple buffer from strings.""" return [[self._char(c) for c in row] for row in rows] def test_empty_buffer(self) -> None: """Empty buffer produces valid SVG.""" svg = render_terminal_svg([], width=80, height=24) assert svg.startswith("") assert 'xmlns="http://www.w3.org/2000/svg"' in svg def test_css_properties(self) -> None: """SVG includes essential CSS properties for proper rendering.""" svg = render_terminal_svg([], width=80, height=24) # Check for legibility optimization assert "text-rendering: optimizeLegibility" in svg # Check for monospace font assert "font-family:" in svg assert "monospace" in svg # Check for pre whitespace handling assert "white-space: pre" in svg def test_buffer_with_empty_rows(self) -> None: """Buffer with rows containing only empty cells produces valid SVG.""" # Row with only empty placeholder cells (no actual characters) buffer = [ [self._char("") for _ in range(10)], # Empty row [self._char("A")], # Normal row [self._char("") for _ in range(10)], # Another empty row ] svg = render_terminal_svg(buffer, width=10, height=3) assert svg.startswith(" None: """Buffer with truly empty row (empty list) is handled.""" buffer = [ [], # Truly empty row (no cells at all) [self._char("B")], # Normal row ] svg = render_terminal_svg(buffer, width=10, height=2) assert svg.startswith("B" in svg def test_basic_text_output(self) -> None: """Basic text is included in SVG (each char with explicit x position).""" buffer = self._make_buffer(["Hello, World!"]) svg = render_terminal_svg(buffer, width=80, height=24) # Each character is rendered individually with explicit x assert ">H" in svg assert ">e" in svg assert ">!" in svg def test_multiline_output(self) -> None: """Multiple lines render correctly.""" buffer = self._make_buffer(["Line 1", "Line 2", "Line 3"]) svg = render_terminal_svg(buffer, width=80, height=24) # Check for characters from each line assert ">L" in svg assert ">1" in svg assert ">2" in svg assert ">3" in svg # Should have 3 text elements assert svg.count("&test"]) svg = render_terminal_svg(buffer, width=80, height=24) assert "<" in svg # < escaped assert ">" in svg # > escaped assert "&" in svg # & escaped assert "