From 0bc9a189aa18c85b9d40044964141c01600824c4 Mon Sep 17 00:00:00 2001 From: Nir <88795475+nrbnlulu@users.noreply.github.com> Date: Wed, 1 Feb 2023 15:04:10 +0200 Subject: [PATCH 01/24] added qmlbot --- .idea/.gitignore | 8 +++ .idea/inspectionProfiles/Project_Default.xml | 70 +++++++++++++++++++ .../inspectionProfiles/profiles_settings.xml | 6 ++ .idea/modules.xml | 8 +++ .idea/pytest-qt.iml | 18 +++++ .idea/vcs.xml | 6 ++ .pre-commit-config.yaml | 2 +- docs/qmlbot.rst | 33 +++++++++ src/pytestqt/plugin.py | 6 ++ src/pytestqt/qml/__init__.py | 0 src/pytestqt/qml/botloader.qml | 18 +++++ src/pytestqt/qml/qmlbot.py | 41 +++++++++++ src/pytestqt/qt_compat.py | 2 + tests/test_qml/__init__.py | 0 tests/test_qml/sample.qml | 6 ++ tests/test_qml/test_qmlbot.py | 31 ++++++++ 16 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/pytest-qt.iml create mode 100644 .idea/vcs.xml create mode 100644 docs/qmlbot.rst create mode 100644 src/pytestqt/qml/__init__.py create mode 100644 src/pytestqt/qml/botloader.qml create mode 100644 src/pytestqt/qml/qmlbot.py create mode 100644 tests/test_qml/__init__.py create mode 100644 tests/test_qml/sample.qml create mode 100644 tests/test_qml/test_qmlbot.py diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..ebb6cafb --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,70 @@ + + + + diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..cc5462da --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..c56d7a44 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/.idea/pytest-qt.iml b/.idea/pytest-qt.iml new file mode 100644 index 00000000..fbe6200e --- /dev/null +++ b/.idea/pytest-qt.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..dcb6b8c4 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0d12cf56..d3a40fb3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,7 +28,7 @@ repos: rev: 1.13.0 hooks: - id: blacken-docs - additional_dependencies: [black==20.8b1] + additional_dependencies: [black>=22.1.0] language_version: python3 - repo: https://github.com/PyCQA/flake8 rev: 6.0.0 diff --git a/docs/qmlbot.rst b/docs/qmlbot.rst new file mode 100644 index 00000000..cd970c4f --- /dev/null +++ b/docs/qmlbot.rst @@ -0,0 +1,33 @@ +========= +qmlbot +========= + +Fixture that helps interacting with QML. + +Example - load qml from string: +.. code-block:: python + + def test_say_hello(qmlbot): + qml = """ + import QtQuick 2.0 + + Rectangle{ + objectName: "sample"; + property string hello: "world" + } + """ + item = qmlbot.loads(qml) + assert item.property("hello") == "world" + +Example - load qml from file + +.. code-block:: python + from pathlib import Path + + + def test_say_hello(qmlbot): + item = qmlbot.load(Path("sayhello.qml")) + assert item.property("hello") == "world" + +Note: if your components depends on any instances or ``@QmlElement``'s you need +to make sure it is acknowledge by ``qmlbot.engine`` diff --git a/src/pytestqt/plugin.py b/src/pytestqt/plugin.py index fab2c723..8410999a 100644 --- a/src/pytestqt/plugin.py +++ b/src/pytestqt/plugin.py @@ -7,6 +7,7 @@ _QtExceptionCaptureManager, ) from pytestqt.logging import QtLoggingPlugin, _QtMessageCapture +from pytestqt.qml.qmlbot import QmlBot from pytestqt.qt_compat import qt_api from pytestqt.qtbot import QtBot, _close_widgets @@ -93,6 +94,11 @@ def qtbot(qapp, request): return result +@pytest.fixture +def qmlbot(qapp): + return QmlBot() + + @pytest.fixture def qtlog(request): """Fixture that can access messages captured during testing""" diff --git a/src/pytestqt/qml/__init__.py b/src/pytestqt/qml/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/pytestqt/qml/botloader.qml b/src/pytestqt/qml/botloader.qml new file mode 100644 index 00000000..7ff9d64e --- /dev/null +++ b/src/pytestqt/qml/botloader.qml @@ -0,0 +1,18 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 + +Window { + id: root + width: 500 + height: 400 + visible: true + + + Item { + anchors.fill: parent + Loader { + objectName: "contentloader" + source: "" + } + } +} diff --git a/src/pytestqt/qml/qmlbot.py b/src/pytestqt/qml/qmlbot.py new file mode 100644 index 00000000..28d59190 --- /dev/null +++ b/src/pytestqt/qml/qmlbot.py @@ -0,0 +1,41 @@ +from pathlib import Path + +from pytestqt.qt_compat import qt_api + + +class QmlBot: + def __init__(self): + self.engine = qt_api.QtQml.QQmlApplicationEngine() + main = Path(__file__).parent / "botloader.qml" + self.engine.load(main.resolve(True)) + + @property + def _loader(self): + self.root = self.engine.rootObjects()[0] + return self.root.findChild(qt_api.QtQuick.QQuickItem, "contentloader") + + def load(self, path: Path): + """ + :returns: `QQuickItem` - the initialized component + """ + self._loader.setProperty("source", str(path.resolve(True))) + return self._loader.property("item") + + def loads(self, content: str): + """ + :returns: `QQuickItem` - the initialized component + """ + self.comp = qt_api.QtQml.QQmlComponent( + self.engine + ) # needed for it not to be collected by the gc + self.comp.setData(content.encode("utf-8"), qt_api.QtCore.QUrl()) + if self.comp.status() != qt_api.QtQml.QQmlComponent.Status.Ready: + raise RuntimeError( + f"component {self.comp} is not Ready:\n" + f"STATUS: {self.comp.status()}\n" + f"HINT: make sure there are no wrong spaces.\n" + f"ERRORS: {self.comp.errors()}" + ) + self._loader.setProperty("source", "") + self._loader.setProperty("sourceComponent", self.comp) + return self._loader.property("item") diff --git a/src/pytestqt/qt_compat.py b/src/pytestqt/qt_compat.py index e0c3a350..9d0bfc9f 100644 --- a/src/pytestqt/qt_compat.py +++ b/src/pytestqt/qt_compat.py @@ -110,6 +110,8 @@ def _import_module(module_name): self.QtGui = _import_module("QtGui") self.QtTest = _import_module("QtTest") self.QtWidgets = _import_module("QtWidgets") + self.QtQml = _import_module("QtQml") + self.QtQuick = _import_module("QtQuick") self._check_qt_api_version() diff --git a/tests/test_qml/__init__.py b/tests/test_qml/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_qml/sample.qml b/tests/test_qml/sample.qml new file mode 100644 index 00000000..8a2b3d47 --- /dev/null +++ b/tests/test_qml/sample.qml @@ -0,0 +1,6 @@ +import QtQuick 2.0 + +Rectangle{ + objectName: "sample"; + property string hello: "world" +} diff --git a/tests/test_qml/test_qmlbot.py b/tests/test_qml/test_qmlbot.py new file mode 100644 index 00000000..02425df6 --- /dev/null +++ b/tests/test_qml/test_qmlbot.py @@ -0,0 +1,31 @@ +from pathlib import Path + +import pytest + + +def test_load_from_string_wrong_syntax(qmlbot): + qml = "import QtQuick 2.0 Rectangle{" + with pytest.raises(RuntimeError): + qmlbot.loads(qml) + + +def test_load_from_string(qmlbot): + text = "that's a template!" + qml = ( + """ +import QtQuick 2.0 + +Rectangle{ + objectName: "sample"; + property string hello: "%s" +} +""" + % text + ) + item = qmlbot.loads(qml) + assert item.property("hello") == text + + +def test_load_from_file(qmlbot): + item = qmlbot.load(Path(__file__).parent / "sample.qml") + assert item.property("hello") == "world" From e3dcc0bec44188e78bcc3c0f19572892b3738911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D7=A0=D7=99=D7=A8?= <88795475+nrbnlulu@users.noreply.github.com> Date: Wed, 1 Feb 2023 16:37:44 +0200 Subject: [PATCH 02/24] Update tests/test_qml/test_qmlbot.py Co-authored-by: Bruno Oliveira --- tests/test_qml/test_qmlbot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_qml/test_qmlbot.py b/tests/test_qml/test_qmlbot.py index 02425df6..4518736c 100644 --- a/tests/test_qml/test_qmlbot.py +++ b/tests/test_qml/test_qmlbot.py @@ -9,7 +9,7 @@ def test_load_from_string_wrong_syntax(qmlbot): qmlbot.loads(qml) -def test_load_from_string(qmlbot): +def test_load_from_string(qmlbot: pytestqt.QmlBot) -> None: text = "that's a template!" qml = ( """ From edd7257e27af5cd3aa8079220b4ed42f8195aff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D7=A0=D7=99=D7=A8?= <88795475+nrbnlulu@users.noreply.github.com> Date: Wed, 1 Feb 2023 16:37:52 +0200 Subject: [PATCH 03/24] Update src/pytestqt/qml/qmlbot.py Co-authored-by: Bruno Oliveira --- src/pytestqt/qml/qmlbot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pytestqt/qml/qmlbot.py b/src/pytestqt/qml/qmlbot.py index 28d59190..96b329d8 100644 --- a/src/pytestqt/qml/qmlbot.py +++ b/src/pytestqt/qml/qmlbot.py @@ -4,7 +4,7 @@ class QmlBot: - def __init__(self): + def __init__(self) -> None: self.engine = qt_api.QtQml.QQmlApplicationEngine() main = Path(__file__).parent / "botloader.qml" self.engine.load(main.resolve(True)) From 24062d35ab9f82c99d1336a7c0856ab990ce8b40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D7=A0=D7=99=D7=A8?= <88795475+nrbnlulu@users.noreply.github.com> Date: Wed, 1 Feb 2023 16:38:52 +0200 Subject: [PATCH 04/24] Update tests/test_qml/test_qmlbot.py Co-authored-by: Bruno Oliveira --- tests/test_qml/test_qmlbot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_qml/test_qmlbot.py b/tests/test_qml/test_qmlbot.py index 4518736c..a860c4d3 100644 --- a/tests/test_qml/test_qmlbot.py +++ b/tests/test_qml/test_qmlbot.py @@ -26,6 +26,6 @@ def test_load_from_string(qmlbot: pytestqt.QmlBot) -> None: assert item.property("hello") == text -def test_load_from_file(qmlbot): +def test_load_from_file(qmlbot: pytestqt.QmlBot) -> None: item = qmlbot.load(Path(__file__).parent / "sample.qml") assert item.property("hello") == "world" From ccd6ce5249ba47f1c10f0e5d06dc906c1925e93c Mon Sep 17 00:00:00 2001 From: Nir <88795475+nrbnlulu@users.noreply.github.com> Date: Wed, 1 Feb 2023 16:50:33 +0200 Subject: [PATCH 05/24] remove .idea --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 2e797d0e..341b582e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ src/pytest_qt.egg-info # auto-generated by setuptools_scm /src/pytestqt/_version.py + +# pycharm +.idea \ No newline at end of file From d55a3bbe705d0059b4c3657f20b6544b2974f1a9 Mon Sep 17 00:00:00 2001 From: Nir <88795475+nrbnlulu@users.noreply.github.com> Date: Wed, 1 Feb 2023 16:52:36 +0200 Subject: [PATCH 06/24] delete .idea --- .gitignore | 2 +- .idea/.gitignore | 8 --- .idea/inspectionProfiles/Project_Default.xml | 70 ------------------- .../inspectionProfiles/profiles_settings.xml | 6 -- .idea/modules.xml | 8 --- .idea/pytest-qt.iml | 18 ----- .idea/vcs.xml | 6 -- 7 files changed, 1 insertion(+), 117 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/pytest-qt.iml delete mode 100644 .idea/vcs.xml diff --git a/.gitignore b/.gitignore index 341b582e..66101775 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,4 @@ src/pytest_qt.egg-info /src/pytestqt/_version.py # pycharm -.idea \ No newline at end of file +.idea diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b81..00000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index ebb6cafb..00000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index cc5462da..00000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index c56d7a44..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/.idea/pytest-qt.iml b/.idea/pytest-qt.iml deleted file mode 100644 index fbe6200e..00000000 --- a/.idea/pytest-qt.iml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index dcb6b8c4..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - From dbc99ad71103799a1641b129ab51ca30d8a8f933 Mon Sep 17 00:00:00 2001 From: Nir <88795475+nrbnlulu@users.noreply.github.com> Date: Wed, 1 Feb 2023 17:06:11 +0200 Subject: [PATCH 07/24] nit: better import scope, added glob on setup.py from qml files. --- setup.py | 2 ++ src/pytestqt/qml/__init__.py | 3 +++ src/pytestqt/qml/qmlbot.py | 14 +++++++------- tests/test_qml/test_qmlbot.py | 21 ++++++++++++--------- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/setup.py b/setup.py index a33eabfc..20d9a9a8 100644 --- a/setup.py +++ b/setup.py @@ -8,6 +8,8 @@ packages=find_packages(where="src"), package_dir={"": "src"}, entry_points={"pytest11": ["pytest-qt = pytestqt.plugin"]}, + include_package_data=True, + package_data={"pytestqt": ["**/*.qml"]}, install_requires=["pytest>=3.0.0"], extras_require={ "doc": ["sphinx", "sphinx_rtd_theme"], diff --git a/src/pytestqt/qml/__init__.py b/src/pytestqt/qml/__init__.py index e69de29b..4505007b 100644 --- a/src/pytestqt/qml/__init__.py +++ b/src/pytestqt/qml/__init__.py @@ -0,0 +1,3 @@ +from .qmlbot import QmlBot + +__all__ = [QmlBot] diff --git a/src/pytestqt/qml/qmlbot.py b/src/pytestqt/qml/qmlbot.py index 96b329d8..b12b6528 100644 --- a/src/pytestqt/qml/qmlbot.py +++ b/src/pytestqt/qml/qmlbot.py @@ -25,17 +25,17 @@ def loads(self, content: str): """ :returns: `QQuickItem` - the initialized component """ - self.comp = qt_api.QtQml.QQmlComponent( + self._comp = qt_api.QtQml.QQmlComponent( self.engine ) # needed for it not to be collected by the gc - self.comp.setData(content.encode("utf-8"), qt_api.QtCore.QUrl()) - if self.comp.status() != qt_api.QtQml.QQmlComponent.Status.Ready: + self._comp.setData(content.encode("utf-8"), qt_api.QtCore.QUrl()) + if self._comp.status() != qt_api.QtQml.QQmlComponent.Status.Ready: raise RuntimeError( - f"component {self.comp} is not Ready:\n" - f"STATUS: {self.comp.status()}\n" + f"component {self._comp} is not Ready:\n" + f"STATUS: {self._comp.status()}\n" f"HINT: make sure there are no wrong spaces.\n" - f"ERRORS: {self.comp.errors()}" + f"ERRORS: {self._comp.errors()}" ) self._loader.setProperty("source", "") - self._loader.setProperty("sourceComponent", self.comp) + self._loader.setProperty("sourceComponent", self._comp) return self._loader.property("item") diff --git a/tests/test_qml/test_qmlbot.py b/tests/test_qml/test_qmlbot.py index a860c4d3..1dc070e8 100644 --- a/tests/test_qml/test_qmlbot.py +++ b/tests/test_qml/test_qmlbot.py @@ -1,7 +1,10 @@ from pathlib import Path +from textwrap import dedent import pytest +from pytestqt import QmlBot + def test_load_from_string_wrong_syntax(qmlbot): qml = "import QtQuick 2.0 Rectangle{" @@ -9,23 +12,23 @@ def test_load_from_string_wrong_syntax(qmlbot): qmlbot.loads(qml) -def test_load_from_string(qmlbot: pytestqt.QmlBot) -> None: +def test_load_from_string(qmlbot: QmlBot) -> None: text = "that's a template!" - qml = ( + qml = dedent( """ -import QtQuick 2.0 + import QtQuick 2.0 -Rectangle{ - objectName: "sample"; - property string hello: "%s" -} -""" + Rectangle{ + objectName: "sample"; + property string hello: "%s" + } + """ % text ) item = qmlbot.loads(qml) assert item.property("hello") == text -def test_load_from_file(qmlbot: pytestqt.QmlBot) -> None: +def test_load_from_file(qmlbot: QmlBot) -> None: item = qmlbot.load(Path(__file__).parent / "sample.qml") assert item.property("hello") == "world" From 457f37f0b6d576e592e328416d2f78803c815cac Mon Sep 17 00:00:00 2001 From: Nir <88795475+nrbnlulu@users.noreply.github.com> Date: Wed, 1 Feb 2023 17:07:06 +0200 Subject: [PATCH 08/24] nit: added qmlbot on index.rst --- docs/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.rst b/docs/index.rst index 8056cbd3..6e0ce34d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -19,6 +19,7 @@ pytest-qt virtual_methods modeltester qapplication + qmlbot note_dialogs debugging troubleshooting From ee691196bdb87e4a150ea5145d195367ee7350de Mon Sep 17 00:00:00 2001 From: Nir <88795475+nrbnlulu@users.noreply.github.com> Date: Wed, 1 Feb 2023 17:08:11 +0200 Subject: [PATCH 09/24] nit: type hints --- src/pytestqt/qml/qmlbot.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pytestqt/qml/qmlbot.py b/src/pytestqt/qml/qmlbot.py index b12b6528..2e773a31 100644 --- a/src/pytestqt/qml/qmlbot.py +++ b/src/pytestqt/qml/qmlbot.py @@ -1,4 +1,5 @@ from pathlib import Path +from typing import Any from pytestqt.qt_compat import qt_api @@ -10,18 +11,18 @@ def __init__(self) -> None: self.engine.load(main.resolve(True)) @property - def _loader(self): + def _loader(self) -> Any: self.root = self.engine.rootObjects()[0] return self.root.findChild(qt_api.QtQuick.QQuickItem, "contentloader") - def load(self, path: Path): + def load(self, path: Path) -> Any: """ :returns: `QQuickItem` - the initialized component """ self._loader.setProperty("source", str(path.resolve(True))) return self._loader.property("item") - def loads(self, content: str): + def loads(self, content: str) -> Any: """ :returns: `QQuickItem` - the initialized component """ From 938332315a4b656ec4dc34e295a12cd641d817a1 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 1 Feb 2023 13:35:26 -0300 Subject: [PATCH 10/24] Apply suggestions from code review --- src/pytestqt/plugin.py | 2 +- src/pytestqt/qml/__init__.py | 2 +- tests/test_qml/test_qmlbot.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pytestqt/plugin.py b/src/pytestqt/plugin.py index 8410999a..2ff74684 100644 --- a/src/pytestqt/plugin.py +++ b/src/pytestqt/plugin.py @@ -95,7 +95,7 @@ def qtbot(qapp, request): @pytest.fixture -def qmlbot(qapp): +def qmlbot(qapp) -> QmlBot: return QmlBot() diff --git a/src/pytestqt/qml/__init__.py b/src/pytestqt/qml/__init__.py index 4505007b..71133444 100644 --- a/src/pytestqt/qml/__init__.py +++ b/src/pytestqt/qml/__init__.py @@ -1,3 +1,3 @@ from .qmlbot import QmlBot -__all__ = [QmlBot] +__all__ = ["QmlBot"] diff --git a/tests/test_qml/test_qmlbot.py b/tests/test_qml/test_qmlbot.py index 1dc070e8..2a020b49 100644 --- a/tests/test_qml/test_qmlbot.py +++ b/tests/test_qml/test_qmlbot.py @@ -6,7 +6,7 @@ from pytestqt import QmlBot -def test_load_from_string_wrong_syntax(qmlbot): +def test_load_from_string_wrong_syntax(qmlbot: QmlBot) -> None: qml = "import QtQuick 2.0 Rectangle{" with pytest.raises(RuntimeError): qmlbot.loads(qml) From a8fa68c396c448d6df68a952cbe8187e33e9c22d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 1 Feb 2023 13:54:33 -0300 Subject: [PATCH 11/24] Update __init__.py --- src/pytestqt/qml/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pytestqt/qml/__init__.py b/src/pytestqt/qml/__init__.py index 71133444..8b137891 100644 --- a/src/pytestqt/qml/__init__.py +++ b/src/pytestqt/qml/__init__.py @@ -1,3 +1 @@ -from .qmlbot import QmlBot -__all__ = ["QmlBot"] From 59d5d7e896711dc99c291dfac880c00294df8fe9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 1 Feb 2023 16:54:43 +0000 Subject: [PATCH 12/24] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/pytestqt/qml/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pytestqt/qml/__init__.py b/src/pytestqt/qml/__init__.py index 8b137891..e69de29b 100644 --- a/src/pytestqt/qml/__init__.py +++ b/src/pytestqt/qml/__init__.py @@ -1 +0,0 @@ - From ac3a9bb7ce1a45e6c482b72a10b9ed4cb94e2391 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 1 Feb 2023 13:55:12 -0300 Subject: [PATCH 13/24] Update __init__.py --- src/pytestqt/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pytestqt/__init__.py b/src/pytestqt/__init__.py index 7c6237c1..d03f03ad 100644 --- a/src/pytestqt/__init__.py +++ b/src/pytestqt/__init__.py @@ -1,4 +1,8 @@ # _version is automatically generated by setuptools_scm from pytestqt._version import version +from .qml.qmlbot import QmlBot + __version__ = version + +__all__ = ["QmlBot", "__version__"] From dd70ad847f6296e524b36d2fb785ec01efc81ba7 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 1 Feb 2023 13:56:41 -0300 Subject: [PATCH 14/24] Cancel running jobs --- .github/workflows/main.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6093d2cc..b72d14ed 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,6 +2,11 @@ name: build on: [push, pull_request] +# Cancel running jobs for the same branch and workflow. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build: From 716f4b8387c80619fc11a7f4802c7f4fa3932aca Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 1 Feb 2023 14:01:59 -0300 Subject: [PATCH 15/24] Update qmlbot.rst --- docs/qmlbot.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/qmlbot.rst b/docs/qmlbot.rst index cd970c4f..f0ff772e 100644 --- a/docs/qmlbot.rst +++ b/docs/qmlbot.rst @@ -5,6 +5,7 @@ qmlbot Fixture that helps interacting with QML. Example - load qml from string: + .. code-block:: python def test_say_hello(qmlbot): @@ -19,9 +20,11 @@ Example - load qml from string: item = qmlbot.loads(qml) assert item.property("hello") == "world" -Example - load qml from file + +Example - load qml from file: .. code-block:: python + from pathlib import Path From 02e9771e8ad8b0c7ca35e4fdd05e0693916c6840 Mon Sep 17 00:00:00 2001 From: Nir <88795475+nrbnlulu@users.noreply.github.com> Date: Wed, 1 Feb 2023 19:08:17 +0200 Subject: [PATCH 16/24] nit: make root private --- src/pytestqt/qml/qmlbot.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pytestqt/qml/qmlbot.py b/src/pytestqt/qml/qmlbot.py index 2e773a31..b3420cf1 100644 --- a/src/pytestqt/qml/qmlbot.py +++ b/src/pytestqt/qml/qmlbot.py @@ -12,8 +12,10 @@ def __init__(self) -> None: @property def _loader(self) -> Any: - self.root = self.engine.rootObjects()[0] - return self.root.findChild(qt_api.QtQuick.QQuickItem, "contentloader") + self._root = self.engine.rootObjects()[ + 0 + ] # self is needed for it not to be collected by the gc + return self._root.findChild(qt_api.QtQuick.QQuickItem, "contentloader") def load(self, path: Path) -> Any: """ From 442e4b26e7639585b3b9b667ff91d1a383a2e58e Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 1 Feb 2023 14:12:49 -0300 Subject: [PATCH 17/24] Fix tests --- src/pytestqt/qml/qmlbot.py | 6 +++--- tests/test_basics.py | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pytestqt/qml/qmlbot.py b/src/pytestqt/qml/qmlbot.py index b3420cf1..d59d99cd 100644 --- a/src/pytestqt/qml/qmlbot.py +++ b/src/pytestqt/qml/qmlbot.py @@ -1,3 +1,4 @@ +import os from pathlib import Path from typing import Any @@ -8,7 +9,7 @@ class QmlBot: def __init__(self) -> None: self.engine = qt_api.QtQml.QQmlApplicationEngine() main = Path(__file__).parent / "botloader.qml" - self.engine.load(main.resolve(True)) + self.engine.load(os.fspath(main)) @property def _loader(self) -> Any: @@ -21,8 +22,7 @@ def load(self, path: Path) -> Any: """ :returns: `QQuickItem` - the initialized component """ - self._loader.setProperty("source", str(path.resolve(True))) - return self._loader.property("item") + return self.loads(path.read_text(encoding="UTF-8")) def loads(self, content: str) -> Any: """ diff --git a/tests/test_basics.py b/tests/test_basics.py index e14fd65e..88a3a71b 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -628,6 +628,8 @@ class Mock: qbackend.QtCore = qtcore qbackend.QtGui = object() qbackend.QtTest = object() + qbackend.QtQml = object() + qbackend.QtQuick = object() qbackend.QtWidgets = qtwidgets import_orig = builtins.__import__ From dca9399ada6dad8963550823f1ec39d02b51fe57 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 1 Feb 2023 14:13:48 -0300 Subject: [PATCH 18/24] Drop load from file API --- src/pytestqt/qml/qmlbot.py | 6 ------ tests/test_qml/test_qmlbot.py | 6 ------ 2 files changed, 12 deletions(-) diff --git a/src/pytestqt/qml/qmlbot.py b/src/pytestqt/qml/qmlbot.py index d59d99cd..79d32a23 100644 --- a/src/pytestqt/qml/qmlbot.py +++ b/src/pytestqt/qml/qmlbot.py @@ -18,12 +18,6 @@ def _loader(self) -> Any: ] # self is needed for it not to be collected by the gc return self._root.findChild(qt_api.QtQuick.QQuickItem, "contentloader") - def load(self, path: Path) -> Any: - """ - :returns: `QQuickItem` - the initialized component - """ - return self.loads(path.read_text(encoding="UTF-8")) - def loads(self, content: str) -> Any: """ :returns: `QQuickItem` - the initialized component diff --git a/tests/test_qml/test_qmlbot.py b/tests/test_qml/test_qmlbot.py index 2a020b49..14bffe8c 100644 --- a/tests/test_qml/test_qmlbot.py +++ b/tests/test_qml/test_qmlbot.py @@ -1,4 +1,3 @@ -from pathlib import Path from textwrap import dedent import pytest @@ -27,8 +26,3 @@ def test_load_from_string(qmlbot: QmlBot) -> None: ) item = qmlbot.loads(qml) assert item.property("hello") == text - - -def test_load_from_file(qmlbot: QmlBot) -> None: - item = qmlbot.load(Path(__file__).parent / "sample.qml") - assert item.property("hello") == "world" From bb9cb0614ba0bddafb1d88b828c4189771979ed5 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 1 Feb 2023 14:16:00 -0300 Subject: [PATCH 19/24] Add CHANGELOG --- CHANGELOG.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 03e638b7..0d415bc3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,12 @@ +4.3.0 (UNRELEASED) +------------------ + +- New ``qmlbot`` fixture to help test QtQuick applications (`#476`_). Thanks `@nrbnlulu`_ for the PR. + +.. _#476: https://github.com/pytest-dev/pytest-qt/pull/476 +.. _@nrbnlulu: https://github.com/nrbnlulu + + 4.2.0 (2022-10-25) ------------------ From e472f83a856ee5640f9243d547e3ef8a4d73da85 Mon Sep 17 00:00:00 2001 From: Nir <88795475+nrbnlulu@users.noreply.github.com> Date: Wed, 1 Feb 2023 19:51:51 +0200 Subject: [PATCH 20/24] restore `QmlBot.load()`` --- src/pytestqt/qml/qmlbot.py | 6 ++++++ tests/test_qml/test_qmlbot.py | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/pytestqt/qml/qmlbot.py b/src/pytestqt/qml/qmlbot.py index 79d32a23..d59d99cd 100644 --- a/src/pytestqt/qml/qmlbot.py +++ b/src/pytestqt/qml/qmlbot.py @@ -18,6 +18,12 @@ def _loader(self) -> Any: ] # self is needed for it not to be collected by the gc return self._root.findChild(qt_api.QtQuick.QQuickItem, "contentloader") + def load(self, path: Path) -> Any: + """ + :returns: `QQuickItem` - the initialized component + """ + return self.loads(path.read_text(encoding="UTF-8")) + def loads(self, content: str) -> Any: """ :returns: `QQuickItem` - the initialized component diff --git a/tests/test_qml/test_qmlbot.py b/tests/test_qml/test_qmlbot.py index 14bffe8c..2a020b49 100644 --- a/tests/test_qml/test_qmlbot.py +++ b/tests/test_qml/test_qmlbot.py @@ -1,3 +1,4 @@ +from pathlib import Path from textwrap import dedent import pytest @@ -26,3 +27,8 @@ def test_load_from_string(qmlbot: QmlBot) -> None: ) item = qmlbot.loads(qml) assert item.property("hello") == text + + +def test_load_from_file(qmlbot: QmlBot) -> None: + item = qmlbot.load(Path(__file__).parent / "sample.qml") + assert item.property("hello") == "world" From ddd7cc92ec8795df53bb39bf33ea658105d97499 Mon Sep 17 00:00:00 2001 From: Nir <88795475+nrbnlulu@users.noreply.github.com> Date: Wed, 1 Feb 2023 19:53:29 +0200 Subject: [PATCH 21/24] restore `QmlBot.load()`` --- src/pytestqt/qml/qmlbot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pytestqt/qml/qmlbot.py b/src/pytestqt/qml/qmlbot.py index d59d99cd..97545e1a 100644 --- a/src/pytestqt/qml/qmlbot.py +++ b/src/pytestqt/qml/qmlbot.py @@ -22,7 +22,8 @@ def load(self, path: Path) -> Any: """ :returns: `QQuickItem` - the initialized component """ - return self.loads(path.read_text(encoding="UTF-8")) + self._loader.setProperty("source", str(path.resolve(True))) + return self._loader.property("item") def loads(self, content: str) -> Any: """ From 355c377cba86a0bf85f9a8b4b65c84d8cc0cec8b Mon Sep 17 00:00:00 2001 From: Nir <88795475+nrbnlulu@users.noreply.github.com> Date: Wed, 1 Feb 2023 20:25:03 +0200 Subject: [PATCH 22/24] fix windows. --- src/pytestqt/qml/qmlbot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pytestqt/qml/qmlbot.py b/src/pytestqt/qml/qmlbot.py index 97545e1a..4550b027 100644 --- a/src/pytestqt/qml/qmlbot.py +++ b/src/pytestqt/qml/qmlbot.py @@ -22,7 +22,7 @@ def load(self, path: Path) -> Any: """ :returns: `QQuickItem` - the initialized component """ - self._loader.setProperty("source", str(path.resolve(True))) + self._loader.setProperty("source", path.resolve(True).as_uri()) return self._loader.property("item") def loads(self, content: str) -> Any: From e3dd1fc940ada4955b22dc3cc39c57d6286d60c3 Mon Sep 17 00:00:00 2001 From: Nir <88795475+nrbnlulu@users.noreply.github.com> Date: Mon, 13 Feb 2023 12:01:44 +0200 Subject: [PATCH 23/24] fix windows. try 1 --- src/pytestqt/qml/qmlbot.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pytestqt/qml/qmlbot.py b/src/pytestqt/qml/qmlbot.py index 4550b027..0a71a9b2 100644 --- a/src/pytestqt/qml/qmlbot.py +++ b/src/pytestqt/qml/qmlbot.py @@ -22,7 +22,9 @@ def load(self, path: Path) -> Any: """ :returns: `QQuickItem` - the initialized component """ - self._loader.setProperty("source", path.resolve(True).as_uri()) + self._loader.setProperty( + "source", qt_api.QtCore.QUrl(path.resolve(True).as_uri()) + ) return self._loader.property("item") def loads(self, content: str) -> Any: From 4f783e173a4283cb60b35f6aa27f92864f535bdb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Feb 2023 10:02:25 +0000 Subject: [PATCH 24/24] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/pytestqt/exceptions.py | 2 +- src/pytestqt/modeltest.py | 1 - tests/test_basics.py | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pytestqt/exceptions.py b/src/pytestqt/exceptions.py index 980f9ac3..e8bcec01 100644 --- a/src/pytestqt/exceptions.py +++ b/src/pytestqt/exceptions.py @@ -79,7 +79,7 @@ def format_captured_exceptions(exceptions): stream.write("Exceptions caught in Qt event loop:\n") sep = "_" * 80 + "\n" stream.write(sep) - for (exc_type, value, tback) in exceptions: + for exc_type, value, tback in exceptions: traceback.print_exception(exc_type, value, tback, file=stream) stream.write(sep) return stream.getvalue() diff --git a/src/pytestqt/modeltest.py b/src/pytestqt/modeltest.py index af877457..b2df3fe6 100644 --- a/src/pytestqt/modeltest.py +++ b/src/pytestqt/modeltest.py @@ -54,7 +54,6 @@ class _ChangeInFlight(enum.Enum): - COLUMNS_INSERTED = enum.auto() COLUMNS_MOVED = enum.auto() COLUMNS_REMOVED = enum.auto() diff --git a/tests/test_basics.py b/tests/test_basics.py index 88a3a71b..428d1215 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -592,7 +592,6 @@ def _fake_is_library_loaded(name, *args): ], ) def test_already_loaded_backend(monkeypatch, option_api, backend): - import builtins class Mock: