Sidebar nav now groups tools under Data Review / Data Cleaners / Transformations / Automations via st.navigation, replacing the flat auto-discovered list. Tool display names switch to action-first phrasing (Find Duplicates, Fix Missing Values, Find Unusual Values, Standardize Formats, Clean Text, Quality Check, Map Columns, Combine Files, Automated Workflows) in EN + ES packs and on each page's H1. The Data Cleaners section follows the requested order: Missing Values → Outliers → Text Cleaner → Format Standardizer → Deduplicator → Quality Check. (Text Cleaner kept inside cleaners since the request didn't list it but the tool still ships.) Registry now carries a section field; helpers added: tools_in_section(), section_label(). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
228 lines
8.3 KiB
Python
228 lines
8.3 KiB
Python
"""Findings panel rendering tests.
|
||
|
||
``render_findings_panel`` is the central widget on the home page and
|
||
the Review page; failures here cascade into the user's first
|
||
impression. We drive it via a tiny test harness page
|
||
(``_findings_panel_harness.py``) so the test can inject findings
|
||
directly into session state — no file_uploader simulation needed.
|
||
|
||
We verify:
|
||
|
||
- Empty findings list → localized "no issues" success message.
|
||
- Findings with tool ids → one expander per tool, labeled in the
|
||
active language.
|
||
- Header + severity summary render at the top.
|
||
- Untargeted findings land in the "Other / file-level" expander.
|
||
|
||
Pack-key parity is already pinned by ``test_lang_packs.py``; this
|
||
file pins the call sites instead.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from pathlib import Path
|
||
|
||
import pandas as pd
|
||
import pytest
|
||
|
||
from streamlit.testing.v1 import AppTest
|
||
|
||
from .conftest import PROJECT_ROOT, collected_text, with_language
|
||
|
||
HARNESS_PATH = Path(__file__).resolve().parent / "_findings_panel_harness.py"
|
||
|
||
|
||
def _harness(findings, lang: str = "en") -> AppTest:
|
||
"""Build an AppTest of the harness page with ``findings`` pre-stashed."""
|
||
app = AppTest.from_file(str(HARNESS_PATH))
|
||
app.session_state["test_findings"] = findings
|
||
if lang != "en":
|
||
app.session_state["ui_lang"] = lang
|
||
return app
|
||
|
||
|
||
def _make_finding(tool: str = "", **overrides):
|
||
"""Build a minimal :class:`Finding` object. ``Finding`` is a frozen
|
||
dataclass; constructor signature is well-pinned by core tests, so
|
||
we use it directly here rather than building dicts."""
|
||
from src.core.analyze import Finding
|
||
kwargs = dict(
|
||
id="test_finding",
|
||
severity="warn",
|
||
tool=tool,
|
||
count=1,
|
||
description="A test finding.",
|
||
column=None,
|
||
samples=[],
|
||
confidence="medium",
|
||
fix_action="",
|
||
)
|
||
kwargs.update(overrides)
|
||
return Finding(**kwargs)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Empty findings → success message
|
||
# ---------------------------------------------------------------------------
|
||
|
||
class TestEmptyFindings:
|
||
def test_empty_renders_no_issues_english(self):
|
||
app = _harness([])
|
||
app.run()
|
||
text = collected_text(app)
|
||
assert "No issues detected" in text
|
||
|
||
def test_empty_renders_no_issues_spanish(self):
|
||
app = _harness([], lang="es")
|
||
app.run()
|
||
text = collected_text(app)
|
||
assert "No se detectaron problemas" in text
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Header text
|
||
# ---------------------------------------------------------------------------
|
||
|
||
class TestHeader:
|
||
def test_header_english(self):
|
||
app = _harness([_make_finding(tool="02_text_cleaner")])
|
||
app.run()
|
||
text = collected_text(app)
|
||
assert "Detected issues" in text
|
||
|
||
def test_header_spanish(self):
|
||
app = _harness([_make_finding(tool="02_text_cleaner")], lang="es")
|
||
app.run()
|
||
text = collected_text(app)
|
||
assert "Problemas detectados" in text
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Per-tool grouping → one expander per tool id
|
||
# ---------------------------------------------------------------------------
|
||
|
||
class TestGrouping:
|
||
def test_findings_grouped_into_per_tool_expanders(self):
|
||
findings = [
|
||
_make_finding(tool="02_text_cleaner", id="whitespace_padding"),
|
||
_make_finding(tool="02_text_cleaner", id="nbsp_padding"),
|
||
_make_finding(tool="03_format_standardizer", id="mixed_case_email"),
|
||
]
|
||
app = _harness(findings)
|
||
app.run()
|
||
labels = [e.label for e in app.expander]
|
||
# Two unique tools → two expanders. Each label carries the
|
||
# tool's display name + finding count.
|
||
text_cleaner_expanders = [lbl for lbl in labels if "Clean Text" in lbl]
|
||
format_expanders = [lbl for lbl in labels if "Standardize Formats" in lbl]
|
||
assert len(text_cleaner_expanders) == 1, (
|
||
f"expected one Clean Text expander; got: {labels}"
|
||
)
|
||
assert len(format_expanders) == 1, (
|
||
f"expected one Standardize Formats expander; got: {labels}"
|
||
)
|
||
|
||
def test_tool_names_localize_in_spanish(self):
|
||
findings = [_make_finding(tool="02_text_cleaner")]
|
||
app = _harness(findings, lang="es")
|
||
app.run()
|
||
labels = [e.label for e in app.expander]
|
||
assert any("Limpiar texto" in lbl for lbl in labels), (
|
||
f"Spanish tool name missing; expanders: {labels}"
|
||
)
|
||
|
||
def test_finding_count_in_expander_label(self):
|
||
findings = [
|
||
_make_finding(tool="02_text_cleaner", id=f"f{i}")
|
||
for i in range(3)
|
||
]
|
||
app = _harness(findings)
|
||
app.run()
|
||
labels = [e.label for e in app.expander]
|
||
# Pack template: "{tool} — {n} finding(s)"
|
||
text_cleaner_label = next(l for l in labels if "Clean Text" in l)
|
||
assert "3" in text_cleaner_label, (
|
||
f"expected count '3' in expander label; got {text_cleaner_label!r}"
|
||
)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Open-tool button localizes
|
||
# ---------------------------------------------------------------------------
|
||
|
||
class TestOpenToolButton:
|
||
"""Each tool section has an ``st.page_link`` to jump to that tool's
|
||
page. AppTest exposes page_links as ``app.button`` entries with
|
||
label ``"Open {tool} →"`` (English) / ``"Abrir {tool} →"`` (Spanish)."""
|
||
|
||
def test_open_tool_label_english(self):
|
||
findings = [_make_finding(tool="02_text_cleaner")]
|
||
app = _harness(findings)
|
||
app.run()
|
||
# ``st.page_link`` may show up under ``app.button`` or in the
|
||
# raw markdown. We probe both.
|
||
text = collected_text(app)
|
||
# Pack template: "Open {tool} →"
|
||
assert "Open Clean Text" in text
|
||
|
||
def test_open_tool_label_spanish(self):
|
||
findings = [_make_finding(tool="02_text_cleaner")]
|
||
app = _harness(findings, lang="es")
|
||
app.run()
|
||
text = collected_text(app)
|
||
# Pack template: "Abrir {tool} →"
|
||
assert "Abrir Limpiar texto" in text
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Untargeted findings (file-level) go in the "Other" expander
|
||
# ---------------------------------------------------------------------------
|
||
|
||
class TestUntargetedFindings:
|
||
def test_untargeted_goes_to_other_expander_en(self):
|
||
findings = [
|
||
_make_finding(tool="", id="csv_bom_stripped"),
|
||
_make_finding(tool="02_text_cleaner", id="nbsp_padding"),
|
||
]
|
||
app = _harness(findings)
|
||
app.run()
|
||
labels = [e.label for e in app.expander]
|
||
# Pack template: "Other / file-level — {n} finding(s)"
|
||
assert any("Other / file-level" in lbl for lbl in labels), (
|
||
f"untargeted expander missing; got: {labels}"
|
||
)
|
||
|
||
def test_untargeted_label_spanish(self):
|
||
findings = [_make_finding(tool="", id="csv_bom_stripped")]
|
||
app = _harness(findings, lang="es")
|
||
app.run()
|
||
labels = [e.label for e in app.expander]
|
||
# Spanish pack: "Otros / a nivel de archivo — {n} hallazgo(s)"
|
||
assert any("Otros / a nivel de archivo" in lbl for lbl in labels), (
|
||
f"Spanish 'Other' expander missing; got: {labels}"
|
||
)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Severity summary
|
||
# ---------------------------------------------------------------------------
|
||
|
||
class TestSeveritySummary:
|
||
"""The panel renders a per-severity summary caption like
|
||
``⚠️ 2 warn · ℹ️ 1 info``. We pin the icon + count rendering."""
|
||
|
||
def test_severity_icons_render(self):
|
||
findings = [
|
||
_make_finding(tool="02_text_cleaner", severity="warn"),
|
||
_make_finding(tool="02_text_cleaner", severity="warn"),
|
||
_make_finding(tool="03_format_standardizer", severity="info"),
|
||
]
|
||
app = _harness(findings)
|
||
app.run()
|
||
text = collected_text(app)
|
||
# Icons live in the per-language pack ("findings.severity_*").
|
||
# The summary template is shared between languages.
|
||
assert "⚠️" in text or "warn" in text
|
||
# Counts present.
|
||
assert "2 warn" in text or "2 warn" in text
|