Revision control
Copy as Markdown
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "bindable.h"
#include <private/qmlutils_p.h>
#include <private/qquickanimation_p.h>
#include <private/qquickbehavior_p.h>
#include <private/qquickitem_p.h>
#include <private/qquickrectangle_p.h>
#include <private/qquicksmoothedanimation_p.h>
#include <private/qquicktext_p.h>
#include <private/qquicktranslate_p.h>
#include <QtTest/qsignalspy.h>
#include <QtTest/qtest.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQml/qqmlengine.h>
#include <QtQuick/qquickview.h>
class tst_qquickbehaviors : public QQmlDataTest
{
Q_OBJECT
public:
tst_qquickbehaviors() : QQmlDataTest(QT_QMLTEST_DATADIR) {}
private slots:
void init() override;
void simpleBehavior();
void scriptTriggered();
void cppTriggered();
void loop();
void colorBehavior();
void parentBehavior();
void replaceBinding();
//void transitionOverrides();
void group();
void valueType();
void emptyBehavior();
void duplicatedBehavior();
void behaviorOnValueTypeProperties();
void explicitSelection();
void nonSelectingBehavior();
void reassignedAnimation();
void disabled();
void dontStart();
void startup();
void groupedPropertyCrash();
void runningTrue();
void sameValue();
void delayedRegistration();
void startOnCompleted();
void multipleChangesToValueType();
void currentValue();
void disabledWriteWhileRunning();
void aliasedProperty();
void innerBehaviorOverwritten();
void oneWay();
void safeToDelete();
void targetProperty();
void bindableProperty();
void defaultQProperty();
};
void tst_qquickbehaviors::init()
{
QQmlDataTest::init();
//work around animation timer bug (QTBUG-22865)
qApp->processEvents();
}
void tst_qquickbehaviors::simpleBehavior()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("simple.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QTRY_VERIFY(qobject_cast<QQuickBehavior*>(rect->findChild<QQuickBehavior*>("MyBehavior"))->animation());
QQuickItemPrivate::get(rect.data())->setState("moved");
QTRY_VERIFY(qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"))->x() > 0);
QTRY_VERIFY(qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"))->x() < 200);
//i.e. the behavior has been triggered
}
void tst_qquickbehaviors::scriptTriggered()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("scripttrigger.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
rect->setColor(QColor("red"));
QTRY_VERIFY(qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"))->x() > 0);
QTRY_VERIFY(qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"))->x() < 200);
//i.e. the behavior has been triggered
}
void tst_qquickbehaviors::cppTriggered()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("cpptrigger.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QQuickRectangle *innerRect = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"));
QTRY_VERIFY(innerRect);
innerRect->setProperty("x", 200);
QTRY_VERIFY(innerRect->x() > 0);
QTRY_VERIFY(innerRect->x() < 200); //i.e. the behavior has been triggered
}
void tst_qquickbehaviors::loop()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("loop.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
//don't crash
QQuickItemPrivate::get(rect.data())->setState("moved");
}
void tst_qquickbehaviors::colorBehavior()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("color.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QQuickItemPrivate::get(rect.data())->setState("red");
QTRY_VERIFY(qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"))->color() != QColor("red"));
QTRY_VERIFY(qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"))->color() != QColor("green"));
//i.e. the behavior has been triggered
}
void tst_qquickbehaviors::parentBehavior()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("parent.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QQuickItemPrivate::get(rect.data())->setState("reparented");
QTRY_VERIFY(rect->findChild<QQuickRectangle*>("MyRect")->parentItem() != rect->findChild<QQuickItem*>("NewParent"));
QTRY_VERIFY(rect->findChild<QQuickRectangle*>("MyRect")->parentItem() == rect->findChild<QQuickItem*>("NewParent"));
}
void tst_qquickbehaviors::replaceBinding()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("binding.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QQuickItemPrivate::get(rect.data())->setState("moved");
QQuickRectangle *innerRect = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"));
QTRY_VERIFY(innerRect);
QTRY_VERIFY(innerRect->x() > 0);
QTRY_VERIFY(innerRect->x() < 200);
//i.e. the behavior has been triggered
QTRY_COMPARE(innerRect->x(), (qreal)200);
rect->setProperty("basex", 10);
QTRY_COMPARE(innerRect->x(), (qreal)200);
rect->setProperty("movedx", 210);
QTRY_COMPARE(innerRect->x(), (qreal)210);
QQuickItemPrivate::get(rect.data())->setState("");
QTRY_VERIFY(innerRect->x() > 10);
QTRY_VERIFY(innerRect->x() < 210); //i.e. the behavior has been triggered
QTRY_COMPARE(innerRect->x(), (qreal)10);
rect->setProperty("movedx", 200);
QTRY_COMPARE(innerRect->x(), (qreal)10);
rect->setProperty("basex", 20);
QTRY_COMPARE(innerRect->x(), (qreal)20);
}
void tst_qquickbehaviors::group()
{
/* XXX TODO Create a test element for this case.
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("groupedProperty.qml")));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QQuickItemPrivate::get(rect.data())->setState("moved");
//QTest::qWait(200);
QTRY_VERIFY(qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"))->x() > 0);
QTRY_VERIFY(qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"))->x() < 200);
//i.e. the behavior has been triggered
}
*/
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("groupedProperty2.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QQuickItemPrivate::get(rect.data())->setState("moved");
QTRY_VERIFY(qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"))->border()->width() > 0);
QTRY_VERIFY(qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"))->border()->width() < 4);
//i.e. the behavior has been triggered
}
}
void tst_qquickbehaviors::valueType()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("valueType.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
//QTBUG-20827
QCOMPARE(rect->color(), QColor::fromRgb(255,0,255));
}
void tst_qquickbehaviors::emptyBehavior()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("empty.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QQuickItemPrivate::get(rect.data())->setState("moved");
qreal x = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"))->x();
QCOMPARE(x, qreal(200)); //should change immediately
}
void tst_qquickbehaviors::duplicatedBehavior()
{
QTest::failOnWarning(QRegularExpression(".*"));
QTest::ignoreMessage(
QtMsgType::QtWarningMsg,
QRegularExpression(
"Attempting to set another interceptor on [^ ]+ property x - unsupported"));
QTest::ignoreMessage(
QtMsgType::QtWarningMsg,
QRegularExpression(
"Attempting to set another interceptor on [^ ]+ property axis - unsupported"));
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("duplicated.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle *>(c.create()));
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
// Expecting no crash
QQuickItemPrivate::get(rect.data())->setState("moved");
}
void tst_qquickbehaviors::behaviorOnValueTypeProperties()
{
QTest::failOnWarning();
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("behaviorOnValueTypeProperties.qml"));
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
std::unique_ptr<QObject> obj(c.create());
QVERIFY(obj);
QQuickRectangle *rect = qobject_cast<QQuickRectangle *>(obj.get());
QVERIFY(rect);
QQmlListProperty<QQuickTransform> transformList = rect->transform();
QCOMPARE(transformList.count(&transformList), 1);
QQuickTransform *transform = transformList.at(&transformList, 0);
QVERIFY(transform);
QQuickRotation *rotation = qobject_cast<QQuickRotation *>(transform);
QVERIFY(rotation);
QCOMPARE(rotation->axis().x(), 1);
QCOMPARE(rotation->axis().y(), 0);
QCOMPARE(rotation->axis().z(), 0);
QTRY_COMPARE(rotation->axis().x(), .2f);
QTRY_COMPARE(rotation->axis().y(), .4f);
QTRY_COMPARE(rotation->axis().z(), .8f);
}
void tst_qquickbehaviors::explicitSelection()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("explicit.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QQuickItemPrivate::get(rect.data())->setState("moved");
QTRY_VERIFY(qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"))->x() > 0);
QTRY_VERIFY(qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"))->x() < 200);
//i.e. the behavior has been triggered
}
void tst_qquickbehaviors::nonSelectingBehavior()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("nonSelecting2.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QQuickItemPrivate::get(rect.data())->setState("moved");
qreal x = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"))->x();
QCOMPARE(x, qreal(200)); //should change immediately
}
void tst_qquickbehaviors::reassignedAnimation()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("reassignedAnimation.qml"));
QString warning = testFileUrl("reassignedAnimation.qml").toString() + ":9:9: QML Behavior: Cannot change the animation assigned to a Behavior.";
QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QCOMPARE(qobject_cast<QQuickNumberAnimation*>(
rect->findChild<QQuickBehavior*>("MyBehavior")->animation())->duration(), 200);
}
void tst_qquickbehaviors::disabled()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("disabled.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QCOMPARE(rect->findChild<QQuickBehavior*>("MyBehavior")->enabled(), false);
QQuickItemPrivate::get(rect.data())->setState("moved");
qreal x = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"))->x();
QCOMPARE(x, qreal(200)); //should change immediately
}
void tst_qquickbehaviors::dontStart()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("dontStart.qml"));
QString warning = c.url().toString() + ":13:13: QML NumberAnimation: setRunning() cannot be used on non-root animation nodes.";
QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QQuickAbstractAnimation *myAnim = rect->findChild<QQuickAbstractAnimation*>("MyAnim");
QVERIFY(myAnim);
QVERIFY(!myAnim->qtAnimation());
}
void tst_qquickbehaviors::startup()
{
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("startup.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QQuickRectangle *innerRect = rect->findChild<QQuickRectangle*>("innerRect");
QVERIFY(innerRect);
QCOMPARE(innerRect->x(), qreal(100)); //should be set immediately
}
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("startup2.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QQuickRectangle *innerRect = rect->findChild<QQuickRectangle*>("innerRect");
QVERIFY(innerRect);
QQuickText *text = rect->findChild<QQuickText*>();
QVERIFY(text);
QCOMPARE(innerRect->x(), text->width()); //should be set immediately
}
}
//QTBUG-10799
void tst_qquickbehaviors::groupedPropertyCrash()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("groupedPropertyCrash.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString())); //don't crash
}
//QTBUG-5491
void tst_qquickbehaviors::runningTrue()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("runningTrue.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QQuickAbstractAnimation *animation = rect->findChild<QQuickAbstractAnimation*>("rotAnim");
QVERIFY(animation);
QSignalSpy runningSpy(animation, SIGNAL(runningChanged(bool)));
rect->setProperty("myValue", 180);
QTRY_VERIFY(runningSpy.size() > 0);
}
//QTBUG-12295
void tst_qquickbehaviors::sameValue()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("qtbug12295.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QQuickRectangle *target = rect->findChild<QQuickRectangle*>("myRect");
QVERIFY(target);
target->setX(100);
QCOMPARE(target->x(), qreal(100));
target->setProperty("x", 0);
QTRY_VERIFY(target->x() != qreal(0) && target->x() != qreal(100));
QTRY_VERIFY(target->x() == qreal(0)); //make sure Behavior has finished.
target->setX(100);
QCOMPARE(target->x(), qreal(100));
//this is the main point of the test -- the behavior needs to be triggered again
//even though we set 0 twice in a row.
target->setProperty("x", 0);
QTRY_VERIFY(target->x() != qreal(0) && target->x() != qreal(100));
}
//QTBUG-18362
void tst_qquickbehaviors::delayedRegistration()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("delayedRegistration.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QQuickItem *innerRect = rect->property("myItem").value<QQuickItem*>();
QVERIFY(innerRect != nullptr);
QCOMPARE(innerRect->property("x").toInt(), int(0));
QTRY_COMPARE(innerRect->property("x").toInt(), int(100));
}
//QTBUG-22555
void tst_qquickbehaviors::startOnCompleted()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("startOnCompleted.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QQuickItem *innerRect = rect->findChild<QQuickRectangle*>();
QVERIFY(innerRect != nullptr);
QCOMPARE(innerRect->property("x").toInt(), int(0));
QTRY_COMPARE(innerRect->property("x").toInt(), int(100));
}
//QTBUG-25139
void tst_qquickbehaviors::multipleChangesToValueType()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("multipleChangesToValueType.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle *>(c.create()));
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QQuickText *text = rect->findChild<QQuickText *>();
QVERIFY(text != nullptr);
QFont value;
value.setPointSize(24);
QCOMPARE(text->property("font").value<QFont>(), value);
QVERIFY(QMetaObject::invokeMethod(rect.data(), "updateFontProperties"));
value.setItalic(true);
value.setWeight(QFont::Bold);
QCOMPARE(text->property("font").value<QFont>(), value);
value.setPointSize(48);
QTRY_COMPARE(text->property("font").value<QFont>(), value);
}
//QTBUG-21549
void tst_qquickbehaviors::currentValue()
{
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("qtbug21549.qml"));
QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(c.create()));
QVERIFY2(!item.isNull(), qPrintable(c.errorString()));
QQuickRectangle *target = item->findChild<QQuickRectangle*>("myRect");
QVERIFY(target);
QCOMPARE(target->x(), qreal(0));
target->setProperty("x", 50);
QCOMPARE(item->property("behaviorCount").toInt(), 1);
QCOMPARE(target->x(), qreal(50));
target->setProperty("x", 50);
QCOMPARE(item->property("behaviorCount").toInt(), 1);
QCOMPARE(target->x(), qreal(50));
target->setX(100);
target->setProperty("x", 100);
QCOMPARE(item->property("behaviorCount").toInt(), 1);
QCOMPARE(target->x(), qreal(100));
}
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("qtbug21549-2.qml"));
QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(c.create()));
QVERIFY2(!item.isNull(), qPrintable(c.errorString()));
QQuickRectangle *target = item->findChild<QQuickRectangle*>("myRect");
QVERIFY(target);
QCOMPARE(target->x(), qreal(0));
target->setProperty("x", 100);
// the spring animation should smoothly transition to the new value triggered
// in the QML (which should be between 50 and 80);
QTRY_COMPARE(item->property("animRunning").toBool(), true);
QTRY_COMPARE(item->property("animRunning").toBool(), false);
QVERIFY2(target->x() > qreal(50) && target->x() < qreal(80), QByteArray::number(target->x()));
}
}
void tst_qquickbehaviors::disabledWriteWhileRunning()
{
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("disabledWriteWhileRunning.qml"));
QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(c.create()));
QVERIFY2(!root.isNull(), qPrintable(c.errorString()));
QQuickRectangle *myRect = qobject_cast<QQuickRectangle*>(root->findChild<QQuickRectangle*>("MyRect"));
QQuickBehavior *myBehavior = qobject_cast<QQuickBehavior*>(root->findChild<QQuickBehavior*>("MyBehavior"));
QQuickNumberAnimation *myAnimation = qobject_cast<QQuickNumberAnimation*>(root->findChild<QQuickNumberAnimation*>("MyAnimation"));
QVERIFY(myRect);
QVERIFY(myBehavior);
QVERIFY(myAnimation);
// initial values
QCOMPARE(myBehavior->enabled(), true);
QCOMPARE(myAnimation->isRunning(), false);
QCOMPARE(myRect->x(), qreal(0));
// start animation
myRect->setProperty("x", 200);
QCOMPARE(myAnimation->isRunning(), true);
QTRY_VERIFY(myRect->x() != qreal(0) && myRect->x() != qreal(200));
QCOMPARE(myBehavior->targetValue(), 200); // grabbed before starting the animation
// set disabled while animation is in progress
myBehavior->setProperty("enabled", false);
QCOMPARE(myAnimation->isRunning(), true);
// force new value while disabled and previous animation is in progress.
// animation should be stopped and value stay at forced value
myRect->setProperty("x", 100);
QCOMPARE(myAnimation->isRunning(), false);
QCOMPARE(myRect->x(), qreal(100));
QCOMPARE(myBehavior->targetValue(), 100);
QTest::qWait(200);
QCOMPARE(myRect->x(), qreal(100));
}
//test additional complications with SmoothedAnimation
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("disabledWriteWhileRunning2.qml"));
QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(c.create()));
QVERIFY2(!root.isNull(), qPrintable(c.errorString()));
QQuickRectangle *myRect = qobject_cast<QQuickRectangle*>(root->findChild<QQuickRectangle*>("MyRect"));
QQuickBehavior *myBehavior = qobject_cast<QQuickBehavior*>(root->findChild<QQuickBehavior*>("MyBehavior"));
QQuickSmoothedAnimation *myAnimation = qobject_cast<QQuickSmoothedAnimation*>(root->findChild<QQuickNumberAnimation*>("MyAnimation"));
QVERIFY(myRect);
QVERIFY(myBehavior);
QVERIFY(myAnimation);
// initial values
QCOMPARE(myBehavior->enabled(), true);
QCOMPARE(myBehavior->targetValue(), QVariant());
QCOMPARE(myAnimation->isRunning(), false);
QCOMPARE(myRect->x(), qreal(0));
// start animation
myRect->setProperty("x", 200);
QCOMPARE(myAnimation->isRunning(), true);
QCOMPARE(myBehavior->targetValue(), 200);
QTRY_VERIFY(myRect->x() != qreal(0) && myRect->x() != qreal(200));
//set second value
myRect->setProperty("x", 300);
QCOMPARE(myAnimation->isRunning(), true);
QCOMPARE(myBehavior->targetValue(), 300);
QTRY_VERIFY(myRect->x() != qreal(0) && myRect->x() != qreal(200));
// set disabled while animation is in progress
myBehavior->setProperty("enabled", false);
QCOMPARE(myAnimation->isRunning(), true);
// force new value while disabled and previous animation is in progress.
// animation should be stopped and value stay at forced value
myRect->setProperty("x", 100);
QCOMPARE(myAnimation->isRunning(), false);
QCOMPARE(myRect->x(), qreal(100));
QCOMPARE(myBehavior->targetValue(), 100);
QTest::qWait(200);
QCOMPARE(myRect->x(), qreal(100));
}
}
void tst_qquickbehaviors::aliasedProperty()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("aliased.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QQuickBehavior* behavior =
qobject_cast<QQuickBehavior*>(rect->findChild<QQuickBehavior*>("behavior"));
QSignalSpy targetValueSpy(behavior, SIGNAL(targetValueChanged()));
QQuickItemPrivate::get(rect.data())->setState("moved");
QCOMPARE(behavior->targetValue(), 400);
QCOMPARE(targetValueSpy.size(), 1);
QScopedPointer<QQuickRectangle> acc(qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("acc")));
QScopedPointer<QQuickRectangle> range(qobject_cast<QQuickRectangle*>(acc->findChild<QQuickRectangle*>("range")));
QTRY_VERIFY(acc->property("value").toDouble() > 0);
QTRY_VERIFY(range->width() > 0);
QTRY_VERIFY(acc->property("value").toDouble() < 400);
QTRY_VERIFY(range->width() < 400);
//i.e. the behavior has been triggered
}
void tst_qquickbehaviors::innerBehaviorOverwritten()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("overwrittenbehavior.qml"));
QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(c.create()));
QQuickBehavior* behavior =
qobject_cast<QQuickBehavior*>(item->findChild<QQuickBehavior*>("behavior"));
QVERIFY(item->property("behaviorTriggered").toBool());
QCOMPARE(behavior->targetValue(), true);
}
void tst_qquickbehaviors::oneWay()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("oneway.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));;
QVERIFY2(!rect.isNull(), qPrintable(c.errorString()));
QQuickBehavior* behavior =
qobject_cast<QQuickBehavior*>(rect->findChild<QQuickBehavior*>("MyBehaviorOneWay"));
QCOMPARE(behavior->enabled(), false);
QCOMPARE(behavior->targetValue(), QVariant());
QSignalSpy targetValueSpy(behavior, SIGNAL(targetValueChanged()));
QQuickRectangle *myRect = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRectOneWay"));
myRect->setProperty("x", 100);
QCOMPARE(behavior->targetValue(), 100);
QCOMPARE(targetValueSpy.size(), 1);
QCOMPARE(behavior->enabled(), false);
qreal x = myRect->x();
QCOMPARE(x, qreal(100)); //should change immediately
QQuickNumberAnimation *myAnimation =
qobject_cast<QQuickNumberAnimation*>(behavior->findChild<QQuickNumberAnimation*>("MyAnimationOneWay"));
QCOMPARE(myAnimation->isRunning(), false);
myRect->setProperty("x", 0);
QCOMPARE(behavior->targetValue(), 0);
QCOMPARE(targetValueSpy.size(), 2);
QCOMPARE(behavior->enabled(), true);
QCOMPARE(myAnimation->isRunning(), true);
QVERIFY(myRect->x() > 0.0);
QTRY_VERIFY(myRect->x() < 100.0);
QTRY_COMPARE(myRect->x(), qreal(0));
QCOMPARE(myAnimation->isRunning(), false);
}
// QTBUG-76749
void tst_qquickbehaviors::safeToDelete()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("delete.qml"));
QScopedPointer<QObject> o(c.create());
QVERIFY(o.data());
}
Q_DECLARE_METATYPE(QQmlProperty)
void tst_qquickbehaviors::targetProperty()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("targetProperty.qml"));
QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(c.create()));
QVERIFY2(!item.isNull(), qPrintable(c.errorString()));
QQuickBehavior* xBehavior =
qobject_cast<QQuickBehavior*>(item->findChild<QQuickBehavior*>("xBehavior"));
QCOMPARE(xBehavior->property("targetProperty").value<QQmlProperty>(), QQmlProperty(item.data(), "x"));
QCOMPARE(item->property("xBehaviorObject").value<QObject*>(), item.data());
QCOMPARE(item->property("xBehaviorName").toString(), "x");
QQuickBehavior* emptyBehavior =
qobject_cast<QQuickBehavior*>(item->findChild<QQuickBehavior*>("emptyBehavior"));
QCOMPARE(emptyBehavior->property("targetProperty").value<QQmlProperty>().isValid(), false);
QCOMPARE(item->property("emptyBehaviorObject").value<QObject*>(), nullptr);
QCOMPARE(item->property("emptyBehaviorName").toString(), "");
}
void tst_qquickbehaviors::bindableProperty()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("bindableProperty.qml"));
QScopedPointer<QObject> root(c.create());
QVERIFY2(root, qPrintable(c.errorString()));
auto testBindable = qobject_cast<TestBindable *>(root.get());
QVERIFY(testBindable);
testBindable->setProperty("targetValue", 100);
QVERIFY(testBindable->prop() != 100);
QTRY_COMPARE(testBindable->prop(), 100);
testBindable->setProperty("enableBehavior", false);
testBindable->setProperty("targetValue", 200);
QCOMPARE(testBindable->prop(), 200);
testBindable->setProperty("enableBehavior", true);
testBindable->setProperty("prop", 300); // write through metaobject system gets intercepted
QVERIFY(testBindable->prop() != 300);
QTRY_COMPARE(testBindable->prop(), 300);
}
void tst_qquickbehaviors::defaultQProperty()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("defaultQProperty.qml"));
QScopedPointer<QObject> root(c.create());
QVERIFY2(root, qPrintable(c.errorString()));
QQuickItem *item = qobject_cast<QQuickItem *>(root.data());
QVERIFY(item);
QCOMPARE(item->height(), 0.0);
QCOMPARE(item->width(), 10.0);
// Both change only once: No intermediate change to 0 on width.
QCOMPARE(root->property("heightChanges").toInt(), 1);
QCOMPARE(root->property("widthChanges").toInt(), 1);
}
QTEST_MAIN(tst_qquickbehaviors)
#include "tst_qquickbehaviors.moc"