"""Unit tests for dashboard/components.py HTML helpers.""" from __future__ import annotations import pytest from unittest.mock import patch, MagicMock def test_ai_badge_default_label(): from dashboard.components import ai_badge html = ai_badge() assert "✦ AI Synthesis" in html assert "#7c3aed" in html assert "#ede9fe" in html def test_ai_badge_custom_label(): from dashboard.components import ai_badge html = ai_badge("AI") assert "✦ AI" in html assert "#7c3aed" in html def test_interpretation_card_contains_badge_and_content(): from dashboard.components import interpretation_card html = interpretation_card("What matters most", "
Some insight
") assert "✦ AI Synthesis" in html assert "#8b5cf6" in html # purple border (PURPLE token value) # card background is now white (#ffffff), not lavender AI_BG assert "What matters most" in html assert "Some insight
" in html def test_interpretation_card_custom_badge_label(): from dashboard.components import interpretation_card html = interpretation_card("Language shift", "tone changed
", badge_label="AI") assert "✦ AI" in html assert "Language shift" in html def _render_signal(sig) -> str: """Render a Signal through signal_card and return the produced HTML.""" import streamlit as st from dashboard.components import signal_card with patch.object(st, "markdown") as mock_md: signal_card(sig) return mock_md.call_args[0][0] def test_signal_card_tension_variant(): """Tension variant: two-sided body, exactly one ✦ AI badge in the header.""" from dashboard.signal_feed import Signal html = _render_signal(Signal( kind="tension", headline="Revenue beat hides quality decline", stance="mixed", significance="HIGH", extra={ "bullish_reading": "Strong top-line momentum", "bearish_reading": "One-time item inflated result", "bullish_evidence": {"evidence_snippet": "q1", "reliability": "HIGH", "source": "10-Q"}, "bearish_evidence": {"evidence_snippet": "q2", "reliability": "HIGH", "source": "10-Q"}, }, )) assert "✦ AI · experimental" in html assert html.count("✦ AI") == 1 # badge is in the outer header, not per reading panel assert "Strong top-line momentum" in html assert "One-time item inflated result" in html assert "Surface reading" in html assert "Deeper reading" in html assert "Material" in html # priority label, not raw HIGH/MED/LOW # mixed stance → amber accent, never bull green or bear red on the frame assert "#f59e0b" in html def test_signal_card_quality_variant(): """Quality variant: assessment chip + rationale + AI badge (AI-assessed kind).""" from dashboard.signal_feed import Signal html = _render_signal(Signal( kind="quality", body="Management raised full-year guidance for the third consecutive quarter.", stance="bull", category="guidance_dynamics", extra={"assessment": "positive"}, evidence_snippet="raised guidance", source="10-Q", reliability="HIGH", )) assert "Management raised full-year guidance" in html assert "Guidance" in html # dimension label for guidance_dynamics assert "▲" in html assert "Positive" in html assert "✦ AI · experimental" in html # quality assessments are experimental AI assert "#10b981" in html # bull stance → green accent def test_signal_card_delta_variant_redline(): """Delta variant: computed-metric box + before/after redline, no AI badge.""" from dashboard.signal_feed import Signal html = _render_signal(Signal( kind="delta", headline="export controls", body="2→8 occurrences (+300%)", stance="bear", significance="HIGH", category="term_frequency", before_text="limited exposure to export restrictions", after_text="new export restrictions may materially affect revenue", source="10-Q", period_range="4Q2024 → 1Q2025", )) assert "computed" in html assert "2→8 occurrences" in html assert "before" in html and "after" in html assert "4Q2024 → 1Q2025" in html assert "✦ AI" not in html # deltas are deterministic, not AI assert "Heuristic · validate" in html def test_signal_card_risk_new_badge(): """Risk variant: category chip + NEW badge when is_new.""" from dashboard.signal_feed import Signal html = _render_signal(Signal( kind="risk", body="China exposure may affect supply chains.", stance="bear", significance="HIGH", category="Geopolitical", is_new=True, source="10-K", reliability="HIGH", impact="HIGH", )) assert "Geopolitical" in html assert "NEW" in html assert "#ef4444" in html # bear stance → red accent # ── New helper tests ────────────────────────────────────────────────────────── def test_eyebrow_label(): from dashboard.components import eyebrow_label html = eyebrow_label("what changed") assert "text-transform:uppercase" in html assert "what changed" in html assert "letter-spacing" in html html_colored = eyebrow_label("section", color="#ef4444") assert "#ef4444" in html_colored def test_importance_marker_levels(): from dashboard.components import importance_marker high = importance_marker("HIGH") assert "Critical" in high assert "#0f172a" in high assert "●" in high # monochrome only — no green or red signal colors assert "#10b981" not in high assert "#ef4444" not in high medium = importance_marker("MEDIUM") assert "Important" in medium assert "#64748b" in medium assert "●" in medium low = importance_marker("LOW") assert "Context" in low assert "#94a3b8" in low assert "●" in low # Unknown / empty returns empty string assert importance_marker("") == "" def test_meta_row_formatting(): from dashboard.components import meta_row result = meta_row("HIGH", "10-Q", "HIGH") assert "High confidence" in result assert "10-Q" in result assert "Critical" in result # from importance_marker("HIGH") # All empty → empty string assert meta_row("", "", "") == "" result2 = meta_row("MEDIUM", "transcript", "LOW") assert "Med. confidence" in result2 assert "transcript" in result2 assert "Context" in result2 # from importance_marker("LOW") def test_sort_by_impact_order(): from dashboard.components import sort_by_impact items = [ {"impact": "HIGH", "name": "h"}, {"impact": "LOW", "name": "l"}, {"impact": "MEDIUM", "name": "m"}, {"name": "none"}, # missing "impact" key ] original = list(items) # copy to check mutation sorted_items = sort_by_impact(items) assert [i.get("impact", "") for i in sorted_items] == ["HIGH", "MEDIUM", "LOW", ""] # Original list must not be mutated assert items == original