"""Behavioural tests that drive the ACTUAL renderMd() in static/ui.js via node. The Python mirrors in test_blockquote_rendering.py and test_renderer_comprehensive.py validate intent, but they can drift from the JS. Twice now (PR #1073 commit 94d63d0 — phantom
; PR #1073 commit 04e7b53 — leading-space-in-blockquote prefix-strip regex) the Python mirror was correct while the JS was not, so the static-mirror tests passed even though the live UI was broken. This file closes that gap by spawning ``node`` on the real ui.js and asserting the rendered HTML for the most common LLM-output shapes. Add a case here whenever the renderer fix targets a class of input the Python mirror cannot exercise faithfully. """ import os import re import shutil import subprocess import sys from pathlib import Path import pytest REPO_ROOT = Path(__file__).parent.parent.resolve() UI_JS_PATH = REPO_ROOT / "static" / "ui.js" NODE = shutil.which("node") pytestmark = pytest.mark.skipif(NODE is None, reason="node not on PATH") _DRIVER_SRC = r""" const fs = require('fs'); const src = fs.readFileSync(process.argv[2], 'utf8'); global.window = {}; global.document = { createElement: () => ({ innerHTML: '', textContent: '' }) }; const esc = s => String(s ?? '').replace(/[&<>"']/g, c => ( {'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); const _IMAGE_EXTS=/\.(png|jpg|jpeg|gif|webp|bmp|ico|avif)$/i; const _SVG_EXTS=/\.svg$/i; const _AUDIO_EXTS=/\.(mp3|ogg|wav|m4a|aac|flac|wma|opus|webm)$/i; const _VIDEO_EXTS=/\.(mp4|webm|mkv|mov|avi|ogv|m4v)$/i; function extractFunc(name) { const re = new RegExp('function\\s+' + name + '\\s*\\('); const start = src.search(re); if (start < 0) throw new Error(name + ' not found'); let i = src.indexOf('{', start); let depth = 1; i++; while (depth > 0 && i < src.length) { if (src[i] === '{') depth++; else if (src[i] === '}') depth--; i++; } return src.slice(start, i); } eval(extractFunc('_matchBacktickFenceLine')); eval(extractFunc('_isBacktickFenceClose')); eval(extractFunc('renderMd')); let buf = ''; process.stdin.on('data', c => { buf += c; }); process.stdin.on('end', () => { process.stdout.write(renderMd(buf)); }); """ @pytest.fixture(scope="module") def driver_path(tmp_path_factory): """Write the node driver to a tmp file (works around `node -e` arg quirks).""" p = tmp_path_factory.mktemp("renderer_driver") / "driver.js" p.write_text(_DRIVER_SRC, encoding="utf-8") return str(p) def _render(driver_path, markdown: str) -> str: """Run renderMd against the actual ui.js and return the rendered HTML.""" result = subprocess.run( [NODE, driver_path, str(UI_JS_PATH)], input=markdown, capture_output=True, text=True, timeout=10, ) if result.returncode != 0: raise RuntimeError(f"node driver failed: {result.stderr}") return result.stdout # ───────────────────────────────────────────────────────────────────────────── # Blockquote prefix strip — the bug commit 04e7b53 introduced was a one-char # regex regression where `^>[\t]?` (only tab) replaced `^>[ \t]?` (space or # tab), producing leading-space artifacts and breaking lists-in-quotes # because the list-detection regex `^( )?[-*+]` couldn't match the # space-prefixed lines. These tests exercise the actual JS so the regex # can't silently regress to tab-only again. # ───────────────────────────────────────────────────────────────────────────── class TestBlockquotePrefixStrip: """Drive the actual renderMd to confirm `> ` is fully stripped.""" def test_single_line_blockquote_no_leading_space(self, driver_path): out = _render(driver_path, "> Hello world").strip() # New shape: recursive renderMd wraps content in

(CommonMark-correct). assert "

Hello world

" in out, ( f"`> Hello world` must render as

Hello world

" f"with no leading space. Got: {out!r}." ) def test_multiline_blockquote_no_leading_space(self, driver_path): out = _render(driver_path, "> Line one\n> Line two").strip() # New shape: single paragraph with
between soft-wrapped lines. assert "

Line one
Line two

" in out, ( f"Multi-line blockquote must strip the space after each `>` and " f"render as a single paragraph. Got: {out!r}" ) # Belt-and-braces: there must be no space-after-newline-in-content assert "\n " not in out.replace("", ""), ( f"Inner content of blockquote should not contain leading-space " f"lines. Got: {out!r}" ) def test_list_inside_blockquote_renders_as_ul(self, driver_path): """The PR explicitly added 'lists inside blockquotes' as a feature. With the prefix-strip bug, the list-detection regex can't match the space-prefixed lines, so the list never renders. This pins it.""" out = _render(driver_path, "> Steps:\n> - one\n> - two") assert "