Fix SVG color handling and alignment issues
- Fix hex color conversion for pyte's 256-color/truecolor format (no # prefix) - Track column count separately from text length for proper wide char alignment - Add tests for rgb() color format, empty rows, unicode slugify - Improve test coverage to 80% Bump version to 0.3.1
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "textual-webterm"
|
name = "textual-webterm"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
description = "Serve terminal sessions over the web"
|
description = "Serve terminal sessions over the web"
|
||||||
authors = ["Will McGugan <will@textualize.io>"]
|
authors = ["Will McGugan <will@textualize.io>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@@ -38,3 +38,12 @@ class TestSlugify:
|
|||||||
"""Test that leading/trailing spaces are handled."""
|
"""Test that leading/trailing spaces are handled."""
|
||||||
result = slugify(" hello ")
|
result = slugify(" hello ")
|
||||||
assert "hello" in result
|
assert "hello" in result
|
||||||
|
|
||||||
|
def test_allow_unicode_preserves_unicode(self):
|
||||||
|
"""Test that allow_unicode=True preserves unicode characters."""
|
||||||
|
# With allow_unicode=True, unicode chars are normalized but preserved
|
||||||
|
result = slugify("héllo wörld", allow_unicode=True)
|
||||||
|
assert result == "héllo-wörld"
|
||||||
|
# Without allow_unicode (default), non-ASCII is transliterated
|
||||||
|
result_ascii = slugify("héllo wörld", allow_unicode=False)
|
||||||
|
assert result_ascii == "hello-world"
|
||||||
|
|||||||
@@ -63,6 +63,11 @@ class TestColorToHex:
|
|||||||
assert _color_to_hex("unknowncolor", is_foreground=True) == DEFAULT_FG
|
assert _color_to_hex("unknowncolor", is_foreground=True) == DEFAULT_FG
|
||||||
assert _color_to_hex("unknowncolor", is_foreground=False) == DEFAULT_BG
|
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:
|
def test_gray_aliases(self) -> None:
|
||||||
"""Gray/grey aliases work."""
|
"""Gray/grey aliases work."""
|
||||||
assert _color_to_hex("gray") == ANSI_COLORS["gray"]
|
assert _color_to_hex("gray") == ANSI_COLORS["gray"]
|
||||||
@@ -298,6 +303,32 @@ class TestBuildRowSpans:
|
|||||||
assert spans[2]["text"] == "o"
|
assert spans[2]["text"] == "o"
|
||||||
assert spans[2]["italic"] is True
|
assert spans[2]["italic"] is True
|
||||||
|
|
||||||
|
def test_placeholder_at_start_ignored(self) -> None:
|
||||||
|
"""Empty placeholder at start of row is ignored."""
|
||||||
|
row = [
|
||||||
|
self._char(""), # Orphan placeholder at start
|
||||||
|
self._char("A"),
|
||||||
|
self._char("B"),
|
||||||
|
]
|
||||||
|
spans = _build_row_spans(row, DEFAULT_FG, DEFAULT_BG)
|
||||||
|
assert len(spans) == 1
|
||||||
|
assert spans[0]["text"] == "AB"
|
||||||
|
assert spans[0]["columns"] == 2 # Placeholder not counted (no prior span)
|
||||||
|
|
||||||
|
def test_style_change_after_wide_char(self) -> None:
|
||||||
|
"""Style change right after a wide character placeholder works."""
|
||||||
|
row = [
|
||||||
|
self._char("中", fg="red"),
|
||||||
|
self._char(""), # Placeholder
|
||||||
|
self._char("A", fg="blue"),
|
||||||
|
]
|
||||||
|
spans = _build_row_spans(row, DEFAULT_FG, DEFAULT_BG)
|
||||||
|
assert len(spans) == 2
|
||||||
|
assert spans[0]["text"] == "中"
|
||||||
|
assert spans[0]["columns"] == 2 # Wide char + placeholder
|
||||||
|
assert spans[1]["text"] == "A"
|
||||||
|
assert spans[1]["columns"] == 1
|
||||||
|
|
||||||
|
|
||||||
class TestRenderTerminalSvg:
|
class TestRenderTerminalSvg:
|
||||||
"""Tests for render_terminal_svg function."""
|
"""Tests for render_terminal_svg function."""
|
||||||
@@ -334,6 +365,20 @@ class TestRenderTerminalSvg:
|
|||||||
assert svg.endswith("</svg>")
|
assert svg.endswith("</svg>")
|
||||||
assert 'xmlns="http://www.w3.org/2000/svg"' in svg
|
assert 'xmlns="http://www.w3.org/2000/svg"' 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("<svg")
|
||||||
|
assert "A" in svg
|
||||||
|
# Should only have 1 text element with content (for "A")
|
||||||
|
assert svg.count("<tspan") == 1
|
||||||
|
|
||||||
def test_basic_text_output(self) -> None:
|
def test_basic_text_output(self) -> None:
|
||||||
"""Basic text is included in SVG."""
|
"""Basic text is included in SVG."""
|
||||||
buffer = self._make_buffer(["Hello, World!"])
|
buffer = self._make_buffer(["Hello, World!"])
|
||||||
|
|||||||
Reference in New Issue
Block a user