diff options
Diffstat (limited to 'sources/pyside6/tests/QtQml')
70 files changed, 3296 insertions, 0 deletions
diff --git a/sources/pyside6/tests/QtQml/CMakeLists.txt b/sources/pyside6/tests/QtQml/CMakeLists.txt new file mode 100644 index 000000000..30bf7e786 --- /dev/null +++ b/sources/pyside6/tests/QtQml/CMakeLists.txt @@ -0,0 +1,37 @@ +# 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) +PYSIDE_TEST(bug_926.py) +PYSIDE_TEST(bug_951.py) +PYSIDE_TEST(bug_995.py) +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) +PYSIDE_TEST(registerextended.py) +PYSIDE_TEST(registerparserstatus.py) +PYSIDE_TEST(registertype.py) +PYSIDE_TEST(registerforeign.py) +PYSIDE_TEST(registerqmlfile.py) +PYSIDE_TEST(registeruncreatabletype.py) +PYSIDE_TEST(registersingletontype.py) +PYSIDE_TEST(javascript_exceptions.py) +PYSIDE_TEST(qqmlincubator_incubateWhile.py) +PYSIDE_TEST(qquickitem_grabToImage.py) +PYSIDE_TEST(signal_arguments.py) diff --git a/sources/pyside6/tests/QtQml/ModuleType.qml b/sources/pyside6/tests/QtQml/ModuleType.qml new file mode 100644 index 000000000..d7e5e653c --- /dev/null +++ b/sources/pyside6/tests/QtQml/ModuleType.qml @@ -0,0 +1,8 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQml + +QtObject { + objectName: "moduleType" +} 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_1029.py b/sources/pyside6/tests/QtQml/bug_1029.py new file mode 100644 index 000000000..69ca5c18d --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_1029.py @@ -0,0 +1,43 @@ +# 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 gc +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 QUrl +from PySide6.QtGui import QGuiApplication +from PySide6.QtQml import qmlRegisterType +from PySide6.QtQuick import QQuickView, QQuickItem + + +def register_qml_types(): + class TestClass(QQuickItem): + def __init__(self, parent=None): + QQuickItem.__init__(self, parent) + + qmlRegisterType(TestClass, "UserTypes", 1, 0, "TestClass") + + +def main(): + app = QGuiApplication([]) + + # reg qml types here + register_qml_types() + + # force gc to run + gc.collect() + + view = QQuickView() + url = QUrl(__file__.replace(".py", ".qml")) + view.setSource(url) + + +if __name__ == "__main__": + main() diff --git a/sources/pyside6/tests/QtQml/bug_1029.qml b/sources/pyside6/tests/QtQml/bug_1029.qml new file mode 100644 index 000000000..e4e95f865 --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_1029.qml @@ -0,0 +1,18 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.0 +import UserTypes 1.0 + +Rectangle +{ + width: 200 + height: 200 + + color: "#ff0000" + + TestClass + { + + } +} diff --git a/sources/pyside6/tests/QtQml/bug_451.py b/sources/pyside6/tests/QtQml/bug_451.py new file mode 100644 index 000000000..d81a99d94 --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_451.py @@ -0,0 +1,93 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +''' +Test bug 451: http://bugs.openbossa.org/show_bug.cgi?id=451 + +An archive of said bug: +https://srinikom.github.io/pyside-bz-archive/451.html +''' + +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 QObject, QUrl, Property +from PySide6.QtGui import QGuiApplication +from PySide6.QtQuick import QQuickView +from PySide6.QtQml import QmlElement + + +QML_IMPORT_NAME = "test.PythonObject" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class PythonObject(QObject): + def __init__(self): + super().__init__(None) + self._called = "" + self._arg1 = None + self._arg2 = None + + def setCalled(self, v): + self._called = v + + def setArg1(self, v): + self._arg1 = v + + def setArg2(self, v): + self._arg2 = v + + def getCalled(self): + return self._called + + def getArg1(self): + return self._arg1 + + def getArg2(self): + return self._arg2 + + called = Property(str, getCalled, setCalled) + arg1 = Property(int, getArg1, setArg1) + arg2 = Property('QVariant', getArg2, setArg2) + + +class TestBug(unittest.TestCase): + def testQMLFunctionCall(self): + app = QGuiApplication(sys.argv) + view = QQuickView() + + obj = PythonObject() + view.setInitialProperties({"python": obj}) + file = Path(__file__).resolve().parent / 'bug_451.qml' + self.assertTrue(file.is_file()) + view.setSource(QUrl.fromLocalFile(file)) + root = view.rootObject() + self.assertTrue(root, quickview_errorstring(view)) + root.simpleFunction() + self.assertEqual(obj.called, "simpleFunction") + + root.oneArgFunction(42) + self.assertEqual(obj.called, "oneArgFunction") + self.assertEqual(obj.arg1, 42) + + root.twoArgFunction(10, app) + self.assertEqual(obj.called, "twoArgFunction") + self.assertEqual(obj.arg1, 10) + self.assertEqual(obj.arg2, app) + + rvalue = root.returnFunction() + self.assertEqual(obj.called, "returnFunction") + self.assertEqual(rvalue, 42) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/bug_451.qml b/sources/pyside6/tests/QtQml/bug_451.qml new file mode 100644 index 000000000..0867b861f --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_451.qml @@ -0,0 +1,31 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.0 +import test.PythonObject 1.0 + +Rectangle { + id: page + required property PythonObject python + + function simpleFunction() { + python.called = "simpleFunction" + } + + function oneArgFunction(x) { + python.called = "oneArgFunction" + python.arg1 = x + } + + function twoArgFunction(x, y) { + python.called = "twoArgFunction" + python.arg1 = x + python.arg2 = y + } + + function returnFunction() { + python.called = "returnFunction" + return 42 + } + +} diff --git a/sources/pyside6/tests/QtQml/bug_456.py b/sources/pyside6/tests/QtQml/bug_456.py new file mode 100644 index 000000000..7743ee3fd --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_456.py @@ -0,0 +1,63 @@ +# 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 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.QtCore import QObject, QTimer, QUrl, Property, Slot +from PySide6.QtQuick import QQuickView +from PySide6.QtQml import QmlElement + +QML_IMPORT_NAME = "test.RotateValue" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class RotateValue(QObject): + def __init__(self): + super().__init__() + + @Slot(result=int) + def val(self): + return 100 + + def setRotation(self, v): + self._rotation = v + + def getRotation(self): + return self._rotation + + rotation = Property(int, getRotation, setRotation) + + +class TestConnectionWithInvalidSignature(TimedQGuiApplication): + + def testSlotRetur(self): + view = QQuickView() + rotatevalue = RotateValue() + + timer = QTimer() + timer.start(2000) + + view.setInitialProperties({"rotatevalue": rotatevalue}) + file = Path(__file__).resolve().parent / 'bug_456.qml' + self.assertTrue(file.is_file()) + view.setSource(QUrl.fromLocalFile(file)) + root = view.rootObject() + self.assertTrue(root, quickview_errorstring(view)) + button = root.findChild(QObject, "buttonMouseArea") + view.show() + button.entered.emit() + self.assertEqual(rotatevalue.rotation, 100) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/bug_456.qml b/sources/pyside6/tests/QtQml/bug_456.qml new file mode 100644 index 000000000..092cca325 --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_456.qml @@ -0,0 +1,36 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.0 +import test.RotateValue 1.0 + +Rectangle { + id: page + + width: 500; height: 200 + color: "lightgray" + required property RotateValue rotatevalue + + Rectangle { + id: button + width: 150; height: 40 + color: "darkgray" + anchors.horizontalCenter: page.horizontalCenter + y: 120 + MouseArea { + id: buttonMouseArea + objectName: "buttonMouseArea" + anchors.fill: parent + onEntered: { + rotatevalue.rotation = rotatevalue.val() + } + } + Text { + id: buttonText + text: "Press me!" + anchors.horizontalCenter: button.horizontalCenter + anchors.verticalCenter: button.verticalCenter + font.pointSize: 16; + } + } +} diff --git a/sources/pyside6/tests/QtQml/bug_557.py b/sources/pyside6/tests/QtQml/bug_557.py new file mode 100644 index 000000000..eb43973f6 --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_557.py @@ -0,0 +1,28 @@ +# 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 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 adjust_filename + +from PySide6.QtCore import QUrl +from PySide6.QtGui import QGuiApplication +from PySide6.QtQml import QQmlEngine, QQmlComponent + +app = QGuiApplication(sys.argv) + +engine = QQmlEngine() +component = QQmlComponent(engine) + +# This should segfault if the QDeclarativeComponent has not QQmlEngine +file = Path(__file__).resolve().parent / 'foo.qml' +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 new file mode 100644 index 000000000..56c1e70f1 --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_726.py @@ -0,0 +1,63 @@ +# 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 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.QtCore import QObject, QUrl, Slot +from PySide6.QtQuick import QQuickView +from PySide6.QtQml import QmlElement + +QML_IMPORT_NAME = "test.ProxyObject" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class ProxyObject(QObject): + def __init__(self): + super().__init__() + self._o = None + self._receivedName = "" + + @Slot(result='QObject*') + def getObject(self): + if self._o: + return self._o + + self._o = QObject() + self._o.setObjectName("PySideObject") + return self._o + + @Slot(str) + def receivedObject(self, name): + self._receivedName = name + + +class TestConnectionWithInvalidSignature(TimedQGuiApplication): + + def testSlotRetur(self): + view = QQuickView() + proxy = ProxyObject() + + view.setInitialProperties({"proxy": proxy}) + file = Path(__file__).resolve().parent / 'bug_726.qml' + self.assertTrue(file.is_file()) + view.setSource(QUrl.fromLocalFile(file)) + root = view.rootObject() + self.assertTrue(root, quickview_errorstring(view)) + button = root.findChild(QObject, "buttonMouseArea") + view.show() + button.entered.emit() + self.assertEqual(proxy._receivedName, "PySideObject") + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/bug_726.qml b/sources/pyside6/tests/QtQml/bug_726.qml new file mode 100644 index 000000000..f80c9cdda --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_726.qml @@ -0,0 +1,36 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.0 +import test.ProxyObject 1.0 + +Rectangle { + id: page + + width: 500; height: 200 + color: "lightgray" + required property ProxyObject proxy + + Rectangle { + id: button + width: 150; height: 40 + color: "darkgray" + anchors.horizontalCenter: page.horizontalCenter + y: 120 + MouseArea { + id: buttonMouseArea + objectName: "buttonMouseArea" + anchors.fill: parent + onEntered: { + proxy.receivedObject(proxy.getObject().objectName) + } + } + Text { + id: buttonText + text: "Press me!" + anchors.horizontalCenter: button.horizontalCenter + anchors.verticalCenter: button.verticalCenter + font.pointSize: 16; + } + } +} diff --git a/sources/pyside6/tests/QtQml/bug_814.py b/sources/pyside6/tests/QtQml/bug_814.py new file mode 100644 index 000000000..0e7858b6c --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_814.py @@ -0,0 +1,65 @@ +#!/usr/bin/python +# Copyright (C) 2022 The Qt Company Ltd. +# Copyright (C) 2011 Thomas Perl <m@thp.io> +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +# Test case for PySide bug 814 +# http://bugs.pyside.org/show_bug.cgi?id=814 +# archive: +# https://srinikom.github.io/pyside-bz-archive/814.html +# 2011-04-08 Thomas Perl <m@thp.io> +# Released under the same terms as PySide itself + +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.QtCore import QUrl, QAbstractListModel, QModelIndex, Qt +from PySide6.QtQuick import QQuickView +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'} + + def rowCount(self, parent=QModelIndex()): + return 3 + + def data(self, index, role): + if index.isValid() and role == Qt.DisplayRole: + return 'blubb' + return None + + +class TestBug814(TimedQGuiApplication): + def testAbstractItemModelTransferToQML(self): + view = QQuickView() + model = ListModel() + view.setInitialProperties({"pythonModel": model}) + file = Path(__file__).resolve().parent / 'bug_814.qml' + self.assertTrue(file.is_file()) + view.setSource(QUrl.fromLocalFile(file)) + root = view.rootObject() + self.assertTrue(root, quickview_errorstring(view)) + view.show() + + +if __name__ == '__main__': + unittest.main() + diff --git a/sources/pyside6/tests/QtQml/bug_814.qml b/sources/pyside6/tests/QtQml/bug_814.qml new file mode 100644 index 000000000..4331e424f --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_814.qml @@ -0,0 +1,13 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.0 +import test.ListModel 1.0 + +ListView { + required property ListModel pythonModel + width: 300; height: 300 + delegate: Text { text: pysideModelData } + model: pythonModel +} + diff --git a/sources/pyside6/tests/QtQml/bug_825.py b/sources/pyside6/tests/QtQml/bug_825.py new file mode 100644 index 000000000..a8bd304ec --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_825.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 corrected version for Python 3. +Unfortunately, this touches a Python 3.8 error that was fixed late. +""" + +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): + pass + + +MetaB = type(QQuickPaintedItem) +B = QQuickPaintedItem + + +class MetaC(MetaA, MetaB): + pass + + +class C(A, B, metaclass=MetaC): + pass + + +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_825.qml b/sources/pyside6/tests/QtQml/bug_825.qml new file mode 100644 index 000000000..77c6b5014 --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_825.qml @@ -0,0 +1,13 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.0 +import bugs 1.0 + +Item { + width: 300; height: 200 + + Bug825 { + anchors.fill: parent + } +} 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 new file mode 100644 index 000000000..947eb494e --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_847.py @@ -0,0 +1,70 @@ +#!/usr/bin/python +# Copyright (C) 2022 The Qt Company Ltd. +# Copyright (C) 2011 Thomas Perl <m@thp.io> +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +# Testcase for PySide bug 847 +# Released under the same terms as PySide itself +# 2011-05-04 Thomas Perl <m@thp.io> + +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.usesqapplication import UsesQApplication + +from PySide6.QtCore import Slot, Signal, QUrl, QTimer, QCoreApplication +from PySide6.QtQuick import QQuickView + + +class View(QQuickView): + def __init__(self): + super().__init__() + + called = Signal(int, int) + + @Slot(int, int) + def blubb(self, x, y): + self.called.emit(x, y) + + +class TestQML(UsesQApplication): + def done(self, x, y): + self._sucess = True + self.app.quit() + print("done called") + + def testPythonSlot(self): + self._sucess = False + view = View() + + # Connect first, then set the property. + view.called.connect(self.done) + file = Path(__file__).resolve().parent / 'bug_847.qml' + self.assertTrue(file.is_file()) + view.setSource(QUrl.fromLocalFile(file)) + while view.status() == QQuickView.Loading: + self.app.processEvents() + self.assertEqual(view.status(), QQuickView.Ready) + self.assertTrue(view.rootObject(), quickview_errorstring(view)) + view.rootObject().setProperty('pythonObject', view) + + view.show() + while not view.isExposed(): + self.app.processEvents() + + # Essentially a timeout in case method invocation fails. + QTimer.singleShot(30000, QCoreApplication.instance().quit) + self.app.exec() + self.assertTrue(self._sucess) + + +if __name__ == '__main__': + unittest.main() + diff --git a/sources/pyside6/tests/QtQml/bug_847.qml b/sources/pyside6/tests/QtQml/bug_847.qml new file mode 100644 index 000000000..18efd80ce --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_847.qml @@ -0,0 +1,37 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.0 + +Rectangle { + width: 500 + height: 500 + color: 'red' + + property variant pythonObject: undefined + + Text { + anchors.centerIn: parent + text: 'click me' + color: 'white' + } + + onPythonObjectChanged: { + if (pythonObject) { + // Delay execution of method invocation, so that the event loop has a chance to start, + // which will subsequently be stopped by the method. + timer.start() + } + } + + Timer { + id: timer + interval: 100; running: false; + onTriggered: { + if (pythonObject) { + pythonObject.blubb(42, 84) + } + } + } +} + diff --git a/sources/pyside6/tests/QtQml/bug_915.py b/sources/pyside6/tests/QtQml/bug_915.py new file mode 100644 index 000000000..3095eba2a --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_915.py @@ -0,0 +1,40 @@ +#!/usr/bin/python +# 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 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.timedqguiapplication import TimedQGuiApplication + +from PySide6.QtQuick import QQuickView, QQuickItem + + +class TestBug915(TimedQGuiApplication): + def testReturnPolicy(self): + view = QQuickView() + + item1 = QQuickItem() + item1.setObjectName("Item1") + item1.setParentItem(view.contentItem()) + self.assertEqual(item1.objectName(), "Item1") # check if the item still valid + + item2 = QQuickItem() + item2.setObjectName("Item2") + item2.setParentItem(view.contentItem()) + item1 = None + self.assertEqual(item2.objectName(), "Item2") # check if the item still valid + + view = None + + +if __name__ == '__main__': + unittest.main() + + diff --git a/sources/pyside6/tests/QtQml/bug_926.py b/sources/pyside6/tests/QtQml/bug_926.py new file mode 100644 index 000000000..085e9a68f --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_926.py @@ -0,0 +1,57 @@ +# 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 sys +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 QUrl, QTimer, QObject, Signal, Property +from PySide6.QtGui import QGuiApplication +from PySide6.QtQml import qmlRegisterType +from PySide6.QtQuick import QQuickView + + +class MyClass (QObject): + + def __init__(self): + super().__init__() + self.__url = QUrl() + + def getUrl(self): + return self.__url + + def setUrl(self, value): + newUrl = QUrl(value) + if (newUrl != self.__url): + self.__url = newUrl + self.urlChanged.emit() + + urlChanged = Signal() + urla = Property(QUrl, getUrl, setUrl, notify=urlChanged) + + +class TestBug926 (unittest.TestCase): + def testIt(self): + app = QGuiApplication([]) + qmlRegisterType(MyClass, 'Example', 1, 0, 'MyClass') + view = QQuickView() + file = Path(__file__).resolve().parent / 'bug_926.qml' + self.assertTrue(file.is_file()) + view.setSource(QUrl.fromLocalFile(file)) + self.assertTrue(view.rootObject(), quickview_errorstring(view)) + + view.show() + QTimer.singleShot(0, app.quit) + app.exec() + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/bug_926.qml b/sources/pyside6/tests/QtQml/bug_926.qml new file mode 100644 index 000000000..6f7b608d0 --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_926.qml @@ -0,0 +1,20 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.0 +import Example 1.0 + +Rectangle { + width: 100 + height: 62 + + MyClass { + id: myClass + urla: "http://www.pyside.org" + } + + Text { + id: name + text: myClass.urla + } +} diff --git a/sources/pyside6/tests/QtQml/bug_951.py b/sources/pyside6/tests/QtQml/bug_951.py new file mode 100644 index 000000000..8a512d06f --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_951.py @@ -0,0 +1,51 @@ +# 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 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.QtCore import QUrl +from PySide6.QtQml import qmlRegisterType +from PySide6.QtQuick import QQuickItem, QQuickView + + +class MyItem(QQuickItem): + COMPONENT_COMPLETE_CALLED = False + + def __init__(self, parent=None): + super().__init__(parent) + self.setObjectName("myitem") + + def componentComplete(self): + MyItem.COMPONENT_COMPLETE_CALLED = True + super(MyItem, self).componentComplete() + + +class TestRegisterQMLType(TimedQGuiApplication): + def setup(self): + super.setup(100 * 3) # 3s + + def testSignalEmission(self): + qmlRegisterType(MyItem, "my.item", 1, 0, "MyItem") + + view = QQuickView() + file = Path(__file__).resolve().parent / 'bug_951.qml' + self.assertTrue(file.is_file()) + view.setSource(QUrl.fromLocalFile(file)) + self.assertTrue(view.rootObject(), quickview_errorstring(view)) + + self.app.exec() + self.assertTrue(MyItem.COMPONENT_COMPLETE_CALLED) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/bug_951.qml b/sources/pyside6/tests/QtQml/bug_951.qml new file mode 100644 index 000000000..02bf0c708 --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_951.qml @@ -0,0 +1,10 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.0 +import my.item 1.0 +Rectangle{ + width:10 + height:10 + MyItem{ } +} diff --git a/sources/pyside6/tests/QtQml/bug_995.py b/sources/pyside6/tests/QtQml/bug_995.py new file mode 100644 index 000000000..868c584e2 --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_995.py @@ -0,0 +1,31 @@ +# 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 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 adjust_filename +from helper.usesqapplication import UsesQApplication + +from PySide6.QtCore import QUrl +from PySide6.QtGui import QGuiApplication +from PySide6.QtQuick import QQuickView + +app = QGuiApplication([]) +file = Path(__file__).resolve().parent / 'bug_995.qml' +assert (file.is_file()) +view = QQuickView(QUrl.fromLocalFile(file)) +view.show() +view.resize(200, 200) +contentItem = view.contentItem() +item = contentItem.childAt(100, 100) + +# it CAN NOT crash here +print(item) + diff --git a/sources/pyside6/tests/QtQml/bug_995.qml b/sources/pyside6/tests/QtQml/bug_995.qml new file mode 100644 index 000000000..4f47cbd05 --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_995.qml @@ -0,0 +1,15 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.0 + +Rectangle { + width: 100 + height: 100 + color: "red" + + Text { + text: "Hello World" + anchors.centerIn: parent + } +} diff --git a/sources/pyside6/tests/QtQml/bug_997.py b/sources/pyside6/tests/QtQml/bug_997.py new file mode 100644 index 000000000..501c221c3 --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_997.py @@ -0,0 +1,51 @@ +# 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 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.usesqapplication import UsesQApplication +from PySide6.QtCore import QCoreApplication, QTimer, QUrl, Slot +from PySide6.QtQml import QQmlPropertyMap +from PySide6.QtQuick import QQuickView + + +class TestBug(UsesQApplication): + + def setUp(self): + super().setUp() + self._complete = False + + @Slot() + def complete(self): + self._complete = True + self.app.quit() + + def testQMLFunctionCall(self): + ownerData = QQmlPropertyMap() + ownerData.insert('name', 'John Smith') + ownerData.insert('phone', '555-5555') + ownerData.insert('newValue', '') + + self._view = QQuickView() + self._view.engine().quit.connect(self.complete) + self._view.setInitialProperties({'owner': ownerData}) + file = Path(__file__).resolve().parent / 'bug_997.qml' + self.assertTrue(file.is_file()) + self._view.setSource(QUrl.fromLocalFile(file)) + self.assertTrue(self._view.rootObject(), quickview_errorstring(self._view)) + self._view.show() + if not self._complete: + self.app.exec() + self.assertEqual(ownerData.value('newName'), ownerData.value('name')) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/bug_997.qml b/sources/pyside6/tests/QtQml/bug_997.qml new file mode 100644 index 000000000..23188f31f --- /dev/null +++ b/sources/pyside6/tests/QtQml/bug_997.qml @@ -0,0 +1,13 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.0 + +Text { + required property var owner + text: owner.name + " " + owner.phone + Component.onCompleted: { + owner.newName = owner.name + Qt.quit() + } +} diff --git a/sources/pyside6/tests/QtQml/connect_python_qml.py b/sources/pyside6/tests/QtQml/connect_python_qml.py new file mode 100644 index 000000000..2e60aec4f --- /dev/null +++ b/sources/pyside6/tests/QtQml/connect_python_qml.py @@ -0,0 +1,55 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +''' +Test case for bug #442 + +archive: +https://srinikom.github.io/pyside-bz-archive/442.html +''' + +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.QtCore import QObject, QUrl, SIGNAL +from PySide6.QtGui import QColor +from PySide6.QtQuick import QQuickItem, QQuickView + + +class TestConnectionWithInvalidSignature(TimedQGuiApplication): + def onButtonClicked(self): + self.buttonClicked = True + self.app.quit() + + def onButtonFailClicked(self): + pass + + def testFailConnection(self): + self.buttonClicked = False + self.buttonFailClicked = False + view = QQuickView() + file = Path(__file__).resolve().parent / 'connect_python_qml.qml' + self.assertTrue(file.is_file()) + view.setSource(QUrl.fromLocalFile(file)) + root = view.rootObject() + self.assertTrue(root, quickview_errorstring(view)) + button = root.findChild(QObject, "buttonMouseArea") + self.assertRaises(TypeError, QObject.connect, + [button, SIGNAL('entered()'), self.onButtonFailClicked]) + button.entered.connect(self.onButtonClicked) + button.entered.emit() + view.show() + self.app.exec() + self.assertTrue(self.buttonClicked) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/connect_python_qml.qml b/sources/pyside6/tests/QtQml/connect_python_qml.qml new file mode 100644 index 000000000..1ed171e96 --- /dev/null +++ b/sources/pyside6/tests/QtQml/connect_python_qml.qml @@ -0,0 +1,23 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.0 + +Rectangle { + id: page + width: 500; height: 200 + color: "lightgray" + + Rectangle { + id: button + width: 150; height: 40 + color: "darkgray" + anchors.horizontalCenter: page.horizontalCenter + y: 150 + MouseArea { + id: buttonMouseArea + objectName: "buttonMouseArea" + anchors.fill: parent + } + } +} diff --git a/sources/pyside6/tests/QtQml/groupedproperty.py b/sources/pyside6/tests/QtQml/groupedproperty.py new file mode 100644 index 000000000..4554d4b31 --- /dev/null +++ b/sources/pyside6/tests/QtQml/groupedproperty.py @@ -0,0 +1,112 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +"""Test grouped properties (PYSIDE-1836).""" + +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 (QCoreApplication, QUrl, QObject, Property) +from PySide6.QtQml import (QQmlComponent, QQmlEngine, QmlAnonymous, QmlElement) + + +QML_IMPORT_NAME = "grouped" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlAnonymous +class ShoeDescription(QObject): + def __init__(self, parent=None): + super().__init__(parent) + self._brand = "" + self._size = 0 + self._price = 0 + + @Property(str) + def brand(self): + return self._brand + + @brand.setter + def brand(self, b): + self._brand = b + + @Property(int) + def size(self): + return self._size + + @size.setter + def size(self, s): + self._size = s + + @Property(int) + def price(self): + return self._price + + @price.setter + def price(self, p): + self._price = p + + +@QmlElement +class Person(QObject): + def __init__(self, parent=None): + super().__init__(parent) + self._name = "" + self._shoe = ShoeDescription() + + @Property(str) + def name(self): + return self._name + + @name.setter + def name(self, n): + self._name = n + + @Property(ShoeDescription) + def shoe(self): + return self._shoe + + +def component_error(component): + result = "" + for e in component.errors(): + if result: + result += "\n" + result += str(e) + return result + + +class TestQmlGroupedProperties(unittest.TestCase): + def testIt(self): + app = QCoreApplication(sys.argv) + file = Path(__file__).resolve().parent / "groupedproperty.qml" + url = QUrl.fromLocalFile(file) + engine = QQmlEngine() + component = QQmlComponent(engine, url) + person = component.create() + self.assertTrue(person, component_error(component)) + + # Check the meta type of the property + meta_object = person.metaObject() + index = meta_object.indexOfProperty("shoe") + self.assertTrue(index > 0) + meta_property = meta_object.property(index) + meta_type = meta_property.metaType() + self.assertTrue(meta_type.isValid()) + + # Check the values + self.assertEqual(person.shoe.brand, "Bikey") + self.assertEqual(person.shoe.price, 90) + self.assertEqual(person.shoe.size, 12) + + del engine + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/groupedproperty.qml b/sources/pyside6/tests/QtQml/groupedproperty.qml new file mode 100644 index 000000000..d32bd6005 --- /dev/null +++ b/sources/pyside6/tests/QtQml/groupedproperty.qml @@ -0,0 +1,9 @@ +// 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 grouped + +Person { + name: "Bob Jones" + shoe { size: 12; brand: "Bikey"; price: 90 } +} diff --git a/sources/pyside6/tests/QtQml/hw.qml b/sources/pyside6/tests/QtQml/hw.qml new file mode 100644 index 000000000..723901d96 --- /dev/null +++ b/sources/pyside6/tests/QtQml/hw.qml @@ -0,0 +1,23 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.0 + +Rectangle { + id: page + width: 500; height: 200 + color: "lightgray" + + Text { + id: helloText + text: "Hello world!" + y: 30 + anchors.horizontalCenter: page.horizontalCenter + font.pointSize: 24; font.bold: true + } + + Image { + // It's okay for this to fail. + source: "http://localhost/logo.png" + } +} diff --git a/sources/pyside6/tests/QtQml/javascript_exceptions.py b/sources/pyside6/tests/QtQml/javascript_exceptions.py new file mode 100644 index 000000000..e2b530aaf --- /dev/null +++ b/sources/pyside6/tests/QtQml/javascript_exceptions.py @@ -0,0 +1,91 @@ +# 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 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.usesqapplication import UsesQApplication + +from PySide6.QtCore import Slot, Property, Signal, QObject, QUrl +from PySide6.QtQml import QJSEngine, qmlRegisterType +from PySide6.QtQuick import QQuickView + +test_error_message = "This is an error." + +method_test_string = """ +(function (obj) { + obj.methodThrows(); +}) +""" + +property_test_string = """ +(function (obj) { + obj.propertyThrows; +}) +""" + +test_1 = False +test_2 = False + + +class TestClass(QObject): + @Slot() + def methodThrows(self): + raise TypeError(test_error_message) + + @Property(str) + def propertyThrows(self): + raise TypeError(test_error_message) + + @Slot(int) + def passTest(self, test): + global test_1, test_2 + + if test == 1: + test_1 = True + else: + test_2 = True + + +class JavaScriptExceptionsTest(UsesQApplication): + def test_jsengine(self): + engine = QJSEngine() + test_object = TestClass() + test_value = engine.newQObject(test_object) + + result_1 = engine.evaluate(method_test_string).call([test_value]) + + self.assertTrue(result_1.isError()) + self.assertEqual(result_1.property('message').toString(), test_error_message) + self.assertEqual(result_1.property('name').toString(), 'TypeError') + + result_2 = engine.evaluate(property_test_string).call([test_value]) + + self.assertTrue(result_2.isError()) + self.assertEqual(result_2.property('message').toString(), test_error_message) + self.assertEqual(result_2.property('name').toString(), 'TypeError') + + def test_qml_type(self): + qmlRegisterType(TestClass, 'JavaScriptExceptions', 1, 0, 'JavaScriptExceptions') + + view = QQuickView() + file = Path(__file__).resolve().parent / 'javascript_exceptions.qml' + self.assertTrue(file.is_file()) + qml_url = QUrl.fromLocalFile(file) + + view.setSource(qml_url) + self.assertTrue(view.rootObject(), quickview_errorstring(view)) + + self.assertTrue(test_1) + self.assertTrue(test_2) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/javascript_exceptions.qml b/sources/pyside6/tests/QtQml/javascript_exceptions.qml new file mode 100644 index 000000000..1ab0fa3bb --- /dev/null +++ b/sources/pyside6/tests/QtQml/javascript_exceptions.qml @@ -0,0 +1,28 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.0 +import QtQuick.Controls 2.0 +import JavaScriptExceptions 1.0 + +Rectangle { + JavaScriptExceptions { + id: obj + } + + Component.onCompleted: { + // Method call test + try { + obj.methodThrows(); + } catch(e) { + obj.passTest(1); + } + + // Property accessor test + try { + obj.propertyThrows; + } catch(e) { + obj.passTest(2); + } + } +} diff --git a/sources/pyside6/tests/QtQml/listproperty.py b/sources/pyside6/tests/QtQml/listproperty.py new file mode 100644 index 000000000..884600d29 --- /dev/null +++ b/sources/pyside6/tests/QtQml/listproperty.py @@ -0,0 +1,136 @@ +# 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 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 # noqa: E402 +init_test_paths(False) + +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): + pass + + +def dummyFunc(): + pass + + +@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 + type_check_error = False + + try: + ListProperty(QObject) + ListProperty(InheritsQObject) + except Exception: + type_check_error = True + + self.assertFalse(type_check_error) + + try: + ListProperty(int) + except TypeError: + type_check_error = True + + self.assertTrue(type_check_error) + + # Verify that method validation works properly + 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=dummyFunc) + ListProperty(QObject, count=dummyFunc, at=dummyFunc) + except Exception: + method_check_error = True + + self.assertFalse(method_check_error) + + try: + 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/qqmlapplicationengine.qml b/sources/pyside6/tests/QtQml/qqmlapplicationengine.qml new file mode 100644 index 000000000..77149ecdc --- /dev/null +++ b/sources/pyside6/tests/QtQml/qqmlapplicationengine.qml @@ -0,0 +1,23 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Window + +Window { + width: 300 + height: 200 + visible: true + + Item { + width: 200 + height: 60 + Text { + anchors { + verticalCenter: parent.verticalCenter; + horizontalCenter: parent.horizontalCenter; + } + text: "Text" + } + } +} diff --git a/sources/pyside6/tests/QtQml/qqmlapplicationengine_test.py b/sources/pyside6/tests/QtQml/qqmlapplicationengine_test.py new file mode 100644 index 000000000..ea54e9e25 --- /dev/null +++ b/sources/pyside6/tests/QtQml/qqmlapplicationengine_test.py @@ -0,0 +1,38 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +'''Test case for QQmlApplicationEngine''' + +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.timedqguiapplication import TimedQGuiApplication + +from PySide6.QtCore import QUrl, QObject, QTimer, Qt +from PySide6.QtQml import QQmlApplicationEngine + + +class TestQQmlApplicationEngine(TimedQGuiApplication): + + def testQQmlApplicationEngine(self): + engine = QQmlApplicationEngine() + + qml_file_path = Path(__file__).resolve().parent / "qqmlapplicationengine.qml" + + # PYSIDE-1736: load from a string. + engine.load(os.fspath(qml_file_path)) + rootObjects = engine.rootObjects() + self.assertTrue(rootObjects) + window = rootObjects[0] + window.setTitle("TestQQmlApplicationEngine") + QTimer.singleShot(100, window.close) + + +if __name__ == '__main__': + unittest.main() 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/qqmlincubator_incubateWhile.py b/sources/pyside6/tests/QtQml/qqmlincubator_incubateWhile.py new file mode 100644 index 000000000..12a73e398 --- /dev/null +++ b/sources/pyside6/tests/QtQml/qqmlincubator_incubateWhile.py @@ -0,0 +1,84 @@ +#!/usr/bin/python +# 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 gc +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 QObject, QUrl, Slot, QTimer +from PySide6.QtGui import QGuiApplication +from PySide6.QtQml import QQmlIncubationController, VolatileBool +from PySide6.QtQuick import QQuickView + + +class CustomIncubationController(QObject, QQmlIncubationController): + def __init__(self, test): + QObject.__init__(self) + QQmlIncubationController.__init__(self) + self.test = test + self.interrupted = False + + # Incubate every 50 milliseconds + self.startTimer(50) + self.incubationShouldContinue = VolatileBool(True) + self.test.assertEqual(self.incubationShouldContinue.get(), True) + + @Slot() + def interrupter(self): + if not self.interrupted: + self.interrupted = True + self.incubationShouldContinue.set(False) + self.test.assertEqual(self.incubationShouldContinue.get(), False) + QTimer.singleShot(0, QGuiApplication.instance().quit) + + def timerEvent(self, ev): + # Incubate items for 2000 milliseconds, or until the volatile bool is set to false. + self.incubateWhile(self.incubationShouldContinue, 2000) + + +class TestBug(unittest.TestCase): + def testIncubateWhileCall(self): + app = QGuiApplication(sys.argv) + view = QQuickView() + controller = CustomIncubationController(self) + view.engine().setIncubationController(controller) + view.setResizeMode(QQuickView.SizeRootObjectToView) + file = Path(__file__).resolve().parent / 'qqmlincubator_incubateWhile.qml' + self.assertTrue(file.is_file()) + view.setSource(QUrl.fromLocalFile(file)) + self.assertTrue(view.rootObject(), quickview_errorstring(view)) + view.show() + + root = view.rootObject() + # The QML code will issue an interrupt signal after half of its items are loaded. + root.shouldInterrupt.connect(controller.interrupter) + res = app.exec() + + itemsToCreate = root.property("itemsToCreate") + loadedItems = root.property("loadedItems") + self.assertEqual(loadedItems, itemsToCreate / 2) + + # Finish incubating the remaining items. + controller.incubateFor(1000) + loadedItems = root.property("loadedItems") + self.assertEqual(loadedItems, itemsToCreate) + + # Deleting the view before it goes out of scope is required to make sure all child QML + # instances are destroyed in the correct order. + del view + del app + # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion + gc.collect() + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/qqmlincubator_incubateWhile.qml b/sources/pyside6/tests/QtQml/qqmlincubator_incubateWhile.qml new file mode 100644 index 000000000..803dec128 --- /dev/null +++ b/sources/pyside6/tests/QtQml/qqmlincubator_incubateWhile.qml @@ -0,0 +1,42 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +import QtQuick 2.0 + + +Rectangle { + id: root + objectName: "theNicestRoot" + width: 400; height: 400 + + signal shouldInterrupt() + property int loadedItems: 0 + property int itemsToCreate: 10 + + Row { + anchors.centerIn: parent + spacing: 20 + + Rectangle { + id: initialRectangle + width: 10; height: 10 + color: "red" + } + + Repeater { + model: itemsToCreate + Loader { + id: loader + asynchronous: true + source: "qqmlincubator_incubateWhile_component.qml" + onLoaded: { + root.loadedItems += 1 + + // Interrupt incubation after half of the items are loaded. + if (root.loadedItems >= (itemsToCreate / 2)) { + root.shouldInterrupt() + } + } + } + } + } +} diff --git a/sources/pyside6/tests/QtQml/qqmlincubator_incubateWhile_component.qml b/sources/pyside6/tests/QtQml/qqmlincubator_incubateWhile_component.qml new file mode 100644 index 000000000..0f6693952 --- /dev/null +++ b/sources/pyside6/tests/QtQml/qqmlincubator_incubateWhile_component.qml @@ -0,0 +1,10 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.0 + +Rectangle { + id: root + width: 10; height: 10 + color: "yellow" +} diff --git a/sources/pyside6/tests/QtQml/qqmlnetwork_test.py b/sources/pyside6/tests/QtQml/qqmlnetwork_test.py new file mode 100644 index 000000000..abdb4529e --- /dev/null +++ b/sources/pyside6/tests/QtQml/qqmlnetwork_test.py @@ -0,0 +1,76 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +'''Test cases for QQmlNetwork''' + +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 QUrl, QTimer +from PySide6.QtGui import QGuiApplication, QWindow +from PySide6.QtQuick import QQuickView +from PySide6.QtQml import QQmlNetworkAccessManagerFactory +from PySide6.QtNetwork import QNetworkAccessManager + +from helper.helper import quickview_errorstring +from helper.timedqguiapplication import TimedQGuiApplication + + +request_created = False + + +def check_done(): + global request_created + if request_created: + windows = QGuiApplication.topLevelWindows() + if windows: + windows[0].close() + + +class CustomManager(QNetworkAccessManager): + """CustomManager (running in a different thread)""" + def createRequest(self, op, req, data=None): + global request_created + print(">> createRequest ", self, op, req.url(), data) + request_created = True + return QNetworkAccessManager.createRequest(self, op, req, data) + + +class CustomFactory(QQmlNetworkAccessManagerFactory): + def create(self, parent=None): + return CustomManager() + + +class TestQQmlNetworkFactory(TimedQGuiApplication): + def setUp(self): + super().setUp(timeout=2000) + + def testQQuickNetworkFactory(self): + view = QQuickView() + self.factory = CustomFactory() + view.engine().setNetworkAccessManagerFactory(self.factory) + + file = Path(__file__).resolve().parent / 'hw.qml' + self.assertTrue(file.is_file()) + url = QUrl.fromLocalFile(file) + + view.setSource(url) + self.assertTrue(view.rootObject(), quickview_errorstring(view)) + view.show() + + self.assertEqual(view.status(), QQuickView.Ready) + + timer = QTimer() + timer.timeout.connect(check_done) + timer.start(50) + self.app.exec() + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/qquickitem_grabToImage.py b/sources/pyside6/tests/QtQml/qquickitem_grabToImage.py new file mode 100644 index 000000000..25341b0b2 --- /dev/null +++ b/sources/pyside6/tests/QtQml/qquickitem_grabToImage.py @@ -0,0 +1,69 @@ +# 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 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.QtCore import QTimer, QUrl +from PySide6.QtGui import QColor +from PySide6.QtQuick import QQuickItem, QQuickView + + +class TestGrabToSharedPointerImage(TimedQGuiApplication): + def setUp(self): + super().setUp(1000) + + def testQQuickItemGrabToImageSharedPointer(self): + view = QQuickView() + file = Path(__file__).resolve().parent / 'qquickitem_grabToImage.qml' + self.assertTrue(file.is_file()) + view.setSource(QUrl.fromLocalFile(file)) + self.assertTrue(view.rootObject(), quickview_errorstring(view)) + view.show() + + # Get the QQuickItem objects for the blue Rectangle and the Image item. + root = view.rootObject() + blueRectangle = root.findChild(QQuickItem, "blueRectangle") + imageContainer = root.findChild(QQuickItem, "imageContainer") + + # Start the image grabbing. + grabResultSharedPtr = blueRectangle.grabToImage() + + # Implicit call of operator bool() of the smart pointer, to check that it holds + # a valid pointer. + self.assertTrue(grabResultSharedPtr) + + self.grabbedColor = None + + def onGrabReady(): + # Signal early exit. + QTimer.singleShot(50, self.app.quit) + + # Show the grabbed image in the QML Image item. + imageContainer.setProperty("source", grabResultSharedPtr.url()) + + # Wait for signal when grabbing is complete. + grabResultSharedPtr.ready.connect(onGrabReady) + self.app.exec() + + # Get the first pixel color of the grabbed image. + self.image = grabResultSharedPtr.image() + self.assertTrue(self.image) + self.grabbedColor = self.image.pixelColor(0, 0) + self.assertTrue(self.grabbedColor.isValid()) + + # Compare the grabbed color with the one we set in the rectangle. + blueColor = QColor("blue") + self.assertEqual(self.grabbedColor, blueColor) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/qquickitem_grabToImage.qml b/sources/pyside6/tests/QtQml/qquickitem_grabToImage.qml new file mode 100644 index 000000000..90235f078 --- /dev/null +++ b/sources/pyside6/tests/QtQml/qquickitem_grabToImage.qml @@ -0,0 +1,44 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.0 + +Item { + id: root + width: 600 + height: 600 + + Rectangle { + id: blue + objectName: "blueRectangle" + width: 200 + height: 200 + anchors.top: root.top + anchors.horizontalCenter: root.horizontalCenter + color: "blue" + } + + Text { + text: qsTr("Original blue rectangle") + anchors.left: blue.right + anchors.verticalCenter: blue.verticalCenter + } + + Image { + id: imageContainer + objectName: "imageContainer" + width: 200 + height: 200 + anchors.bottom: root.bottom + anchors.horizontalCenter: root.horizontalCenter + } + + Text { + text: qsTr("Image with the source URL set to the result of calling QQuickItem::grabToImage on the rectangle. If you see a second blue rectangle, that means it works.") + anchors.left: imageContainer.right + anchors.verticalCenter: imageContainer.verticalCenter + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + width: 200 + } + +} diff --git a/sources/pyside6/tests/QtQml/qquickview_test.py b/sources/pyside6/tests/QtQml/qquickview_test.py new file mode 100644 index 000000000..226509669 --- /dev/null +++ b/sources/pyside6/tests/QtQml/qquickview_test.py @@ -0,0 +1,87 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +'''Test cases for QQuickView''' + +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.QtCore import QUrl, QObject, Property, Slot, Signal +from PySide6.QtQml import QQmlEngine, QQmlContext +from PySide6.QtQuick import QQuickView + + +class MyObject(QObject): + titleChanged = Signal() + + def __init__(self, text, parent=None): + QObject.__init__(self, parent) + self._text = text + + def getText(self): + return self._text + + @Slot(str) + def qmlText(self, text): + self._qmlText = text + + title = Property(str, getText, notify=titleChanged) + + +class TestQQuickView(TimedQGuiApplication): + + def testQQuickViewList(self): + view = QQuickView() + + dataList = ["Item 1", "Item 2", "Item 3", "Item 4"] + + view.setInitialProperties({"model": dataList}) + + file = Path(__file__).resolve().parent / 'view.qml' + self.assertTrue(file.is_file()) + url = QUrl.fromLocalFile(file) + view.setSource(url) + self.assertTrue(view.rootObject(), quickview_errorstring(view)) + view.show() + + self.assertEqual(view.status(), QQuickView.Ready) + rootObject = view.rootObject() + self.assertTrue(rootObject) + context = QQmlEngine.contextForObject(rootObject) + self.assertTrue(context) + self.assertTrue(context.engine()) + + test_context = QQmlContext(context) # Context properties, PYSIDE-1921 + prop_pair = QQmlContext.PropertyPair() + prop_pair.name = "testProperty" + prop_pair.value = 42 + test_context.setContextProperties([prop_pair]) + self.assertTrue(test_context.contextProperty("testProperty"), 42) + + def testModelExport(self): + view = QQuickView() + dataList = [MyObject("Item 1"), MyObject("Item 2"), MyObject("Item 3"), MyObject("Item 4")] + + view.setInitialProperties({"model": dataList}) + + file = Path(__file__).resolve().parent / 'viewmodel.qml' + self.assertTrue(file.is_file()) + url = QUrl.fromLocalFile(file) + view.setSource(url) + self.assertTrue(view.rootObject(), quickview_errorstring(view)) + view.show() + + self.assertEqual(view.status(), QQuickView.Ready) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/registerattached.py b/sources/pyside6/tests/QtQml/registerattached.py new file mode 100644 index 000000000..dd300dc89 --- /dev/null +++ b/sources/pyside6/tests/QtQml/registerattached.py @@ -0,0 +1,101 @@ +# 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 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 (QCoreApplication, QUrl, QObject, Property) +from PySide6.QtQml import (QQmlComponent, QQmlEngine, QmlAnonymous, + QmlAttached, QmlElement, ListProperty, + qmlAttachedPropertiesObject) + + +QML_IMPORT_NAME = "TestLayouts" +QML_IMPORT_MAJOR_VERSION = 1 + + +EXPECTED_MARGINS = [10, 20] + + +def component_error(component): + result = "" + for e in component.errors(): + if result: + result += "\n" + result += str(e) + return result + + +@QmlAnonymous +class TestLayoutAttached(QObject): + + def __init__(self, parent=None): + super().__init__(parent) + self._leftMargin = 0 + + @Property(int) + def leftMargin(self): + return self._leftMargin + + @leftMargin.setter + def leftMargin(self, m): + self._leftMargin = m + + +@QmlElement +class TestWidget(QObject): + + def __init__(self, parent=None): + super().__init__(parent) + + +@QmlElement +@QmlAttached(TestLayoutAttached) +class TestLayout(QObject): + + def __init__(self, parent=None): + super().__init__(parent) + self._widgets = [] + + def widget(self, n): + return self._widgets[n] + + def widgetCount(self): + return len(self._widgets) + + def addWidget(self, w): + self._widgets.append(w) + + @staticmethod + def qmlAttachedProperties(self, o): + return TestLayoutAttached(o) + + widgets = ListProperty(TestWidget, addWidget) + + +class TestQmlAttached(unittest.TestCase): + def testIt(self): + app = QCoreApplication(sys.argv) + file = Path(__file__).resolve().parent / 'registerattached.qml' + url = QUrl.fromLocalFile(file) + engine = QQmlEngine() + component = QQmlComponent(engine, url) + layout = component.create() + self.assertTrue(layout, component_error(component)) + + actual_margins = [] + for i in range(layout.widgetCount()): + w = layout.widget(i) + a = qmlAttachedPropertiesObject(TestLayout, w, False) + actual_margins.append(a.leftMargin) + self.assertEqual(EXPECTED_MARGINS, actual_margins) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/registerattached.qml b/sources/pyside6/tests/QtQml/registerattached.qml new file mode 100644 index 000000000..7ae8730bd --- /dev/null +++ b/sources/pyside6/tests/QtQml/registerattached.qml @@ -0,0 +1,20 @@ +// 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 TestLayouts + +TestLayout { + id: layout + + widgets: [ + TestWidget { + id: widget1 + TestLayout.leftMargin: 10 + }, + + TestWidget { + id: widget2 + TestLayout.leftMargin: 20 + } + ] +} diff --git a/sources/pyside6/tests/QtQml/registerextended.py b/sources/pyside6/tests/QtQml/registerextended.py new file mode 100644 index 000000000..b87b5aaaf --- /dev/null +++ b/sources/pyside6/tests/QtQml/registerextended.py @@ -0,0 +1,74 @@ +# 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 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 (QCoreApplication, QUrl, QObject, + Property) +from PySide6.QtQml import (QQmlComponent, QQmlEngine, QmlExtended, + QmlElement) + + +"""Test for the QmlExtended decorator. Extends a class TestWidget + by a property leftMargin through a TestExtension and verifies the setting.""" + + +QML_IMPORT_NAME = "TestExtension" +QML_IMPORT_MAJOR_VERSION = 1 + + +def component_error(component): + result = "" + for e in component.errors(): + if result: + result += "\n" + result += str(e) + return result + + +class TestExtension(QObject): + + def __init__(self, parent=None): + super().__init__(parent) + self._leftMargin = 0 + + @Property(int) + def leftMargin(self): + return self._leftMargin + + @leftMargin.setter + def leftMargin(self, m): + self._leftMargin = m + + +@QmlElement +@QmlExtended(TestExtension) +class TestWidget(QObject): + + def __init__(self, parent=None): + super().__init__(parent) + + +class TestQmlExtended(unittest.TestCase): + def testIt(self): + app = QCoreApplication(sys.argv) + file = Path(__file__).resolve().parent / 'registerextended.qml' + url = QUrl.fromLocalFile(file) + engine = QQmlEngine() + component = QQmlComponent(engine, url) + widget = component.create() + self.assertTrue(widget, component_error(component)) + extension = widget.findChild(TestExtension) + self.assertTrue(extension) + self.assertEqual(extension.leftMargin, 10) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/registerextended.qml b/sources/pyside6/tests/QtQml/registerextended.qml new file mode 100644 index 000000000..74095f83f --- /dev/null +++ b/sources/pyside6/tests/QtQml/registerextended.qml @@ -0,0 +1,9 @@ +// 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 TestExtension + +TestWidget { + id: widget1 + leftMargin: 10 +} diff --git a/sources/pyside6/tests/QtQml/registerforeign.py b/sources/pyside6/tests/QtQml/registerforeign.py new file mode 100644 index 000000000..d9a982d95 --- /dev/null +++ b/sources/pyside6/tests/QtQml/registerforeign.py @@ -0,0 +1,52 @@ +# 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 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 qmlcomponent_errorstring +from helper.timedqguiapplication import TimedQGuiApplication + +from PySide6.QtCore import Property, QObject, QUrl, qVersion +from PySide6.QtGui import QGuiApplication, QRasterWindow +from PySide6.QtQml import (QmlNamedElement, QmlForeign, QQmlEngine, + QQmlComponent) + + +"""Test the QmlForeign decorator, letting the QQmlEngine create a QRasterWindow.""" + + +QML_IMPORT_NAME = "Foreign" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlNamedElement("QRasterWindow") +@QmlForeign(QRasterWindow) +class RasterWindowForeign(QObject): + def __init__(self, parent=None): + super().__init__(parent) + + +class TestQmlForeign(TimedQGuiApplication): + + def testIt(self): + engine = QQmlEngine() + file = Path(__file__).resolve().parent / 'registerforeign.qml' + self.assertTrue(file.is_file()) + component = QQmlComponent(engine, QUrl.fromLocalFile(file)) + window = component.create() + self.assertTrue(window, qmlcomponent_errorstring(component)) + self.assertEqual(type(window), QRasterWindow) + window.setTitle(f"Qt {qVersion()}") + window.show() + self.app.exec() + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/registerforeign.qml b/sources/pyside6/tests/QtQml/registerforeign.qml new file mode 100644 index 000000000..0537abb7c --- /dev/null +++ b/sources/pyside6/tests/QtQml/registerforeign.qml @@ -0,0 +1,8 @@ +// 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 Foreign + +QRasterWindow { + id: rasterWindow +} diff --git a/sources/pyside6/tests/QtQml/registerparserstatus.py b/sources/pyside6/tests/QtQml/registerparserstatus.py new file mode 100644 index 000000000..bbcc14635 --- /dev/null +++ b/sources/pyside6/tests/QtQml/registerparserstatus.py @@ -0,0 +1,60 @@ +# 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 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 (QCoreApplication, QUrl) +from PySide6.QtQml import (QQmlComponent, QQmlEngine, + QmlElement, QPyQmlParserStatus) + + +QML_IMPORT_NAME = "ParserStatus" +QML_IMPORT_MAJOR_VERSION = 1 + + +def component_error(component): + result = "" + for e in component.errors(): + if result: + result += "\n" + result += str(e) + return result + + +@QmlElement +class TestItem(QPyQmlParserStatus): + + def __init__(self, parent=None): + super().__init__(parent) + self.component_complete_called = False + self.class_begin_called = False + + def componentComplete(self): + self.component_complete_called = True + + def classBegin(self): + self.class_begin_called = True + + +class TestQmlAttached(unittest.TestCase): + def testIt(self): + app = QCoreApplication(sys.argv) + file = Path(__file__).resolve().parent / 'registerparserstatus.qml' + url = QUrl.fromLocalFile(file) + engine = QQmlEngine() + component = QQmlComponent(engine, url) + item = component.create() + self.assertTrue(item, component_error(component)) + self.assertTrue(item.component_complete_called) + self.assertTrue(item.class_begin_called) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/registerparserstatus.qml b/sources/pyside6/tests/QtQml/registerparserstatus.qml new file mode 100644 index 000000000..a39f03227 --- /dev/null +++ b/sources/pyside6/tests/QtQml/registerparserstatus.qml @@ -0,0 +1,8 @@ +// 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 ParserStatus + +TestItem { + id: item +} diff --git a/sources/pyside6/tests/QtQml/registerqmlfile.py b/sources/pyside6/tests/QtQml/registerqmlfile.py new file mode 100644 index 000000000..335652e5d --- /dev/null +++ b/sources/pyside6/tests/QtQml/registerqmlfile.py @@ -0,0 +1,30 @@ +# 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 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 QDir, QUrl +from PySide6.QtGui import QGuiApplication +from PySide6.QtQml import qmlRegisterType + + +class TestQmlSupport(unittest.TestCase): + + def testIt(self): + app = QGuiApplication([]) + + file = os.fspath(Path(__file__).resolve().parent / 'ModuleType.qml') + url = QUrl.fromLocalFile(QDir.fromNativeSeparators(file)) + result = qmlRegisterType(url, "CustomModule", 1, 0, "ModuleType") + self.assertTrue(result != -1) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/registersingletontype.py b/sources/pyside6/tests/QtQml/registersingletontype.py new file mode 100644 index 000000000..6beca1131 --- /dev/null +++ b/sources/pyside6/tests/QtQml/registersingletontype.py @@ -0,0 +1,152 @@ +# 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 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 Property, Signal, QTimer, QUrl, QObject, Slot +from PySide6.QtGui import QGuiApplication +from PySide6.QtQml import (qmlRegisterSingletonType, qmlRegisterSingletonInstance, + QmlElement, QmlSingleton, QJSValue) +from PySide6.QtQuick import QQuickView + + +URI = "Singletons" + + +finalResult = 0 +qObjectQmlTypeId = 0 + + +class SingletonQObject(QObject): + def __init__(self, parent=None): + QObject.__init__(self, parent) + self._data = 100 + + def getData(self): + return self._data + + def setData(self, data): + global finalResult + finalResult = self._data = data + + data = Property(int, getData, setData) + + +def singletonQObjectCallback(engine): + obj = SingletonQObject() + obj.setData(50) + return obj + + +def singletonQJSValueCallback(engine): + return engine.evaluate("new Object({data: 50})") + + +QML_IMPORT_NAME = URI +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +@QmlSingleton +class DecoratedSingletonQObject(QObject): + def __init__(self, parent=None): + super().__init__(parent) + self._data = 200 + + def getData(self): + return self._data + + def setData(self, data): + self._data = data + + 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([]) + + qObjectQmlTypeId = qmlRegisterSingletonType(SingletonQObject, URI, 1, 0, + 'SingletonQObjectNoCallback') + qmlRegisterSingletonType(SingletonQObject, URI, 1, 0, 'SingletonQObjectCallback', + singletonQObjectCallback) + + qmlRegisterSingletonType(URI, 1, 0, 'SingletonQJSValue', singletonQJSValueCallback) + + # Accepts only QObject derived types + l = [1, 2] + with self.assertRaises(TypeError): + qmlRegisterSingletonInstance(SingletonQObject, URI, 1, 0, 'SingletonInstance', l) + + # Modify value on the instance + s = SingletonQObject() + s.setData(99) + qmlRegisterSingletonInstance(SingletonQObject, URI, 1, 0, 'SingletonInstance', s) + + 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.testSlot) + app.exec() + 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() diff --git a/sources/pyside6/tests/QtQml/registersingletontype.qml b/sources/pyside6/tests/QtQml/registersingletontype.qml new file mode 100644 index 000000000..31ca7fe4d --- /dev/null +++ b/sources/pyside6/tests/QtQml/registersingletontype.qml @@ -0,0 +1,14 @@ +// 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 QtQuick 2.0 +import Singletons 1.0 + +Item { + Component.onCompleted: { + SingletonQObjectCallback.data += SingletonQObjectNoCallback.data + + SingletonQJSValue.data + + SingletonInstance.data + + DecoratedSingletonQObject.data + DecoratedSingletonWithCreate.data; + } +} diff --git a/sources/pyside6/tests/QtQml/registertype.py b/sources/pyside6/tests/QtQml/registertype.py new file mode 100644 index 000000000..6c9874f32 --- /dev/null +++ b/sources/pyside6/tests/QtQml/registertype.py @@ -0,0 +1,116 @@ +# 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 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 Property, QObject, QTimer, QUrl +from PySide6.QtGui import QGuiApplication, QPen, QColor, QPainter +from PySide6.QtQml import (qjsEngine, qmlContext, qmlEngine, qmlRegisterType, + ListProperty, QmlElement, QmlNamedElement) +from PySide6.QtQuick import QQuickView, QQuickItem, QQuickPaintedItem + + +QML_IMPORT_NAME = "Charts" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class PieSlice (QQuickPaintedItem): + def __init__(self, parent=None): + QQuickPaintedItem.__init__(self, parent) + self._color = QColor() + self._fromAngle = 0 + self._angleSpan = 0 + + def getColor(self): + return self._color + + def setColor(self, value): + self._color = value + + def getFromAngle(self): + return self._angle + + def setFromAngle(self, value): + self._fromAngle = value + + def getAngleSpan(self): + return self._angleSpan + + def setAngleSpan(self, value): + self._angleSpan = value + + color = Property(QColor, getColor, setColor) + fromAngle = Property(int, getFromAngle, setFromAngle) + angleSpan = Property(int, getAngleSpan, setAngleSpan) + + def paint(self, painter): + global paintCalled + pen = QPen(self._color, 2) + painter.setPen(pen) + painter.setRenderHints(QPainter.Antialiasing, True) + painter.drawPie(self.boundingRect(), self._fromAngle * 16, self._angleSpan * 16) + paintCalled = True + + +@QmlNamedElement("PieChart") +class PieChartOriginalName(QQuickItem): + def __init__(self, parent=None): + QQuickItem.__init__(self, parent) + self._name = '' + self._slices = [] + + def getName(self): + return self._name + + def setName(self, value): + self._name = value + + name = Property(str, getName, setName) + + def appendSlice(self, _slice): + global appendCalled + _slice.setParentItem(self) + self._slices.append(_slice) + appendCalled = True + + slices = ListProperty(PieSlice, append=appendSlice) + + +appendCalled = False +paintCalled = False + + +class TestQmlSupport(unittest.TestCase): + + def testIt(self): + app = QGuiApplication([]) + + view = QQuickView() + file = Path(__file__).resolve().parent / 'registertype.qml' + self.assertTrue(file.is_file()) + view.setSource(QUrl.fromLocalFile(file)) + root_object = view.rootObject() + self.assertTrue(root_object, quickview_errorstring(view)) + self.assertTrue(qjsEngine(root_object)) + self.assertEqual(qmlEngine(root_object), view.engine()) + self.assertTrue(qmlContext(root_object)) + + view.show() + QTimer.singleShot(250, view.close) + app.exec() + self.assertTrue(appendCalled) + self.assertTrue(paintCalled) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/registertype.qml b/sources/pyside6/tests/QtQml/registertype.qml new file mode 100644 index 000000000..3be2f9f04 --- /dev/null +++ b/sources/pyside6/tests/QtQml/registertype.qml @@ -0,0 +1,32 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.0 +import Charts 1.0 + +Item { + width: 300; height: 200 + + PieChart { + anchors.centerIn: parent + width: 100; height: 100 + + slices: [ + PieSlice { + anchors.fill: parent + color: "red" + fromAngle: 0; angleSpan: 110 + }, + PieSlice { + anchors.fill: parent + color: "black" + fromAngle: 110; angleSpan: 50 + }, + PieSlice { + anchors.fill: parent + color: "blue" + fromAngle: 160; angleSpan: 100 + } + ] + } +} diff --git a/sources/pyside6/tests/QtQml/registeruncreatable.qml b/sources/pyside6/tests/QtQml/registeruncreatable.qml new file mode 100644 index 000000000..b121c014d --- /dev/null +++ b/sources/pyside6/tests/QtQml/registeruncreatable.qml @@ -0,0 +1,13 @@ +// 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 QtQuick 2.0 +import Charts 1.0 + +Item { + width: 300; height: 200 + + Uncreatable { + name : 'uncreatable' + } +} diff --git a/sources/pyside6/tests/QtQml/registeruncreatabletype.py b/sources/pyside6/tests/QtQml/registeruncreatabletype.py new file mode 100644 index 000000000..3a4df69f6 --- /dev/null +++ b/sources/pyside6/tests/QtQml/registeruncreatabletype.py @@ -0,0 +1,62 @@ +# 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 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 qmlcomponent_errorstring + +from PySide6.QtCore import Property, QObject, QUrl +from PySide6.QtGui import QGuiApplication +from PySide6.QtQml import QmlElement, QmlUncreatable, QQmlEngine, QQmlComponent + +noCreationReason = 'Cannot create an item of type: Uncreatable (expected)' + +QML_IMPORT_NAME = "Charts" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +@QmlUncreatable(noCreationReason) +class Uncreatable(QObject): + def __init__(self, parent=None): + QObject.__init__(self, parent) + self._name = 'uncreatable' + + def getName(self): + return self._name + + def setName(self, value): + self._name = value + + name = Property(str, getName, setName) + + +class TestQmlSupport(unittest.TestCase): + + def testIt(self): + app = QGuiApplication([]) + + engine = QQmlEngine() + file = Path(__file__).resolve().parent / 'registeruncreatable.qml' + self.assertTrue(file.is_file()) + component = QQmlComponent(engine, QUrl.fromLocalFile(file)) + + # Check that the uncreatable item produces the correct error + self.assertEqual(component.status(), QQmlComponent.Error) + errorFound = False + for e in component.errors(): + if noCreationReason in e.toString(): + errorFound = True + break + self.assertTrue(errorFound) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/signal_arguments.py b/sources/pyside6/tests/QtQml/signal_arguments.py new file mode 100644 index 000000000..f5b0f8bd3 --- /dev/null +++ b/sources/pyside6/tests/QtQml/signal_arguments.py @@ -0,0 +1,61 @@ +# 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 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, QTimer, Property +from PySide6.QtQml import QmlElement + +QML_IMPORT_NAME = "test.Obj" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class Obj(QObject): + def __init__(self): + super().__init__() + self.value = 0 + + sumResult = Signal(int, name="sumResult", arguments=['sum']) + + @Slot(int, int) + def sum(self, arg1, arg2): + self.sumResult.emit(arg1 + arg2) + + @Slot(str) + def sendValue(self, s): + self.value = int(s) + + +class TestConnectionWithQml(TimedQGuiApplication): + + def testSignalArguments(self): + view = QQuickView() + obj = Obj() + + view.setInitialProperties({"o": obj}) + file = Path(__file__).resolve().parent / 'signal_arguments.qml' + self.assertTrue(file.is_file()) + view.setSource(QUrl.fromLocalFile(file)) + root = view.rootObject() + self.assertTrue(root, quickview_errorstring(view)) + button = root.findChild(QObject, "button") + self.assertTrue(button) + view.show() + button.clicked.emit() + self.assertEqual(obj.value, 42) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/signal_arguments.qml b/sources/pyside6/tests/QtQml/signal_arguments.qml new file mode 100644 index 000000000..dbc991c77 --- /dev/null +++ b/sources/pyside6/tests/QtQml/signal_arguments.qml @@ -0,0 +1,36 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + +import QtQuick 2.5 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.2 +import test.Obj 1.0 + +Rectangle { + visible: true + required property Obj o + GridLayout { + Button { + id: button + objectName: "button" + text: "sum!" + onClicked: { + o.sum(40, 2) + } + } + Text { + id: sumResultText + } + } + Connections { + target: o + function onSumResult(sum) { + // set the value on the Qml side + sumResultText.text = sum + // set internal Python value from the already + // modified value + o.sendValue(sumResultText.text) + } + } +} 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) + } + } +} diff --git a/sources/pyside6/tests/QtQml/view.qml b/sources/pyside6/tests/QtQml/view.qml new file mode 100644 index 000000000..8557271c2 --- /dev/null +++ b/sources/pyside6/tests/QtQml/view.qml @@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.0 + +ListView { + width: 100 + height: 100 + anchors.fill: parent + model: myModel + delegate: Component { + Rectangle { + required property string modelData + height: 25 + width: 100 + Text { text: modelData } + } + } +} diff --git a/sources/pyside6/tests/QtQml/viewmodel.qml b/sources/pyside6/tests/QtQml/viewmodel.qml new file mode 100644 index 000000000..33db6072e --- /dev/null +++ b/sources/pyside6/tests/QtQml/viewmodel.qml @@ -0,0 +1,16 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.0 + +ListView { + width: 100; height: 100 + anchors.fill: parent + + delegate: Rectangle { + height: 25 + width: 100 + Text { text: model.modelData.title } + } +} + |