// Copyright (C) 2025 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include #include #include #include #include class tst_qqmlsynchronizer : public QQmlDataTest { Q_OBJECT public: tst_qqmlsynchronizer(); private slots: void basics_data(); void basics(); void ignored(); void valueTypes(); void modelObject(); void warnings(); }; class BindablePoint : public QObject { Q_OBJECT QML_ELEMENT Q_PROPERTY(QPointF point BINDABLE bindablePoint READ default WRITE default) public: BindablePoint(QObject *parent = nullptr) : QObject(parent), m_point(QPointF(101, 102)) {} QBindable bindablePoint() { return QBindable(&m_point); } private: QProperty m_point; }; tst_qqmlsynchronizer::tst_qqmlsynchronizer() : QQmlDataTest(QT_QMLTEST_DATADIR) { qmlRegisterTypesAndRevisions("Test", 1); } void tst_qqmlsynchronizer::basics_data() { QTest::addColumn("url"); QTest::addColumn("initializesOther"); QTest::addRow("onVsAlias") << testFileUrl("onVsAlias.qml") << true; QTest::addRow("onVsTargetProperty") << testFileUrl("onVsTargetProperty.qml") << true; QTest::addRow("aliasVsTargetProperty") << testFileUrl("aliasVsTargetProperty.qml") << false; QTest::addRow("onVsSourceProperty") << testFileUrl("onVsSourceProperty.qml") << false; QTest::addRow("aliasVsSourceProperty") << testFileUrl("aliasVsSourceProperty.qml") << true; } void tst_qqmlsynchronizer::basics() { QFETCH(QUrl, url); QFETCH(bool, initializesOther); QQmlEngine engine; QQmlComponent c(&engine, url); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer o(c.create()); QVERIFY(!o.isNull()); QObject *other = o->property("other").value(); QVERIFY(other); QCOMPARE(o->objectName(), initializesOther ? "bar" : "foo"); QCOMPARE(other->objectName(), initializesOther ? "bar" : "foo"); o->setObjectName("baz"); QCOMPARE(o->objectName(), "baz"); QCOMPARE(other->objectName(), "baz"); other->setObjectName("foo"); QCOMPARE(o->objectName(), "foo"); QCOMPARE(other->objectName(), "foo"); QCOMPARE(o->property("unbindable").toString(), initializesOther ? "a" : "b"); QCOMPARE(other->property("unbindable").toString(), initializesOther ? "a" : "b"); o->setProperty("unbindable", QStringLiteral("c")); QCOMPARE(o->property("unbindable").toString(), "c"); QCOMPARE(other->property("unbindable").toString(), "c"); other->setProperty("unbindable", QStringLiteral("d")); QCOMPARE(o->property("unbindable").toString(), "d"); QCOMPARE(other->property("unbindable").toString(), "d"); QCOMPARE(o->property("count").toInt(), initializesOther ? 12 : 13); QCOMPARE(other->property("count").toDouble(), 13.5); int countBounces = initializesOther ? 1 : 0; QCOMPARE(o->property("countBounces").toInt(), countBounces); QCOMPARE(o->property("countIgnores").toInt(), 0); o->setProperty("count", 17); ++countBounces; QCOMPARE(o->property("count").toInt(), 17); QCOMPARE(other->property("count").toDouble(), 13.5); QCOMPARE(o->property("countBounces").toInt(), countBounces); QCOMPARE(o->property("countIgnores").toInt(), 0); other->setProperty("count", 18); QCOMPARE(o->property("count").toInt(), 13); QCOMPARE(other->property("count").toDouble(), 13.5); QCOMPARE(o->property("countBounces").toInt(), countBounces); QCOMPARE(o->property("countIgnores").toInt(), 0); } void tst_qqmlsynchronizer::ignored() { QQmlEngine engine; QQmlComponent c(&engine, testFileUrl("ignored.qml")); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer o(c.create()); QVERIFY(!o.isNull()); QObject *other = o->property("other").value(); QVERIFY(other); QCOMPARE(o->property("count").toInt(), 12); QCOMPARE(other->property("count").toDouble(), 12); QCOMPARE(o->property("countBounces").toInt(), 0); QCOMPARE(o->property("countIgnores").toInt(), 0); other->setProperty("count", 12.4); QCOMPARE(o->property("count").toInt(), 12); QCOMPARE(o->property("countBounces").toInt(), 0); QCOMPARE(o->property("countIgnores").toInt(), 1); } void tst_qqmlsynchronizer::valueTypes() { QQmlEngine engine; QQmlComponent c(&engine, testFileUrl("valueTypes.qml")); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer o(c.create()); QVERIFY(!o.isNull()); QObject *other = o->property("other").value(); QVERIFY(other); QCOMPARE(o->property("point").value(), QPointF(101, 102)); QCOMPARE(other->property("point").value(), QPointF(12, 11)); QCOMPARE(o->objectName(), "11"); QCOMPARE(other->objectName(), "102"); QCOMPARE(other->property("count"), 101); QCOMPARE(o->property("count"), 12); // Since point can only be summarily signaled, this updates the other synchronizer, too. o->setProperty("count", 77); QCOMPARE(other->property("point").value(), QPointF(77, 11)); QCOMPARE(o->objectName(), "11"); other->setProperty("point", QPointF(97, 98)); QCOMPARE(o->property("count"), 97); QCOMPARE(o->objectName(), "98"); o->setObjectName("54"); QCOMPARE(other->property("point").value(), QPointF(97, 54)); o->setProperty("point", QPointF(33.25, 34.8)); QCOMPARE(other->property("count").toDouble(), 33.25); QCOMPARE(other->objectName(), "34.8"); other->setProperty("count", 55.5); QCOMPARE(o->property("point").value(), QPointF(55.5, 34.8)); QCOMPARE(other->objectName(), "34.8"); other->setObjectName("77.2"); QCOMPARE(o->property("point").value(), QPointF(55.5, 77.2)); } void tst_qqmlsynchronizer::modelObject() { QQmlEngine engine; QQmlComponent c(&engine, testFileUrl("modelObject.qml")); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer o(c.create()); QVERIFY(!o.isNull()); QQmlDelegateModel *delegateModel = qobject_cast(o.data()); QVERIFY(delegateModel); QObject *delegate = delegateModel->object(0); QVERIFY(delegate); QAbstractListModel *model = delegateModel->property("listModel").value(); QVERIFY(model); const auto roleNames = model->roleNames(); int role = -1; for (auto it = roleNames.begin(), end = roleNames.end(); it != end; ++it) { if (it.value() == "a") { role = it.key(); break; } } const QModelIndex modelIndex = model->index(0); QCOMPARE(delegate->objectName(), "foo"); QCOMPARE(model->data(modelIndex, role), "foo"); model->setData(modelIndex, "c", role); QCOMPARE(model->data(modelIndex, role), "c"); QCOMPARE(delegate->objectName(), "c"); delegate->setObjectName("d"); QCOMPARE(model->data(modelIndex, role), "d"); } void tst_qqmlsynchronizer::warnings() { QQmlEngine engine; const QUrl url = testFileUrl("warnings.qml"); QQmlComponent c(&engine, url); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QTest::ignoreMessage( QtWarningMsg, qPrintable(url.toString() + ":31:44: QML Synchronizer: Target object has no property called doesNotExist")); QTest::ignoreMessage( QtWarningMsg, qPrintable(url.toString() + ":37:39: QML Synchronizer: Member doThings of target object is a function")); QScopedPointer o(c.create()); QVERIFY(!o.isNull()); QTest::ignoreMessage( QtWarningMsg, qPrintable(url.toString() + ":25:43: QML Synchronizer: Cannot convert from QPointF to QObject*")); o->setProperty("point", QPointF(13, 14)); } QTEST_MAIN(tst_qqmlsynchronizer) #include "tst_qqmlsynchronizer.moc"