JudgeGPT / tests /test_ui_rendering.py
AliIqbal05's picture
Fix transparent foreground prop assets (#2)
8b9620e
Raw
History Blame Contribute Delete
10.2 kB
import inspect
from pathlib import Path
from PIL import Image
import app
from sovereign_bench.models import AgentTurn, EvidenceItem, JurorVote, TrialEvent
OLD_CARD_CLASSES = [
"paper-panel",
"juror-panel",
"mind-panel",
"empty-state",
"trial-downloads",
]
def _event_with_lower_tab_data() -> TrialEvent:
evidence = EvidenceItem(
id="E1",
title="Ledger entry",
source="Archive",
excerpt="A short exhibit excerpt.",
supports="claimant",
reliability=0.82,
note="Useful but incomplete.",
)
vote = JurorVote(
juror="Karl Marx",
persona=app.JUROR_PERSONAS["Karl Marx"],
vote="liable",
reason="The exhibit supports the claim.",
evidence_ids=["E1"],
)
return TrialEvent(
phase="deliberation",
title="Jury weighs the record",
body="The jury reviews the record.",
turns=[
AgentTurn(
agent="Nemotron Jury",
role="juror panel",
content="The jurors compare E1 and state their votes.",
model="nvidia/Nemotron-Orchestrator-8B",
confidence=0.84,
input="SYSTEM:\nYou are the jury.\n\nUSER:\nWeigh E1 and explain the vote.",
)
],
evidence=[evidence],
votes=[vote],
trace={"mode": "test"},
)
def _speaker_event(agent: str, phase: str = "questions") -> TrialEvent:
return TrialEvent(
phase=phase,
title=f"{agent} speaks",
body="A single speaker takes the floor.",
turns=[
AgentTurn(
agent=agent,
role="test speaker",
content=f"{agent} has the visible floor.",
model="test-model",
confidence=0.9,
input="SYSTEM:\nTest prompt.",
)
],
)
def test_lower_tab_renderers_emit_plain_text_classes():
event = _event_with_lower_tab_data()
html = "\n".join(
[
app.render_evidence([]),
app.render_evidence([event]),
app.render_jurors([]),
app.render_jurors([event]),
app.render_mind([], True),
app.render_mind([event], True),
app.render_mind([event], False),
]
)
for class_name in OLD_CARD_CLASSES:
assert class_name not in html
assert "drawer-text-block" in html
assert "drawer-empty" in html
assert "mind-text" in html
def test_download_controls_are_not_wired_into_app():
source = inspect.getsource(app.build_app)
assert "DownloadButton" not in source
assert "Download decree" not in source
assert "Download agent trace" not in source
def test_courtroom_splits_six_jurors_between_side_benches():
html = app.render_court([_event_with_lower_tab_data()], started=True)
assert "jury-benches left" in html
assert "jury-benches right" in html
assert html.count("<a class='juror") == 6
assert html.find("jury-benches left") < html.find("jury-benches right")
assert ".jury-benches.left {\n left: 1%;" in app.CSS
assert ".jury-benches.right {\n right: 1%;" in app.CSS
assert ".jury-benches.left {\n left: .5%;" in app.CSS
assert ".jury-benches.right {\n right: .5%;" in app.CSS
def test_courtroom_threads_show_model_input_output_on_hover_and_click():
html = app.render_court([_event_with_lower_tab_data()], started=True)
assert "tooltip-io-label'>Input" in html
assert "tooltip-io-label'>Output" in html
assert "Click to open full thread" in html
assert "class='ai-thread-modal'" in html
assert "thread-block'>SYSTEM:" in html
assert "The jurors compare E1 and state their votes." in html
assert "href='#ai-thread-karl-marx'" in html
def test_courtroom_renders_historical_judge_and_juror_assets():
html = app.render_court([_event_with_lower_tab_data()], started=True)
assert "Marcus Aurelius" in html
assert "assets/characters/marcus-aurelius.png" in html
for name, image in app.JUROR_IMAGES.items():
assert name in html
assert image in html
assert html.count("class='juror-portrait'") == 6
def test_courtroom_renders_foreground_fences_and_judge_table_above_characters():
html = app.render_court([_event_with_lower_tab_data()], started=True)
assert html.count("assets/foreground/foregroundFence.png") == 2
assert "assets/foreground/JudgeTable.png" in html
assert html.find("class='puppet judge") < html.find("class='foreground-props'")
assert ".foreground-props {\n position: absolute;\n inset: 0;\n z-index: 13;" in app.CSS
assert ".puppet {\n --skin: #c99257;" in app.CSS
assert "z-index: 8;" in app.CSS
def test_foreground_prop_assets_have_real_transparency():
for path in [
Path("assets/foreground/foregroundFence.png"),
Path("assets/foreground/JudgeTable.png"),
]:
alpha = Image.open(path).convert("RGBA").getchannel("A")
histogram = alpha.histogram()
assert histogram[0] > 0, f"{path} has no fully transparent pixels"
assert histogram[255] > 0, f"{path} has no fully opaque prop pixels"
def test_latest_speaker_sets_stage_class_and_speech_bubble():
html = app.render_court([_speaker_event("Advocate Auric", phase="claims")], started=True)
assert "speaker-auric" in html
assert "class='speech-bubble'" in html
assert "Advocate Auric has the visible floor." in html
assert "puppet auric active walking" in html
assert "puppet sable active" not in html
def test_individual_juror_can_be_active_speaker():
event = TrialEvent(
phase="deliberation",
title="Juror Karl Marx Votes",
body=app.JUROR_PERSONAS["Karl Marx"],
turns=[
AgentTurn(
agent="Karl Marx",
role="juror",
content="Liable. E1 carries the record.",
model="nvidia/Nemotron-Orchestrator-8B",
confidence=0.86,
input="SYSTEM:\nJury JSON prompt.",
)
],
votes=[
JurorVote(
juror="Karl Marx",
persona=app.JUROR_PERSONAS["Karl Marx"],
vote="liable",
reason="E1 carries the record.",
evidence_ids=["E1"],
)
],
)
html = app.render_court([event], started=True)
assert "speaker-karl-marx" in html
assert "<a class='juror active'" in html
assert "Liable. E1 carries the record." in html
def test_lawyer_movement_css_is_speaker_specific_not_phase_wide():
assert ".speaker-auric .puppet.auric" in app.CSS
assert ".speaker-sable .puppet.sable" in app.CSS
assert ".phase-claims .puppet.auric" not in app.CSS
assert ".phase-opening .puppet.sable" not in app.CSS
def test_closed_book_is_smaller_and_key_characters_are_lowered():
assert ".episode-book.closed {\n top: 61%;\n width: min(163px, 20vw);" in app.CSS
assert ".puppet.judge {\n left: 50%;\n top: 56%;" in app.CSS
assert ".puppet.auric {\n left: 24%;\n top: 87%;" in app.CSS
assert ".speaker-auric .puppet.auric {\n left: 43%;\n top: 91%;" in app.CSS
assert ".puppet.auditor {\n left: 71%;\n top: 80%;" in app.CSS
assert ".episode-book.closed {\n top: 750px;\n width: 140px;" in app.CSS
assert ".puppet.judge {\n top: 717px;" in app.CSS
assert ".puppet.auric {\n left: 20%;\n top: 970px;" in app.CSS
assert ".puppet.auditor {\n left: 78%;\n top: 860px;" in app.CSS
def test_run_ui_yields_five_outputs_without_download_status(monkeypatch):
event = _event_with_lower_tab_data()
monkeypatch.setattr(app, "get_events", lambda request: iter([event]))
outputs = list(app.run_ui("Trial of Socrates", "", "", "swift", True))
assert outputs
assert all(len(output) == 5 for output in outputs)
assert outputs[1][-1] == "Step 1: Jury weighs the record"
assert outputs[-1][-1] == "Verdict sealed."
assert "download" not in outputs[-1][-1].lower()
def test_run_ui_stops_with_model_unavailable_error(monkeypatch):
def broken_events(request):
raise RuntimeError("Marcus Aurelius unavailable: offline")
yield
monkeypatch.setattr(app, "get_events", broken_events)
outputs = list(app.run_ui("Trial of Socrates", "", "", "swift", True))
assert outputs[-1][-1] == "Model response required. Trial stopped: Marcus Aurelius unavailable: offline"
assert "Claimant score" not in outputs[-1][0]
def test_court_renders_sound_toggle():
html = app.render_court([])
assert "sound-toggle" in html
assert "aria-label='Toggle sound'" in html
assert "aria-pressed='false'" in html
def test_audio_controller_has_score_breathing_and_mute_toggle():
assert "SCORE_BREATH_INTERVAL_MS = 20000" in app.APP_JS
assert "SCORE_BREATH_DURATION_MS = 5000" in app.APP_JS
assert "toggleMuted()" in app.APP_JS
assert "this.fadeScore(SCORE_QUIET_VOLUME, halfDuration" in app.APP_JS
def test_courtroom_background_has_no_overlay_or_character_shadow():
assert "background: #141413 !important;" in app.CSS
assert "background-color: #141413 !important;" in app.CSS
assert "cover fixed no-repeat" not in app.CSS
assert ".court-episode-stage::before {\n content: \"\";\n display: none;" in app.CSS
assert ".court-episode-stage::after {\n content: \"\";\n display: none;" in app.CSS
assert "url('/gradio_api/file=assets/background/CourtRoom.png') center center / 100% 100% no-repeat" in app.CSS
assert "filter: drop-shadow(0 12px 14px" not in app.CSS
assert "filter: drop-shadow(0 8px 10px" not in app.CSS
def test_synthetic_stage_props_do_not_tint_background():
assert ".bench-front {\n display: none;" in app.CSS
assert ".trial-floor-mark {\n display: none;" in app.CSS
assert ".gallery-benches {\n display: none;" in app.CSS
assert ".prop-label {\n display: none;" in app.CSS
assert ".counsel-table" in app.CSS
assert "background: transparent;\n box-shadow: none;" in app.CSS
assert ".witness-area" in app.CSS