119 lines
3.8 KiB
Python
119 lines
3.8 KiB
Python
"""Message d'aide localisant les documents non livrés (P1-5) + ouverture dossier.
|
|
|
|
Pur : pas de display. ``failure_hint`` formate un texte ; ``open_in_file_manager``
|
|
dispatch vers la bonne commande OS (monkeypatchée).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
import gui_v6.fsutil as fsutil
|
|
from gui_v6.processing_runner import RunSummary
|
|
from gui_v6.tabs.tab_usage import failure_hint
|
|
|
|
|
|
def test_no_hint_when_all_ok():
|
|
s = RunSummary(total=3, succeeded=3, failed=0)
|
|
assert failure_hint(s, Path("/out")) is None
|
|
|
|
|
|
def test_hint_when_failures_mentions_output_dir():
|
|
s = RunSummary(total=3, succeeded=2, failed=1)
|
|
hint = failure_hint(s, Path("/out/anonymise"))
|
|
assert hint is not None
|
|
assert "/out/anonymise" in hint
|
|
# Honnêteté : préciser que les échecs ne sont PAS anonymisés.
|
|
assert "pas" in hint.lower()
|
|
|
|
|
|
def test_hint_when_stopped():
|
|
s = RunSummary(total=3, succeeded=1, failed=0, stopped=True)
|
|
assert failure_hint(s, Path("/out")) is not None
|
|
|
|
|
|
def test_no_hint_without_output_dir():
|
|
s = RunSummary(total=1, succeeded=0, failed=1)
|
|
assert failure_hint(s, None) is None
|
|
|
|
|
|
def test_open_in_file_manager_dispatches(monkeypatch):
|
|
calls = {}
|
|
monkeypatch.setattr(fsutil.sys, "platform", "linux")
|
|
monkeypatch.setattr(fsutil.subprocess, "Popen", lambda args, **k: calls.setdefault("args", args))
|
|
fsutil.open_in_file_manager(Path("/out"))
|
|
assert calls["args"][0] == "xdg-open"
|
|
assert calls["args"][1] == "/out"
|
|
|
|
|
|
def test_no_claim_written_when_zero_succeeded():
|
|
"""0 succès : ne pas prétendre qu'un dossier contient des documents écrits."""
|
|
s = RunSummary(total=2, succeeded=0, failed=2)
|
|
hint = failure_hint(s, Path("/out"))
|
|
assert hint is not None
|
|
assert "écrits dans" not in hint
|
|
assert "Aucun document" in hint
|
|
|
|
|
|
def test_hint_with_path_when_some_succeeded():
|
|
"""≥1 succès : localiser le dossier de sortie effectif."""
|
|
s = RunSummary(total=3, succeeded=2, failed=1)
|
|
hint = failure_hint(s, Path("/out"))
|
|
assert hint is not None
|
|
assert "/out" in hint
|
|
assert "écrits dans" in hint
|
|
|
|
|
|
# -- garde anti-régression du bug Critical (empilement de widgets) -----------
|
|
|
|
|
|
@pytest.fixture
|
|
def usage_tab():
|
|
"""``UsageTab`` headless (Xvfb) — skip propre si pas de display."""
|
|
pytest.importorskip("customtkinter")
|
|
try:
|
|
import customtkinter as ctk
|
|
|
|
from gui_v6.tabs.tab_usage import UsageTab
|
|
|
|
root = ctk.CTk()
|
|
root.withdraw()
|
|
tab = UsageTab(root)
|
|
except Exception as exc: # pas de display Tk
|
|
pytest.skip(f"display Tk indisponible: {exc}")
|
|
try:
|
|
yield tab
|
|
finally:
|
|
try:
|
|
root.destroy()
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def test_show_failure_hint_does_not_accumulate(usage_tab):
|
|
"""Bug Critical : deux runs en échec ne doivent pas empiler de hints sous
|
|
``_rsec`` (handle ``_hint_row`` détruit inconditionnellement)."""
|
|
usage_tab._last_output_dir = Path("/out")
|
|
summary = RunSummary(total=3, succeeded=2, failed=1)
|
|
|
|
usage_tab._show_failure_hint(summary)
|
|
count_after_first = len(usage_tab._rsec.winfo_children())
|
|
|
|
usage_tab._show_failure_hint(summary)
|
|
count_after_second = len(usage_tab._rsec.winfo_children())
|
|
|
|
assert count_after_second == count_after_first
|
|
|
|
|
|
def test_show_failure_hint_clears_stale_hint(usage_tab):
|
|
"""Un run en échec suivi d'un run nominal ne doit pas laisser de hint périmé."""
|
|
usage_tab._last_output_dir = Path("/out")
|
|
usage_tab._show_failure_hint(RunSummary(total=3, succeeded=2, failed=1))
|
|
with_hint = len(usage_tab._rsec.winfo_children())
|
|
|
|
usage_tab._show_failure_hint(RunSummary(total=3, succeeded=3, failed=0))
|
|
without_hint = len(usage_tab._rsec.winfo_children())
|
|
|
|
assert without_hint == with_hint - 1
|