Files
datatools-dev/tests/gui/test_findings_panel.py
Michael 6627895a10 test: fix v3 branding drift, add reconcile CLI + registry coverage
GUI/lang-pack tests were asserting against pre-v3 strings ("Data
Cleaning Mastery", "Maestría en limpieza…") that the brand refresh
replaced with "UNALOGIX DataTools" + "Clean. Normalize. Transform."
Updated assertions to the current copy and switched the findings
panel tests to the redesigned flat-list layout (per-finding "Open
Tool →" buttons instead of per-tool expanders).

New coverage:
- tests/test_cli_reconcile.py (13) — preview/apply, tolerance flags,
  sign inversion, key flags, error paths, Excel input.
- tests/test_tools_registry.py (27) — unique tool_ids, page_slug →
  real file, valid sections/tiers, localized accessor fallbacks,
  explicit pins for PDF Extractor + Reconciler entries.
- tests/test_reconcile.py — one-side-empty, key-pass tagging,
  additional validation cases, input-DataFrame immutability.
- tests/gui/test_smoke.py — PAGE_SLUGS now includes 10_PDF_Extractor
  and 11_Reconciler in both en/es.
- tests/gui/test_workflows.py — TestPdfExtractorWorkflow and
  TestReconcilerWorkflow render checks.

Net: 2317 passed → 2418 passed, 0 failures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 19:30:02 +00:00

236 lines
8.8 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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-finding row → one "Open Tool" button per targeted finding
# ---------------------------------------------------------------------------
#
# The findings panel was redesigned (mockup-v2): it now renders ONE
# severity-sorted flat list rather than per-tool expanders. Each finding
# with a known tool id gets a tertiary button labelled
# ``"{Tool display name} →"`` that switches pages on click. Findings
# with no tool id (file-level CSV-shape warnings, encoding flags, etc.)
# render without a button — the description still shows so the user
# isn't blind to them.
class TestRowsRenderForFindings:
def test_one_button_per_targeted_finding(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 = [b.label for b in app.button]
# Each targeted finding gets its own "Open Tool" button — three
# findings → three buttons (two pointing at Clean Text, one at
# Standardize Formats).
clean_text_buttons = [l for l in labels if l == "Clean Text →"]
format_buttons = [l for l in labels if l == "Standardize Formats →"]
assert len(clean_text_buttons) == 2, (
f"expected 2 Clean Text buttons; got: {labels}"
)
assert len(format_buttons) == 1, (
f"expected 1 Standardize Formats button; 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 = [b.label for b in app.button]
assert any("Limpiar texto" in lbl for lbl in labels), (
f"Spanish tool name missing; buttons: {labels}"
)
# ---------------------------------------------------------------------------
# Open-tool button labels — confirm the arrow + name format
# ---------------------------------------------------------------------------
class TestOpenToolButton:
"""Each finding with a known tool gets a tertiary button labelled
``"{Tool name} →"``. The arrow + spacing is the affordance that
distinguishes the row's primary action from the title text."""
def test_open_tool_label_english(self):
findings = [_make_finding(tool="02_text_cleaner")]
app = _harness(findings)
app.run()
labels = [b.label for b in app.button]
assert "Clean Text →" in labels, (
f"expected 'Clean Text →' button; got: {labels}"
)
def test_open_tool_label_spanish(self):
findings = [_make_finding(tool="02_text_cleaner")]
app = _harness(findings, lang="es")
app.run()
labels = [b.label for b in app.button]
assert "Limpiar texto →" in labels, (
f"expected 'Limpiar texto →' button; got: {labels}"
)
# ---------------------------------------------------------------------------
# Untargeted findings (file-level) render without an action button
# ---------------------------------------------------------------------------
class TestUntargetedFindings:
"""A finding with ``tool=""`` (e.g., CSV BOM stripped at read time)
is file-level — no tool page to jump to — and the redesigned panel
renders the description without a button. We assert that the row
contributes nothing to ``app.button`` while still appearing in the
rendered markdown."""
def test_untargeted_renders_no_button_en(self):
findings = [
_make_finding(tool="", id="csv_bom_stripped", description="BOM stripped"),
_make_finding(tool="02_text_cleaner", id="nbsp_padding"),
]
app = _harness(findings)
app.run()
labels = [b.label for b in app.button]
# Only the targeted finding contributed a button.
assert "Clean Text →" in labels
# The BOM finding's description must still be visible somewhere.
all_md = "\n".join(
m.body for m in app.markdown if hasattr(m, "body")
)
assert "BOM stripped" in all_md, (
"untargeted finding's description should still render"
)
def test_untargeted_renders_no_button_es(self):
findings = [_make_finding(
tool="", id="csv_bom_stripped", description="BOM eliminado",
)]
app = _harness(findings, lang="es")
app.run()
labels = [b.label for b in app.button]
# No tool id → no tool-jump button at all.
assert not any("" in lbl for lbl in labels), (
f"untargeted finding should not render a tool button; 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