from unittest.mock import patch, MagicMock from src.md_renderer_py import MarkdownRenderer, MdOptions def _mock_imgui(mock_imgui): mock_imgui.TableFlags_ = type("T", (), {"borders": 1, "row_bg": 2, "resizable": 4})() mock_imgui.TableColumnFlags_ = type("C", (), {"width_stretch": 8})() mock_imgui.Col_ = type("C", (), { "text": 0, "text_disabled": 1, "frame_bg": 2, "button": 3, "button_hovered": 4, "button_active": 5, })() mock_imgui.begin_table = MagicMock(return_value=True) mock_imgui.end_table = MagicMock() mock_imgui.table_setup_column = MagicMock() mock_imgui.table_next_row = MagicMock() mock_imgui.table_next_column = MagicMock() mock_imgui.text = MagicMock() mock_imgui.text_wrapped = MagicMock() mock_imgui.bullet = MagicMock() mock_imgui.same_line = MagicMock() mock_imgui.new_line = MagicMock() mock_imgui.indent = MagicMock() mock_imgui.unindent = MagicMock() mock_imgui.spacing = MagicMock() mock_imgui.separator = MagicMock() mock_imgui.push_style_color = MagicMock() mock_imgui.pop_style_color = MagicMock() mock_imgui.push_font = MagicMock() mock_imgui.pop_font = MagicMock() mock_imgui.small_button = MagicMock() mock_imgui.get_style = MagicMock(return_value=MagicMock( colors={ 0: MagicMock(x=1, y=1, z=1, w=1), 1: MagicMock(x=0.5, y=0.5, z=0.5, w=1), 2: MagicMock(x=0.1, y=0.1, z=0.1, w=1), 3: MagicMock(x=0.3, y=0.5, z=1.0, w=1), 4: MagicMock(x=0.4, y=0.6, z=1.0, w=1), 5: MagicMock(x=0.5, y=0.7, z=1.0, w=1), } )) mock_imgui.get_io = MagicMock(return_value=MagicMock(fonts=MagicMock(fonts=[None]))) mock_imgui.calc_text_size = MagicMock(return_value=MagicMock(x=50, y=20)) mock_imgui.get_item_rect_min = MagicMock(return_value=MagicMock(x=0, y=0)) mock_imgui.get_window_draw_list = MagicMock(return_value=MagicMock( add_line=MagicMock() )) mock_imgui.is_item_hovered = MagicMock(return_value=False) mock_imgui.is_mouse_released = MagicMock(return_value=False) return mock_imgui def test_renderer_parses_simple_paragraph(): r = MarkdownRenderer() with patch("src.md_renderer_py.imgui") as mock_imgui: _mock_imgui(mock_imgui) r.render("Hello, world.") assert mock_imgui.text_wrapped.called def test_renderer_renders_h1_with_separator(): r = MarkdownRenderer() with patch("src.md_renderer_py.imgui") as mock_imgui: _mock_imgui(mock_imgui) r.render("# Heading 1") assert mock_imgui.push_font.called assert mock_imgui.pop_font.called assert mock_imgui.separator.called def test_renderer_renders_bullet_list_with_bullets(): r = MarkdownRenderer() with patch("src.md_renderer_py.imgui") as mock_imgui: _mock_imgui(mock_imgui) r.render("- one\n- two\n- three") assert mock_imgui.bullet.call_count == 3, f"expected 3 bullets, got {mock_imgui.bullet.call_count}" assert mock_imgui.indent.call_count == 3 assert mock_imgui.unindent.call_count == 3 def test_renderer_renders_ordered_list_with_numbers(): r = MarkdownRenderer() with patch("src.md_renderer_py.imgui") as mock_imgui: _mock_imgui(mock_imgui) r.render("1. first\n2. second\n3. third") assert mock_imgui.bullet.call_count == 0, "ordered list should not use bullets" assert mock_imgui.text.called, "ordered list should render the number+delim text" for call in mock_imgui.text.call_args_list: args, _ = call assert any(s in str(args[0]) for s in ["1.", "2.", "3."]), f"got {args[0]!r}" def test_renderer_renders_emphasis_with_dim_color(): r = MarkdownRenderer() with patch("src.md_renderer_py.imgui") as mock_imgui: _mock_imgui(mock_imgui) r.render("This is *emphasized* text.") assert mock_imgui.push_style_color.call_count >= 2, "em should push 2 style colors" assert mock_imgui.pop_style_color.call_count >= 2, "em should pop 2 style colors" assert mock_imgui.text_wrapped.called def test_renderer_renders_strong_with_bright_color(): r = MarkdownRenderer() with patch("src.md_renderer_py.imgui") as mock_imgui: _mock_imgui(mock_imgui) r.render("This is **strong** text.") assert mock_imgui.push_style_color.call_count >= 2, "strong should push 2 style colors" assert mock_imgui.pop_style_color.call_count >= 2 def test_renderer_renders_inline_code_with_button(): r = MarkdownRenderer() with patch("src.md_renderer_py.imgui") as mock_imgui: _mock_imgui(mock_imgui) r.render("Use `code` inline.") assert mock_imgui.small_button.called, "inline code should use small_button" assert mock_imgui.same_line.called def test_renderer_renders_table_with_columns_and_rows(): r = MarkdownRenderer() with patch("src.md_renderer_py.imgui") as mock_imgui: _mock_imgui(mock_imgui) md = "| A | B |\n|---|---|\n| 1 | 2 |\n| 3 | 4 |" r.render(md) assert mock_imgui.begin_table.called, "table should call begin_table" assert mock_imgui.end_table.called, "table should call end_table" assert mock_imgui.table_setup_column.call_count == 2, f"expected 2 columns, got {mock_imgui.table_setup_column.call_count}" assert mock_imgui.table_next_row.call_count == 3, f"expected 3 rows (1 header + 2 body), got {mock_imgui.table_next_row.call_count}" def test_renderer_renders_link_with_underline(): r = MarkdownRenderer() with patch("src.md_renderer_py.imgui") as mock_imgui: _mock_imgui(mock_imgui) r.render("Click [here](https://example.com).") assert mock_imgui.push_style_color.called assert mock_imgui.text_wrapped.called def test_renderer_link_callback_fires_on_click(): callback = MagicMock() opts = MdOptions() opts.callbacks.on_open_link = callback r = MarkdownRenderer(opts) with patch("src.md_renderer_py.imgui") as mock_imgui: mock_imgui = _mock_imgui(mock_imgui) mock_imgui.is_item_hovered = MagicMock(return_value=True) mock_imgui.is_mouse_released = MagicMock(return_value=True) r.render("[link](https://example.com)") assert callback.called args, _ = callback.call_args assert "https://example.com" in args def test_renderer_renders_code_block_via_external_handler(): r = MarkdownRenderer() handler = MagicMock() r.set_external_code_block_handler(handler) with patch("src.md_renderer_py.imgui") as mock_imgui: _mock_imgui(mock_imgui) r.render("```python\nprint('hi')\n```") assert handler.called args, _ = handler.call_args assert args[0] == "print('hi')\n", f"expected code content, got {args[0]!r}" assert args[1] == "python", f"expected lang, got {args[1]!r}" def test_renderer_explicit_newline_between_list_paragraphs(): r = MarkdownRenderer() with patch("src.md_renderer_py.imgui") as mock_imgui: _mock_imgui(mock_imgui) r.render("- first\n\n continuation paragraph") assert mock_imgui.new_line.call_count >= 3, f"expected at least 3 new_lines (one before bullet, one between paragraphs, one after list), got {mock_imgui.new_line.call_count}" def test_renderer_handles_empty_input(): r = MarkdownRenderer() r.render("") r.render(" \n \n") def test_renderer_handles_nested_lists(): r = MarkdownRenderer() with patch("src.md_renderer_py.imgui") as mock_imgui: _mock_imgui(mock_imgui) r.render("- outer\n - inner1\n - inner2\n- outer2") assert mock_imgui.bullet.call_count == 4, f"expected 4 bullets (1 outer + 2 inner + 1 outer2), got {mock_imgui.bullet.call_count}" assert mock_imgui.unindent.call_count == 4 def test_renderer_renders_horizontal_rule(): r = MarkdownRenderer() with patch("src.md_renderer_py.imgui") as mock_imgui: _mock_imgui(mock_imgui) r.render("Above\n\n---\n\nBelow") assert mock_imgui.separator.called def test_renderer_render_unindented_strips_common_indent(): r = MarkdownRenderer() with patch("src.md_renderer_py.imgui") as mock_imgui: _mock_imgui(mock_imgui) r.render_unindented(" - a\n - b\n - c") assert mock_imgui.bullet.call_count == 3