"""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)