1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
|
# ------------------------------------------------------------------------------
# Copyright (c) 2013-2025, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# ------------------------------------------------------------------------------
"""Pytest fixtures.
"""
import math
import os
import pathlib
from traceback import format_exc
from enaml.application import Application
from enaml.widgets.api import Window
from enaml.widgets.widget import Widget
from utils import wait_for_window_displayed, close_window_or_popup
# Make sure enaml already imported qt to avoid issues with pytest
try:
from enaml.qt import QT_API, PYQT5_API, PYSIDE2_API, PYQT6_API, PYSIDE6_API
if QT_API in PYQT5_API:
os.environ.setdefault("PYTEST_QT_API", "pyqt5")
elif QT_API in PYSIDE2_API:
os.environ.setdefault("PYTEST_QT_API", "pyside2")
elif QT_API in PYQT6_API:
os.environ.setdefault("PYTEST_QT_API", "pyqt6")
elif QT_API in PYSIDE6_API:
os.environ.setdefault("PYTEST_QT_API", "pyside6")
from enaml.qt import QtCore, QtGui
pytest_plugins = (str("pytest-qt"),)
QT_AVAILABLE = True
except Exception:
QT_AVAILABLE = False
import pytest
from utils import close_all_windows, close_all_popups
#: Global variable linked to the --enaml-sleep cmd line option.
DIALOG_SLEEP = 0
def pytest_addoption(parser):
"""Add command line options."""
parser.addoption(
"--enaml-sleep",
action="store",
type=int,
help="Time to sleep after handling a ui event",
)
def pytest_configure(config):
"""Turn the --enaml-sleep command line into a global variable."""
s = config.getoption("--enaml-sleep")
if s is not None:
global DIALOG_SLEEP
DIALOG_SLEEP = s
@pytest.fixture(scope="session", autouse=True)
def validate_parser_is_up_to_date():
"""Check that the generated parser is up to date with its sources."""
from enaml.core.parser import base_enaml_parser, base_python_parser, enaml_parser
last_source_modif = max(
os.path.getmtime(base_enaml_parser.__file__),
os.path.getmtime(base_python_parser.__file__),
os.path.getmtime(
pathlib.Path(base_enaml_parser.__file__).parent / "enaml.gram"
),
)
# We round the times to avoid spurious failure because both files were
# written at slightly different times.
assert math.ceil(os.path.getmtime(enaml_parser.__file__)) >= math.floor(
last_source_modif
), (
"Generated parser appears outdated compared to its sources, "
"re-generate it using `python -m enaml.core.parser.generate_enaml_parser`"
)
@pytest.fixture
def enaml_sleep():
"""Return the time to sleep in s as set by the --enaml-sleep option."""
return DIALOG_SLEEP
@pytest.fixture(scope="session")
def qapp():
"""Make sure a QtApplication is active."""
if not QT_AVAILABLE:
pytest.skip("Requires a Qt binding")
try:
from enaml.qt.qt_application import QtApplication
except Exception:
pytest.skip("No Qt binding found: %s" % format_exc())
# Import it before starting the app as otherwise it can cause issues
# in particular with PySide
try:
from enaml.qt import QtWebEngineWidgets
except Exception:
pass
app = QtApplication("enaml-testing-app")
yield app._qapp
app.stop()
@pytest.fixture
def enaml_qtbot(qapp, qtbot):
from enaml.qt.qt_application import QtApplication
qtbot.enaml_app = QtApplication.instance()
assert qtbot.enaml_app is not None
pixel_ratio = QtGui.QGuiApplication.primaryScreen().devicePixelRatio()
def get_global_pos(widget: Widget) -> QtCore.QPoint:
assert widget.proxy
qw = widget.proxy.widget
# Take into account the pixel ratio so that PyQt and PyAutoGUI agree on
# the coordinates
return qw.mapToGlobal(qw.rect().center()) * pixel_ratio
qtbot.get_global_pos = get_global_pos
def post_event(widget, event):
qapp.postEvent(widget, event)
qtbot.post_event = post_event
with close_all_windows(qtbot), close_all_popups(qtbot):
yield qtbot
@pytest.fixture
def enaml_run(enaml_qtbot, monkeypatch):
"""Patches the QtApplication to allow using the qtbot when the
enaml application is started. It also patches QApplication.exit as
recommended in the pytest-qt docs.
Yields
-------
handler: object
an object with a `run` attribute that can be set to a callback that
will be invoked with the application and first window shown.
References
----------
1. https://pytest-qt.readthedocs.io/en/latest/app_exit.html
"""
from enaml.qt.qt_application import QtApplication, QApplication
app = Application.instance()
if app:
Application._instance = None
class Runner:
# Set this to a callback
run = None
runner = Runner()
def start(self):
for window in Window.windows:
wait_for_window_displayed(enaml_qtbot, window)
if callable(runner.run):
runner.run(self, window)
else:
close_window_or_popup(enaml_qtbot, window)
break
try:
with monkeypatch.context() as m:
m.setattr(QtApplication, "start", start)
m.setattr(QApplication, "exit", lambda self: None)
yield runner
finally:
Application._instance = app
|