Files
proxmark3/tools/ePassport/tests/test_dialogs.py
T

168 lines
5.9 KiB
Python

"""Popup wiring.
These are plain-Python checks of the parts that do not need a window, because
the bugs worth catching here are wiring mistakes, not pixels.
"""
from __future__ import annotations
import inspect
from pathlib import Path
from epassport.ui import dialogs
def test_confirm_callback_is_not_named_with_an_on_prefix() -> None:
"""Kivy binds any ``on_*`` constructor keyword as an event observer.
Naming the callback property ``on_confirm`` meant ``ConfirmPopup(
on_confirm=fn)`` silently bound an observer and left the property None,
so confirming a deletion did nothing at all.
"""
names = [n for n in vars(dialogs.ConfirmPopup) if not n.startswith("__")]
assert "confirm_action" in names
assert not any(
n.startswith("on_")
and n not in ("on_touch_down", "on_touch_move", "on_touch_up")
for n in names
), "a callback property must not start with on_"
def test_confirm_runs_the_action() -> None:
calls: list[int] = []
popup = dialogs.ConfirmPopup.__new__(dialogs.ConfirmPopup)
popup.confirm_action = lambda: calls.append(1)
popup.dismiss = lambda *a, **k: None
dialogs.ConfirmPopup.confirm(popup)
assert calls == [1]
def test_confirm_without_an_action_is_harmless() -> None:
popup = dialogs.ConfirmPopup.__new__(dialogs.ConfirmPopup)
popup.confirm_action = None
popup.dismiss = lambda *a, **k: None
dialogs.ConfirmPopup.confirm(popup) # must not raise
def test_dump_rows_expose_both_actions() -> None:
assert "on_open" in dialogs.DumpRow.__events__
assert "on_delete" in dialogs.DumpRow.__events__
def test_deleting_asks_before_it_removes_anything() -> None:
source = inspect.getsource(dialogs.DumpsPopup.confirm_delete)
assert "ConfirmPopup" in source, "deletion must be confirmed, never immediate"
assert "cannot be undone" in source
# ------------------------------------------------------------- quick jumps
def test_shortcuts_offer_the_places_dumps_actually_live(monkeypatch, tmp_path) -> None:
from epassport import config
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path))
monkeypatch.setattr(config, "samples_dir", lambda: None)
labels = [label for label, _path in dialogs.ChooseDirPopup.shortcuts()]
assert "Dumps" in labels
assert "Home" in labels
assert "Samples" not in labels, "not offered until the samples are generated"
def test_samples_appear_once_generated(monkeypatch, tmp_path) -> None:
from epassport import config
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path))
generated = tmp_path / "samples"
generated.mkdir()
monkeypatch.setattr(config, "samples_dir", lambda: generated)
entries = dict(dialogs.ChooseDirPopup.shortcuts())
assert entries["Samples"] == generated.resolve()
def test_shortcuts_are_all_real_directories(monkeypatch, tmp_path) -> None:
from epassport import config
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path))
monkeypatch.setattr(config, "samples_dir", lambda: tmp_path / "does-not-exist")
for label, path in dialogs.ChooseDirPopup.shortcuts():
assert path.is_dir(), f"{label} points at {path}"
def test_duplicate_shortcuts_are_collapsed(monkeypatch, tmp_path) -> None:
"""Launched from the project root, 'Working dir' and 'Samples' can be the
same place; offering it twice is just noise."""
from epassport import config
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path))
monkeypatch.setattr(config, "samples_dir", lambda: Path.cwd())
paths = [path for _label, path in dialogs.ChooseDirPopup.shortcuts()]
assert len(paths) == len(set(paths))
def test_samples_dir_is_none_until_generated(tmp_path, monkeypatch) -> None:
from epassport import config
monkeypatch.setattr(config, "project_root", lambda: tmp_path)
assert config.samples_dir() is None
(tmp_path / "samples").mkdir()
assert config.samples_dir() == tmp_path / "samples"
# --------------------------------------------------- remembering where you were
def test_the_picker_reopens_beside_the_last_dump(tmp_path, monkeypatch) -> None:
"""A dump was picked *inside* some folder; offer that folder again.
Opening the app's own dumps directory every time meant navigating back to
wherever your dumps actually live on every single trip through the picker.
"""
from epassport import config
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path / "data"))
elsewhere = tmp_path / "captures" / "20260101T000000Z"
elsewhere.mkdir(parents=True)
assert config.browse_start(str(elsewhere)) == elsewhere.parent
def test_no_history_falls_back_to_the_app_dumps(tmp_path, monkeypatch) -> None:
from epassport import config
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path))
assert config.browse_start("") == config.dumps_dir()
def test_a_dump_that_has_been_deleted_falls_back(tmp_path, monkeypatch) -> None:
from epassport import config
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path))
gone = tmp_path / "gone" / "also-gone"
assert config.browse_start(str(gone)) == config.dumps_dir()
def test_a_deleted_dump_whose_parent_survives_offers_the_parent(
tmp_path, monkeypatch
) -> None:
from epassport import config
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path / "data"))
parent = tmp_path / "captures"
parent.mkdir()
assert config.browse_start(str(parent / "removed")) == parent
def test_the_start_is_absolute(tmp_path, monkeypatch) -> None:
"""The chooser is handed this path directly; a relative one is fragile."""
from epassport import config
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path / "data"))
nested = tmp_path / "rel" / "dump"
nested.mkdir(parents=True)
monkeypatch.chdir(tmp_path)
assert config.browse_start("rel/dump").is_absolute()
def test_recent_is_offered_as_a_shortcut() -> None:
import inspect
source = inspect.getsource(dialogs.ChooseDirPopup.shortcuts)
assert '"Recent"' in source
assert "browse_start" in source