Revision control

Copy as Markdown

// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtQuickTestUtils/private/qmlutils_p.h>
#include <QtQmlModels/private/qqmldelegatemodel_p.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQml/qqmlengine.h>
#include <QtTest/qtest.h>
#include <QtCore/qproperty.h>
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<QPointF> bindablePoint() { return QBindable<QPointF>(&m_point); }
private:
QProperty<QPointF> m_point;
};
tst_qqmlsynchronizer::tst_qqmlsynchronizer()
: QQmlDataTest(QT_QMLTEST_DATADIR)
{
qmlRegisterTypesAndRevisions<BindablePoint>("Test", 1);
}
void tst_qqmlsynchronizer::basics_data()
{
QTest::addColumn<QUrl>("url");
QTest::addColumn<bool>("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<QObject> o(c.create());
QVERIFY(!o.isNull());
QObject *other = o->property("other").value<QObject *>();
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<QObject> o(c.create());
QVERIFY(!o.isNull());
QObject *other = o->property("other").value<QObject *>();
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<QObject> o(c.create());
QVERIFY(!o.isNull());
QObject *other = o->property("other").value<QObject *>();
QVERIFY(other);
QCOMPARE(o->property("point").value<QPointF>(), QPointF(101, 102));
QCOMPARE(other->property("point").value<QPointF>(), 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>(), 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>(), 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>(), QPointF(55.5, 34.8));
QCOMPARE(other->objectName(), "34.8");
other->setObjectName("77.2");
QCOMPARE(o->property("point").value<QPointF>(), QPointF(55.5, 77.2));
}
void tst_qqmlsynchronizer::modelObject()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("modelObject.qml"));
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
QScopedPointer<QObject> o(c.create());
QVERIFY(!o.isNull());
QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel *>(o.data());
QVERIFY(delegateModel);
QObject *delegate = delegateModel->object(0);
QVERIFY(delegate);
QAbstractListModel *model = delegateModel->property("listModel").value<QAbstractListModel *>();
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<QObject> 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"