from bs4 import BeautifulSoup
from src.visual_report import _source_evidence_summary, generate_visual_report
def test_visual_report_toc_links_match_rendered_heading_ids():
report = """
# Automated Crypto Trading Bot Strategies
### **1.0 Introduction & Research Scope**
Intro body.
### **2.0 Determining the "Best" Configuration**
Configuration body.
"""
html = generate_visual_report(
"crypto bot strategies",
report,
sources=[],
stats={},
session_id="rp-test",
)
soup = BeautifulSoup(html, "html.parser")
links = soup.select(".toc-sidebar nav a")
assert [link.get_text(strip=True) for link in links] == [
"1.0 Introduction & Research Scope",
'2.0 Determining the "Best" Configuration',
]
for link in links:
target_id = link["href"].removeprefix("#")
target = soup.find(id=target_id)
assert target is not None
assert target.name in {"h2", "h3"}
def test_visual_report_surfaces_evidence_profile_and_source_quality():
sources = [
{
"title": "Official documentation",
"url": "https://docs.example.com/guide",
"source_kind": "primary",
"source_score": 90,
"source_reason": "official project documentation",
"retrieval": "browser",
},
{
"title": "Independent analysis",
"url": "https://analysis.example.net/report",
"source_kind": "secondary",
"source_score": 70,
"retrieval": "fetch",
},
]
summary = _source_evidence_summary(sources)
assert summary == {
"sources": 2,
"domains": 2,
"primary": 1,
"browser": 1,
"average_score": 80,
"rated": 2,
}
soup = BeautifulSoup(
generate_visual_report(
"Evidence report",
"## Findings\n\nEvidence-backed text.\n\n### Detail\n\nMore detail.",
sources=sources,
stats={},
session_id="rp-evidence",
),
"html.parser",
)
values = [node.get_text(strip=True) for node in soup.select(".evidence-value")]
assert values == ["2", "2", "1", "80/100"]
assert soup.select_one(".evidence-read-time").get_text(" ", strip=True) == "1 min read"
assert soup.select_one(".source-badge.primary").get_text(strip=True) == "primary"
assert soup.find(string="Rendered read") is not None
assert soup.find(string="90/100") is not None
def test_visual_report_has_reading_progress_and_mobile_section_navigation():
soup = BeautifulSoup(
generate_visual_report(
"Navigation report",
"# Navigation report\n\n## First section\n\nBody.\n\n### Detail\n\nMore body.",
sources=[],
stats={},
),
"html.parser",
)
assert soup.select_one(".reading-progress-bar") is not None
options = soup.select("#mobile-section-select option")
assert [option.get_text(strip=True) for option in options] == [
"First section",
"— Detail",
]
assert [option["value"] for option in options] == ["first-section", "detail"]
def test_visual_report_wraps_editorial_sections_and_captions_source_images():
soup = BeautifulSoup(
generate_visual_report(
"Visual report",
"# Visual report\n\n## Opening\n\nBody.\n\n## Analysis\n\nMore body.",
sources=[{
"title": "Official release notes",
"url": "https://example.com/releases",
"image": "https://cdn.example.com/release-cover.jpg",
}],
stats={},
),
"html.parser",
)
sections = soup.select("main.content > section.report-section")
assert [section.h2.get_text(strip=True) for section in sections] == [
"Opening",
"Analysis",
]
hero = soup.select_one("figure.hero-image")
assert hero.img["alt"] == "Official release notes"
assert hero.figcaption.get_text(" ", strip=True) == (
"Official release notes example.com"
)
def test_visual_report_parses_and_compacts_research_trace_details():
report = """# Trace report
## Findings
Body.
Research trace
### Planned Actions
- **Round 1** `planner` -> `web_search`
```text
Sources analyzed: 3
```
","detail":"First"},{"label":"Second","detail":"Done"}]}
```
## Invalid
```visual-diagram
{"title":"Broken","nodes":[}
```
'''
soup = BeautifulSoup(
generate_visual_report(
"Safe visual explanation",
report,
sources=[],
stats={},
category="visual",
),
"html.parser",
)
assert len(soup.select("figure.visual-diagram")) == 1
assert soup.select_one("figure.visual-diagram-layers") is not None
assert soup.select_one("main.content script") is None
assert soup.select_one("main.content img") is None
assert "
" in soup.select_one(".visual-node-label").get_text()
assert "Broken" not in soup.get_text()
def test_visual_explanation_embeds_sandboxed_visual_html_and_removes_active_content():
report = r'''# How a black hole forms
```visual-html