zonza/tests/test_interface.py
2026-08-23 19:16:41 +02:00

100 lines
3.5 KiB
Python

"""La fabrique rend un overlay conforme au contrat, sans ouvrir de fenêtre."""
import pytest
from interface import classe_overlay_pour
def test_l_overlay_macos_declare_tout_le_contrat():
classe = classe_overlay_pour("darwin")
for methode in ("show", "hide", "set_level", "set_status", "rearmer_garde"):
assert callable(getattr(classe, methode, None)), f"{methode} manque"
def test_une_plateforme_inconnue_est_refusee_clairement():
with pytest.raises(NotImplementedError) as e:
classe_overlay_pour("haiku-os")
assert "haiku-os" in str(e.value)
def test_la_fabrique_rend_overlayqt_pour_win32():
classe = classe_overlay_pour("win32")
from interface.qt import OverlayQt
assert classe is OverlayQt
def test_l_overlay_qt_declare_tout_le_contrat():
classe = classe_overlay_pour("win32")
for methode in ("show", "hide", "set_level", "set_status", "rearmer_garde"):
assert callable(getattr(classe, methode, None)), f"{methode} manque"
# --- garde de duree de vie : annulation reelle a hide() ---------------------
#
# Exception assumee au principe "aucune fenetre" du haut de ce fichier : la
# seule facon honnete de prouver qu'une garde est REELLEMENT annulee (et pas
# seulement oubliee dans le champ `_garde`) est d'interroger l'etat vrai de la
# minuterie native — NSTimer.isValid() cote Cocoa, QTimer.isActive() cote Qt.
# Une doublure (fake) ne fournirait aucune garantie sur ce point : elle ne
# ferait que rejouer ce que le code de production affirme deja faire.
#
# Piege du 21/08 (halo fige) : une garde dereferencee (`_garde = None`) mais
# jamais invalidee continue de courir en coulisse, et peut refermer un overlay
# rouvert entre-temps, en pleine dictee. D'ou : on capture l'objet minuterie
# AVANT hide()/rearmer_garde(), pour verifier SON etat a lui, pas seulement le
# champ qui le reference.
from interface.cocoa import PulseWindow
from interface.qt import OverlayQt
def _garde_active_cocoa(garde):
return bool(garde.isValid())
def _garde_active_qt(garde):
return garde.isActive()
_IMPLEMENTATIONS_REELLES = [
(lambda: PulseWindow.alloc().init(), _garde_active_cocoa),
(OverlayQt, _garde_active_qt),
]
_IDS_IMPLEMENTATIONS = ["cocoa", "qt"]
@pytest.fixture(params=_IMPLEMENTATIONS_REELLES, ids=_IDS_IMPLEMENTATIONS)
def overlay_et_garde_active(request):
"""Une vraie instance d'overlay, plus la fonction qui lit l'etat reel de sa
minuterie de garde. Ferme toujours la fenetre a la fin, meme si le test echoue."""
construire, garde_active = request.param
ov = construire()
try:
yield ov, garde_active
finally:
ov.hide()
def test_show_arme_une_garde_valide(overlay_et_garde_active):
ov, garde_active = overlay_et_garde_active
ov.show()
assert ov._garde is not None
assert garde_active(ov._garde)
def test_hide_annule_reellement_la_garde(overlay_et_garde_active):
ov, garde_active = overlay_et_garde_active
ov.show()
garde = ov._garde # reference gardee AVANT hide()
ov.hide()
assert ov._garde is None
assert not garde_active(garde), "la minuterie doit etre invalidee, pas seulement oubliee"
def test_rearmer_garde_ne_laisse_qu_une_seule_garde_active(overlay_et_garde_active):
ov, garde_active = overlay_et_garde_active
ov.show()
ancienne_garde = ov._garde
ov.rearmer_garde(5.0)
assert ov._garde is not ancienne_garde
assert not garde_active(ancienne_garde), "l'ancienne garde doit etre invalidee au remplacement"
assert garde_active(ov._garde)