bureaucat / tests /test_ui_renderers.py
ravinsingh15's picture
Bureaucat — Build Small Hackathon submission (Qwen3-VL-8B, ZeroGPU, gr.Server)
6b5e47d
Raw
History Blame Contribute Delete
13.6 kB
"""
tests/test_ui_renderers.py — Unit tests for app.py UI renderer functions.
Covers: render_quip, render_deadlines_html, render_footer,
render_panic_meter, render_mascot, select_result_state (Plan 03).
Run: BUREAUCAT_NO_MODEL=1 .venv/bin/python -m pytest tests/test_ui_renderers.py -x -q
"""
import os
import sys
# Set escape hatch BEFORE importing app so model weights are never downloaded.
os.environ["BUREAUCAT_NO_MODEL"] = "1"
# Ensure the project root is on the path when running from tests/
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import app # noqa: E402
# ---------------------------------------------------------------------------
# render_quip
# ---------------------------------------------------------------------------
class TestRenderQuip:
def test_empty_string_returns_empty(self):
assert app.render_quip("") == ""
def test_whitespace_only_returns_empty(self):
assert app.render_quip(" ") == ""
def test_none_like_empty(self):
# render_quip is typed str -> str but test falsy behavior
assert app.render_quip("") == ""
def test_quip_contains_prefix_exactly_once(self):
result = app.render_quip("witty remark")
assert result.count("Bureaucat says:") == 1
def test_quip_contains_prefix_exactly_once_with_existing_prefix(self):
# Even if quip itself mentions the prefix, we only add it once
result = app.render_quip("Skatteverket wants your money")
assert result.count("Bureaucat says:") == 1
def test_quip_contains_quip_text(self):
result = app.render_quip("witty")
assert "witty" in result
def test_quip_no_double_prefix(self):
# parse_output already stripped "Bureaucat says:" from quip; renderer adds once
result = app.render_quip("the cat is watching")
count = result.count("Bureaucat says:")
assert count == 1, f"Expected exactly 1 prefix occurrence, got {count}: {result!r}"
def test_quip_is_markdown_not_raw_html(self):
# Should use Markdown formatting (**...**) not raw HTML tags for safety
result = app.render_quip("test quip")
assert "Bureaucat says:" in result
assert "test quip" in result
# ---------------------------------------------------------------------------
# render_deadlines_html
# ---------------------------------------------------------------------------
class TestRenderDeadlinesHtml:
def test_value_line_contains_mark_tag(self):
result = app.render_deadlines_html("- 15 juni 2026 — last day")
assert "<mark" in result
def test_value_line_contains_verbatim_value(self):
result = app.render_deadlines_html("- 15 juni 2026 — last day")
assert "15 juni 2026" in result
def test_value_line_contains_amber_background(self):
result = app.render_deadlines_html("- 15 juni 2026 — last day")
assert "#FFF3CD" in result
def test_none_found_has_no_mark(self):
result = app.render_deadlines_html("None found.")
assert "<mark" not in result
def test_none_found_case_insensitive(self):
result = app.render_deadlines_html("NONE FOUND.")
assert "<mark" not in result
def test_empty_string_has_no_mark(self):
result = app.render_deadlines_html("")
assert "<mark" not in result
def test_multiple_items_all_wrapped(self):
deadlines = "- 15 juni 2026 — last day\n- 1 234 kr — belopp"
result = app.render_deadlines_html(deadlines)
assert result.count("<mark") == 2
def test_mark_pill_color_values(self):
result = app.render_deadlines_html("- 2026-06-15 — deadline")
# Verify pill spec matches UI-SPEC §Deadlines & Money Card
assert "#FFF3CD" in result
assert "#8B6914" in result
def test_html_escaping_in_value(self):
# Model-generated text should be HTML-escaped (T-02-02-01)
result = app.render_deadlines_html("- <script>alert(1)</script> — test")
assert "<script>" not in result
def test_uses_li_elements(self):
result = app.render_deadlines_html("- 2026-06-15 — deadline")
assert "<li" in result
# ---------------------------------------------------------------------------
# render_footer
# ---------------------------------------------------------------------------
class TestRenderFooter:
def test_contains_privacy_note(self):
result = app.render_footer()
assert "Nothing is stored after your session ends." in result
def test_contains_legal_disclaimer(self):
result = app.render_footer()
assert "does not give legal advice" in result
def test_contains_trust_01_full_copy(self):
result = app.render_footer()
# Full TRUST-01 verbatim (UI-SPEC Copywriting Contract line 295)
assert "Everything runs on a small model inside this Space." in result
assert "Nothing is sent to external APIs." in result
assert "Nothing is stored after your session ends." in result
def test_contains_trust_05_full_copy(self):
result = app.render_footer()
# Full TRUST-05 verbatim (UI-SPEC Copywriting Contract line 296)
assert "Bureaucat explains letters" in result
assert "does not give legal advice" in result
assert "consult a qualified professional" in result
def test_returns_string(self):
result = app.render_footer()
assert isinstance(result, str)
assert len(result) > 0
# ---------------------------------------------------------------------------
# render_panic_meter (Plan 03)
# ---------------------------------------------------------------------------
class TestRenderPanicMeter:
def test_severity_5_contains_label(self):
result = app.render_panic_meter(5)
assert "5 — Act immediately" in result
def test_severity_5_contains_color(self):
result = app.render_panic_meter(5)
assert "#C0392B" in result
def test_severity_5_contains_denominator(self):
result = app.render_panic_meter(5)
# Verdict badge shows "5/5" — not a width-fill bar (UAT redesign 2026-06-06)
assert "/5" in result
assert "severity-5" in result
assert "width" not in result # no progress-bar fill
def test_severity_1_contains_label(self):
result = app.render_panic_meter(1)
assert "1 — Informational" in result
def test_severity_1_is_badge_not_bar(self):
result = app.render_panic_meter(1)
assert "panic-badge" in result
assert "severity-1" in result
def test_severity_1_contains_color(self):
result = app.render_panic_meter(1)
assert "#27AE60" in result
def test_severity_none_does_not_raise(self):
result = app.render_panic_meter(None)
assert isinstance(result, str)
def test_severity_none_contains_unavailable_label(self):
result = app.render_panic_meter(None)
assert "Result unavailable — try re-uploading" in result
def test_severity_none_contains_gray(self):
result = app.render_panic_meter(None)
assert "#9E9E9E" in result
def test_severity_2_label_and_color(self):
result = app.render_panic_meter(2)
assert "2 — Low concern" in result
assert "#8BC34A" in result
assert "/5" in result
def test_severity_3_label_and_color(self):
result = app.render_panic_meter(3)
assert "3 — Action needed" in result
assert "#F39C12" in result
assert "/5" in result
def test_severity_4_label_and_color(self):
result = app.render_panic_meter(4)
assert "4 — Urgent" in result
assert "#E67E22" in result
assert "/5" in result
def test_every_level_has_integer_and_word(self):
"""Accessibility: color alone must not carry meaning (UI-SPEC line 92)."""
for sev in (1, 2, 3, 4, 5):
result = app.render_panic_meter(sev)
assert str(sev) in result, f"Integer {sev} missing from output"
# Each label dict value contains " — " + word; ensure it's present
assert " — " in result, f"Word label separator missing at severity {sev}"
# ---------------------------------------------------------------------------
# select_result_state (Plan 03)
# ---------------------------------------------------------------------------
def _make_result(deadlines: str):
"""Helper: construct a minimal StructuredResult with given deadlines text."""
return app.StructuredResult(
transcription="",
quip="",
tldr="",
why="",
actions="",
deadlines=deadlines,
severity=None,
raw="",
)
class TestSelectResultState:
def test_money_amount_returns_money(self):
result = _make_result("- 4 250 kr — kvarskatt")
assert app.select_result_state(result) == "money"
def test_date_only_returns_deadline(self):
result = _make_result("- 15 juni 2026 — sista dag")
assert app.select_result_state(result) == "deadline"
def test_none_found_returns_allclear(self):
result = _make_result("None found.")
assert app.select_result_state(result) == "allclear"
def test_none_found_case_insensitive(self):
result = _make_result("NONE FOUND.")
assert app.select_result_state(result) == "allclear"
def test_money_wins_over_deadline(self):
"""Money takes priority even when a date is also present."""
result = _make_result("- 15 juni 2026 — datum\n- 4 250 kr — belopp")
assert app.select_result_state(result) == "money"
def test_sek_amount_returns_money(self):
result = _make_result("- 1000 SEK — belopp")
assert app.select_result_state(result) == "money"
def test_iso_date_returns_deadline(self):
result = _make_result("- 2026-06-15 — deadline date")
assert app.select_result_state(result) == "deadline"
def test_empty_deadlines_returns_allclear(self):
result = _make_result("")
assert app.select_result_state(result) == "allclear"
def test_no_money_no_date_returns_allclear(self):
result = _make_result("- Some reference: 123456789")
assert app.select_result_state(result) == "allclear"
# ---------------------------------------------------------------------------
# render_mascot (Plan 03)
# ---------------------------------------------------------------------------
class TestRenderMascot:
def test_verifying_contains_correct_png(self):
result = app.render_mascot("verifying")
assert "assets/mascot/verifying.png" in result
def test_verifying_contains_class(self):
result = app.render_mascot("verifying")
assert "mascot-verifying" in result
def test_verifying_contains_mascot_img_class(self):
result = app.render_mascot("verifying")
assert "mascot-img" in result
def test_unknown_state_falls_back_to_idle(self):
result = app.render_mascot("bogus")
assert "assets/mascot/idle.png" in result
def test_idle_state_renders(self):
result = app.render_mascot("idle")
assert "assets/mascot/idle.png" in result
assert "mascot-idle" in result
def test_money_state_renders(self):
result = app.render_mascot("money")
assert "assets/mascot/money.png" in result
assert "mascot-money" in result
def test_allclear_state_renders(self):
result = app.render_mascot("allclear")
assert "assets/mascot/allclear.png" in result
assert "mascot-allclear" in result
def test_deadline_state_renders(self):
result = app.render_mascot("deadline")
assert "assets/mascot/deadline.png" in result
assert "mascot-deadline" in result
def test_reading_state_renders(self):
result = app.render_mascot("reading")
assert "assets/mascot/reading.png" in result
assert "mascot-reading" in result
def test_confused_and_wrong_document_in_assets(self):
"""Phase 3 wires confused and wrong_document error states into MASCOT_ASSETS."""
assert "confused" in app.MASCOT_ASSETS
assert "wrong_document" in app.MASCOT_ASSETS
assert "confused.png" in app.MASCOT_ASSETS["confused"]
assert "wrong_document.png" in app.MASCOT_ASSETS["wrong_document"]
# ---------------------------------------------------------------------------
# ANIMATION_CSS_HTML content verification (Plan 03)
# ---------------------------------------------------------------------------
class TestAnimationCssHtml:
def test_contains_keyframe_bob(self):
assert "@keyframes bob" in app.ANIMATION_CSS_HTML
def test_contains_keyframe_sway(self):
assert "@keyframes sway" in app.ANIMATION_CSS_HTML
def test_contains_keyframe_blink(self):
assert "@keyframes blink" in app.ANIMATION_CSS_HTML
def test_contains_keyframe_pop(self):
assert "@keyframes pop" in app.ANIMATION_CSS_HTML
def test_contains_keyframe_shake(self):
assert "@keyframes shake" in app.ANIMATION_CSS_HTML
def test_contains_mascot_img_transition(self):
assert "mascot-img" in app.ANIMATION_CSS_HTML
assert "opacity 0.2s ease" in app.ANIMATION_CSS_HTML
def test_no_javascript(self):
"""CSS-only requirement (D2-10): no <script> tags in animation block."""
assert "<script" not in app.ANIMATION_CSS_HTML.lower()