"""Tests for issue #1325 — fenced code blocks in user message bubbles.""" import os import subprocess import tempfile UI_JS = os.path.join(os.path.dirname(__file__), '..', 'static', 'ui.js') def _extract_js_functions(): """Extract esc and _renderUserFencedBlocks from ui.js by line numbers.""" lines = open(UI_JS).read().split('\n') # esc is on line 52 (0-indexed: 51) esc_def = lines[51] # _renderUserFencedBlocks starts at line 61 (0-indexed: 60) # Find the end by matching closing brace at column 0 fn_lines = [] i = 60 # 0-indexed depth = 0 while i < len(lines): fn_lines.append(lines[i]) depth += lines[i].count('{') - lines[i].count('}') if depth <= 0: break i += 1 fn_def = '\n'.join(fn_lines) return esc_def, fn_def def _run_user_render(text_input): """Return the HTML output of _renderUserFencedBlocks for the given input text.""" import json esc_def, fn_def = _extract_js_functions() js_code = esc_def + '\n' + fn_def + '\n' js_code += 'var input = JSON.parse(process.argv[2]);\n' js_code += 'process.stdout.write(_renderUserFencedBlocks(input));\n' tf = tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False, encoding='utf-8') tf.write(js_code) tf.close() try: result = subprocess.run( ['node', tf.name, json.dumps(text_input)], capture_output=True, text=True, timeout=10 ) if result.returncode != 0: raise RuntimeError(f"node error: {result.stderr}") return result.stdout finally: os.unlink(tf.name) class TestUserFencedBlocks: """Fenced code blocks in user messages should render as
."""

    def test_simple_fenced_block(self):
        out = _run_user_render("hello\n```python\nprint(1)\n```\nworld")
        assert '
' in out
        assert 'print(1)' in out
        # Newlines around the fenced block become 
(same as original plain-text path) assert 'hello
' in out assert '
world' in out def test_fenced_block_escaped_html(self): """HTML in code blocks should be escaped.""" out = _run_user_render("```html\n
hi
\n```") assert '<div>' in out # No raw
in code content assert '
' not in out.replace('<div>', '').replace('>', '') def test_plain_text_not_interpreted_as_markdown(self): """Bold/italic/links in non-fenced text should stay escaped.""" out = _run_user_render("**bold** and *italic* and ") assert '**bold**' in out assert '*italic*' in out assert '<script>' in out assert '' not in out def test_language_header_shown(self): out = _run_user_render("```javascript\nconst x = 1;\n```") assert 'class="pre-header"' in out assert 'javascript' in out def test_no_language_no_header(self): out = _run_user_render("```\nsome code\n```") assert 'class="pre-header"' not in out assert '
' in out
        assert 'some code' in out

    def test_diff_block_colored(self):
        out = _run_user_render("```diff\n+added\n-removed\n```")
        assert 'diff-block' in out
        assert 'diff-plus' in out
        assert 'diff-minus' in out

    def test_multiple_fenced_blocks(self):
        out = _run_user_render("first\n```python\n1\n```\nmiddle\n```js\n2\n```\nlast")
        assert 'language-python' in out
        assert 'language-js' in out
        assert 'first
' in out assert '
last' in out def test_fenced_block_with_ampersand(self): out = _run_user_render("```python\nx & y\n```") assert 'x & y' in out def test_empty_code_block(self): out = _run_user_render("```\n```") assert '
' in out

    def test_special_chars_outside_blocks_escaped(self):
        out = _run_user_render("a < b > c & d")
        assert 'a < b > c & d' in out

    def test_links_not_rendered_in_plain_text(self):
        """URLs in plain text should NOT become clickable links."""
        out = _run_user_render("Check https://example.com for details")
        assert '' not in out