111 lines
3.8 KiB
Python
111 lines
3.8 KiB
Python
"""Tests for the transcript renderer (Task 33).
|
|
|
|
Lightweight markdown for transcript turns:
|
|
- ``*action*`` → ``<em class="action">action</em>``
|
|
- ``**bold**`` → ``<strong>bold</strong>``
|
|
- ``((ooc))`` → ``<span class="ooc">((ooc))</span>``
|
|
- ``> line`` → ``<blockquote>line</blockquote>``
|
|
- paragraph breaks (double newline) → ``</p><p>``
|
|
- everything HTML-escaped first
|
|
|
|
No headings, no code blocks, no links — out of scope per Requirements §16.3.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from chat.web.render import render_prose, render_turn_html
|
|
|
|
|
|
def test_render_prose_escapes_html():
|
|
"""Raw HTML in user content must be escaped — no XSS surface."""
|
|
out = render_prose("<script>alert(1)</script>")
|
|
assert "<script>" not in out
|
|
assert "<script>" in out
|
|
|
|
|
|
def test_render_prose_action_to_italic():
|
|
out = render_prose("*walks over*")
|
|
assert '<em class="action">walks over</em>' in out
|
|
|
|
|
|
def test_render_prose_bold_before_action():
|
|
"""Bold (``**``) must be processed before action (``*``)."""
|
|
out = render_prose("**emphasis** and *action*")
|
|
assert "<strong>emphasis</strong>" in out
|
|
assert '<em class="action">action</em>' in out
|
|
# Make sure we didn't double-wrap: no stray asterisks left behind.
|
|
assert "*" not in out
|
|
|
|
|
|
def test_render_prose_ooc_wrapped():
|
|
out = render_prose("((this is OOC))")
|
|
assert '<span class="ooc">' in out
|
|
assert "((this is OOC))" in out
|
|
|
|
|
|
def test_render_prose_paragraphs():
|
|
out = render_prose("First.\n\nSecond.")
|
|
# Two <p> opens and two closes.
|
|
assert out.count("<p>") == 2
|
|
assert out.count("</p>") == 2
|
|
assert "<p>First.</p>" in out
|
|
assert "<p>Second.</p>" in out
|
|
|
|
|
|
def test_render_prose_blockquote():
|
|
out = render_prose("> a quote")
|
|
assert "<blockquote>a quote</blockquote>" in out
|
|
|
|
|
|
def test_render_prose_empty():
|
|
"""Empty / whitespace-only inputs produce empty output, not stray tags."""
|
|
assert render_prose("") == ""
|
|
assert render_prose(" ") == ""
|
|
|
|
|
|
def test_render_turn_html_includes_role_class():
|
|
out = render_turn_html("BotA", "Hello.", role="bot")
|
|
assert 'class="turn turn-bot"' in out
|
|
assert "<strong>BotA</strong>" in out
|
|
assert "Hello." in out
|
|
|
|
|
|
def test_render_turn_html_escapes_speaker():
|
|
"""Speaker label is also HTML-escaped — names are user-controlled."""
|
|
out = render_turn_html("<bad>", "hi", role="you")
|
|
# Raw tag should not appear; escaped form should.
|
|
assert "<bad>" not in out
|
|
assert "<bad>" in out
|
|
|
|
|
|
def test_render_prose_mixed_full_message():
|
|
"""Realistic turn with action, dialogue, and an OOC aside."""
|
|
text = "*looks up* \"You're back late.\" ((she's tired))"
|
|
out = render_prose(text)
|
|
assert '<em class="action">looks up</em>' in out
|
|
# The apostrophe in ``she's`` is HTML-escaped to ``'``.
|
|
assert '<span class="ooc">((she's tired))</span>' in out
|
|
|
|
|
|
def test_render_turn_html_stamps_event_id_when_provided():
|
|
"""T86 follow-up: when ``event_id`` is supplied the wrapper DIV
|
|
carries ``id="turn-<event_id>"`` so the chat-page
|
|
``turn_html_replace`` SSE handler can locate the prior turn DOM
|
|
node by id and swap it in-place. Without the id the handler's
|
|
``getElementById('turn-' + supersedes_id)`` lookup misses and
|
|
the regenerated turn appends instead of replaces.
|
|
"""
|
|
out = render_turn_html("BotA", "Hello.", role="bot", event_id=42)
|
|
assert 'id="turn-42"' in out
|
|
# The id must sit on the wrapper DIV, not somewhere nested inside.
|
|
assert out.startswith('<div id="turn-42" class="turn turn-bot">')
|
|
|
|
|
|
def test_render_turn_html_omits_id_when_event_id_missing():
|
|
"""Legacy callers (no ``event_id`` passed) get a clean DIV with no
|
|
id attribute — preserves the pre-T86 fragment shape.
|
|
"""
|
|
out = render_turn_html("BotA", "Hello.", role="bot")
|
|
assert "id=" not in out
|
|
assert out.startswith('<div class="turn turn-bot">')
|