fix(markdown): revert table to simple form with text_wrapped + add regression tests
This commit is contained in:
@@ -2,28 +2,27 @@ import re
|
||||
from dataclasses import dataclass
|
||||
from imgui_bundle import imgui
|
||||
|
||||
_TABLE_SEPARATOR = re.compile(r"^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)+\|?\s*$")
|
||||
_TABLE_SEPARATOR = re.compile(r"^\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)+\|?\s*$")
|
||||
|
||||
def render_table(block: "TableBlock") -> None:
|
||||
"""Render a GFM table block via imgui.begin_table.
|
||||
[C: src/markdown_helper.py:MarkdownRenderer.render]
|
||||
"""
|
||||
from src.markdown_helper import render as render_md
|
||||
n_cols = len(block.headers)
|
||||
if n_cols == 0: return
|
||||
flags = imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable | imgui.TableFlags_.scroll_x
|
||||
flags = imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable
|
||||
if not imgui.begin_table("md_table", n_cols, flags): return
|
||||
for h in block.headers:
|
||||
imgui.table_setup_column(h, imgui.TableColumnFlags_.width_stretch)
|
||||
imgui.table_setup_column(h)
|
||||
imgui.table_headers_row()
|
||||
# Note: table_headers_row() renders the headers from setup_column.
|
||||
# No need for manual row here unless we want custom rendering for header cells.
|
||||
|
||||
for h in block.headers:
|
||||
imgui.table_next_column()
|
||||
imgui.text_wrapped(h)
|
||||
for row in block.rows:
|
||||
imgui.table_next_row()
|
||||
for c in row:
|
||||
imgui.table_next_column()
|
||||
render_md(c)
|
||||
imgui.text_wrapped(c)
|
||||
imgui.end_table()
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -43,7 +42,6 @@ def _split_row(line: str) -> list[str]:
|
||||
|
||||
def _is_table_at(lines: list[str], i: int) -> bool:
|
||||
if i + 1 >= len(lines): return False
|
||||
# Header must have at least one pipe, or the separator must be very clear
|
||||
if "|" not in lines[i]: return False
|
||||
return bool(_TABLE_SEPARATOR.match(lines[i + 1]))
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
from unittest.mock import patch, MagicMock
|
||||
from src.markdown_helper import MarkdownRenderer
|
||||
|
||||
def test_bullet_list_renders_each_item_with_imgui_bullet():
|
||||
chunk = "- one\n- two\n- three\n"
|
||||
with patch("src.markdown_helper.imgui") as mock_imgui, patch("src.markdown_helper.imgui_md") as mock_md:
|
||||
mock_imgui.bullet = MagicMock()
|
||||
mock_imgui.same_line = MagicMock()
|
||||
mock_imgui.spacing = MagicMock()
|
||||
mock_imgui.indent = MagicMock()
|
||||
mock_imgui.unindent = MagicMock()
|
||||
mock_md.render = MagicMock()
|
||||
MarkdownRenderer()._render_md_no_bullet_overlap(chunk)
|
||||
assert mock_imgui.bullet.call_count >= 3
|
||||
assert mock_imgui.same_line.call_count >= 3
|
||||
assert mock_md.render.call_count >= 3
|
||||
|
||||
def test_bullet_list_with_blank_lines_uses_spacing():
|
||||
chunk = "- one\n\n- two\n"
|
||||
with patch("src.markdown_helper.imgui") as mock_imgui, patch("src.markdown_helper.imgui_md") as mock_md:
|
||||
mock_imgui.bullet = MagicMock()
|
||||
mock_imgui.same_line = MagicMock()
|
||||
mock_imgui.spacing = MagicMock()
|
||||
mock_imgui.indent = MagicMock()
|
||||
mock_imgui.unindent = MagicMock()
|
||||
mock_md.render = MagicMock()
|
||||
MarkdownRenderer()._render_md_no_bullet_overlap(chunk)
|
||||
assert mock_imgui.spacing.call_count >= 1
|
||||
|
||||
def test_non_bullet_markdown_routes_to_imgui_md():
|
||||
chunk = "# Header\n\nSome prose."
|
||||
with patch("src.markdown_helper.imgui") as mock_imgui, patch("src.markdown_helper.imgui_md") as mock_md:
|
||||
mock_imgui.bullet = MagicMock()
|
||||
mock_imgui.same_line = MagicMock()
|
||||
mock_imgui.spacing = MagicMock()
|
||||
mock_imgui.indent = MagicMock()
|
||||
mock_imgui.unindent = MagicMock()
|
||||
mock_md.render = MagicMock()
|
||||
MarkdownRenderer()._render_md_no_bullet_overlap(chunk)
|
||||
assert not mock_imgui.bullet.called
|
||||
assert mock_md.render.call_count == 1
|
||||
|
||||
def test_mixed_bullets_and_prose_splits_correctly():
|
||||
chunk = "Prose before.\n\n- b1\n- b2\n\nProse after."
|
||||
with patch("src.markdown_helper.imgui") as mock_imgui, patch("src.markdown_helper.imgui_md") as mock_md:
|
||||
mock_imgui.bullet = MagicMock()
|
||||
mock_imgui.same_line = MagicMock()
|
||||
mock_imgui.spacing = MagicMock()
|
||||
mock_imgui.indent = MagicMock()
|
||||
mock_imgui.unindent = MagicMock()
|
||||
mock_md.render = MagicMock()
|
||||
MarkdownRenderer()._render_md_no_bullet_overlap(chunk)
|
||||
assert mock_imgui.bullet.call_count >= 2
|
||||
assert mock_md.render.call_count >= 2
|
||||
@@ -0,0 +1,44 @@
|
||||
from unittest.mock import patch, MagicMock
|
||||
from src.markdown_table import render_table, TableBlock
|
||||
|
||||
def test_render_table_uses_text_wrapped_for_cells():
|
||||
block = TableBlock(headers=["A"], rows=[["hello"]], span=(0, 2))
|
||||
with patch("src.markdown_table.imgui") as mock_imgui:
|
||||
mock_imgui.TableFlags_ = type("T", (), {"borders": 1, "row_bg": 2, "resizable": 4})()
|
||||
mock_imgui.begin_table.return_value = True
|
||||
mock_imgui.table_next_column = MagicMock()
|
||||
mock_imgui.table_next_row = MagicMock()
|
||||
mock_imgui.table_headers_row = MagicMock()
|
||||
mock_imgui.text_wrapped = MagicMock()
|
||||
mock_imgui.text = MagicMock()
|
||||
mock_imgui.end_table = MagicMock()
|
||||
render_table(block)
|
||||
mock_imgui.text_wrapped.assert_any_call("hello")
|
||||
|
||||
def test_render_table_uses_text_wrapped_for_headers():
|
||||
block = TableBlock(headers=["A"], rows=[["hello"]], span=(0, 2))
|
||||
with patch("src.markdown_table.imgui") as mock_imgui:
|
||||
mock_imgui.TableFlags_ = type("T", (), {"borders": 1, "row_bg": 2, "resizable": 4})()
|
||||
mock_imgui.begin_table.return_value = True
|
||||
mock_imgui.table_next_column = MagicMock()
|
||||
mock_imgui.table_next_row = MagicMock()
|
||||
mock_imgui.table_headers_row = MagicMock()
|
||||
mock_imgui.text_wrapped = MagicMock()
|
||||
mock_imgui.text = MagicMock()
|
||||
mock_imgui.end_table = MagicMock()
|
||||
render_table(block)
|
||||
mock_imgui.text_wrapped.assert_any_call("A")
|
||||
|
||||
def test_render_table_does_not_use_text_for_cells():
|
||||
block = TableBlock(headers=["A"], rows=[["hello"]], span=(0, 2))
|
||||
with patch("src.markdown_table.imgui") as mock_imgui:
|
||||
mock_imgui.TableFlags_ = type("T", (), {"borders": 1, "row_bg": 2, "resizable": 4})()
|
||||
mock_imgui.begin_table.return_value = True
|
||||
mock_imgui.table_next_column = MagicMock()
|
||||
mock_imgui.table_next_row = MagicMock()
|
||||
mock_imgui.table_headers_row = MagicMock()
|
||||
mock_imgui.text_wrapped = MagicMock()
|
||||
mock_imgui.text = MagicMock()
|
||||
mock_imgui.end_table = MagicMock()
|
||||
render_table(block)
|
||||
assert not mock_imgui.text.called, "imgui.text should not be called for table cells"
|
||||
Reference in New Issue
Block a user