"""Extensive tests for the custom SVG exporter."""
from __future__ import annotations
from textual_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."""
def test_default_foreground(self) -> None:
"""Default color returns DEFAULT_FG for foreground."""
assert _color_to_hex("default", is_foreground=True) == DEFAULT_FG
def test_default_background(self) -> None:
"""Default color returns DEFAULT_BG for background."""
assert _color_to_hex("default", is_foreground=False) == DEFAULT_BG
def test_hex_color_passthrough(self) -> None:
"""Hex colors pass through unchanged."""
assert _color_to_hex("#ff0000") == "#ff0000"
assert _color_to_hex("#123456") == "#123456"
assert _color_to_hex("#AABBCC") == "#AABBCC"
def test_hex_color_without_hash(self) -> None:
"""Hex colors without # prefix (pyte's 256-color/truecolor) get # added."""
assert _color_to_hex("ff0000") == "#ff0000"
assert _color_to_hex("123456") == "#123456"
assert _color_to_hex("AABBCC") == "#AABBCC"
assert _color_to_hex("ff8700") == "#ff8700" # Common 256-color orange
def test_named_colors(self) -> None:
"""Named ANSI colors map correctly."""
assert _color_to_hex("red") == ANSI_COLORS["red"]
assert _color_to_hex("green") == ANSI_COLORS["green"]
assert _color_to_hex("blue") == ANSI_COLORS["blue"]
assert _color_to_hex("white") == ANSI_COLORS["white"]
assert _color_to_hex("black") == ANSI_COLORS["black"]
def test_bright_colors(self) -> None:
"""Bright color variants map correctly."""
assert _color_to_hex("brightred") == ANSI_COLORS["brightred"]
assert _color_to_hex("brightgreen") == ANSI_COLORS["brightgreen"]
assert _color_to_hex("brightblue") == ANSI_COLORS["brightblue"]
def test_case_insensitive(self) -> None:
"""Color names are case-insensitive."""
assert _color_to_hex("RED") == ANSI_COLORS["red"]
assert _color_to_hex("Green") == ANSI_COLORS["green"]
assert _color_to_hex("BRIGHTBLUE") == ANSI_COLORS["brightblue"]
def test_unknown_color_returns_default(self) -> None:
"""Unknown color names return default."""
assert _color_to_hex("unknowncolor", is_foreground=True) == DEFAULT_FG
assert _color_to_hex("unknowncolor", is_foreground=False) == DEFAULT_BG
def test_rgb_format_returns_default(self) -> None:
"""RGB format falls back to default (not commonly used in terminals)."""
assert _color_to_hex("rgb(255,0,0)", is_foreground=True) == DEFAULT_FG
assert _color_to_hex("rgb(0,255,0)", is_foreground=False) == DEFAULT_BG
def test_gray_aliases(self) -> None:
"""Gray/grey aliases work."""
assert _color_to_hex("gray") == ANSI_COLORS["gray"]
assert _color_to_hex("grey") == ANSI_COLORS["grey"]
assert _color_to_hex("lightgray") == ANSI_COLORS["lightgray"]
assert _color_to_hex("lightgrey") == ANSI_COLORS["lightgrey"]
class TestEscapeXml:
"""Tests for XML escaping."""
def test_no_special_chars(self) -> None:
"""Plain text passes through unchanged."""
assert _escape_xml("hello world") == "hello world"
def test_less_than(self) -> None:
"""Less than is escaped."""
assert _escape_xml("<") == "<"
assert _escape_xml("a < b") == "a < b"
def test_greater_than(self) -> None:
"""Greater than is escaped."""
assert _escape_xml(">") == ">"
assert _escape_xml("a > b") == "a > b"
def test_ampersand(self) -> None:
"""Ampersand is escaped."""
assert _escape_xml("&") == "&"
assert _escape_xml("a & b") == "a & b"
def test_quotes(self) -> None:
"""Quotes are escaped."""
assert _escape_xml('"') == """
assert _escape_xml("'") == "'"
def test_mixed_special_chars(self) -> None:
"""Multiple special chars are all escaped."""
assert _escape_xml('') == (
"<script>"alert"</script>"
)
def test_unicode_preserved(self) -> None:
"""Unicode characters are preserved."""
assert _escape_xml("你好世界") == "你好世界"
assert _escape_xml("🎉🚀") == "🎉🚀"
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("