/**************************************************************************** ** ** 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$ ** ****************************************************************************/ #include #include #include #include #include using namespace QtPrivate; class tst_QProperty : public QObject { Q_OBJECT private slots: void functorBinding(); void basicDependencies(); void multipleDependencies(); void bindingWithDeletedDependency(); void recursiveDependency(); void bindingAfterUse(); void switchBinding(); void avoidDependencyAllocationAfterFirstEval(); void propertyArrays(); void boolProperty(); void takeBinding(); void replaceBinding(); void swap(); void moveNotifies(); void moveCtor(); void changeHandler(); void propertyChangeHandlerApi(); void subscribe(); void changeHandlerThroughBindings(); void dontTriggerDependenciesIfUnchangedValue(); void bindingSourceLocation(); void bindingError(); void bindingLoop(); void changePropertyFromWithinChangeHandler(); void changePropertyFromWithinChangeHandlerThroughDependency(); void changePropertyFromWithinChangeHandler2(); void settingPropertyValueDoesRemoveBinding(); void genericPropertyBinding(); void genericPropertyBindingBool(); void staticChangeHandler(); }; void tst_QProperty::functorBinding() { QProperty property([]() { return 42; }); QCOMPARE(property.value(), int(42)); property = Qt::makePropertyBinding([]() { return 100; }); QCOMPARE(property.value(), int(100)); property.setBinding([]() { return 50; }); QCOMPARE(property.value(), int(50)); } void tst_QProperty::basicDependencies() { QProperty right(100); QProperty left = Qt::makePropertyBinding(right); QCOMPARE(left.value(), int(100)); right = 42; QCOMPARE(left.value(), int(42)); } void tst_QProperty::multipleDependencies() { QProperty firstDependency(1); QProperty secondDependency(2); QProperty sum; sum = Qt::makePropertyBinding([&]() { return firstDependency + secondDependency; }); QCOMPARE(QPropertyBasePointer::get(firstDependency).observerCount(), 0); QCOMPARE(QPropertyBasePointer::get(secondDependency).observerCount(), 0); QCOMPARE(sum.value(), int(3)); QCOMPARE(QPropertyBasePointer::get(firstDependency).observerCount(), 1); QCOMPARE(QPropertyBasePointer::get(secondDependency).observerCount(), 1); firstDependency = 10; QCOMPARE(sum.value(), int(12)); QCOMPARE(QPropertyBasePointer::get(firstDependency).observerCount(), 1); QCOMPARE(QPropertyBasePointer::get(secondDependency).observerCount(), 1); secondDependency = 20; QCOMPARE(sum.value(), int(30)); QCOMPARE(QPropertyBasePointer::get(firstDependency).observerCount(), 1); QCOMPARE(QPropertyBasePointer::get(secondDependency).observerCount(), 1); firstDependency = 1; secondDependency = 1; QCOMPARE(sum.value(), int(2)); QCOMPARE(QPropertyBasePointer::get(firstDependency).observerCount(), 1); QCOMPARE(QPropertyBasePointer::get(secondDependency).observerCount(), 1); } void tst_QProperty::bindingWithDeletedDependency() { QScopedPointer> dynamicProperty(new QProperty(100)); QProperty staticProperty(1000); QProperty bindingReturnsDynamicProperty(false); QProperty propertySelector; propertySelector = Qt::makePropertyBinding([&]() { if (bindingReturnsDynamicProperty && !dynamicProperty.isNull()) return dynamicProperty->value(); else return staticProperty.value(); }); QCOMPARE(propertySelector.value(), staticProperty.value()); bindingReturnsDynamicProperty = true; QCOMPARE(propertySelector.value(), dynamicProperty->value()); dynamicProperty.reset(); QCOMPARE(propertySelector.value(), 100); bindingReturnsDynamicProperty = false; QCOMPARE(propertySelector.value(), staticProperty.value()); } void tst_QProperty::recursiveDependency() { QProperty first(1); QProperty second; second = Qt::makePropertyBinding(first); QProperty third; third = Qt::makePropertyBinding(second); QCOMPARE(third.value(), int(1)); first = 2; QCOMPARE(third.value(), int(2)); } void tst_QProperty::bindingAfterUse() { QProperty propWithBindingLater(1); QProperty propThatUsesFirstProp; propThatUsesFirstProp = Qt::makePropertyBinding(propWithBindingLater); QCOMPARE(propThatUsesFirstProp.value(), int(1)); QCOMPARE(QPropertyBasePointer::get(propWithBindingLater).observerCount(), 1); QProperty injectedValue(42); propWithBindingLater = Qt::makePropertyBinding(injectedValue); QCOMPARE(propThatUsesFirstProp.value(), int(42)); QCOMPARE(QPropertyBasePointer::get(propWithBindingLater).observerCount(), 1); } void tst_QProperty::switchBinding() { QProperty first(1); QProperty propWithChangingBinding; propWithChangingBinding = Qt::makePropertyBinding(first); QCOMPARE(propWithChangingBinding.value(), 1); QProperty output; output = Qt::makePropertyBinding(propWithChangingBinding); QCOMPARE(output.value(), 1); QProperty second(2); propWithChangingBinding = Qt::makePropertyBinding(second); QCOMPARE(output.value(), 2); } void tst_QProperty::avoidDependencyAllocationAfterFirstEval() { QProperty firstDependency(1); QProperty secondDependency(10); QProperty propWithBinding; propWithBinding = Qt::makePropertyBinding([&]() { return firstDependency + secondDependency; }); QCOMPARE(propWithBinding.value(), int(11)); QVERIFY(QPropertyBasePointer::get(propWithBinding).bindingPtr()); QCOMPARE(QPropertyBasePointer::get(propWithBinding).bindingPtr()->dependencyObserverCount, 2); firstDependency = 100; QCOMPARE(propWithBinding.value(), int(110)); QCOMPARE(QPropertyBasePointer::get(propWithBinding).bindingPtr()->dependencyObserverCount, 2); } void tst_QProperty::propertyArrays() { std::vector> properties; int expectedSum = 0; for (int i = 0; i < 10; ++i) { properties.emplace_back(i); expectedSum += i; } QProperty sum; sum = Qt::makePropertyBinding([&]() { return std::accumulate(properties.begin(), properties.end(), 0); }); QCOMPARE(sum.value(), expectedSum); properties[4] = properties[4] + 42; expectedSum += 42; QCOMPARE(sum.value(), expectedSum); } void tst_QProperty::boolProperty() { static_assert(sizeof(QProperty) == sizeof(void*), "Size of QProperty specialization must not exceed size of pointer"); QProperty first(true); QProperty second(false); QProperty all; all = Qt::makePropertyBinding([&]() { return first && second; }); QCOMPARE(all.value(), false); second = true; QCOMPARE(all.value(), true); } void tst_QProperty::takeBinding() { QPropertyBinding existingBinding; QVERIFY(existingBinding.isNull()); QProperty first(100); QProperty second = Qt::makePropertyBinding(first); QCOMPARE(second.value(), int(100)); existingBinding = second.takeBinding(); QVERIFY(!existingBinding.isNull()); first = 10; QCOMPARE(second.value(), int(100)); second = 25; QCOMPARE(second.value(), int(25)); second = existingBinding; QCOMPARE(second.value(), int(10)); QVERIFY(!existingBinding.isNull()); } void tst_QProperty::replaceBinding() { QProperty first(100); QProperty second = Qt::makePropertyBinding(first); QCOMPARE(second.value(), 100); auto constantBinding = Qt::makePropertyBinding([]() { return 42; }); auto oldBinding = second.setBinding(constantBinding); QCOMPARE(second.value(), 42); second = oldBinding; QCOMPARE(second.value(), 100); } void tst_QProperty::swap() { QProperty firstDependency(1); QProperty secondDependency(2); QProperty first = Qt::makePropertyBinding(firstDependency); QProperty second = Qt::makePropertyBinding(secondDependency); QCOMPARE(first.value(), 1); QCOMPARE(second.value(), 2); std::swap(first, second); QCOMPARE(first.value(), 2); QCOMPARE(second.value(), 1); secondDependency = 20; QCOMPARE(first.value(), 20); QCOMPARE(second.value(), 1); firstDependency = 100; QCOMPARE(first.value(), 20); QCOMPARE(second.value(), 100); } void tst_QProperty::moveNotifies() { QProperty first(1); QProperty second(2); QProperty propertyInTheMiddle = Qt::makePropertyBinding(first); QProperty finalProp1 = Qt::makePropertyBinding(propertyInTheMiddle); QProperty finalProp2 = Qt::makePropertyBinding(propertyInTheMiddle); QCOMPARE(finalProp1.value(), 1); QCOMPARE(finalProp2.value(), 1); QCOMPARE(QPropertyBasePointer::get(propertyInTheMiddle).observerCount(), 2); QProperty other = Qt::makePropertyBinding(second); QCOMPARE(other.value(), 2); QProperty otherDep = Qt::makePropertyBinding(other); QCOMPARE(otherDep.value(), 2); QCOMPARE(QPropertyBasePointer::get(other).observerCount(), 1); propertyInTheMiddle = std::move(other); QCOMPARE(QPropertyBasePointer::get(other).observerCount(), 0); QCOMPARE(finalProp1.value(), 2); QCOMPARE(finalProp2.value(), 2); } void tst_QProperty::moveCtor() { QProperty first(1); QProperty intermediate = Qt::makePropertyBinding(first); QCOMPARE(intermediate.value(), 1); QCOMPARE(QPropertyBasePointer::get(first).observerCount(), 1); QProperty targetProp(std::move(first)); QCOMPARE(QPropertyBasePointer::get(targetProp).observerCount(), 0); } void tst_QProperty::changeHandler() { QProperty testProperty(0); QVector recordedValues; { auto handler = testProperty.onValueChanged([&]() { recordedValues << testProperty; }); testProperty = 1; testProperty = 2; } testProperty = 3; QCOMPARE(recordedValues.count(), 2); QCOMPARE(recordedValues.at(0), 1); QCOMPARE(recordedValues.at(1), 2); } void tst_QProperty::propertyChangeHandlerApi() { int changeHandlerCallCount = 0; QPropertyChangeHandler handler([&changeHandlerCallCount]() { ++changeHandlerCallCount; }); QProperty source1; QProperty source2; handler.setSource(source1); source1 = 100; QCOMPARE(changeHandlerCallCount, 1); handler.setSource(source2); source1 = 101; QCOMPARE(changeHandlerCallCount, 1); source2 = 200; QCOMPARE(changeHandlerCallCount, 2); } void tst_QProperty::subscribe() { QProperty testProperty(42); QVector recordedValues; { auto handler = testProperty.subscribe([&]() { recordedValues << testProperty; }); testProperty = 1; testProperty = 2; } testProperty = 3; QCOMPARE(recordedValues.count(), 3); QCOMPARE(recordedValues.at(0), 42); QCOMPARE(recordedValues.at(1), 1); QCOMPARE(recordedValues.at(2), 2); } void tst_QProperty::changeHandlerThroughBindings() { QProperty trigger(false); QProperty blockTrigger(false); QProperty condition = Qt::makePropertyBinding([&]() { bool triggerValue = trigger; bool blockTriggerValue = blockTrigger; return triggerValue && !blockTriggerValue; }); bool changeHandlerCalled = false; auto handler = condition.onValueChanged([&]() { changeHandlerCalled = true; }); QVERIFY(!condition); QVERIFY(!changeHandlerCalled); trigger = true; QVERIFY(condition); QVERIFY(changeHandlerCalled); changeHandlerCalled = false; trigger = false; QVERIFY(!condition); QVERIFY(changeHandlerCalled); changeHandlerCalled = false; blockTrigger = true; QVERIFY(!condition); QVERIFY(!changeHandlerCalled); } void tst_QProperty::dontTriggerDependenciesIfUnchangedValue() { QProperty property(42); bool triggered = false; QProperty observer = Qt::makePropertyBinding([&]() { triggered = true; return property.value(); }); QCOMPARE(observer.value(), 42); QVERIFY(triggered); triggered = false; property = 42; QCOMPARE(observer.value(), 42); QVERIFY(!triggered); } void tst_QProperty::bindingSourceLocation() { #if defined(QT_PROPERTY_COLLECT_BINDING_LOCATION) auto bindingLine = std::experimental::source_location::current().line() + 1; auto binding = Qt::makePropertyBinding([]() { return 42; }); QCOMPARE(QPropertyBindingPrivate::get(binding)->sourceLocation().line, bindingLine); #else QSKIP("Skipping this in the light of missing binding source location support"); #endif } void tst_QProperty::bindingError() { QProperty prop = Qt::makePropertyBinding([]() -> std::variant { QPropertyBindingError error(QPropertyBindingError::UnknownError); error.setDescription(QLatin1String("my error")); return error; }); QCOMPARE(prop.value(), 0); QCOMPARE(prop.binding().error().description(), QString("my error")); } void tst_QProperty::bindingLoop() { QScopedPointer> firstProp; QProperty secondProp = Qt::makePropertyBinding([&]() -> int { return firstProp ? firstProp->value() : 0; }); QProperty thirdProp = Qt::makePropertyBinding([&]() -> int { return secondProp.value(); }); firstProp.reset(new QProperty()); *firstProp = Qt::makePropertyBinding([&]() -> int { return secondProp.value(); }); QCOMPARE(thirdProp.value(), 0); QCOMPARE(secondProp.binding().error().type(), QPropertyBindingError::BindingLoop); } void tst_QProperty::changePropertyFromWithinChangeHandler() { QProperty property(100); bool resetPropertyOnChange = false; int changeHandlerCallCount = 0; auto handler = property.onValueChanged([&]() { ++changeHandlerCallCount; if (resetPropertyOnChange) property = 100; }); QCOMPARE(property.value(), 100); resetPropertyOnChange = true; property = 42; QCOMPARE(property.value(), 100); // changing the property value inside the change handler won't result in the change // handler being called again. QCOMPARE(changeHandlerCallCount, 1); changeHandlerCallCount = 0; } void tst_QProperty::changePropertyFromWithinChangeHandlerThroughDependency() { QProperty sourceProperty(100); QProperty property = Qt::makePropertyBinding(sourceProperty); bool resetPropertyOnChange = false; int changeHandlerCallCount = 0; auto handler = property.onValueChanged([&]() { ++changeHandlerCallCount; if (resetPropertyOnChange) sourceProperty = 100; }); QCOMPARE(property.value(), 100); resetPropertyOnChange = true; sourceProperty = 42; QCOMPARE(property.value(), 100); // changing the property value inside the change handler won't result in the change // handler being called again. QCOMPARE(changeHandlerCallCount, 1); changeHandlerCallCount = 0; } void tst_QProperty::changePropertyFromWithinChangeHandler2() { QProperty property(100); int changeHandlerCallCount = 0; auto handler = property.onValueChanged([&]() { ++changeHandlerCallCount; property = property.value() + 1; }); QCOMPARE(property.value(), 100); property = 42; QCOMPARE(property.value(), 43); } void tst_QProperty::settingPropertyValueDoesRemoveBinding() { QProperty source(42); QProperty property = Qt::makePropertyBinding(source); QCOMPARE(property.value(), 42); QVERIFY(!property.binding().isNull()); property = 100; QCOMPARE(property.value(), 100); QVERIFY(property.binding().isNull()); source = 1; QCOMPARE(property.value(), 100); QVERIFY(property.binding().isNull()); } void tst_QProperty::genericPropertyBinding() { QProperty property; { QUntypedPropertyBinding doubleBinding(QMetaType::fromType(), [](const QMetaType &, void *) -> bool { Q_ASSERT(false); return false; }, QPropertyBindingSourceLocation()); QVERIFY(!property.setBinding(doubleBinding)); } QUntypedPropertyBinding intBinding(QMetaType::fromType(), [](const QMetaType &metaType, void *dataPtr) -> bool { Q_ASSERT(metaType.id() == qMetaTypeId()); int *intPtr = reinterpret_cast(dataPtr); if (*intPtr == 100) return false; *intPtr = 100; return true; }, QPropertyBindingSourceLocation()); QVERIFY(property.setBinding(intBinding)); QCOMPARE(property.value(), 100); } void tst_QProperty::genericPropertyBindingBool() { QProperty property; QVERIFY(!property.value()); QUntypedPropertyBinding boolBinding(QMetaType::fromType(), [](const QMetaType &, void *dataPtr) -> bool { auto boolPtr = reinterpret_cast(dataPtr); if (*boolPtr) return false; *boolPtr = true; return true; }, QPropertyBindingSourceLocation()); QVERIFY(property.setBinding(boolBinding)); QVERIFY(property.value()); } struct ItemType { QProperty x; QVector observedValues; void xChanged() { observedValues << x.value(); } QPropertyMemberChangeHandler<&ItemType::x, &ItemType::xChanged> test{this}; }; void tst_QProperty::staticChangeHandler() { ItemType t; t.x = 42; t.x = 100; QVector values{42, 100}; QCOMPARE(t.observedValues, values); } QTEST_MAIN(tst_QProperty); #include "tst_qproperty.moc"