fix(markdown): remove table double-header + add imgui_md bullet workaround
Table fix (src/markdown_table.py):
- Add TableColumnFlags_.width_stretch to each table_setup_column call
(was missing — columns had no width to wrap against, so text_wrapped
couldn't grow row height → all rows squished together)
- Remove the explicit for-h-in-headers: table_next_column + text_wrapped(h)
loop. table_headers_row() already renders the header from the
table_setup_column() names; the explicit loop was drawing it AGAIN on
top → double-rendered header rows.
Bullet fix (src/markdown_helper.py):
- Revert _render_md_no_bullet_overlap → simple imgui_md.render(chunk);
imgui.spacing() (the original af0bbe97 approach). The complex
workaround was stripping '- ' and rendering stripped text to imgui_md,
which double-rendered '- 1. ...' content (imgui.bullet from my code +
numbered list marker from imgui_md).
- Add MarkdownRenderer._normalize_bullet_delimiters: regex-converts
'* ' markers to '- ' before passing to imgui_md. This works around
the upstream bug in mekhontsev/imgui_md BLOCK_LI where the '*' case
calls ImGui::Bullet() without ImGui::SameLine(), causing the bullet
to render on its own Y with the text on the next Y. The '-' case
uses Text+SameLine which is correct. Cannot fix from Python (we
can't subclass the C++ class) — pre-conversion is the cheapest fix.
Tests:
- test_markdown_table_wrapped.py: updated to assert new behavior
(text_wrapped count == cell count, not header+cell).
- test_markdown_table_columns.py: updated to assert exactly 6
table_next_column calls (cells only, not 9).
- test_markdown_helper_bullets.py: rewrote for new public-API behavior
(imgui_md.render called with the unstripped chunk).
16/16 markdown unit tests pass.
This commit is contained in:
@@ -1,54 +1,59 @@
|
||||
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 _mock_table(mock_imgui):
|
||||
mock_imgui.TableFlags_ = type("T", (), {"borders": 1, "row_bg": 2, "resizable": 4})()
|
||||
mock_imgui.TableColumnFlags_ = type("C", (), {"width_stretch": 8})()
|
||||
mock_imgui.begin_table.return_value = True
|
||||
mock_imgui.table_next_column = lambda: None
|
||||
mock_imgui.table_next_row = lambda: None
|
||||
mock_imgui.table_headers_row = lambda: None
|
||||
mock_imgui.text = lambda *a, **k: None
|
||||
mock_imgui.text_wrapped = lambda *a, **k: None
|
||||
mock_imgui.end_table = lambda: None
|
||||
mock_imgui.same_line = lambda: None
|
||||
mock_imgui.spacing = lambda: None
|
||||
mock_imgui.indent = lambda *a: None
|
||||
mock_imgui.unindent = lambda *a: None
|
||||
|
||||
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()
|
||||
def test_render_calls_imgui_md_render_for_bullet_chunks():
|
||||
md = "- one\n- two\n- three\n"
|
||||
with patch("src.markdown_helper.imgui_md") as mock_md, \
|
||||
patch("src.markdown_helper.imgui") as mock_imgui, \
|
||||
patch("src.markdown_table.imgui") as mock_table_imgui:
|
||||
_mock_table(mock_table_imgui)
|
||||
mock_md.render = MagicMock()
|
||||
MarkdownRenderer()._render_md_no_bullet_overlap(chunk)
|
||||
assert mock_imgui.spacing.call_count >= 1
|
||||
mock_imgui.spacing = MagicMock()
|
||||
MarkdownRenderer().render(md, context_id="bullets")
|
||||
assert mock_md.render.call_count == 1, f"expected 1 imgui_md.render call (full chunk passed through), got {mock_md.render.call_count}"
|
||||
assert mock_imgui.spacing.call_count >= 1, "imgui.spacing must be called to force vertical gap between chunks"
|
||||
|
||||
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()
|
||||
def test_render_does_not_strip_bullet_prefix_from_markdown():
|
||||
md = "- one\n- two\n"
|
||||
with patch("src.markdown_helper.imgui_md") as mock_md, \
|
||||
patch("src.markdown_helper.imgui") as mock_imgui, \
|
||||
patch("src.markdown_table.imgui") as mock_table_imgui:
|
||||
_mock_table(mock_table_imgui)
|
||||
mock_md.render = MagicMock()
|
||||
MarkdownRenderer()._render_md_no_bullet_overlap(chunk)
|
||||
assert not mock_imgui.bullet.called
|
||||
mock_imgui.spacing = MagicMock()
|
||||
MarkdownRenderer().render(md, context_id="bullets")
|
||||
args, _ = mock_md.render.call_args
|
||||
rendered_text = args[0]
|
||||
assert "- one" in rendered_text, f"bullet prefix must NOT be stripped (regression: was double-rendering as bullet + imgui_md numbered list), got {rendered_text!r}"
|
||||
assert "- two" in rendered_text
|
||||
|
||||
def test_render_passes_numbered_list_intact_to_imgui_md():
|
||||
md = "1. First question\n2. Second question\n"
|
||||
with patch("src.markdown_helper.imgui_md") as mock_md, \
|
||||
patch("src.markdown_helper.imgui") as mock_imgui, \
|
||||
patch("src.markdown_table.imgui") as mock_table_imgui:
|
||||
_mock_table(mock_table_imgui)
|
||||
mock_md.render = MagicMock()
|
||||
mock_imgui.spacing = MagicMock()
|
||||
MarkdownRenderer().render(md, context_id="numbered")
|
||||
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
|
||||
args, _ = mock_md.render.call_args
|
||||
rendered_text = args[0]
|
||||
assert "1. First question" in rendered_text
|
||||
assert "2. Second question" in rendered_text
|
||||
assert not mock_imgui.bullet.called, "no manual imgui.bullet should be added — let imgui_md handle list rendering"
|
||||
|
||||
Reference in New Issue
Block a user