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: