"""
Sprint 16 Tests: safe HTML rendering in renderMd(), active session styling,
session sidebar polish (SVG icons, overlay actions).
"""
import html as _html
import pathlib
import re
import urllib.request
BASE = "http://127.0.0.1:8788"
REPO_ROOT = pathlib.Path(__file__).parent.parent
# ── Helpers ──────────────────────────────────────────────────────────────────
def get_text(path):
with urllib.request.urlopen(BASE + path, timeout=10) as r:
return r.read().decode("utf-8"), r.status
def esc(s):
"""Mirror of esc() in ui.js — HTML-escapes a string."""
return _html.escape(str(s), quote=True)
SAFE_TAGS = re.compile(
r"^<\/?(strong|em|code|pre|h[1-6]|ul|ol|li|table|thead|tbody|tr|th|td"
r"|hr|blockquote|p|br|a|div)([\s>]|$)",
re.I,
)
SAFE_INLINE = re.compile(r"^<\/?(strong|em|code|a)([\s>]|$)", re.I)
def inline_md(t):
"""Mirror of inlineMd() in ui.js — for use inside list items / blockquotes."""
t = re.sub(r"\*\*\*(.+?)\*\*\*", lambda m: "" + esc(m.group(1)) + "", t)
t = re.sub(r"\*\*(.+?)\*\*", lambda m: "" + esc(m.group(1)) + "", t)
t = re.sub(r"\*([^*\n]+)\*", lambda m: "" + esc(m.group(1)) + "", t)
t = re.sub(r"`([^`\n]+)`", lambda m: "" + esc(m.group(1)) + "", t)
t = re.sub(
r"\[([^\]]+)\]\((https?://[^\)]+)\)",
lambda m: f'{esc(m.group(1))}',
t,
)
t = re.sub(r"?[a-zA-Z][^>]*>", lambda m: m.group() if SAFE_INLINE.match(m.group()) else esc(m.group()), t)
return t
def render_md(raw):
"""
Python mirror of renderMd() in static/ui.js.
Kept in sync with the JS implementation so tests catch regressions
if the JS logic drifts from the documented behaviour.
"""
s = raw or ""
# Pre-pass: stash code blocks/spans, convert safe HTML → markdown equivalents
fence_stash = []
def stash(m):
fence_stash.append(m.group())
return "\x00F" + str(len(fence_stash) - 1) + "\x00"
s = re.sub(r"(```[\s\S]*?```|`[^`\n]+`)", stash, s)
s = re.sub(r"([\s\S]*?)", lambda m: "**" + m.group(1) + "**", s, flags=re.I)
s = re.sub(r"([\s\S]*?)", lambda m: "**" + m.group(1) + "**", s, flags=re.I)
s = re.sub(r"([\s\S]*?)", lambda m: "*" + m.group(1) + "*", s, flags=re.I)
s = re.sub(r"([\s\S]*?)", lambda m: "*" + m.group(1) + "*", s, flags=re.I)
s = re.sub(r"([^<]*?)", lambda m: "`" + m.group(1) + "`", s, flags=re.I)
s = re.sub(r"
", "\n", s, flags=re.I)
s = re.sub(r"\x00F(\d+)\x00", lambda m: fence_stash[int(m.group(1))], s)
# Fenced code blocks
def fenced(m):
lang, code = m.group(1), m.group(2).rstrip("\n")
h = f'
" + esc(code) + ""
s = re.sub(r"```([\w+-]*)\n?([\s\S]*?)```", fenced, s)
s = re.sub(r"`([^`\n]+)`", lambda m: "" + esc(m.group(1)) + "", s)
# Inline formatting (top-level, outside list items)
s = re.sub(r"\*\*\*(.+?)\*\*\*", lambda m: "" + esc(m.group(1)) + "", s)
s = re.sub(r"\*\*(.+?)\*\*", lambda m: "" + esc(m.group(1)) + "", s)
s = re.sub(r"\*([^*\n]+)\*", lambda m: "" + esc(m.group(1)) + "", s)
# Block elements using inlineMd for their content
s = re.sub(r"^### (.+)$", lambda m: "" + inline_md(m.group(1)) + "", s, flags=re.M) def handle_ul(block): lines = block.strip().split("\n") out = "
" + p.replace("\n", "
") + "
text renders as inline code."""
out = render_md("use print()")
assert "print()" in out
def test_render_md_html_br_becomes_newline(cleanup_test_sessions):
""" later)."""
out = render_md("line one
line two")
assert "line one\nline two" in out or "line one
line two" in out
def test_render_md_mixed_markdown_and_html(cleanup_test_sessions):
"""Markdown and HTML formatting can coexist in the same response."""
out = render_md("**markdown** and html")
assert "markdown" in out
assert "html" in out
def test_render_md_html_strong_in_list_item(cleanup_test_sessions):
"""THE SCREENSHOT BUG: tags inside list items must render as bold,
not as escaped literal text like <strong>."""
out = render_md(
"- All items get `border-radius: 0 8px 8px 0`\n"
"- Active item uses #e8a030\n"
"- Project items show their color\n"
"- Regular items stay muted"
)
assert "<strong>" not in out, \
"Escaped literal found in list output — bold not rendering"
assert "All items" in out
assert "Active item" in out
assert "border-radius: 0 8px 8px 0" in out
assert "#e8a030" in out
def test_render_md_exact_screenshot_content(cleanup_test_sessions):
"""Exact text from the ui-changes-unrendered-html-tags.png screenshot.
This is the canonical regression test for the inlineMd fix.
All four bullet points must render and as HTML, not literal text."""
out = render_md(
"- All items now have border-radius: 0 8px 8px 0"
" \u2014 straight left edge everywhere, rounded on the right\n"
"- Active item is now gold/amber (#e8a030)"
" \u2014 same warm gold used in the logo \u2014 instead of blue,"
" so it stands out distinctly from everything else\n"
"- Project items still show their project color on the left"
" border, but only when they're not the active item (active always wins with gold)\n"
"- Regular items (no project) still have no left border color"
)
# None of the safe tags should appear as literal escaped text
assert "<strong>" not in out, \
"Literal <strong> found — is not rendering as bold"
assert "</strong>" not in out, \
"Literal </strong> found — closing tag is not rendering"
assert "<code>" not in out, \
"Literal <code> found — is not rendering as inline code"
# Each item's bold label must render correctly
assert "All items" in out
assert "Active item" in out
assert "Project items" in out
assert "Regular items" in out
# The code spans in items 1 and 2 must render correctly
assert "border-radius: 0 8px 8px 0" in out
assert "#e8a030" in out
# The surrounding prose text must be preserved
assert "straight left edge everywhere" in out
assert "same warm gold used in the logo" in out
assert "active always wins with gold" in out
def test_render_md_markdown_bold_in_list_item(cleanup_test_sessions):
"""**bold** markdown inside list items must render as ."""
out = render_md("- **First** item\n- **Second** item with `code`")
assert "First" in out
assert "Second" in out
assert "code" in out
def test_render_md_html_strong_in_blockquote(cleanup_test_sessions):
""" inside blockquote must render as bold."""
out = render_md("> Note: pay attention")
assert "<strong>" not in out
assert "Note:" in out
def test_render_md_html_strong_in_heading(cleanup_test_sessions):
""" inside a heading must render as bold."""
out = render_md("## Important Section")
assert "<strong>" not in out
assert "Important" in out
def test_render_md_xss_in_list_still_blocked(cleanup_test_sessions):
"""XSS attempts in list items must still be escaped."""
out = render_md("- bad")
assert "
")
assert " must be HTML-escaped."""
out = render_md("")
assert "")
assert "