Le test exerce enfin l'annulation de la garde, dans les deux interfaces
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
82e0e16393
commit
99376a8e12
@ -23,8 +23,8 @@ repère basculé, pour ne pas sortir à l'envers.
|
|||||||
import threading
|
import threading
|
||||||
|
|
||||||
from PySide6.QtCore import QPointF, QRectF, Qt, QTimer
|
from PySide6.QtCore import QPointF, QRectF, Qt, QTimer
|
||||||
from PySide6.QtGui import QColor, QFontMetrics, QPainter, QPainterPath, QPen
|
from PySide6.QtGui import QColor, QFontMetrics, QGuiApplication, QPainter, QPainterPath, QPen
|
||||||
from PySide6.QtWidgets import QWidget
|
from PySide6.QtWidgets import QApplication, QWidget
|
||||||
|
|
||||||
from audio_level import bar_heights, push_level
|
from audio_level import bar_heights, push_level
|
||||||
from core import CONFIG, bar_metrics
|
from core import CONFIG, bar_metrics
|
||||||
@ -184,7 +184,10 @@ class VueOverlay(QWidget):
|
|||||||
painter.drawPath(tail)
|
painter.drawPath(tail)
|
||||||
|
|
||||||
def paintEvent(self, event):
|
def paintEvent(self, event):
|
||||||
painter = QPainter(self)
|
# `with` ferme le QPainter (.end()) meme si une exception surgit en
|
||||||
|
# cours de dessin : sans ca, la fermeture ne tenait qu'au comptage de
|
||||||
|
# references de PySide6 — suffisant aujourd'hui, fragile demain.
|
||||||
|
with QPainter(self) as painter:
|
||||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
|
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
|
||||||
|
|
||||||
w = CONFIG["bar_width"]
|
w = CONFIG["bar_width"]
|
||||||
@ -253,13 +256,11 @@ class OverlayQt:
|
|||||||
def _ensure_view(self):
|
def _ensure_view(self):
|
||||||
if self._view is not None:
|
if self._view is not None:
|
||||||
return
|
return
|
||||||
# Import local : PySide6 ne doit être exigé QUE quand cette classe est
|
# PySide6 est déjà exigé dès l'IMPORT de ce module (QWidget, QTimer...
|
||||||
# réellement instanciée (cf interface/__init__.py::classe_overlay_pour),
|
# ci-dessus) : le report réel n'est pas ici, mais dans la fabrique
|
||||||
# jamais au chargement du module — sinon les tests réclameraient Qt sur
|
# (interface/__init__.py::classe_overlay_pour), qui n'importe
|
||||||
# toute machine, y compris ce Mac où Cocoa suffit.
|
# `interface.qt` que pour la branche win32 — jamais sur ce Mac, où
|
||||||
from PySide6.QtGui import QGuiApplication
|
# Cocoa suffit.
|
||||||
from PySide6.QtWidgets import QApplication
|
|
||||||
|
|
||||||
if QApplication.instance() is None:
|
if QApplication.instance() is None:
|
||||||
QApplication([])
|
QApplication([])
|
||||||
|
|
||||||
|
|||||||
@ -26,3 +26,74 @@ def test_l_overlay_qt_declare_tout_le_contrat():
|
|||||||
classe = classe_overlay_pour("win32")
|
classe = classe_overlay_pour("win32")
|
||||||
for methode in ("show", "hide", "set_level", "set_status", "rearmer_garde"):
|
for methode in ("show", "hide", "set_level", "set_status", "rearmer_garde"):
|
||||||
assert callable(getattr(classe, methode, None)), f"{methode} manque"
|
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)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user