diff options
Diffstat (limited to 'sources/pyside6/tests/QtQml')
26 files changed, 619 insertions, 39 deletions
diff --git a/sources/pyside6/tests/QtQml/CMakeLists.txt b/sources/pyside6/tests/QtQml/CMakeLists.txt index fc931c0f2..30bf7e786 100644 --- a/sources/pyside6/tests/QtQml/CMakeLists.txt +++ b/sources/pyside6/tests/QtQml/CMakeLists.txt @@ -1,8 +1,12 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + PYSIDE_TEST(bug_451.py) PYSIDE_TEST(bug_456.py) PYSIDE_TEST(bug_557.py) PYSIDE_TEST(bug_726.py) PYSIDE_TEST(bug_814.py) +PYSIDE_TEST(bug_825_old.py) PYSIDE_TEST(bug_825.py) PYSIDE_TEST(bug_847.py) PYSIDE_TEST(bug_915.py) @@ -13,8 +17,10 @@ PYSIDE_TEST(bug_997.py) PYSIDE_TEST(bug_1029.py) PYSIDE_TEST(groupedproperty.py) PYSIDE_TEST(listproperty.py) +PYSIDE_TEST(qmlregistertype_test.py) PYSIDE_TEST(qqmlapplicationengine_test.py) PYSIDE_TEST(qqmlnetwork_test.py) +PYSIDE_TEST(qqmlcomponent_test.py) PYSIDE_TEST(qquickview_test.py) PYSIDE_TEST(connect_python_qml.py) PYSIDE_TEST(registerattached.py) diff --git a/sources/pyside6/tests/QtQml/QtQml.pyproject b/sources/pyside6/tests/QtQml/QtQml.pyproject new file mode 100644 index 000000000..5a05c71a1 --- /dev/null +++ b/sources/pyside6/tests/QtQml/QtQml.pyproject @@ -0,0 +1,65 @@ +{ + "files": ["ModuleType.qml", + "bug_1029.py", + "bug_1029.qml", + "bug_451.py", + "bug_451.qml", + "bug_456.py", + "bug_456.qml", + "bug_557.py", + "bug_726.py", + "bug_726.qml", + "bug_814.py", + "bug_814.qml", + "bug_825.py", + "bug_825.qml", + "bug_847.py", + "bug_847.qml", + "bug_915.py", + "bug_926.py", + "bug_926.qml", + "bug_951.py", + "bug_951.qml", + "bug_995.py", + "bug_995.qml", + "bug_997.py", + "bug_997.qml", + "connect_python_qml.py", + "connect_python_qml.qml", + "groupedproperty.py", + "groupedproperty.qml", + "hw.qml", + "javascript_exceptions.py", + "javascript_exceptions.qml", + "listproperty.py", + "qqmlapplicationengine.qml", + "qqmlapplicationengine_test.py", + "qqmlincubator_incubateWhile.py", + "qqmlincubator_incubateWhile.qml", + "qqmlincubator_incubateWhile_component.qml", + "qqmlnetwork_test.py", + "qquickitem_grabToImage.py", + "qquickitem_grabToImage.qml", + "qquickview_test.py", + "registerattached.py", + "registerattached.qml", + "registerextended.py", + "registerextended.qml", + "registerforeign.py", + "registerforeign.qml", + "registerparserstatus.py", + "registerparserstatus.qml", + "registerqmlfile.py", + "registersingletontype.py", + "registersingletontype.qml", + "registertype.py", + "registertype.qml", + "registeruncreatable.qml", + "registeruncreatabletype.py", + "signal_arguments.py", + "signal_arguments.qml", + "signal_types.py", + "signal_types.qml", + "view.qml", + "viewmodel.qml"] +} diff --git a/sources/pyside6/tests/QtQml/bug_451.py b/sources/pyside6/tests/QtQml/bug_451.py index 299864ae4..d81a99d94 100644 --- a/sources/pyside6/tests/QtQml/bug_451.py +++ b/sources/pyside6/tests/QtQml/bug_451.py @@ -28,6 +28,7 @@ from PySide6.QtQml import QmlElement QML_IMPORT_NAME = "test.PythonObject" QML_IMPORT_MAJOR_VERSION = 1 + @QmlElement class PythonObject(QObject): def __init__(self): diff --git a/sources/pyside6/tests/QtQml/bug_456.py b/sources/pyside6/tests/QtQml/bug_456.py index 7148102cc..7743ee3fd 100644 --- a/sources/pyside6/tests/QtQml/bug_456.py +++ b/sources/pyside6/tests/QtQml/bug_456.py @@ -19,6 +19,7 @@ from PySide6.QtQml import QmlElement QML_IMPORT_NAME = "test.RotateValue" QML_IMPORT_MAJOR_VERSION = 1 + @QmlElement class RotateValue(QObject): def __init__(self): diff --git a/sources/pyside6/tests/QtQml/bug_557.py b/sources/pyside6/tests/QtQml/bug_557.py index cc06a4877..eb43973f6 100644 --- a/sources/pyside6/tests/QtQml/bug_557.py +++ b/sources/pyside6/tests/QtQml/bug_557.py @@ -23,6 +23,6 @@ component = QQmlComponent(engine) # This should segfault if the QDeclarativeComponent has not QQmlEngine file = Path(__file__).resolve().parent / 'foo.qml' -assert(not file.is_file()) +assert (not file.is_file()) component.loadUrl(QUrl.fromLocalFile(file)) diff --git a/sources/pyside6/tests/QtQml/bug_726.py b/sources/pyside6/tests/QtQml/bug_726.py index 7ddc8e93b..56c1e70f1 100644 --- a/sources/pyside6/tests/QtQml/bug_726.py +++ b/sources/pyside6/tests/QtQml/bug_726.py @@ -18,6 +18,8 @@ from PySide6.QtQml import QmlElement QML_IMPORT_NAME = "test.ProxyObject" QML_IMPORT_MAJOR_VERSION = 1 + + @QmlElement class ProxyObject(QObject): def __init__(self): diff --git a/sources/pyside6/tests/QtQml/bug_814.py b/sources/pyside6/tests/QtQml/bug_814.py index 9db6488ef..0e7858b6c 100644 --- a/sources/pyside6/tests/QtQml/bug_814.py +++ b/sources/pyside6/tests/QtQml/bug_814.py @@ -29,13 +29,14 @@ from PySide6.QtQml import QmlElement QML_IMPORT_NAME = "test.ListModel" QML_IMPORT_MAJOR_VERSION = 1 + @QmlElement class ListModel(QAbstractListModel): def __init__(self): super().__init__() def roleNames(self): - return { Qt.DisplayRole: b'pysideModelData' } + return {Qt.DisplayRole: b'pysideModelData'} def rowCount(self, parent=QModelIndex()): return 3 diff --git a/sources/pyside6/tests/QtQml/bug_825.py b/sources/pyside6/tests/QtQml/bug_825.py index 9771d0634..a8bd304ec 100644 --- a/sources/pyside6/tests/QtQml/bug_825.py +++ b/sources/pyside6/tests/QtQml/bug_825.py @@ -1,6 +1,11 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +""" +This is the corrected version for Python 3. +Unfortunately, this touches a Python 3.8 error that was fixed late. +""" + import os import sys import unittest @@ -25,8 +30,8 @@ class MetaA(type): pass -class A(object): - __metaclass__ = MetaA +class A(object, metaclass=MetaA): + pass MetaB = type(QQuickPaintedItem) @@ -37,8 +42,8 @@ class MetaC(MetaA, MetaB): pass -class C(A, B): - __metaclass__ = MetaC +class C(A, B, metaclass=MetaC): + pass class Bug825 (C): diff --git a/sources/pyside6/tests/QtQml/bug_825_old.py b/sources/pyside6/tests/QtQml/bug_825_old.py new file mode 100644 index 000000000..c44fa75f4 --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_825_old.py @@ -0,0 +1,80 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +""" +This is the now incorrect old version from Python 2. +It happens to work in another way and will be retained. +""" + +import os +import sys +import unittest + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) +from init_paths import init_test_paths +init_test_paths(False) + +from helper.helper import quickview_errorstring + +from PySide6.QtCore import Qt, QUrl, QTimer +from PySide6.QtGui import QGuiApplication, QPen +from PySide6.QtWidgets import QGraphicsItem +from PySide6.QtQml import qmlRegisterType +from PySide6.QtQuick import QQuickView, QQuickItem, QQuickPaintedItem + +paintCalled = False + + +class MetaA(type): + pass + + +class A(object): + __metaclass__ = MetaA + + +MetaB = type(QQuickPaintedItem) +B = QQuickPaintedItem + + +class MetaC(MetaA, MetaB): + pass + + +class C(A, B): + __metaclass__ = MetaC + + +class Bug825 (C): + def __init__(self, parent=None): + QQuickPaintedItem.__init__(self, parent) + + def paint(self, painter): + global paintCalled + pen = QPen(Qt.black, 2) + painter.setPen(pen) + painter.drawPie(self.boundingRect(), 0, 128) + paintCalled = True + + +class TestBug825 (unittest.TestCase): + def testIt(self): + global paintCalled + app = QGuiApplication([]) + qmlRegisterType(Bug825, 'bugs', 1, 0, 'Bug825') + self.assertRaises(TypeError, qmlRegisterType, A, 'bugs', 1, 0, 'A') + + view = QQuickView() + file = Path(__file__).resolve().parent / 'bug_825.qml' + self.assertTrue(file.is_file()) + view.setSource(QUrl.fromLocalFile(file)) + self.assertTrue(view.rootObject(), quickview_errorstring(view)) + view.show() + QTimer.singleShot(250, view.close) + app.exec() + self.assertTrue(paintCalled) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/bug_847.py b/sources/pyside6/tests/QtQml/bug_847.py index 8ccde82ff..947eb494e 100644 --- a/sources/pyside6/tests/QtQml/bug_847.py +++ b/sources/pyside6/tests/QtQml/bug_847.py @@ -17,7 +17,7 @@ from init_paths import init_test_paths init_test_paths(False) from helper.helper import quickview_errorstring -from helper.usesqguiapplication import UsesQGuiApplication +from helper.usesqapplication import UsesQApplication from PySide6.QtCore import Slot, Signal, QUrl, QTimer, QCoreApplication from PySide6.QtQuick import QQuickView @@ -34,7 +34,7 @@ class View(QQuickView): self.called.emit(x, y) -class TestQML(UsesQGuiApplication): +class TestQML(UsesQApplication): def done(self, x, y): self._sucess = True self.app.quit() diff --git a/sources/pyside6/tests/QtQml/bug_926.py b/sources/pyside6/tests/QtQml/bug_926.py index c4853b9ad..085e9a68f 100644 --- a/sources/pyside6/tests/QtQml/bug_926.py +++ b/sources/pyside6/tests/QtQml/bug_926.py @@ -41,7 +41,7 @@ class MyClass (QObject): class TestBug926 (unittest.TestCase): def testIt(self): app = QGuiApplication([]) - qmlRegisterType(MyClass,'Example', 1, 0, 'MyClass') + qmlRegisterType(MyClass, 'Example', 1, 0, 'MyClass') view = QQuickView() file = Path(__file__).resolve().parent / 'bug_926.qml' self.assertTrue(file.is_file()) diff --git a/sources/pyside6/tests/QtQml/bug_995.py b/sources/pyside6/tests/QtQml/bug_995.py index bd1315d52..868c584e2 100644 --- a/sources/pyside6/tests/QtQml/bug_995.py +++ b/sources/pyside6/tests/QtQml/bug_995.py @@ -11,7 +11,7 @@ from init_paths import init_test_paths init_test_paths(False) from helper.helper import adjust_filename -from helper.usesqguiapplication import UsesQGuiApplication +from helper.usesqapplication import UsesQApplication from PySide6.QtCore import QUrl from PySide6.QtGui import QGuiApplication @@ -19,7 +19,7 @@ from PySide6.QtQuick import QQuickView app = QGuiApplication([]) file = Path(__file__).resolve().parent / 'bug_995.qml' -assert(file.is_file()) +assert (file.is_file()) view = QQuickView(QUrl.fromLocalFile(file)) view.show() view.resize(200, 200) diff --git a/sources/pyside6/tests/QtQml/bug_997.py b/sources/pyside6/tests/QtQml/bug_997.py index cc7cf4d66..501c221c3 100644 --- a/sources/pyside6/tests/QtQml/bug_997.py +++ b/sources/pyside6/tests/QtQml/bug_997.py @@ -11,13 +11,13 @@ from init_paths import init_test_paths init_test_paths(False) from helper.helper import quickview_errorstring -from helper.usesqguiapplication import UsesQGuiApplication +from helper.usesqapplication import UsesQApplication from PySide6.QtCore import QCoreApplication, QTimer, QUrl, Slot from PySide6.QtQml import QQmlPropertyMap from PySide6.QtQuick import QQuickView -class TestBug(UsesQGuiApplication): +class TestBug(UsesQApplication): def setUp(self): super().setUp() diff --git a/sources/pyside6/tests/QtQml/connect_python_qml.py b/sources/pyside6/tests/QtQml/connect_python_qml.py index f0df1c83d..2e60aec4f 100644 --- a/sources/pyside6/tests/QtQml/connect_python_qml.py +++ b/sources/pyside6/tests/QtQml/connect_python_qml.py @@ -42,7 +42,8 @@ class TestConnectionWithInvalidSignature(TimedQGuiApplication): root = view.rootObject() self.assertTrue(root, quickview_errorstring(view)) button = root.findChild(QObject, "buttonMouseArea") - self.assertRaises(TypeError, QObject.connect, [button,SIGNAL('entered()'), self.onButtonFailClicked]) + self.assertRaises(TypeError, QObject.connect, + [button, SIGNAL('entered()'), self.onButtonFailClicked]) button.entered.connect(self.onButtonClicked) button.entered.emit() view.show() diff --git a/sources/pyside6/tests/QtQml/javascript_exceptions.py b/sources/pyside6/tests/QtQml/javascript_exceptions.py index ec99c930d..e2b530aaf 100644 --- a/sources/pyside6/tests/QtQml/javascript_exceptions.py +++ b/sources/pyside6/tests/QtQml/javascript_exceptions.py @@ -11,7 +11,7 @@ from init_paths import init_test_paths init_test_paths(False) from helper.helper import quickview_errorstring -from helper.usesqguiapplication import UsesQGuiApplication +from helper.usesqapplication import UsesQApplication from PySide6.QtCore import Slot, Property, Signal, QObject, QUrl from PySide6.QtQml import QJSEngine, qmlRegisterType @@ -54,7 +54,7 @@ class TestClass(QObject): test_2 = True -class JavaScriptExceptionsTest(UsesQGuiApplication): +class JavaScriptExceptionsTest(UsesQApplication): def test_jsengine(self): engine = QJSEngine() test_object = TestClass() diff --git a/sources/pyside6/tests/QtQml/listproperty.py b/sources/pyside6/tests/QtQml/listproperty.py index 8916aefe5..884600d29 100644 --- a/sources/pyside6/tests/QtQml/listproperty.py +++ b/sources/pyside6/tests/QtQml/listproperty.py @@ -7,11 +7,25 @@ import unittest from pathlib import Path sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) -from init_paths import init_test_paths +from init_paths import init_test_paths # noqa: E402 init_test_paths(False) -from PySide6.QtCore import QObject -from PySide6.QtQml import ListProperty +from helper.usesqapplication import UsesQApplication # noqa: E402, F401 + +from PySide6.QtCore import QObject, QUrl, Property, qInstallMessageHandler # noqa: E402 +from PySide6.QtQml import ListProperty, QmlElement # noqa: E402 +from PySide6.QtQuick import QQuickView # noqa: E402 + + +QML_IMPORT_NAME = "test.ListPropertyTest" +QML_IMPORT_MAJOR_VERSION = 1 + +output_messages = [] + + +def message_handler(mode, context, message): + global output_messages + output_messages.append(f"{message}") class InheritsQObject(QObject): @@ -22,7 +36,46 @@ def dummyFunc(): pass -class TestListProperty(unittest.TestCase): +@QmlElement +class Person(QObject): + def __init__(self, parent=None): + super().__init__(parent=None) + self._name = '' + self._friends = [] + + def appendFriend(self, friend): + self._friends.append(friend) + + def friendCount(self): + return len(self._friends) + + def friend(self, index): + return self._friends[index] + + def removeLastItem(self): + if len(self._friends) > 0: + self._friends.pop() + + def replace(self, index, friend): + if 0 <= index < len(self._friends): + self._friends[index] = friend + + def clear(self): + self._friends.clear() + + @Property(str, final=True) + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = value + + friends = ListProperty(QObject, append=appendFriend, count=friendCount, at=friend, + removeLast=removeLastItem, replace=replace, clear=clear) + + +class TestListProperty(UsesQApplication): def testIt(self): # Verify that type checking works properly @@ -31,7 +84,7 @@ class TestListProperty(unittest.TestCase): try: ListProperty(QObject) ListProperty(InheritsQObject) - except: + except Exception: type_check_error = True self.assertFalse(type_check_error) @@ -47,21 +100,37 @@ class TestListProperty(unittest.TestCase): method_check_error = False try: - ListProperty(QObject, append=None, at=None, count=None, replace=None, clear=None, removeLast=None) # Explicitly setting None + ListProperty(QObject, append=None, at=None, count=None, replace=None, clear=None, + removeLast=None) # Explicitly setting None ListProperty(QObject, append=dummyFunc) ListProperty(QObject, count=dummyFunc, at=dummyFunc) - except: + except Exception: method_check_error = True self.assertFalse(method_check_error) try: - ListPropery(QObject, append=QObject()) - except: + ListProperty(QObject, append=QObject()) + except Exception: method_check_error = True self.assertTrue(method_check_error) + def testListPropParameters(self): + global output_messages + qInstallMessageHandler(message_handler) + view = QQuickView() + file = Path(__file__).resolve().parent / 'listproperty.qml' + self.assertTrue(file.is_file()) + view.setSource(QUrl.fromLocalFile(file)) + view.show() + self.assertEqual(output_messages[0], "List length: 3") + self.assertEqual(output_messages[1], "First element: Alice") + self.assertEqual(output_messages[2], "Removing last item: Charlie") + self.assertEqual(output_messages[3], "Replacing last item: Bob") + self.assertEqual(output_messages[4], "Replaced last item: David") + self.assertEqual(output_messages[5], "List length after clearing: 0") + if __name__ == '__main__': unittest.main() diff --git a/sources/pyside6/tests/QtQml/listproperty.qml b/sources/pyside6/tests/QtQml/listproperty.qml new file mode 100644 index 000000000..7b71e30ba --- /dev/null +++ b/sources/pyside6/tests/QtQml/listproperty.qml @@ -0,0 +1,50 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick 2.0 +import test.ListPropertyTest + +Rectangle { + width: 360 + height: 360 + + Person { + id: person + friends: [ + Person{ + name: "Alice" + }, + Person{ + name: "Bob" + }, + Person{ + name: "Charlie" + } + ] + } + + Person{ + id: david + name: "David" + } + + Component.onCompleted: { + // Access the length of the list + console.log("List length: " + person.friends.length); + + // Access the first element of the list + console.log("First element: " + person.friends[0].name); + + // Remove the last item of the list + console.log("Removing last item: " + person.friends.pop().name); + + // Repalce the last item of the list + console.log("Replacing last item: " + person.friends[person.friends.length - 1].name); + person.friends[person.friends.length - 1] = david; + console.log("Replaced last item: " + person.friends[person.friends.length - 1].name); + + // Clear the list + person.friends = []; + console.log("List length after clearing: " + person.friends.length); + } +} diff --git a/sources/pyside6/tests/QtQml/qmlregistertype_test.py b/sources/pyside6/tests/QtQml/qmlregistertype_test.py new file mode 100644 index 000000000..0042d6fd3 --- /dev/null +++ b/sources/pyside6/tests/QtQml/qmlregistertype_test.py @@ -0,0 +1,53 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import os +import sys +import unittest + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) +from init_paths import init_test_paths +init_test_paths(False) + +from helper.usesqapplication import UsesQApplication + + +from PySide6.QtCore import QCoreApplication, QObject # noqa: F401 +from PySide6.QtQml import QQmlApplicationEngine, qmlRegisterType + + +class BaseClass(QObject): + def __init__(self, p=None): + super().__init__(p) + + +class ChildClass(BaseClass): + def __init__(self, p=None): + super().__init__(p) + + +class TestQmlRegisterType(UsesQApplication): + """Test the legacy QML register functions.""" + + def test(self): + qmlRegisterType(BaseClass, 'test', 1, 0, 'BaseClass') + qmlRegisterType(ChildClass, 'test', 1, 0, 'ChildClass') + # PYSIDE-2709: qmlRegisterType() would set additional class info + # on the meta objects for registration which caused another meta + # object to be created, breaking inheritance. + child = ChildClass() + base = BaseClass() + self.assertTrue(child.metaObject().inherits(base.metaObject())) + + engine = QQmlApplicationEngine() + file = Path(__file__).resolve().parent / 'qmlregistertype_test.qml' + + engine.load(file) + rootObjects = engine.rootObjects() + self.assertTrue(rootObjects) + self.assertTrue(type(rootObjects[0]), ChildClass) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/qmlregistertype_test.qml b/sources/pyside6/tests/QtQml/qmlregistertype_test.qml new file mode 100644 index 000000000..108bb84b1 --- /dev/null +++ b/sources/pyside6/tests/QtQml/qmlregistertype_test.qml @@ -0,0 +1,7 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import test + +ChildClass { +} diff --git a/sources/pyside6/tests/QtQml/qqmlcomponent_test.py b/sources/pyside6/tests/QtQml/qqmlcomponent_test.py new file mode 100644 index 000000000..5521c64fa --- /dev/null +++ b/sources/pyside6/tests/QtQml/qqmlcomponent_test.py @@ -0,0 +1,36 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import os +import sys +import unittest + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) +from init_paths import init_test_paths +init_test_paths(False) + +from PySide6.QtCore import Property, QObject +from PySide6.QtQml import QQmlComponent + + +class WithComponent(QObject): + def get_component(self): + return None + + component = Property(QQmlComponent, fget=get_component) + + +class TestQmlSupport(unittest.TestCase): + + def testMetatypeValid(self): + m = WithComponent.staticMetaObject + c = m.property(m.indexOfProperty("component")) + + self.assertTrue(c.typeId() > 0) + self.assertTrue(c.typeName() == "QQmlComponent*") + self.assertTrue(c.metaType().isValid()) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/qquickview_test.py b/sources/pyside6/tests/QtQml/qquickview_test.py index 5cf5550ed..226509669 100644 --- a/sources/pyside6/tests/QtQml/qquickview_test.py +++ b/sources/pyside6/tests/QtQml/qquickview_test.py @@ -22,6 +22,7 @@ from PySide6.QtQuick import QQuickView class MyObject(QObject): titleChanged = Signal() + def __init__(self, text, parent=None): QObject.__init__(self, parent) self._text = text diff --git a/sources/pyside6/tests/QtQml/registersingletontype.py b/sources/pyside6/tests/QtQml/registersingletontype.py index f2e318e46..6beca1131 100644 --- a/sources/pyside6/tests/QtQml/registersingletontype.py +++ b/sources/pyside6/tests/QtQml/registersingletontype.py @@ -12,13 +12,18 @@ init_test_paths(False) from helper.helper import quickview_errorstring -from PySide6.QtCore import Property, Signal, QTimer, QUrl, QObject +from PySide6.QtCore import Property, Signal, QTimer, QUrl, QObject, Slot from PySide6.QtGui import QGuiApplication from PySide6.QtQml import (qmlRegisterSingletonType, qmlRegisterSingletonInstance, - QmlElement, QmlSingleton) + QmlElement, QmlSingleton, QJSValue) from PySide6.QtQuick import QQuickView + +URI = "Singletons" + + finalResult = 0 +qObjectQmlTypeId = 0 class SingletonQObject(QObject): @@ -46,9 +51,10 @@ def singletonQJSValueCallback(engine): return engine.evaluate("new Object({data: 50})") -QML_IMPORT_NAME = "Singletons" +QML_IMPORT_NAME = URI QML_IMPORT_MAJOR_VERSION = 1 + @QmlElement @QmlSingleton class DecoratedSingletonQObject(QObject): @@ -65,37 +71,82 @@ class DecoratedSingletonQObject(QObject): data = Property(int, getData, setData) +@QmlElement +@QmlSingleton +class DecoratedSingletonWithCreate(QObject): + def __init__(self, data, parent=None): + super().__init__(parent) + self._data = data + + @staticmethod + def create(engine): + return DecoratedSingletonWithCreate(400) + + def getData(self): + return self._data + + def setData(self, data): + self._data = data + + data = Property(int, getData, setData) + + +class TestQuickView(QQuickView): + def __init__(self, parent=None): + super().__init__(parent) + self._singleton_instance_qobject_int = False + self._singleton_instance_qobject_str = False + self._singleton_instance_jsvalue_int = False + + @Slot() + def testSlot(self): + engine = self.engine() + instance = engine.singletonInstance(qObjectQmlTypeId) + if instance is not None and isinstance(instance, QObject): + self._singleton_instance_qobject_int = True + instance = engine.singletonInstance(URI, 'SingletonQObjectNoCallback') + if instance is not None and isinstance(instance, QObject): + self._singleton_instance_qobject_str = True + instance = engine.singletonInstance(URI, 'SingletonQJSValue') + if instance is not None and isinstance(instance, QJSValue): + self._singleton_instance_jsvalue_int = True + self.close() + + class TestQmlSupport(unittest.TestCase): def testIt(self): app = QGuiApplication([]) - qmlRegisterSingletonType(SingletonQObject, 'Singletons', 1, 0, 'SingletonQObjectNoCallback') - qmlRegisterSingletonType(SingletonQObject, 'Singletons', 1, 0, 'SingletonQObjectCallback', + qObjectQmlTypeId = qmlRegisterSingletonType(SingletonQObject, URI, 1, 0, + 'SingletonQObjectNoCallback') + qmlRegisterSingletonType(SingletonQObject, URI, 1, 0, 'SingletonQObjectCallback', singletonQObjectCallback) - qmlRegisterSingletonType('Singletons', 1, 0, 'SingletonQJSValue', singletonQJSValueCallback) + qmlRegisterSingletonType(URI, 1, 0, 'SingletonQJSValue', singletonQJSValueCallback) # Accepts only QObject derived types l = [1, 2] with self.assertRaises(TypeError): - qmlRegisterSingletonInstance(SingletonQObject, 'Singletons', 1, 0, 'SingletonInstance', l) + qmlRegisterSingletonInstance(SingletonQObject, URI, 1, 0, 'SingletonInstance', l) # Modify value on the instance s = SingletonQObject() s.setData(99) - qmlRegisterSingletonInstance(SingletonQObject, 'Singletons', 1, 0, 'SingletonInstance', s) + qmlRegisterSingletonInstance(SingletonQObject, URI, 1, 0, 'SingletonInstance', s) - view = QQuickView() + view = TestQuickView() file = Path(__file__).resolve().parent / 'registersingletontype.qml' self.assertTrue(file.is_file()) view.setSource(QUrl.fromLocalFile(file)) self.assertTrue(view.rootObject(), quickview_errorstring(view)) view.resize(200, 200) view.show() - QTimer.singleShot(250, view.close) + QTimer.singleShot(250, view.testSlot) app.exec() - self.assertEqual(finalResult, 499) + self.assertEqual(finalResult, 899) + self.assertTrue(view._singleton_instance_qobject_int) + self.assertTrue(view._singleton_instance_qobject_str) + self.assertTrue(view._singleton_instance_jsvalue_int) -if __name__ == '__main__': - unittest.main() +if __name__ == '__main__': unittest.main() diff --git a/sources/pyside6/tests/QtQml/registersingletontype.qml b/sources/pyside6/tests/QtQml/registersingletontype.qml index 4c45b198e..31ca7fe4d 100644 --- a/sources/pyside6/tests/QtQml/registersingletontype.qml +++ b/sources/pyside6/tests/QtQml/registersingletontype.qml @@ -9,6 +9,6 @@ Item { SingletonQObjectCallback.data += SingletonQObjectNoCallback.data + SingletonQJSValue.data + SingletonInstance.data - + DecoratedSingletonQObject.data; + + DecoratedSingletonQObject.data + DecoratedSingletonWithCreate.data; } } diff --git a/sources/pyside6/tests/QtQml/signal_arguments.py b/sources/pyside6/tests/QtQml/signal_arguments.py index 304c83ca3..f5b0f8bd3 100644 --- a/sources/pyside6/tests/QtQml/signal_arguments.py +++ b/sources/pyside6/tests/QtQml/signal_arguments.py @@ -20,6 +20,7 @@ from PySide6.QtQml import QmlElement QML_IMPORT_NAME = "test.Obj" QML_IMPORT_MAJOR_VERSION = 1 + @QmlElement class Obj(QObject): def __init__(self): diff --git a/sources/pyside6/tests/QtQml/signal_types.py b/sources/pyside6/tests/QtQml/signal_types.py new file mode 100644 index 000000000..240c0fd6e --- /dev/null +++ b/sources/pyside6/tests/QtQml/signal_types.py @@ -0,0 +1,124 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import json +import os +import sys +import unittest + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) +from init_paths import init_test_paths +init_test_paths(False) + +from helper.helper import quickview_errorstring +from helper.timedqguiapplication import TimedQGuiApplication + +from PySide6.QtQuick import QQuickView +from PySide6.QtCore import QObject, Signal, Slot, QUrl +from PySide6.QtQml import QmlElement + +"""PYSIDE-2098: Roundtrip test for signals using QVariantList/QVariantMap. + +@QmlElement Obj has signals of list/dict type which are connected to an +instance of Connections in QML. The QML instance sends them back to Obj's +slots and additionally sends them back as stringified JSON. This verifies that +a conversion is done instead of falling back to the default PyObject +passthrough converter, resulting in a QVariant<PyObject> and reference leaks +on the PyObject. +""" + +QML_IMPORT_NAME = "test.Obj" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class Obj(QObject): + listSignal = Signal(list) + dictSignal = Signal(dict) + + def __init__(self, parent=None): + super().__init__(parent) + self._last_data = None + self._last_json_data = None + + def clear(self): + self._last_data = None + self._last_json_data = None + + def last_data(self): + """Last data received.""" + return self._last_data + + def last_json_data(self): + """Last data converted from JSON.""" + return self._last_json_data + + def emit_list(self, test_list): + self.listSignal.emit(test_list) + + def emit_dict(self, test_dict): + self.dictSignal.emit(test_dict) + + @Slot(list) + def list_slot(self, l): + self._last_data = l + print("list_slot", l) + + @Slot(dict) + def dict_slot(self, d): + self._last_data = d + print("dict_slot", d) + + @Slot(str) + def json_slot(self, s): + self._last_json_data = json.loads(s) + print(f'json_slot "{s}"->', self._last_json_data) + + +class TestConnectionWithQml(TimedQGuiApplication): + + def setUp(self): + super().setUp() + self._view = QQuickView() + self._obj = Obj() + + self._view.setInitialProperties({"o": self._obj}) + file = Path(__file__).resolve().parent / "signal_types.qml" + self.assertTrue(file.is_file()) + self._view.setSource(QUrl.fromLocalFile(file)) + root = self._view.rootObject() + self.assertTrue(root, quickview_errorstring(self._view)) + + def tearDown(self): + super().tearDown() + del self._view + self._view = None + + def testVariantList(self): + self._obj.clear() + test_list = [1, 2] + before_refcount = sys.getrefcount(test_list) + self._obj.emit_list(test_list) + received = self._obj.last_data() + self.assertTrue(isinstance(received, list)) + self.assertEqual(test_list, received) + self.assertEqual(test_list, self._obj.last_json_data()) + refcount = sys.getrefcount(test_list) + self.assertEqual(before_refcount, refcount) + + def testVariantDict(self): + self._obj.clear() + test_dict = {"1": 1, "2": 2} + before_refcount = sys.getrefcount(test_dict) + self._obj.emit_dict(test_dict) + received = self._obj.last_data() + self.assertTrue(isinstance(received, dict)) + self.assertEqual(test_dict, received) + self.assertEqual(test_dict, self._obj.last_json_data()) + refcount = sys.getrefcount(test_dict) + self.assertEqual(before_refcount, refcount) + + +if __name__ == "__main__": + unittest.main() diff --git a/sources/pyside6/tests/QtQml/signal_types.qml b/sources/pyside6/tests/QtQml/signal_types.qml new file mode 100644 index 000000000..6b03b3abd --- /dev/null +++ b/sources/pyside6/tests/QtQml/signal_types.qml @@ -0,0 +1,26 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import test.Obj + +Rectangle { + visible: true + required property Obj o + + Connections { + target: o + function onListSignal(list) { + var json_data = JSON.stringify(list) + console.log("Connections.onListSignal: " + typeof(list) + " " + json_data) + o.list_slot(list) + o.json_slot(json_data) + } + function onDictSignal(dict) { + var json_data = JSON.stringify(dict) + console.log("Connections.onDictSignal: " + typeof(dict) + " " + json_data) + o.dict_slot(dict) + o.json_slot(json_data) + } + } +} |