fd5f4d0eda
FIX 1 (src/markdown_table.py): Cells now use imgui_md.render(c) instead of imgui.text_wrapped(c). imgui_md uses MD4C which strips backtick-delimited inline code spans BEFORE rendering, so backticks no longer appear as literal characters in cell content. Side benefit: inline emphasis (*foo*, **bar**) now renders in cells too. FIX 2 (src/markdown_helper.py): Added MarkdownRenderer._normalize_nested_list_endings. Upstream imgui_md (mekhontsev/imgui_md) BLOCK_UL exit only calls ImGui::NewLine() for top-level list endings. For nested list endings, no NewLine is emitted, so the next text starts at the same Y as the last list item, causing visual overlap. The preprocessor inserts a blank line before any line that follows a list item with MORE indent than itself, forcing a paragraph break. Cannot fix the C++ from Python. Tests: - test_markdown_table_wrapped.py: updated to assert imgui_md.render is called for cell content (not imgui.text_wrapped). - test_markdown_helper_bullets.py: added 4 tests for the new preprocessors (nested-list blank insertion + bullet delimiter conversion + edge cases). 20/20 markdown unit tests pass. 1-space indentation throughout. KNOWN LIMITATIONS (cannot fix without forking imgui_md C++): - Inline code spans render as plain text (no monospace font in cells) - The ' * ' bullet delimiter has a Y-overlap bug upstream (workaround: pre-convert to '- ' via _normalize_bullet_delimiters) - Nested list ending overlap (workaround: insert blank line via _normalize_nested_list_endings)
86 lines
4.0 KiB
Python
86 lines
4.0 KiB
Python
from unittest.mock import patch, MagicMock
|
|
from src.markdown_helper import MarkdownRenderer
|
|
|
|
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_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()
|
|
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_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()
|
|
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
|
|
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"
|
|
|
|
def test_normalize_nested_list_endings_inserts_blank_after_nested_item():
|
|
r = MarkdownRenderer()
|
|
text = "- top\n - nested last\nnext paragraph\n"
|
|
out = r._normalize_nested_list_endings(text)
|
|
assert " - nested last\n\nnext paragraph" in out, f"expected blank line between nested list item and less-indented next line, got {out!r}"
|
|
|
|
def test_normalize_nested_list_endings_does_not_insert_blank_for_top_level_list():
|
|
r = MarkdownRenderer()
|
|
text = "- one\n- two\n- three\nnext paragraph\n"
|
|
out = r._normalize_nested_list_endings(text)
|
|
assert out == text, f"top-level list ending should not trigger blank line insertion, got {out!r}"
|
|
|
|
def test_normalize_nested_list_endings_does_not_double_blank():
|
|
r = MarkdownRenderer()
|
|
text = "- a\n - b\n\nalready blank\n"
|
|
out = r._normalize_nested_list_endings(text)
|
|
assert out == text, f"already-blank separator should not get doubled, got {out!r}"
|
|
|
|
def test_normalize_bullet_delimiters_still_converts_asterisk():
|
|
r = MarkdownRenderer()
|
|
text = "- one\n* two\n+ three\n"
|
|
out = r._normalize_bullet_delimiters(text)
|
|
assert "- one" in out
|
|
assert "- two" in out and "* two" not in out, f"* must be converted to -, got {out!r}"
|
|
assert "+ three" in out, f"+ must be left alone, got {out!r}"
|