""" Tests for issue #470 — markdown link rendering bugs in renderMd(): 1. Double-linking: [label](url) converted to , then autolink re-matches the URL inside href="..." and wraps it in a second . 2. esc() applied to URLs in href attributes turns & → &, breaking URLs with query strings and producing & in displayed link text. 3. Same double-linking bug inside table cells via inlineMd(). These tests verify the fixes by asserting against the rendered HTML that ui.js serves, using a live server request to evaluate the actual JS output indirectly (via checking ui.js source for the fixed patterns) AND by running a lightweight Python mirror of the fixed renderMd logic. Strategy: verify the fix is present in the JS source, then test the expected rendering behaviour through the Python mirror. """ import pathlib import re import html as _html REPO_ROOT = pathlib.Path(__file__).parent.parent UI_JS = (REPO_ROOT / "static" / "ui.js").read_text() # ── Helpers ────────────────────────────────────────────────────────────────── def esc(s): return _html.escape(str(s), quote=True) def _make_link(url, label): """Expected output for a [label](url) link after fix: href is NOT esc()-ed.""" return f'{esc(label)}' # Minimal Python mirror of the FIXED renderMd() — enough to test link behaviour. # Mirrors the stash-based approach introduced by the fix. def render_links_only(text): """ Simplified render that only applies the link-related passes from the fixed renderMd(): [label](url) conversion + autolink, with the stash protection. Sufficient for testing that links render correctly without double-linking. """ s = text # Stash [label](url) links (fix: store href as raw URL, not esc(url)) link_stash = [] def stash_link(m): label, url = m.group(1), m.group(2) link_stash.append(f'{esc(label)}') return f'\x00L{len(link_stash)-1}\x00' s = re.sub(r'\[([^\]]+)\]\((https?://[^\)]+)\)', stash_link, s) # Autolink bare URLs (should NOT match inside already-stashed placeholders) def autolink(m): url = m.group(1) trail = url[-1] if url[-1] in '.,;:!?)' else '' clean = url[:-1] if trail else url return f'{esc(clean)}{trail}' s = re.sub(r'(https?://[^\s<>"\')\]]+)', autolink, s) # Restore stashed links s = re.sub(r'\x00L(\d+)\x00', lambda m: link_stash[int(m.group(1))], s) return s def render_table_with_links(md): """ Render a markdown table that may contain [label](url) cells. Mirrors the fixed inlineMd() + table rendering. """ lines = md.strip().split('\n') if len(lines) < 2: return md def is_sep(r): return bool(re.match(r'^\|[\s|:-]+\|$', r.strip())) if not is_sep(lines[1]): return md def inline_md_fixed(t): """Fixed inlineMd: stash links before autolink.""" stash = [] def stash_fn(m): lb, u = m.group(1), m.group(2) stash.append(f'{esc(lb)}') return f'\x00L{len(stash)-1}\x00' t = re.sub(r'\[([^\]]+)\]\((https?://[^\)]+)\)', stash_fn, t) # autolink remaining bare URLs def autolink(m): url = m.group(1) trail = url[-1] if url[-1] in '.,;:!?)' else '' clean = url[:-1] if trail else url return f'{esc(clean)}{trail}' t = re.sub(r'(https?://[^\s<>"\')\]]+)', autolink, t) t = re.sub(r'\x00L(\d+)\x00', lambda m: stash[int(m.group(1))], t) return t def parse_row(r): cells = r.strip().lstrip('|').rstrip('|').split('|') return ''.join(f'
...,
not with escaped <code> tags visible on screen."""
# This was the pre-existing bug: **`esc()`** → <code>esc()</code>
text = '**`esc()` on `href`**: breaks URLs'
# Simulate the fixed inlineMd()
code_stash = []
t = text
t = re.sub(r'`([^`\n]+)`',
lambda m: (code_stash.append(f'{esc(m.group(1))}') or f'\x00C{len(code_stash)-1}\x00'), t)
t = re.sub(r'\*\*(.+?)\*\*', lambda m: f'{esc(m.group(1))}', t)
t = re.sub(r'\x00C(\d+)\x00', lambda m: code_stash[int(m.group(1))], t)
assert '<code>' not in t, (
f"Code tags should not be HTML-escaped inside bold. Got: {t}"
)
assert 'esc()' in t, (
f"Code tags should render as elements inside bold. Got: {t}"
)
assert '' in t, "Bold should still render"
def test_code_and_bold_mixed_no_escaping():
"""Bold text containing multiple backtick spans must render all code tags correctly."""
cases = [
('**`esc()` on `href`**', '', 'esc()', 'href'),
('***`code` in bold-italic***', '', 'code'),
('`code` then **bold**', 'code', 'bold'),
]
for args in cases:
text = args[0]
expected_fragments = args[1:]
code_stash = []
t = text
t = re.sub(r'`([^`\n]+)`',
lambda m: (code_stash.append(f'{esc(m.group(1))}') or f'\x00C{len(code_stash)-1}\x00'), t)
t = re.sub(r'\*\*\*(.+?)\*\*\*', lambda m: f'{esc(m.group(1))}', t)
t = re.sub(r'\*\*(.+?)\*\*', lambda m: f'{esc(m.group(1))}', t)
t = re.sub(r'\x00C(\d+)\x00', lambda m: code_stash[int(m.group(1))], t)
assert '<code>' not in t, f"Escaped code tag in: {text!r} → {t}"
for frag in expected_fragments:
assert frag in t, f"Expected {frag!r} in output of {text!r}, got: {t}"