Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qpropertytesthelper_p.h
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#ifndef QPROPERTYTESTHELPER_P_H
5#define QPROPERTYTESTHELPER_P_H
6
7//
8// W A R N I N G
9// -------------
10//
11// This file is not part of the Qt API. It exists purely as an
12// implementation detail. This header file may change from version to
13// version without notice, or even be removed.
14//
15// We mean it.
16//
17
18#include <QtCore/QObject>
19#include <QtCore/QProperty>
20#include <QtTest/QSignalSpy>
21#include <QTest>
22#include <private/qglobal_p.h>
23
25
26namespace QTestPrivate {
27
45#define QPROPERTY_TEST_COMPARISON_HELPER(actual, expected, comparator, represent) \
46 do { \
47 const size_t maxMsgLen = 1024; \
48 char msg[maxMsgLen] = { '\0' }; \
49 auto actualStr = represent(actual); \
50 auto expectedStr = represent(expected); \
51 const size_t len1 = mbstowcs(nullptr, #actual, maxMsgLen); \
52 const size_t len2 = mbstowcs(nullptr, #expected, maxMsgLen); \
53 qsnprintf(msg, maxMsgLen, "\n%s\n Actual (%s)%*s %s\n Expected (%s)%*s %s\n", \
54 "Comparison failed!", #actual, qMax(len1, len2) - len1 + 1, ":", \
55 actualStr ? actualStr : "<null>", #expected, qMax(len1, len2) - len2 + 1, ":", \
56 expectedStr ? expectedStr : "<null>"); \
57 delete[] actualStr; \
58 delete[] expectedStr; \
59 QVERIFY2(comparator(actual, expected), msg); \
60 } while (false)
61
104template<typename TestedClass, typename PropertyType>
106 TestedClass &instance, const PropertyType &initial, const PropertyType &changed,
107 const char *propertyName,
108 std::function<bool(const PropertyType &, const PropertyType &)> comparator =
109 [](const PropertyType &lhs, const PropertyType &rhs) { return lhs == rhs; },
110 std::function<char *(const PropertyType &)> represent =
111 [](const PropertyType &val) { return QTest::toString(val); },
112 std::function<std::unique_ptr<TestedClass>(void)> helperConstructor =
113 []() { return std::make_unique<TestedClass>(); })
114{
115 // get the property
116 const QMetaObject *metaObject = instance.metaObject();
117 QMetaProperty metaProperty = metaObject->property(metaObject->indexOfProperty(propertyName));
118 QVERIFY2(metaProperty.metaType() == QMetaType::fromType<PropertyType>(),
119 QByteArray("Preconditions not met for ") + propertyName + "\n"
120 "The type of initial and changed value does not match the type of the property.\n"
121 "Please ensure that the types match exactly (convertability is not enough).\n"
122 "You can provide the template types to the "
123 "function explicitly to force a certain type.\n"
124 "Expected was a " + metaProperty.metaType().name()
125 + " but " + QMetaType::fromType<PropertyType>().name() + " was provided.");
126
127 // in case the TestedClass has setProperty()/property() methods.
128 QObject &testedObj = static_cast<QObject &>(instance);
129
130 QVERIFY2(metaProperty.isBindable() && metaProperty.isWritable(),
131 "Preconditions not met for " + QByteArray(propertyName));
132
133 QScopedPointer<QSignalSpy> spy(nullptr);
134 if (metaProperty.hasNotifySignal())
135 spy.reset(new QSignalSpy(&instance, metaProperty.notifySignal()));
136
137 testedObj.setProperty(propertyName, QVariant::fromValue(initial));
139 testedObj.property(propertyName).template value<PropertyType>(), initial, comparator,
140 represent);
141 if (spy)
142 QCOMPARE(spy->size(), 1);
143
144 QUntypedBindable bindable = metaProperty.bindable(&instance);
145
146 // Bind to the object's property (using both lambda and
147 // Qt:makePropertyBinding).
148 QProperty<PropertyType> propObserver(changed);
149 propObserver.setBinding(bindable.makeBinding());
150 QPROPERTY_TEST_COMPARISON_HELPER(propObserver.value(), initial, comparator, represent);
151
152 QProperty<PropertyType> propObserverLambda(changed);
153 propObserverLambda.setBinding(
154 [&]() { return testedObj.property(propertyName).template value<PropertyType>(); });
155 QPROPERTY_TEST_COMPARISON_HELPER(propObserverLambda.value(), initial, comparator, represent);
156
157 testedObj.setProperty(propertyName, QVariant::fromValue(changed));
158 QPROPERTY_TEST_COMPARISON_HELPER(propObserver.value(), changed, comparator, represent);
159 QPROPERTY_TEST_COMPARISON_HELPER(propObserverLambda.value(), changed, comparator, represent);
160 if (spy)
161 QCOMPARE(spy->size(), 2);
162
163 // Bind object's property to other property
164 QProperty<PropertyType> propSetter(initial);
165 QVERIFY(!bindable.hasBinding());
166 bindable.setBinding(Qt::makePropertyBinding(propSetter));
167
168 QVERIFY(bindable.hasBinding());
170 testedObj.property(propertyName).template value<PropertyType>(), initial, comparator,
171 represent);
172 QPROPERTY_TEST_COMPARISON_HELPER(propObserver.value(), initial, comparator, represent);
173 QPROPERTY_TEST_COMPARISON_HELPER(propObserverLambda.value(), initial, comparator, represent);
174 if (spy)
175 QCOMPARE(spy->size(), 3);
176
177 // Count notifications triggered; should only happen on actual change.
178 int updateCount = 0;
179 auto handler = bindable.onValueChanged([&updateCount]() { ++updateCount; });
180 Q_UNUSED(handler)
181
182 propSetter.setValue(changed);
184 testedObj.property(propertyName).template value<PropertyType>(), changed, comparator,
185 represent);
186 QPROPERTY_TEST_COMPARISON_HELPER(propObserver.value(), changed, comparator, represent);
187 QPROPERTY_TEST_COMPARISON_HELPER(propObserverLambda.value(), changed, comparator, represent);
188 QCOMPARE(updateCount, 1);
189 if (spy)
190 QCOMPARE(spy->size(), 4);
191
192 // Test that manually setting the value (even the same one) breaks the
193 // binding.
194 testedObj.setProperty(propertyName, QVariant::fromValue(changed));
195 QVERIFY(!bindable.hasBinding());
196 // Setting the same value should have no impact on udpateCount.
197 QCOMPARE(updateCount, 1);
198
199 // value didn't change -> the signal should not be emitted
200 if (spy)
201 QCOMPARE(spy->size(), 4);
202
203 // test binding loop
204 if (std::unique_ptr<TestedClass> helperObj = helperConstructor()) {
205 // Reset to 'initial', so that the binding loop test could check the
206 // 'changed' value, because some tests already rely on the 'instance' to
207 // have the 'changed' value once this test passes
208 testedObj.setProperty(propertyName, QVariant::fromValue(initial));
209 const QPropertyBinding<PropertyType> binding([&]() {
210 QObject *obj = static_cast<QObject *>(helperObj.get());
211 obj->setProperty(propertyName, QVariant::fromValue(changed));
212 return obj->property(propertyName).template value<PropertyType>();
213 }, {});
214 bindable.setBinding(binding);
216 testedObj.property(propertyName).template value<PropertyType>(), changed,
217 comparator, represent);
218 QVERIFY2(!binding.error().hasError(), qPrintable(binding.error().description()));
219 }
220}
221
230template<typename TestedClass, typename PropertyType>
232 TestedClass &instance, const PropertyType &initial, const PropertyType &changed,
233 const char *propertyName,
234 std::function<std::unique_ptr<TestedClass>(void)> helperConstructor)
235{
236 testReadWritePropertyBasics<TestedClass, PropertyType>(
237 instance, initial, changed, propertyName,
238 [](const PropertyType &lhs, const PropertyType &rhs) { return lhs == rhs; },
239 [](const PropertyType &val) { return QTest::toString(val); },
240 helperConstructor);
241}
242
294template<typename TestedClass, typename PropertyType>
296 TestedClass &instance, const PropertyType &prior, const PropertyType &changed,
297 const char *propertyName,
298 bool bindingPreservedOnWrite = true,
299 std::function<bool(const PropertyType &, const PropertyType &)> comparator =
300 [](const PropertyType &lhs, const PropertyType &rhs) { return lhs == rhs; },
301 std::function<char *(const PropertyType &)> represent =
302 [](const PropertyType &val) { return QTest::toString(val); },
303 std::function<std::unique_ptr<TestedClass>(void)> helperConstructor =
304 []() { return std::make_unique<TestedClass>(); })
305{
306 // get the property
307 const QMetaObject *metaObject = instance.metaObject();
308 QMetaProperty metaProperty = metaObject->property(metaObject->indexOfProperty(propertyName));
309
310 // in case the TestedClass has setProperty()/property() methods.
311 QObject &testedObj = static_cast<QObject &>(instance);
312
313 QVERIFY2(metaProperty.metaType() == QMetaType::fromType<PropertyType>(),
314 QByteArray("Preconditions not met for ") + propertyName + "\n"
315 "The type of prior and changed value does not match the type of the property.\n"
316 "Please ensure that the types match exactly (convertability is not enough).\n"
317 "You can provide the template types to the "
318 "function explicitly to force a certain type.\n"
319 "Property is " + metaProperty.metaType().name()
320 + " but parameters are " + QMetaType::fromType<PropertyType>().name() + ".\n");
321
322 QVERIFY2(metaProperty.isBindable(), "Preconditions not met for " + QByteArray(propertyName));
323
324 QUntypedBindable bindable = metaProperty.bindable(&instance);
325
326 QScopedPointer<QSignalSpy> spy(nullptr);
327 if (metaProperty.hasNotifySignal())
328 spy.reset(new QSignalSpy(&instance, metaProperty.notifySignal()));
329
331 testedObj.property(propertyName).template value<PropertyType>(), prior, comparator,
332 represent);
333
334 QProperty<PropertyType> propObserver;
335 propObserver.setBinding(bindable.makeBinding());
336 QPROPERTY_TEST_COMPARISON_HELPER(propObserver.value(), prior, comparator, represent);
337
338 // Create a binding that sets the 'changed' value to the property.
339 // This also tests binding loops.
340 QVERIFY(!bindable.hasBinding());
341 std::unique_ptr<TestedClass> helperObj = helperConstructor();
342 QProperty<PropertyType> propSetter(changed); // if the helperConstructor() returns nullptr
343 const QPropertyBinding<PropertyType> binding = helperObj
345 QObject *obj = static_cast<QObject *>(helperObj.get());
346 obj->setProperty(propertyName, QVariant::fromValue(changed));
347 return obj->property(propertyName).template value<PropertyType>();
348 })
349 : Qt::makePropertyBinding(propSetter);
350 bindable.setBinding(binding);
351 QVERIFY(bindable.hasBinding());
352
354 testedObj.property(propertyName).template value<PropertyType>(), changed, comparator,
355 represent);
356 QPROPERTY_TEST_COMPARISON_HELPER(propObserver.value(), changed, comparator, represent);
357 if (spy)
358 QCOMPARE(spy->size(), 1);
359
360 // Attempt to set back the 'prior' value and verify that it has no effect
361 testedObj.setProperty(propertyName, QVariant::fromValue(prior));
363 testedObj.property(propertyName).template value<PropertyType>(), changed, comparator,
364 represent);
365 QPROPERTY_TEST_COMPARISON_HELPER(propObserver.value(), changed, comparator, represent);
366 if (spy)
367 QCOMPARE(spy->size(), 1);
368 if (bindingPreservedOnWrite)
369 QVERIFY(bindable.hasBinding());
370 else
371 QVERIFY(!bindable.hasBinding());
372}
373
382template<typename TestedClass, typename PropertyType>
384 TestedClass &instance, const PropertyType &prior, const PropertyType &changed,
385 const char *propertyName,
386 bool bindingPreservedOnWrite,
387 std::function<std::unique_ptr<TestedClass>(void)> helperConstructor)
388{
389 testWriteOncePropertyBasics<TestedClass, PropertyType>(
390 instance, prior, changed, propertyName, bindingPreservedOnWrite,
391 [](const PropertyType &lhs, const PropertyType &rhs) { return lhs == rhs; },
392 [](const PropertyType &val) { return QTest::toString(val); },
393 helperConstructor);
394}
395
430template<typename TestedClass, typename PropertyType>
432 TestedClass &instance, const PropertyType &initial, const PropertyType &changed,
433 const char *propertyName,
434 std::function<void()> mutator = []() { QFAIL("Data modifier function must be provided"); },
435 std::function<bool(const PropertyType &, const PropertyType &)> comparator =
436 [](const PropertyType &lhs, const PropertyType &rhs) { return lhs == rhs; },
437 std::function<char *(const PropertyType &)> represent =
438 [](const PropertyType &val) { return QTest::toString(val); })
439{
440 // get the property
441 const QMetaObject *metaObject = instance.metaObject();
442 QMetaProperty metaProperty = metaObject->property(metaObject->indexOfProperty(propertyName));
443
444 // in case the TestedClass has setProperty()/property() methods.
445 QObject &testedObj = static_cast<QObject &>(instance);
446
447 QVERIFY2(metaProperty.metaType() == QMetaType::fromType<PropertyType>(),
448 QByteArray("Preconditions not met for ") + propertyName + "\n"
449 "The type of initial and changed value does not match the type of the property.\n"
450 "Please ensure that the types match exactly (convertability is not enough).\n"
451 "You can provide the template types to the "
452 "function explicitly to force a certain type.\n"
453 "Expected was a " + metaProperty.metaType().name()
454 + " but " + QMetaType::fromType<PropertyType>().name() + " was provided.");
455
456 QVERIFY2(metaProperty.isBindable(), "Preconditions not met for " + QByteArray(propertyName));
457
458 QUntypedBindable bindable = metaProperty.bindable(&instance);
459
460 QScopedPointer<QSignalSpy> spy(nullptr);
461 if (metaProperty.hasNotifySignal())
462 spy.reset(new QSignalSpy(&instance, metaProperty.notifySignal()));
463
465 testedObj.property(propertyName).template value<PropertyType>(), initial, comparator,
466 represent);
467
468 // Check that attempting to bind this read-only property to another property has no effect:
469 QProperty<PropertyType> propSetter(initial);
470 QVERIFY(!bindable.hasBinding());
471 bindable.setBinding(Qt::makePropertyBinding(propSetter));
472 QVERIFY(!bindable.hasBinding());
473 propSetter.setValue(changed);
475 testedObj.property(propertyName).template value<PropertyType>(), initial, comparator,
476 represent);
477 if (spy)
478 QCOMPARE(spy->size(), 0);
479
480 QProperty<PropertyType> propObserver;
481 propObserver.setBinding(bindable.makeBinding());
482
483 QPROPERTY_TEST_COMPARISON_HELPER(propObserver.value(), initial, comparator, represent);
484
485 // Invoke mutator function. Now property value should be changed.
486 mutator();
487
489 testedObj.property(propertyName).template value<PropertyType>(), changed, comparator,
490 represent);
491 QPROPERTY_TEST_COMPARISON_HELPER(propObserver.value(), changed, comparator, represent);
492
493 if (spy)
494 QCOMPARE(spy->size(), 1);
495}
496
497} // namespace QTestPrivate
498
500
501#endif // QPROPERTYTESTHELPER_P_H
\inmodule QtCore
Definition qbytearray.h:57
qsizetype size() const noexcept
Definition qlist.h:397
\inmodule QtCore
bool isBindable() const
QMetaType metaType() const
QMetaMethod notifySignal() const
bool isWritable() const
Returns true if this property is writable; otherwise returns false.
QUntypedBindable bindable(QObject *object) const
bool hasNotifySignal() const
Returns true if this property has a corresponding change notify signal; otherwise returns false.
constexpr const char * name() const
Definition qmetatype.h:2680
\inmodule QtCore
Definition qobject.h:103
bool setProperty(const char *name, const QVariant &value)
Sets the value of the object's name property to value.
\inmodule QtTest
Definition qsignalspy.h:22
\inmodule QtCore
Definition qproperty.h:679
QUntypedPropertyBinding makeBinding(const QPropertyBindingSourceLocation &location=QT_PROPERTY_DEFAULT_BINDING_LOCATION) const
Creates a binding returning the underlying properties' value, using a specified source location.
Definition qproperty.h:703
bool hasBinding() const
Returns true if the underlying property has a binding.
Definition qproperty.h:787
bool setBinding(const QUntypedPropertyBinding &binding)
Sets the underlying property's binding to binding.
Definition qproperty.h:768
QPropertyChangeHandler< Functor > onValueChanged(Functor f) const
Installs f as a change handler.
Definition qproperty.h:735
static auto fromValue(T &&value) noexcept(std::is_nothrow_copy_constructible_v< T > &&Private::CanUseInternalSpace< T >) -> std::enable_if_t< std::conjunction_v< std::is_copy_constructible< T >, std::is_destructible< T > >, QVariant >
Definition qvariant.h:536
QSignalSpy spy(myCustomObject, SIGNAL(mySignal(int, QString, double)))
[0]
Combined button and popup list for selecting options.
void testReadWritePropertyBasics(TestedClass &instance, const PropertyType &initial, const PropertyType &changed, const char *propertyName, std::function< bool(const PropertyType &, const PropertyType &)> comparator=[](const PropertyType &lhs, const PropertyType &rhs) { return lhs==rhs;}, std::function< char *(const PropertyType &)> represent=[](const PropertyType &val) { return QTest::toString(val);}, std::function< std::unique_ptr< TestedClass >(void)> helperConstructor=[]() { return std::make_unique< TestedClass >();})
void testReadOnlyPropertyBasics(TestedClass &instance, const PropertyType &initial, const PropertyType &changed, const char *propertyName, std::function< void()> mutator=[]() { QFAIL("Data modifier function must be provided");}, std::function< bool(const PropertyType &, const PropertyType &)> comparator=[](const PropertyType &lhs, const PropertyType &rhs) { return lhs==rhs;}, std::function< char *(const PropertyType &)> represent=[](const PropertyType &val) { return QTest::toString(val);})
void testWriteOncePropertyBasics(TestedClass &instance, const PropertyType &prior, const PropertyType &changed, const char *propertyName, bool bindingPreservedOnWrite=true, std::function< bool(const PropertyType &, const PropertyType &)> comparator=[](const PropertyType &lhs, const PropertyType &rhs) { return lhs==rhs;}, std::function< char *(const PropertyType &)> represent=[](const PropertyType &val) { return QTest::toString(val);}, std::function< std::unique_ptr< TestedClass >(void)> helperConstructor=[]() { return std::make_unique< TestedClass >();})
char * toString(const MyPoint &point)
Definition qcompare.h:63
auto makePropertyBinding(Functor &&f, const QPropertyBindingSourceLocation &location=QT_PROPERTY_DEFAULT_BINDING_LOCATION, std::enable_if_t< std::is_invocable_v< Functor > > *=nullptr)
Definition qproperty.h:212
GLhandleARB obj
[2]
GLuint GLfloat * val
#define QPROPERTY_TEST_COMPARISON_HELPER(actual, expected, comparator, represent)
#define qPrintable(string)
Definition qstring.h:1531
#define QFAIL(message)
Definition qtestcase.h:64
#define QCOMPARE(actual, expected)
Definition qtestcase.h:81
#define QVERIFY(statement)
Definition qtestcase.h:58
#define QVERIFY2(statement, description)
Definition qtestcase.h:70
#define Q_UNUSED(x)
obj metaObject() -> className()
\inmodule QtCore