| """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", "<p>Some insight</p>") |
| assert "β¦ AI Synthesis" in html |
| assert "#8b5cf6" in html |
| |
| assert "What matters most" in html |
| assert "<p>Some insight</p>" in html |
|
|
|
|
| def test_interpretation_card_custom_badge_label(): |
| from dashboard.components import interpretation_card |
| html = interpretation_card("Language shift", "<p>tone changed</p>", 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 |
| 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 |
| |
| 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 |
| assert "β²" in html |
| assert "Positive" in html |
| assert "β¦ AI Β· experimental" in html |
| assert "#10b981" in html |
|
|
|
|
| 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 |
| 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 |
|
|
|
|
| |
|
|
| 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 |
| |
| 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 |
|
|
| |
| 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 |
|
|
| |
| assert meta_row("", "", "") == "" |
|
|
| result2 = meta_row("MEDIUM", "transcript", "LOW") |
| assert "Med. confidence" in result2 |
| assert "transcript" in result2 |
| assert "Context" in result2 |
|
|
|
|
| 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"}, |
| ] |
| original = list(items) |
|
|
| sorted_items = sort_by_impact(items) |
|
|
| assert [i.get("impact", "") for i in sorted_items] == ["HIGH", "MEDIUM", "LOW", ""] |
|
|
| |
| assert items == original |
|
|