summaryrefslogtreecommitdiffstats
path: root/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp')
-rw-r--r--tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp1044
1 files changed, 894 insertions, 150 deletions
diff --git a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp
index b1f5a780a6..cc7edb8bf2 100644
--- a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp
+++ b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp
@@ -1,47 +1,23 @@
-/****************************************************************************
-**
-** Copyright (C) 2020 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QObject>
#include <QSignalSpy>
#include <qtest.h>
#include <qproperty.h>
#include <private/qproperty_p.h>
+#include <private/qobject_p.h>
-#if __has_include(<source_location>) && __cplusplus >= 202002L && !defined(Q_CLANG_QDOC)
+#if __has_include(<source_location>) && __cplusplus >= 202002L && !defined(Q_QDOC)
#include <source_location>
#define QT_SOURCE_LOCATION_NAMESPACE std
-#elif __has_include(<experimental/source_location>) && __cplusplus >= 201703L && !defined(Q_CLANG_QDOC)
+#elif __has_include(<experimental/source_location>) && !defined(Q_QDOC)
#include <experimental/source_location>
#define QT_SOURCE_LOCATION_NAMESPACE std::experimental
#endif
using namespace QtPrivate;
-
+using namespace Qt::StringLiterals;
struct DtorCounter {
static inline int counter = 0;
@@ -53,10 +29,12 @@ class tst_QProperty : public QObject
{
Q_OBJECT
private slots:
+ void inheritQUntypedPropertyData();
void functorBinding();
void basicDependencies();
void multipleDependencies();
void bindingWithDeletedDependency();
+ void dependencyChangeDuringDestruction();
void recursiveDependency();
void bindingAfterUse();
void bindingFunctionDtorCalled();
@@ -83,7 +61,6 @@ private slots:
void genericPropertyBindingBool();
void setBindingFunctor();
void multipleObservers();
- void propertyAlias();
void arrowAndStarOperator();
void notifiedProperty();
void typeNoOperatorEqual();
@@ -91,26 +68,103 @@ private slots:
void quntypedBindableApi();
void readonlyConstQBindable();
void qobjectBindableManualNotify();
+ void qobjectBindableReallocatedBindingStorage();
void qobjectBindableSignalTakingNewValue();
void testNewStuff();
void qobjectObservers();
void compatBindings();
void metaProperty();
- void aliasOnMetaProperty();
void modifyObserverListWhileIterating();
+ void noDoubleCapture();
void compatPropertyNoDobuleNotification();
void compatPropertySignals();
void noFakeDependencies();
+#if QT_CONFIG(thread)
+ void threadSafety();
+ void threadSafety2();
+#endif // QT_CONFIG(thread)
void bindablePropertyWithInitialization();
void noDoubleNotification();
void groupedNotifications();
void groupedNotificationConsistency();
+ void bindingGroupMovingBindingData();
+ void bindingGroupBindingDeleted();
+ void uninstalledBindingDoesNotEvaluate();
+
+ void notify();
+
+ void bindableInterfaceOfCompatPropertyUsesSetter();
+
+ void selfBindingShouldNotCrash();
+
+ void qpropertyAlias();
+ void scheduleNotify();
+
+ void notifyAfterAllDepsGone();
+
+ void propertyAdaptorBinding();
+ void propertyUpdateViaSignaledProperty();
+
+ void derefFromObserver();
};
+namespace {
+template <class T>
+constexpr auto isDerivedFromQUntypedPropertyData = std::is_base_of_v<QUntypedPropertyData, T>;
+
+template <typename Property>
+constexpr auto isDerivedFromQUntypedPropertyDataFunc(const Property &property)
+{
+ Q_UNUSED(property);
+ return isDerivedFromQUntypedPropertyData<Property>;
+}
+
+template <typename Property>
+constexpr auto isDerivedFromQUntypedPropertyDataFunc(Property *property)
+{
+ Q_UNUSED(property);
+ return isDerivedFromQUntypedPropertyData<Property>;
+}
+} // namespace
+
+void tst_QProperty::inheritQUntypedPropertyData()
+{
+ class propertyPublic : public QUntypedPropertyData
+ {
+ };
+ class propertyPrivate : private QUntypedPropertyData
+ {
+ };
+
+ // Compile time test
+ static_assert(isDerivedFromQUntypedPropertyData<propertyPublic>);
+ static_assert(isDerivedFromQUntypedPropertyData<propertyPrivate>);
+ static_assert(isDerivedFromQUntypedPropertyData<QPropertyData<int>>);
+ static_assert(isDerivedFromQUntypedPropertyData<QProperty<int>>);
+
+ // Run time test
+ propertyPublic _propertyPublic;
+ propertyPrivate _propertyPrivate;
+ QPropertyData<int> qpropertyData;
+ QProperty<int> qproperty;
+ std::unique_ptr<propertyPublic> _propertyPublicPtr{ new propertyPublic };
+ std::unique_ptr<propertyPrivate> _propertyPrivatePtr{ new propertyPrivate };
+ std::unique_ptr<QPropertyData<int>> qpropertyDataPtr{ new QPropertyData<int> };
+ std::unique_ptr<QProperty<int>> qpropertyPtr{ new QProperty<int> };
+ QVERIFY(isDerivedFromQUntypedPropertyDataFunc(_propertyPublic));
+ QVERIFY(isDerivedFromQUntypedPropertyDataFunc(_propertyPrivate));
+ QVERIFY(isDerivedFromQUntypedPropertyDataFunc(qpropertyData));
+ QVERIFY(isDerivedFromQUntypedPropertyDataFunc(qproperty));
+ QVERIFY(isDerivedFromQUntypedPropertyDataFunc(_propertyPublicPtr.get()));
+ QVERIFY(isDerivedFromQUntypedPropertyDataFunc(_propertyPrivatePtr.get()));
+ QVERIFY(isDerivedFromQUntypedPropertyDataFunc(qpropertyDataPtr.get()));
+ QVERIFY(isDerivedFromQUntypedPropertyDataFunc(qpropertyPtr.get()));
+}
+
void tst_QProperty::functorBinding()
{
QProperty<int> property([]() { return 42; });
@@ -198,6 +252,35 @@ void tst_QProperty::bindingWithDeletedDependency()
QCOMPARE(propertySelector.value(), staticProperty.value());
}
+class ChangeDuringDtorTester : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(int prop READ prop WRITE setProp BINDABLE bindableProp)
+
+public:
+ void setProp(int i) { m_prop = i;}
+ int prop() const { return m_prop; }
+ QBindable<int> bindableProp() { return &m_prop; }
+private:
+ Q_OBJECT_COMPAT_PROPERTY(ChangeDuringDtorTester, int, m_prop, &ChangeDuringDtorTester::setProp)
+};
+
+void tst_QProperty::dependencyChangeDuringDestruction()
+{
+ auto tester = std::make_unique<ChangeDuringDtorTester>();
+ QProperty<int> iprop {42};
+ tester->bindableProp().setBinding(Qt::makePropertyBinding(iprop));
+ QObject::connect(tester.get(), &QObject::destroyed, [&](){
+ iprop = 12;
+ });
+ bool failed = false;
+ auto handler = tester->bindableProp().onValueChanged([&](){
+ failed = true;
+ });
+ tester.reset();
+ QVERIFY(!failed);
+}
+
void tst_QProperty::recursiveDependency()
{
QProperty<int> first(1);
@@ -234,6 +317,7 @@ void tst_QProperty::bindingAfterUse()
void tst_QProperty::bindingFunctionDtorCalled()
{
+ DtorCounter::counter = 0;
DtorCounter dc;
{
QProperty<int> prop;
@@ -372,7 +456,7 @@ void tst_QProperty::changeHandler()
}
testProperty = 3;
- QCOMPARE(recordedValues.count(), 2);
+ QCOMPARE(recordedValues.size(), 2);
QCOMPARE(recordedValues.at(0), 1);
QCOMPARE(recordedValues.at(1), 2);
}
@@ -415,7 +499,7 @@ void tst_QProperty::subscribe()
}
testProperty = 3;
- QCOMPARE(recordedValues.count(), 3);
+ QCOMPARE(recordedValues.size(), 3);
QCOMPARE(recordedValues.at(0), 42);
QCOMPARE(recordedValues.at(1), 1);
QCOMPARE(recordedValues.at(2), 2);
@@ -707,7 +791,7 @@ void tst_QProperty::genericPropertyBinding()
{
QUntypedPropertyBinding doubleBinding(QMetaType::fromType<double>(),
- [](const QMetaType &, void *) -> bool {
+ [](QMetaType , void *) -> bool {
Q_ASSERT(false);
return true;
}, QPropertyBindingSourceLocation());
@@ -715,7 +799,7 @@ void tst_QProperty::genericPropertyBinding()
}
QUntypedPropertyBinding intBinding(QMetaType::fromType<int>(),
- [](const QMetaType &metaType, void *dataPtr) -> bool {
+ [](QMetaType metaType, void *dataPtr) -> bool {
Q_ASSERT(metaType.id() == qMetaTypeId<int>());
int *intPtr = reinterpret_cast<int*>(dataPtr);
@@ -735,7 +819,7 @@ void tst_QProperty::genericPropertyBindingBool()
QVERIFY(!property.value());
QUntypedPropertyBinding boolBinding(QMetaType::fromType<bool>(),
- [](const QMetaType &, void *dataPtr) -> bool {
+ [](QMetaType, void *dataPtr) -> bool {
auto boolPtr = reinterpret_cast<bool *>(dataPtr);
*boolPtr = true;
return true;
@@ -791,52 +875,6 @@ void tst_QProperty::multipleObservers()
QCOMPARE(property.value(), 22);
}
-void tst_QProperty::propertyAlias()
-{
- QScopedPointer<QProperty<int>> property(new QProperty<int>);
- property->setValue(5);
- QPropertyAlias alias(property.get());
- QVERIFY(alias.isValid());
- QCOMPARE(alias.value(), 5);
-
- int value1 = 1;
- auto changeHandler = alias.onValueChanged([&]() { value1 = alias.value(); });
- QCOMPARE(value1, 1);
-
- int value2 = 2;
- auto subscribeHandler = alias.subscribe([&]() { value2 = alias.value(); });
- QCOMPARE(value2, 5);
-
- alias.setValue(6);
- QVERIFY(alias.isValid());
- QCOMPARE(alias.value(), 6);
- QCOMPARE(value1, 6);
- QCOMPARE(value2, 6);
-
- alias.setBinding([]() { return 12; });
- QCOMPARE(value1, 12);
- QCOMPARE(value2, 12);
- QCOMPARE(alias.value(), 12);
-
- alias.setValue(22);
- QCOMPARE(value1, 22);
- QCOMPARE(value2, 22);
- QCOMPARE(alias.value(), 22);
-
- property.reset();
-
- QVERIFY(!alias.isValid());
- QCOMPARE(alias.value(), int());
- QCOMPARE(value1, 22);
- QCOMPARE(value2, 22);
-
- // Does not crash
- alias.setValue(25);
- QCOMPARE(alias.value(), int());
- QCOMPARE(value1, 22);
- QCOMPARE(value2, 22);
-}
-
void tst_QProperty::arrowAndStarOperator()
{
QString str("Hello");
@@ -896,7 +934,7 @@ void tst_QProperty::notifiedProperty()
check();
instance.property.setValue(42);
- QCOMPARE(instance.recordedValues.count(), 1);
+ QCOMPARE(instance.recordedValues.size(), 1);
QCOMPARE(instance.recordedValues.at(0), 42);
instance.recordedValues.clear();
check();
@@ -926,7 +964,7 @@ void tst_QProperty::notifiedProperty()
subscribedCount = 0;
QCOMPARE(instance.property.value(), 100);
- QCOMPARE(instance.recordedValues.count(), 1);
+ QCOMPARE(instance.recordedValues.size(), 1);
QCOMPARE(instance.recordedValues.at(0), 100);
instance.recordedValues.clear();
check();
@@ -934,7 +972,7 @@ void tst_QProperty::notifiedProperty()
injectedValue = 200;
QCOMPARE(instance.property.value(), 200);
- QCOMPARE(instance.recordedValues.count(), 1);
+ QCOMPARE(instance.recordedValues.size(), 1);
QCOMPARE(instance.recordedValues.at(0), 200);
instance.recordedValues.clear();
check();
@@ -943,7 +981,7 @@ void tst_QProperty::notifiedProperty()
injectedValue = 400;
QCOMPARE(instance.property.value(), 400);
- QCOMPARE(instance.recordedValues.count(), 1);
+ QCOMPARE(instance.recordedValues.size(), 1);
QCOMPARE(instance.recordedValues.at(0), 400);
instance.recordedValues.clear();
check();
@@ -1182,12 +1220,12 @@ void tst_QProperty::qobjectBindableManualNotify()
object.fooData.setValueBypassingBindings(42);
// there is no change.
QCOMPARE(fooChangeCount, 0);
- QCOMPARE(fooChangedSpy.count(), 0);
+ QCOMPARE(fooChangedSpy.size(), 0);
// Once we notify manually
object.fooData.notify();
// observers are notified and the signal arrives.
QCOMPARE(fooChangeCount, 1);
- QCOMPARE(fooChangedSpy.count(), 1);
+ QCOMPARE(fooChangedSpy.size(), 1);
// If we set a binding
int i = 1;
@@ -1196,20 +1234,37 @@ void tst_QProperty::qobjectBindableManualNotify()
QCOMPARE(object.foo(), 1);
// and the change and signal count are incremented.
QCOMPARE(fooChangeCount, 2);
- QCOMPARE(fooChangedSpy.count(), 2);
+ QCOMPARE(fooChangedSpy.size(), 2);
// Changing a non-property won't trigger any notification.
i = 2;
QCOMPARE(fooChangeCount, 2);
- QCOMPARE(fooChangedSpy.count(), 2);
+ QCOMPARE(fooChangedSpy.size(), 2);
// Manually triggering the notification
object.fooData.notify();
// increments the change count
QCOMPARE(fooChangeCount, 3);
- QCOMPARE(fooChangedSpy.count(), 3);
+ QCOMPARE(fooChangedSpy.size(), 3);
// but doesn't actually cause a binding reevaluation.
QCOMPARE(object.foo(), 1);
}
+
+struct ReallocObject : QObject {
+ ReallocObject()
+ { v.setBinding([this] { return x.value() + y.value() + z.value(); }); }
+ Q_OBJECT_BINDABLE_PROPERTY(ReallocObject, int, v)
+ Q_OBJECT_BINDABLE_PROPERTY(ReallocObject, int, x)
+ Q_OBJECT_BINDABLE_PROPERTY(ReallocObject, int, y)
+ Q_OBJECT_BINDABLE_PROPERTY(ReallocObject, int, z)
+};
+
+void tst_QProperty::qobjectBindableReallocatedBindingStorage()
+{
+ ReallocObject object;
+ object.x = 1;
+ QCOMPARE(object.v.value(), 1);
+}
+
void tst_QProperty::qobjectBindableSignalTakingNewValue()
{
// Given an object of type MyQObject,
@@ -1445,42 +1500,6 @@ void tst_QProperty::metaProperty()
QCOMPARE(object.fooData.value(), 1);
}
-void tst_QProperty::aliasOnMetaProperty()
-{
- MyQObject object;
- QPropertyAlias<int> alias(object.bindableFoo());
-
- QVERIFY(alias.isValid());
- QCOMPARE(alias.value(), object.foo());
- QVERIFY(!alias.hasBinding());
-
- object.setFoo(42);
- QCOMPARE(alias.value(), 42);
-
- auto f = [&object]() -> int {
- return object.barData;
- };
- object.bindableFoo().setBinding(f);
- QVERIFY(alias.hasBinding());
- QCOMPARE(alias.value(), object.bar());
-
- object.setBar(111);
- QCOMPARE(alias.value(), 111);
-
- int changedCount = 0;
- auto observer = alias.onValueChanged([&changedCount]() { ++changedCount; });
- QCOMPARE(changedCount, 0);
- object.setBar(666);
- QCOMPARE(changedCount, 1);
-
- alias.setBinding([&object]() { return object.read(); });
- QCOMPARE(changedCount, 2);
- QCOMPARE(alias.value(), 0);
- object.readData = 100;
- QCOMPARE(changedCount, 3);
- QCOMPARE(alias.value(), 100);
-}
-
void tst_QProperty::modifyObserverListWhileIterating()
{
struct DestructingObserver : QPropertyObserver {
@@ -1499,6 +1518,7 @@ void tst_QProperty::modifyObserverListWhileIterating()
DestructingObserver observer = {};
char* memory;
~ObserverOrUninit() {}
+ ObserverOrUninit() {}
};
{
// observer deletes itself while running the notification
@@ -1532,12 +1552,29 @@ void tst_QProperty::modifyObserverListWhileIterating()
}
}
+void tst_QProperty::noDoubleCapture()
+{
+ QProperty<long long> size;
+ size = 3;
+ QProperty<int> max;
+ max.setBinding([&size]() -> int {
+ // each loop run attempts to capture size
+ for (int i = 0; i < size; ++i) {}
+ return size.value();
+ });
+ auto bindingPriv = QPropertyBindingPrivate::get(max.binding());
+ QCOMPARE(bindingPriv->dependencyObserverCount, 1U);
+ size = 4; // should not crash
+ QCOMPARE(max.value(), 4);
+}
+
class CompatPropertyTester : public QObject
{
Q_OBJECT
Q_PROPERTY(int prop1 READ prop1 WRITE setProp1 BINDABLE bindableProp1)
Q_PROPERTY(int prop2 READ prop2 WRITE setProp2 NOTIFY prop2Changed BINDABLE bindableProp2)
Q_PROPERTY(int prop3 READ prop3 WRITE setProp3 NOTIFY prop3Changed BINDABLE bindableProp3)
+ Q_PROPERTY(int prop4 READ prop4 WRITE setProp4 NOTIFY prop4Changed BINDABLE bindableProp4)
public:
CompatPropertyTester(QObject *parent = nullptr) : QObject(parent) { }
@@ -1565,9 +1602,25 @@ public:
}
QBindable<int> bindableProp3() { return QBindable<int>(&prop3Data); }
+ int prop4() const
+ {
+ auto val = prop4Data.value();
+ return val == 0 ? 42 : val;
+ }
+
+ void setProp4(int i)
+ {
+ if (i == prop4Data)
+ return;
+ prop4Data.setValue(i);
+ prop4Data.notify();
+ }
+ QBindable<int> bindableProp4() { return QBindable<int>(&prop4Data); }
+
signals:
void prop2Changed(int value);
void prop3Changed();
+ void prop4Changed(int value);
private:
Q_OBJECT_COMPAT_PROPERTY(CompatPropertyTester, int, prop1Data, &CompatPropertyTester::setProp1)
@@ -1576,6 +1629,10 @@ private:
Q_OBJECT_COMPAT_PROPERTY_WITH_ARGS(CompatPropertyTester, int, prop3Data,
&CompatPropertyTester::setProp3,
&CompatPropertyTester::prop3Changed, 1)
+ Q_OBJECT_COMPAT_PROPERTY_WITH_ARGS(CompatPropertyTester, int, prop4Data,
+ &CompatPropertyTester::setProp4,
+ &CompatPropertyTester::prop4Changed,
+ &CompatPropertyTester::prop4, 0)
};
void tst_QProperty::compatPropertyNoDobuleNotification()
@@ -1602,8 +1659,8 @@ void tst_QProperty::compatPropertySignals()
tester.setProp2(10);
QCOMPARE(prop2Observer.value(), 10);
- QCOMPARE(prop2Spy.count(), 1);
- const QList<QVariant> arguments = prop2Spy.takeFirst();
+ QCOMPARE(prop2Spy.size(), 1);
+ QList<QVariant> arguments = prop2Spy.takeFirst();
QCOMPARE(arguments.size(), 1);
QCOMPARE(arguments.at(0).metaType().id(), QMetaType::Int);
QCOMPARE(arguments.at(0).toInt(), 10);
@@ -1618,7 +1675,41 @@ void tst_QProperty::compatPropertySignals()
tester.setProp3(5);
QCOMPARE(prop3Observer.value(), 5);
- QCOMPARE(prop3Spy.count(), 1);
+ QCOMPARE(prop3Spy.size(), 1);
+
+ // Compat property with signal, default value, and custom setter. Signal has parameter.
+ QProperty<int> prop4Observer;
+ prop4Observer.setBinding(tester.bindableProp4().makeBinding());
+ QCOMPARE(prop4Observer.value(), 42);
+
+ QSignalSpy prop4Spy(&tester, &CompatPropertyTester::prop4Changed);
+
+ tester.setProp4(10);
+
+ QCOMPARE(prop4Observer.value(), 10);
+ QCOMPARE(prop4Spy.size(), 1);
+ arguments = prop4Spy.takeFirst();
+ QCOMPARE(arguments.size(), 1);
+ QCOMPARE(arguments.at(0).metaType().id(), QMetaType::Int);
+ QCOMPARE(arguments.at(0).toInt(), 10);
+
+ tester.setProp4(42);
+
+ QCOMPARE(prop4Observer.value(), 42);
+ QCOMPARE(prop4Spy.size(), 1);
+ arguments = prop4Spy.takeFirst();
+ QCOMPARE(arguments.size(), 1);
+ QCOMPARE(arguments.at(0).metaType().id(), QMetaType::Int);
+ QCOMPARE(arguments.at(0).toInt(), 42);
+
+ tester.setProp4(0);
+
+ QCOMPARE(prop4Observer.value(), 42);
+ QCOMPARE(prop4Spy.size(), 1);
+ arguments = prop4Spy.takeFirst();
+ QCOMPARE(arguments.size(), 1);
+ QCOMPARE(arguments.at(0).metaType().id(), QMetaType::Int);
+ QCOMPARE(arguments.at(0).toInt(), 42);
}
class FakeDependencyCreator : public QObject
@@ -1670,6 +1761,323 @@ void tst_QProperty::noFakeDependencies()
QCOMPARE(old, bindingFunctionCalled);
}
+class PropertyAdaptorTester : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(int foo READ foo WRITE setFoo NOTIFY fooChanged)
+ Q_PROPERTY(int foo1 READ foo WRITE setFoo)
+
+signals:
+ void dummySignal1();
+ void dummySignal2();
+ void dummySignal3();
+ void dummySignal4();
+ void dummySignal5();
+ void dummySignal6();
+ void dummySignal7();
+ void dummySignal8();
+ void fooChanged(int newFoo);
+
+public slots:
+ void fooHasChanged() { fooChangedCount++; }
+
+public:
+ int foo() const { return fooData; }
+ void setFoo(int i)
+ {
+ if (i != fooData) {
+ fooData = i;
+ fooChanged(fooData);
+ }
+ }
+
+public:
+ int fooData = 0;
+ int fooChangedCount = 0;
+};
+
+void tst_QProperty::propertyAdaptorBinding()
+{
+ QProperty<int> source { 5 };
+ QProperty<int> dest1 { 99 };
+ QProperty<int> dest2 { 98 };
+
+ // Check binding of non BINDABLE property
+ PropertyAdaptorTester object;
+ // set up a dummy connection (needed to verify that the QBindable avoids an out-of-bounds read)
+ QObject::connect(&object, &PropertyAdaptorTester::dummySignal1, [](){});
+ QBindable<int> binding(&object, "foo");
+ QObject::connect(&object, &PropertyAdaptorTester::fooChanged, &object,
+ &PropertyAdaptorTester::fooHasChanged);
+ binding.setBinding([&]() { return source + 1; });
+ QCOMPARE(object.foo(), 6);
+ QCOMPARE(object.fooChangedCount, 1);
+
+ struct MyBindable : QBindable<int> {
+ using QBindable<int>::QBindable;
+ QtPrivate::QPropertyAdaptorSlotObject* data() {
+ return static_cast<QtPrivate::QPropertyAdaptorSlotObject*>(QUntypedBindable::data);
+ }
+ } dataBinding(&object, "foo");
+ QPropertyBindingDataPointer data{&dataBinding.data()->bindingData()};
+
+ QCOMPARE(data.observerCount(), 0);
+ dest1.setBinding(binding.makeBinding());
+ QCOMPARE(data.observerCount(), 1);
+ dest2.setBinding([=]() { return binding.value() + 1; });
+ binding = {};
+ QCOMPARE(data.observerCount(), 2);
+
+ // Check addNotifer
+ {
+ int local_foo = 0;
+ auto notifier = QBindable<int>(&object, "foo").addNotifier([&]() { local_foo++; });
+ QCOMPARE(data.observerCount(), 3);
+ QCOMPARE(object.foo(), 6);
+ QCOMPARE(dest1.value(), 6);
+ QCOMPARE(dest2.value(), 7);
+ QCOMPARE(local_foo, 0);
+ QCOMPARE(object.fooChangedCount, 1);
+
+ source = 7;
+ QCOMPARE(object.foo(), 8);
+ QCOMPARE(dest1.value(), 8);
+ QCOMPARE(dest2.value(), 9);
+ QCOMPARE(local_foo, 1);
+ QCOMPARE(object.fooChangedCount, 2);
+ }
+
+ QCOMPARE(data.observerCount(), 2);
+
+ // Check a new QBindable object can override the existing binding
+ QBindable<int>(&object, "foo").setValue(10);
+ QCOMPARE(object.foo(), 10);
+ QCOMPARE(dest1.value(), 10);
+ QCOMPARE(dest2.value(), 11);
+ QCOMPARE(object.fooChangedCount, 3);
+ source.setValue(99);
+ QCOMPARE(object.foo(), 10);
+ QCOMPARE(dest1.value(), 10);
+ QCOMPARE(dest2.value(), 11);
+ QCOMPARE(object.fooChangedCount, 3);
+ object.setFoo(12);
+ QCOMPARE(object.foo(), 12);
+ QCOMPARE(dest1.value(), 12);
+ QCOMPARE(dest2.value(), 13);
+ QCOMPARE(object.fooChangedCount, 4);
+
+ // Check binding multiple notifiers
+ QProperty<int> source2 { 20 };
+ source.setValue(21);
+ binding = QBindable<int>(&object, "foo");
+ binding.setBinding([&]() { return source + source2; });
+ QCOMPARE(object.foo(), 41);
+ QCOMPARE(dest1.value(), 41);
+ QCOMPARE(object.fooChangedCount, 5);
+ source.setValue(22);
+ QCOMPARE(object.foo(), 42);
+ QCOMPARE(dest1.value(), 42);
+ QCOMPARE(object.fooChangedCount, 6);
+ source2.setValue(21);
+ QCOMPARE(object.foo(), 43);
+ QCOMPARE(dest1.value(), 43);
+ QCOMPARE(object.fooChangedCount, 7);
+
+ // Check update group
+ {
+ const QScopedPropertyUpdateGroup guard;
+ source.setValue(23);
+ source2.setValue(22);
+ QCOMPARE(object.foo(), 43);
+ QCOMPARE(dest1.value(), 43);
+ QCOMPARE(object.fooChangedCount, 7);
+ }
+ QCOMPARE(object.foo(), 45);
+ QCOMPARE(dest1.value(), 45);
+ QCOMPARE(object.fooChangedCount, 8);
+
+ PropertyAdaptorTester object2;
+ PropertyAdaptorTester object3;
+
+ // Check multiple observers
+ QBindable<int> binding2(&object2, "foo");
+ QBindable<int> binding3(&object3, "foo");
+ binding.setBinding([=]() { return binding2.value(); });
+ binding3.setBinding([=]() { return binding.value(); });
+ QCOMPARE(object.foo(), 0);
+ QCOMPARE(object2.foo(), 0);
+ QCOMPARE(object3.foo(), 0);
+ QCOMPARE(dest1.value(), 0);
+ object2.setFoo(1);
+ QCOMPARE(object.foo(), 1);
+ QCOMPARE(object2.foo(), 1);
+ QCOMPARE(object3.foo(), 1);
+ QCOMPARE(dest1.value(), 1);
+
+ // Check interoperation with BINDABLE properties
+ MyQObject bindableObject;
+ bindableObject.fooData.setBinding([]() { return 5; });
+ QVERIFY(bindableObject.fooData.hasBinding());
+ QVERIFY(!bindableObject.barData.hasBinding());
+ QVERIFY(QBindable<int>(&bindableObject, "foo").hasBinding());
+ QBindable<int> bindableBar(&bindableObject, "bar");
+ QVERIFY(!bindableBar.hasBinding());
+ bindableBar.setBinding([]() { return 6; });
+ QVERIFY(bindableBar.hasBinding());
+ QVERIFY(bindableObject.barData.hasBinding());
+
+ // Check bad arguments
+#ifndef QT_NO_DEBUG
+ QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QUntypedBindable: Property is not valid");
+#endif
+ QVERIFY(!QBindable<int>(&object, QMetaProperty{}).isValid());
+#ifndef QT_NO_DEBUG
+ QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QUntypedBindable: Property foo1 has no notify signal");
+#endif
+ QVERIFY(!QBindable<int>(&object, "foo1").isValid());
+#ifndef QT_NO_DEBUG
+ QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QUntypedBindable: Property foo of type int does not match requested type bool");
+#endif
+ QVERIFY(!QBindable<bool>(&object, "foo").isValid());
+#ifndef QT_NO_DEBUG
+ QTest::ignoreMessage(QtMsgType::QtWarningMsg,
+ "QUntypedBindable: Property foo does not belong to this object");
+#endif
+ QObject qobj;
+ QVERIFY(!QBindable<int>(
+ &qobj,
+ object.metaObject()->property(object.metaObject()->indexOfProperty("foo")))
+ .isValid());
+#ifndef QT_NO_DEBUG
+ QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QUntypedBindable: No property named fizz");
+ QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QUntypedBindable: Property is not valid");
+#endif
+ QVERIFY(!QBindable<int>(&object, "fizz").isValid());
+}
+
+#if QT_CONFIG(thread)
+struct ThreadSafetyTester : public QObject
+{
+ Q_OBJECT
+
+public:
+ ThreadSafetyTester(QObject *parent = nullptr) : QObject(parent) {}
+
+ Q_INVOKABLE bool hasCorrectStatus() const
+ {
+ return qGetBindingStorage(this)->status({}) == QtPrivate::getBindingStatus({});
+ }
+
+ Q_INVOKABLE bool bindingTest()
+ {
+ QProperty<QString> name(u"inThread"_s);
+ bindableObjectName().setBinding([&]() -> QString { return name; });
+ name = u"inThreadChanged"_s;
+ const bool nameChangedCorrectly = objectName() == name;
+ bindableObjectName().takeBinding();
+ return nameChangedCorrectly;
+ }
+};
+
+
+void tst_QProperty::threadSafety()
+{
+ QThread workerThread;
+ auto cleanup = qScopeGuard([&](){
+ QMetaObject::invokeMethod(&workerThread, "quit");
+ workerThread.wait();
+ });
+ QScopedPointer<ThreadSafetyTester> scopedObj1(new ThreadSafetyTester);
+ auto obj1 = scopedObj1.data();
+ auto child1 = new ThreadSafetyTester(obj1);
+ obj1->moveToThread(&workerThread);
+ const auto mainThreadBindingStatus = QtPrivate::getBindingStatus({});
+ QCOMPARE(qGetBindingStorage(child1)->status({}), nullptr);
+ workerThread.start();
+
+ bool correctStatus = false;
+ bool ok = QMetaObject::invokeMethod(obj1, "hasCorrectStatus", Qt::BlockingQueuedConnection,
+ Q_RETURN_ARG(bool, correctStatus));
+ QVERIFY(ok);
+ QVERIFY(correctStatus);
+
+ bool bindingWorks = false;
+ ok = QMetaObject::invokeMethod(obj1, "bindingTest", Qt::BlockingQueuedConnection,
+ Q_RETURN_ARG(bool, bindingWorks));
+ QVERIFY(ok);
+ QVERIFY(bindingWorks);
+
+ correctStatus = false;
+ ok = QMetaObject::invokeMethod(child1, "hasCorrectStatus", Qt::BlockingQueuedConnection,
+ Q_RETURN_ARG(bool, correctStatus));
+ QVERIFY(ok);
+ QVERIFY(correctStatus);
+
+ QScopedPointer scopedObj2(new ThreadSafetyTester);
+ auto obj2 = scopedObj2.data();
+ QCOMPARE(qGetBindingStorage(obj2)->status({}), mainThreadBindingStatus);
+
+ obj2->setObjectName("moved");
+ QCOMPARE(obj2->objectName(), "moved");
+
+ obj2->moveToThread(&workerThread);
+ correctStatus = false;
+ ok = QMetaObject::invokeMethod(obj2, "hasCorrectStatus", Qt::BlockingQueuedConnection,
+ Q_RETURN_ARG(bool, correctStatus));
+
+ QVERIFY(ok);
+ QVERIFY(correctStatus);
+ // potentially unsafe, but should still work (no writes in owning thread)
+ QCOMPARE(obj2->objectName(), "moved");
+
+
+ QScopedPointer scopedObj3(new ThreadSafetyTester);
+ auto obj3 = scopedObj3.data();
+ obj3->setObjectName("moved");
+ QCOMPARE(obj3->objectName(), "moved");
+ obj3->moveToThread(nullptr);
+ QCOMPARE(obj2->objectName(), "moved");
+ obj3->setObjectName("moved again");
+ QCOMPARE(obj3->objectName(), "moved again");
+}
+
+class QPropertyUsingThread : public QThread
+{
+public:
+ QPropertyUsingThread(QObject **dest, QThread *destThread) : dest(dest), destThread(destThread) {}
+ void run() override
+ {
+ scopedObj1.reset(new ThreadSafetyTester());
+ scopedObj1->setObjectName("test");
+ QObject *child = new ThreadSafetyTester(scopedObj1.get());
+ child->setObjectName("child");
+ exec();
+ scopedObj1->moveToThread(destThread);
+ *dest = scopedObj1.release();
+ }
+ std::unique_ptr<ThreadSafetyTester> scopedObj1;
+ QObject **dest;
+ QThread *destThread;
+};
+
+void tst_QProperty::threadSafety2()
+{
+ std::unique_ptr<QObject> movedObj;
+ {
+ QObject *tmp = nullptr;
+ QPropertyUsingThread workerThread(&tmp, QThread::currentThread());
+ workerThread.start();
+ workerThread.quit();
+ workerThread.wait();
+ movedObj.reset(tmp);
+ }
+
+ QCOMPARE(movedObj->objectName(), "test");
+ QCOMPARE(movedObj->children().first()->objectName(), "child");
+}
+#endif // QT_CONFIG(thread)
+
struct CustomType
{
CustomType() = default;
@@ -1781,27 +2189,29 @@ void tst_QProperty::groupedNotifications()
QCOMPARE(nNotifications, 1);
expected = 2;
- Qt::beginPropertyUpdateGroup();
- a = 1;
- QCOMPARE(b.value(), 0);
- QCOMPARE(c.value(), 0);
- QCOMPARE(d.value(), 0);
- QCOMPARE(nNotifications, 1);
- Qt::endPropertyUpdateGroup();
+ {
+ const QScopedPropertyUpdateGroup guard;
+ a = 1;
+ QCOMPARE(b.value(), 0);
+ QCOMPARE(c.value(), 0);
+ QCOMPARE(d.value(), 0);
+ QCOMPARE(nNotifications, 1);
+ }
QCOMPARE(b.value(), 1);
QCOMPARE(c.value(), 1);
QCOMPARE(e.value(), 2);
QCOMPARE(nNotifications, 2);
expected = 7;
- Qt::beginPropertyUpdateGroup();
- a = 2;
- d = 3;
- QCOMPARE(b.value(), 1);
- QCOMPARE(c.value(), 1);
- QCOMPARE(d.value(), 3);
- QCOMPARE(nNotifications, 2);
- Qt::endPropertyUpdateGroup();
+ {
+ const QScopedPropertyUpdateGroup guard;
+ a = 2;
+ d = 3;
+ QCOMPARE(b.value(), 1);
+ QCOMPARE(c.value(), 1);
+ QCOMPARE(d.value(), 3);
+ QCOMPARE(nNotifications, 2);
+ }
QCOMPARE(b.value(), 2);
QCOMPARE(c.value(), 2);
QCOMPARE(e.value(), 7);
@@ -1824,13 +2234,347 @@ void tst_QProperty::groupedNotificationConsistency()
j = 1;
QVERIFY(!areEqual); // value changed runs before j = 1
- Qt::beginPropertyUpdateGroup();
- i = 2;
- j = 2;
- Qt::endPropertyUpdateGroup();
+ {
+ const QScopedPropertyUpdateGroup guard;
+ i = 2;
+ j = 2;
+ }
QVERIFY(areEqual); // value changed runs after everything has been evaluated
}
+void tst_QProperty::bindingGroupMovingBindingData()
+{
+ auto tester = std::make_unique<ClassWithNotifiedProperty>();
+ auto testerPriv = QObjectPrivate::get(tester.get());
+
+ auto dummyNotifier = tester->property.addNotifier([](){});
+ auto bindingData = testerPriv->bindingStorage.bindingData(&tester->property);
+ QVERIFY(bindingData); // we have a notifier, so there should be binding data
+
+ Qt::beginPropertyUpdateGroup();
+ auto cleanup = qScopeGuard([](){ Qt::endPropertyUpdateGroup(); });
+ tester->property = 42;
+ QCOMPARE(testerPriv->bindingStorage.bindingData(&tester->property), bindingData);
+ auto proxyData = QPropertyBindingDataPointer::proxyData(bindingData);
+ // as we've modified the property, we now should have a proxy for the delayed notification
+ QVERIFY(proxyData);
+ // trigger binding data reallocation
+ std::array<QUntypedPropertyData, 10> propertyDataArray;
+ for (auto&& data: propertyDataArray)
+ testerPriv->bindingStorage.bindingData(&data, true);
+ // binding data has moved
+ QVERIFY(testerPriv->bindingStorage.bindingData(&tester->property) != bindingData);
+ bindingData = testerPriv->bindingStorage.bindingData(&tester->property);
+ // the proxy data has been updated
+ QCOMPARE(proxyData->originalBindingData, bindingData);
+
+ tester.reset();
+ // the property data is gone, proxyData should have been informed
+ QCOMPARE(proxyData->originalBindingData, nullptr);
+ QVERIFY(proxyData);
+}
+
+void tst_QProperty::bindingGroupBindingDeleted()
+{
+ auto deleter = std::make_unique<ClassWithNotifiedProperty>();
+ auto toBeDeleted = std::make_unique<ClassWithNotifiedProperty>();
+
+ bool calledHandler = false;
+ deleter->property.setBinding([&](){
+ int newValue = toBeDeleted->property;
+ if (newValue == 42)
+ toBeDeleted.reset();
+ return newValue;
+ });
+ auto handler = toBeDeleted->property.onValueChanged([&]() { calledHandler = true; } );
+ {
+ Qt::beginPropertyUpdateGroup();
+ auto cleanup = qScopeGuard([](){ Qt::endPropertyUpdateGroup(); });
+ QVERIFY(toBeDeleted);
+ toBeDeleted->property = 42;
+ // ASAN should not complain here
+ }
+ QVERIFY(!toBeDeleted);
+ // the change notification is sent, even if the binding is deleted during evaluation
+ QVERIFY(calledHandler);
+}
+
+void tst_QProperty::uninstalledBindingDoesNotEvaluate()
+{
+ QProperty<int> i;
+ QProperty<int> j;
+ int bindingEvaluationCounter = 0;
+ i.setBinding([&](){
+ bindingEvaluationCounter++;
+ return j.value();
+ });
+ QCOMPARE(bindingEvaluationCounter, 1);
+ // Sanity check: if we force a binding reevaluation,
+ j = 42;
+ // the binding function will be called again.
+ QCOMPARE(bindingEvaluationCounter, 2);
+ // If we keep referencing the binding
+ auto keptBinding = i.binding();
+ // but have it not installed on a property
+ i = 10;
+ QVERIFY(!i.hasBinding());
+ QVERIFY(!keptBinding.isNull());
+ // then changing a dependency
+ j = 12;
+ // does not lead to the binding being reevaluated.
+ QCOMPARE(bindingEvaluationCounter, 2);
+}
+
+void tst_QProperty::notify()
+{
+ QProperty<int> testProperty(0);
+ QList<int> recordedValues;
+ int value = 0;
+ QPropertyNotifier notifier;
+
+ {
+ QPropertyNotifier handler = testProperty.addNotifier([&]() {
+ recordedValues << testProperty;
+ });
+ notifier = testProperty.addNotifier([&]() {
+ value = testProperty;
+ });
+
+ testProperty = 1;
+ testProperty = 2;
+ }
+ QCOMPARE(value, 2);
+ testProperty = 3;
+ QCOMPARE(value, 3);
+ notifier = {};
+ testProperty = 4;
+ QCOMPARE(value, 3);
+
+ QCOMPARE(recordedValues.size(), 2);
+ QCOMPARE(recordedValues.at(0), 1);
+ QCOMPARE(recordedValues.at(1), 2);
+}
+
+void tst_QProperty::bindableInterfaceOfCompatPropertyUsesSetter()
+{
+ MyQObject obj;
+ QBindable<int> bindable = obj.bindableCompat();
+ QCOMPARE(obj.setCompatCalled, 0);
+ bindable.setValue(42);
+ QCOMPARE(obj.setCompatCalled, 1);
+}
+
+void tst_QProperty::selfBindingShouldNotCrash()
+{
+ QProperty<int> i;
+ i.setBinding([&](){ return i+1; });
+ QVERIFY(i.binding().error().hasError());
+}
+
+void tst_QProperty::qpropertyAlias()
+{
+#if QT_DEPRECATED_SINCE(6, 6)
+ QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED
+ std::unique_ptr<QProperty<int>> i {new QProperty<int>};
+ QPropertyAlias<int> alias(i.get());
+ QVERIFY(alias.isValid());
+ alias.setValue(42);
+ QCOMPARE(i->value(), 42);
+ QProperty<int> j;
+ bool notifierCalled = false;
+ auto myNotifier = alias.addNotifier([&](){notifierCalled = true;});
+ i->setBinding([&]() -> int { return j; });
+ QVERIFY(notifierCalled);
+ j.setValue(42);
+ QCOMPARE(alias.value(), 42);
+ i.reset();
+ QVERIFY(!alias.isValid());
+ QT_WARNING_POP
+#endif
+}
+
+void tst_QProperty::scheduleNotify()
+{
+ int notifications = 0;
+ QProperty<int> p;
+ QCOMPARE(p.value(), 0);
+ const auto handler = p.addNotifier([&](){ ++notifications; });
+ QCOMPARE(notifications, 0);
+ QPropertyBinding<int> b([]() { return 0; }, QPropertyBindingSourceLocation());
+ QPropertyBindingPrivate::get(b)->scheduleNotify();
+ QCOMPARE(notifications, 0);
+ p.setBinding(b);
+ QCOMPARE(notifications, 1);
+ QCOMPARE(p.value(), 0);
+}
+
+void tst_QProperty::notifyAfterAllDepsGone()
+{
+ bool b = true;
+ QProperty<int> iprop;
+ QProperty<int> jprop(42);
+ iprop.setBinding([&](){
+ if (b)
+ return jprop.value();
+ return 13;
+ });
+ int changeCounter = 0;
+ auto keepAlive = iprop.onValueChanged([&](){ changeCounter++; });
+ QCOMPARE(iprop.value(), 42);
+ jprop = 44;
+ QCOMPARE(iprop.value(), 44);
+ QCOMPARE(changeCounter, 1);
+ b = false;
+ jprop = 43;
+ QCOMPARE(iprop.value(), 13);
+ QCOMPARE(changeCounter, 2);
+}
+
+class TestObject : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(int signaled READ signaled WRITE setSignaled NOTIFY signaledChanged FINAL)
+ Q_PROPERTY(int bindable1 READ bindable1 WRITE setBindable1 BINDABLE bindable1Bindable NOTIFY bindable1Changed FINAL)
+ Q_PROPERTY(int bindable2 READ bindable2 WRITE setBindable2 BINDABLE bindable2Bindable NOTIFY bindable2Changed FINAL)
+
+public:
+ int signaled() const
+ {
+ return m_signaled;
+ }
+
+ void setSignaled(int newSignaled)
+ {
+ if (m_signaled == newSignaled)
+ return;
+ m_signaled = newSignaled;
+ emit signaledChanged();
+ }
+
+ int bindable1() const
+ {
+ return m_bindable1;
+ }
+
+ void setBindable1(int newBindable1)
+ {
+ if (m_bindable1 == newBindable1)
+ return;
+ m_bindable1 = newBindable1;
+ emit bindable1Changed();
+ }
+
+ QBindable<int> bindable1Bindable()
+ {
+ return QBindable<int>(&m_bindable1);
+ }
+
+ int bindable2() const
+ {
+ return m_bindable2;
+ }
+
+ void setBindable2(int newBindable2)
+ {
+ if (m_bindable2 == newBindable2)
+ return;
+ m_bindable2 = newBindable2;
+ emit bindable2Changed();
+ }
+
+ QBindable<int> bindable2Bindable()
+ {
+ return QBindable<int>(&m_bindable2);
+ }
+
+signals:
+ void signaledChanged();
+ void bindable1Changed();
+ void bindable2Changed();
+
+private:
+ int m_signaled = 0;
+ Q_OBJECT_COMPAT_PROPERTY(TestObject, int, m_bindable1, &TestObject::setBindable1, &TestObject::bindable1Changed);
+ Q_OBJECT_COMPAT_PROPERTY(TestObject, int, m_bindable2, &TestObject::setBindable2, &TestObject::bindable2Changed);
+};
+
+void tst_QProperty::propertyUpdateViaSignaledProperty()
+{
+ TestObject o;
+ QProperty<int> rootTrigger;
+ QProperty<int> signalTrigger;
+
+ o.bindable1Bindable().setBinding([&]() {
+ return rootTrigger.value();
+ });
+
+ QObject::connect(&o, &TestObject::bindable1Changed, &o, [&]() {
+ // Signaled changes only once, doesn't actually depend on bindable1.
+ // In reality, there could be some complicated calculation behind this that changes
+ // on certain checkpoints, but not on every iteration.
+ o.setSignaled(40);
+ });
+
+ o.bindable2Bindable().setBinding([&]() {
+ return signalTrigger.value() - o.bindable1();
+ });
+
+ QObject::connect(&o, &TestObject::signaledChanged, &o, [&]() {
+ signalTrigger.setValue(o.signaled());
+ });
+
+ rootTrigger.setValue(2);
+ QCOMPARE(o.bindable1(), 2);
+ QCOMPARE(o.bindable2(), 38);
+ rootTrigger.setValue(3);
+ QCOMPARE(o.bindable1(), 3);
+ QCOMPARE(o.bindable2(), 37);
+ rootTrigger.setValue(4);
+ QCOMPARE(o.bindable1(), 4);
+ QCOMPARE(o.bindable2(), 36);
+}
+
+void tst_QProperty::derefFromObserver()
+{
+ int triggered = 0;
+ QProperty<int> source(11);
+
+ DtorCounter::counter = 0;
+ DtorCounter dc;
+
+ QProperty<int> target([&triggered, &source, dc]() mutable {
+ dc.shouldIncrement = true;
+ return ++triggered + source.value();
+ });
+ QCOMPARE(triggered, 1);
+
+ {
+ auto propObserver = std::make_unique<QPropertyObserver>();
+ QPropertyObserverPointer propObserverPtr { propObserver.get() };
+ propObserverPtr.setBindingToNotify(QPropertyBindingPrivate::get(target.binding()));
+
+ QBindingObserverPtr bindingPtr(propObserver.get());
+
+ QCOMPARE(triggered, 1);
+ source = 25;
+ QCOMPARE(triggered, 2);
+ QCOMPARE(target, 27);
+
+ target.setBinding([]() { return 8; });
+ QCOMPARE(target, 8);
+
+ // The QBindingObserverPtr still holds on to the binding.
+ QCOMPARE(dc.counter, 0);
+ }
+
+ // The binding is actually gone now.
+ QCOMPARE(dc.counter, 1);
+
+ source = 26;
+ QCOMPARE(triggered, 2);
+ QCOMPARE(target, 8);
+}
+
QTEST_MAIN(tst_QProperty);
#undef QT_SOURCE_LOCATION_NAMESPACE