Revision control

Copy as Markdown

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <qtest.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQuick/qquickview.h>
#include <private/qquickstateoperations_p.h>
#include <private/qquickanchors_p_p.h>
#include <QtQuick/private/qquickrectangle_p.h>
#include <private/qquickimage_p.h>
#include <QtQuick/private/qquickpropertychanges_p.h>
#include <QtQuick/private/qquickstategroup_p.h>
#include <private/qquickitem_p.h>
#include <private/qqmlproperty_p.h>
#include <QtQuickTestUtils/private/qmlutils_p.h>
#include <QtTest/qsignalspy.h>
class MyAttached : public QObject
{
Q_OBJECT
Q_PROPERTY(int foo READ foo WRITE setFoo)
public:
MyAttached(QObject *parent) : QObject(parent), m_foo(13) {}
int foo() const { return m_foo; }
void setFoo(int f) { m_foo = f; }
private:
int m_foo;
};
class MyRect : public QQuickRectangle
{
Q_OBJECT
Q_PROPERTY(int propertyWithNotify READ propertyWithNotify WRITE setPropertyWithNotify NOTIFY oddlyNamedNotifySignal)
public:
MyRect() {}
void doSomething() { emit didSomething(); }
int propertyWithNotify() const { return m_prop; }
void setPropertyWithNotify(int i) { m_prop = i; emit oddlyNamedNotifySignal(); }
static MyAttached *qmlAttachedProperties(QObject *o) {
return new MyAttached(o);
}
Q_SIGNALS:
void didSomething();
void oddlyNamedNotifySignal();
private:
int m_prop;
};
class MyBindable : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(int prop READ prop WRITE setProp BINDABLE bindableProp)
public:
int prop() {return m_prop; }
void setProp(int i) { m_prop = i; }
QBindable<int> bindableProp() { return &m_prop; }
Q_OBJECT_BINDABLE_PROPERTY(MyBindable, int, m_prop);
};
QML_DECLARE_TYPE(MyRect)
QML_DECLARE_TYPEINFO(MyRect, QML_HAS_ATTACHED_PROPERTIES)
class RemovableObj : public QObject
{
Q_OBJECT
Q_PROPERTY(int prop READ prop WRITE setProp NOTIFY propChanged)
public:
RemovableObj(QObject *parent) : QObject(parent), m_prop(4321) { }
int prop() const { return m_prop; }
public slots:
void setProp(int prop)
{
if (m_prop == prop)
return;
m_prop = prop;
emit propChanged(m_prop);
}
signals:
void propChanged(int prop);
private:
int m_prop;
};
class ContainingObj : public QObject
{
Q_OBJECT
Q_PROPERTY(RemovableObj *obj READ obj NOTIFY objChanged)
RemovableObj *m_obj;
public:
ContainingObj() : m_obj(new RemovableObj(this)) { }
RemovableObj *obj() const { return m_obj; }
Q_INVOKABLE void reset()
{
if (m_obj) {
m_obj->deleteLater();
}
m_obj = new RemovableObj(this);
emit objChanged();
}
signals:
void objChanged();
};
class tst_qquickstates : public QQmlDataTest
{
Q_OBJECT
public:
tst_qquickstates() : QQmlDataTest(QT_QMLTEST_DATADIR)
{
#ifdef QML_DISABLE_INTERNAL_DEFERRED_PROPERTIES
qputenv("QML_DISABLE_INTERNAL_DEFERRED_PROPERTIES", "1");
#endif
}
private:
QByteArray fullDataPath(const QString &path) const;
private slots:
void initTestCase() override;
void basicChanges();
void attachedPropertyChanges();
void basicExtension();
void basicBinding();
void signalOverride();
void signalOverrideCrash();
void signalOverrideCrash2();
void signalOverrideCrash3();
void signalOverrideCrash4();
void parentChange();
void parentChangeErrors();
void anchorChanges();
void anchorChanges2();
void anchorChanges3();
void anchorChanges4();
void anchorChanges5();
void anchorChangesRTL();
void anchorChangesRTL2();
void anchorChangesRTL3();
void anchorChangesCrash();
void anchorRewindBug();
void anchorRewindBug2();
void anchorRewind_keepsSize_whenStateResetsDefaultAnchors();
void script();
void restoreEntryValues();
void explicitChanges();
void propertyErrors();
void incorrectRestoreBug();
void autoStateAtStartupRestoreBug();
void deletingChange();
void deletingState();
void tempState();
void illegalTempState();
void nonExistantProperty();
void reset();
void illegalObjectCreation();
void whenOrdering();
void urlResolution();
void unnamedWhen();
void returnToBase();
void extendsBug();
void editProperties();
void QTBUG_14830();
void avoidFastForward();
void revertListBug();
void QTBUG_38492();
void revertListMemoryLeak();
void duplicateStateName();
void trivialWhen();
void jsValueWhen_data();
void jsValueWhen();
void noStateOsciallation();
void parentChangeCorrectReversal();
void revertNullObjectBinding();
void bindableProperties();
void parentChangeInvolvingBindings();
void deferredProperties();
void rewindAnchorChange();
void rewindAnchorChangeSize();
void bindingProperlyRemovedWithTransition();
void doNotCrashOnBroken();
};
void tst_qquickstates::initTestCase()
{
QQmlDataTest::initTestCase();
qmlRegisterType<MyRect>("Qt.test", 1, 0, "MyRectangle");
qmlRegisterSingletonType<ContainingObj>(
"Qt.test", 1, 0, "ContainingObj", [](QQmlEngine *engine, QJSEngine *) {
static ContainingObj instance;
engine->setObjectOwnership(&instance, QQmlEngine::CppOwnership);
return &instance;
});
qmlRegisterUncreatableType<RemovableObj>("Qt.test", 1, 0, "RemovableObj", "Uncreatable");
}
QByteArray tst_qquickstates::fullDataPath(const QString &path) const
{
return testFileUrl(path).toString().toUtf8();
}
void tst_qquickstates::basicChanges()
{
QQmlEngine engine;
{
QQmlComponent rectComponent(&engine, testFileUrl("basicChanges.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QCOMPARE(rect->color(),QColor("red"));
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
rectPrivate->setState("");
QCOMPARE(rect->color(),QColor("red"));
}
{
QQmlComponent rectComponent(&engine, testFileUrl("basicChanges2.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QCOMPARE(rect->color(),QColor("red"));
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
rectPrivate->setState("green");
QCOMPARE(rect->color(),QColor("green"));
rectPrivate->setState("");
QCOMPARE(rect->color(),QColor("red"));
rectPrivate->setState("green");
QCOMPARE(rect->color(),QColor("green"));
}
{
QQmlComponent rectComponent(&engine, testFileUrl("basicChanges3.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QCOMPARE(rect->color(),QColor("red"));
QCOMPARE(rect->border()->width(),1.0);
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
QCOMPARE(rect->border()->width(),1.0);
rectPrivate->setState("bordered");
QCOMPARE(rect->color(),QColor("red"));
QCOMPARE(rect->border()->width(),2.0);
rectPrivate->setState("");
QCOMPARE(rect->color(),QColor("red"));
QCOMPARE(rect->border()->width(),1.0);
//### we should be checking that this is an implicit rather than explicit 1 (which currently fails)
rectPrivate->setState("bordered");
QCOMPARE(rect->color(),QColor("red"));
QCOMPARE(rect->border()->width(),2.0);
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
QCOMPARE(rect->border()->width(),1.0);
}
{
// Test basicChanges4.qml can magically connect to propertyWithNotify's notify
// signal using 'onPropertyWithNotifyChanged' even though the signal name is
// actually 'oddlyNamedNotifySignal'
QQmlComponent component(&engine, testFileUrl("basicChanges4.qml"));
QVERIFY(component.isReady());
QScopedPointer<MyRect> rect(qobject_cast<MyRect*>(component.create()));
QVERIFY(rect != nullptr);
QMetaProperty prop = rect->metaObject()->property(rect->metaObject()->indexOfProperty("propertyWithNotify"));
QVERIFY(prop.hasNotifySignal());
QString notifySignal = prop.notifySignal().methodSignature();
QVERIFY(!notifySignal.startsWith("propertyWithNotifyChanged("));
QCOMPARE(rect->color(), QColor(Qt::red));
rect->setPropertyWithNotify(100);
QCOMPARE(rect->color(), QColor(Qt::blue));
}
}
void tst_qquickstates::attachedPropertyChanges()
{
QQmlEngine engine;
QQmlComponent component(&engine, testFileUrl("attachedPropertyChanges.qml"));
QVERIFY(component.isReady());
QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(component.create()));
QVERIFY(item != nullptr);
QCOMPARE(item->width(), 50.0);
// Ensure attached property has been changed
QObject *attObj = qmlAttachedPropertiesObject<MyRect>(item.get(), false);
QVERIFY(attObj);
MyAttached *att = qobject_cast<MyAttached*>(attObj);
QVERIFY(att);
QCOMPARE(att->foo(), 1);
}
void tst_qquickstates::basicExtension()
{
QQmlEngine engine;
{
QQmlComponent rectComponent(&engine, testFileUrl("basicExtension.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QCOMPARE(rect->color(),QColor("red"));
QCOMPARE(rect->border()->width(),1.0);
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
QCOMPARE(rect->border()->width(),1.0);
rectPrivate->setState("bordered");
QCOMPARE(rect->color(),QColor("blue"));
QCOMPARE(rect->border()->width(),2.0);
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
QCOMPARE(rect->border()->width(),1.0);
rectPrivate->setState("");
QCOMPARE(rect->color(),QColor("red"));
QCOMPARE(rect->border()->width(),1.0);
rectPrivate->setState("bordered");
QCOMPARE(rect->color(),QColor("blue"));
QCOMPARE(rect->border()->width(),2.0);
rectPrivate->setState("");
QCOMPARE(rect->color(),QColor("red"));
QCOMPARE(rect->border()->width(),1.0);
}
{
QQmlComponent rectComponent(&engine, testFileUrl("fakeExtension.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QCOMPARE(rect->color(),QColor("red"));
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
rectPrivate->setState("green");
QCOMPARE(rect->color(),QColor("green"));
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
rectPrivate->setState("green");
QCOMPARE(rect->color(),QColor("green"));
rectPrivate->setState("");
QCOMPARE(rect->color(),QColor("red"));
rectPrivate->setState("green");
QCOMPARE(rect->color(),QColor("green"));
}
}
void tst_qquickstates::basicBinding()
{
QQmlEngine engine;
{
QQmlComponent rectComponent(&engine, testFileUrl("basicBinding.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QCOMPARE(rect->color(),QColor("red"));
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
rectPrivate->setState("");
QCOMPARE(rect->color(),QColor("red"));
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
rect->setProperty("sourceColor", QColor("green"));
QCOMPARE(rect->color(),QColor("green"));
rectPrivate->setState("");
QCOMPARE(rect->color(),QColor("red"));
rect->setProperty("sourceColor", QColor("yellow"));
QCOMPARE(rect->color(),QColor("red"));
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("yellow"));
}
{
QQmlComponent rectComponent(&engine, testFileUrl("basicBinding2.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QCOMPARE(rect->color(),QColor("red"));
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
rectPrivate->setState("");
QCOMPARE(rect->color(),QColor("red"));
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
rect->setProperty("sourceColor", QColor("green"));
QCOMPARE(rect->color(),QColor("blue"));
rectPrivate->setState("");
QCOMPARE(rect->color(),QColor("green"));
rect->setProperty("sourceColor", QColor("yellow"));
QCOMPARE(rect->color(),QColor("yellow"));
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
rectPrivate->setState("");
QCOMPARE(rect->color(),QColor("yellow"));
}
{
QQmlComponent rectComponent(&engine, testFileUrl("basicBinding3.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QCOMPARE(rect->color(),QColor("red"));
rect->setProperty("sourceColor", QColor("green"));
QCOMPARE(rect->color(),QColor("green"));
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
rect->setProperty("sourceColor", QColor("red"));
QCOMPARE(rect->color(),QColor("blue"));
rect->setProperty("sourceColor2", QColor("yellow"));
QCOMPARE(rect->color(),QColor("yellow"));
rectPrivate->setState("");
QCOMPARE(rect->color(),QColor("red"));
rect->setProperty("sourceColor2", QColor("green"));
QCOMPARE(rect->color(),QColor("red"));
rect->setProperty("sourceColor", QColor("yellow"));
QCOMPARE(rect->color(),QColor("yellow"));
}
{
QQmlComponent rectComponent(&engine, testFileUrl("basicBinding4.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QCOMPARE(rect->color(),QColor("red"));
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
rect->setProperty("sourceColor", QColor("yellow"));
QCOMPARE(rect->color(),QColor("yellow"));
rectPrivate->setState("green");
QCOMPARE(rect->color(),QColor("green"));
rect->setProperty("sourceColor", QColor("purple"));
QCOMPARE(rect->color(),QColor("green"));
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("purple"));
rectPrivate->setState("green");
QCOMPARE(rect->color(),QColor("green"));
rectPrivate->setState("");
QCOMPARE(rect->color(),QColor("red"));
}
}
void tst_qquickstates::signalOverride()
{
QQmlEngine engine;
{
QQmlComponent rectComponent(&engine, testFileUrl("signalOverride.qml"));
QScopedPointer<MyRect> rect(qobject_cast<MyRect*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QCOMPARE(rect->color(),QColor("red"));
rect->doSomething();
QCOMPARE(rect->color(),QColor("blue"));
QQuickItemPrivate::get(rect.get())->setState("green");
rect->doSomething();
QCOMPARE(rect->color(),QColor("green"));
}
{
QQmlComponent rectComponent(&engine, testFileUrl("signalOverride2.qml"));
QScopedPointer<MyRect>rect(qobject_cast<MyRect*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QCOMPARE(rect->color(),QColor("white"));
rect->doSomething();
QCOMPARE(rect->color(),QColor("blue"));
QQuickRectangle *innerRect = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("extendedRect"));
QQuickItemPrivate::get(innerRect)->setState("green");
rect->doSomething();
QCOMPARE(rect->color(),QColor("blue"));
QCOMPARE(innerRect->color(),QColor("green"));
QCOMPARE(innerRect->property("extendedColor").value<QColor>(),QColor("green"));
}
}
void tst_qquickstates::signalOverrideCrash()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("signalOverrideCrash.qml"));
QScopedPointer<MyRect> rect(qobject_cast<MyRect*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate::get(rect.get())->setState("overridden");
rect->doSomething();
}
void tst_qquickstates::signalOverrideCrash2()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("signalOverrideCrash2.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
auto priv = QQuickItemPrivate::get(rect.get());
priv->setState("state1");
priv->setState("state2");
priv->setState("state1");
}
void tst_qquickstates::signalOverrideCrash3()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("signalOverrideCrash3.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
auto priv = QQuickItemPrivate::get(rect.get());
priv->setState("state1");
priv->setState("");
priv->setState("state2");
priv->setState("");
}
void tst_qquickstates::signalOverrideCrash4()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("signalOverrideCrash4.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
rectPrivate->setState("state1");
rectPrivate->setState("state2");
rectPrivate->setState("state1");
rectPrivate->setState("state2");
rectPrivate->setState("");
}
void tst_qquickstates::parentChange()
{
QQmlEngine engine;
{
QQmlComponent rectComponent(&engine, testFileUrl("parentChange1.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickRectangle *innerRect = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"));
QVERIFY(innerRect != nullptr);
QQmlListReference list(rect.get(), "states");
QQuickState *state = qobject_cast<QQuickState*>(list.at(0));
QVERIFY(state != nullptr);
qmlExecuteDeferred(state);
QQuickParentChange *pChange = qobject_cast<QQuickParentChange*>(state->operationAt(0));
QVERIFY(pChange != nullptr);
QQuickItem *nParent = qobject_cast<QQuickItem*>(rect->findChild<QQuickItem*>("NewParent"));
QVERIFY(nParent != nullptr);
QCOMPARE(pChange->parent(), nParent);
QQuickItemPrivate::get(rect.get())->setState("reparented");
QCOMPARE(innerRect->rotation(), qreal(0));
QCOMPARE(innerRect->scale(), qreal(1));
QCOMPARE(innerRect->x(), qreal(-133));
QCOMPARE(innerRect->y(), qreal(-300));
}
{
QQmlComponent rectComponent(&engine, testFileUrl("parentChange2.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QQuickRectangle *innerRect = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"));
QVERIFY(innerRect != nullptr);
rectPrivate->setState("reparented");
QCOMPARE(innerRect->rotation(), qreal(15));
QCOMPARE(innerRect->scale(), qreal(.5));
QCOMPARE(QString("%1").arg(innerRect->x()), QString("%1").arg(-19.9075));
QCOMPARE(QString("%1").arg(innerRect->y()), QString("%1").arg(-8.73433));
}
{
QQmlComponent rectComponent(&engine, testFileUrl("parentChange3.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QQuickRectangle *innerRect = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"));
QVERIFY(innerRect != nullptr);
rectPrivate->setState("reparented");
QCOMPARE(innerRect->rotation(), qreal(-37));
QCOMPARE(innerRect->scale(), qreal(.25));
QCOMPARE(QString("%1").arg(innerRect->x()), QString("%1").arg(-217.305));
QCOMPARE(QString("%1").arg(innerRect->y()), QString("%1").arg(-164.413));
rectPrivate->setState("");
QCOMPARE(innerRect->rotation(), qreal(0));
QCOMPARE(innerRect->scale(), qreal(1));
QCOMPARE(innerRect->x(), qreal(5));
//do a non-qFuzzyCompare fuzzy compare
QVERIFY(innerRect->y() < qreal(0.00001) && innerRect->y() > qreal(-0.00001));
}
{
QQmlComponent rectComponent(&engine, testFileUrl("parentChange6.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickRectangle *innerRect = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"));
QVERIFY(innerRect != nullptr);
QQuickItemPrivate::get(rect.get())->setState("reparented");
QCOMPARE(innerRect->rotation(), qreal(180));
QCOMPARE(innerRect->scale(), qreal(1));
QCOMPARE(innerRect->x(), qreal(-105));
QCOMPARE(innerRect->y(), qreal(-105));
}
}
void tst_qquickstates::parentChangeErrors()
{
QQmlEngine engine;
{
QQmlComponent rectComponent(&engine, testFileUrl("parentChange4.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickRectangle *innerRect = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"));
QVERIFY(innerRect != nullptr);
QTest::ignoreMessage(QtWarningMsg, fullDataPath("parentChange4.qml") + ":25:9: QML ParentChange: Unable to preserve appearance under non-uniform scale");
QQuickItemPrivate::get(rect.get())->setState("reparented");
QCOMPARE(innerRect->rotation(), qreal(0));
QCOMPARE(innerRect->scale(), qreal(1));
QCOMPARE(innerRect->x(), qreal(5));
QCOMPARE(innerRect->y(), qreal(5));
}
{
QQmlComponent rectComponent(&engine, testFileUrl("parentChange5.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickRectangle *innerRect = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"));
QVERIFY(innerRect != nullptr);
QTest::ignoreMessage(QtWarningMsg, fullDataPath("parentChange5.qml") + ":25:9: QML ParentChange: Unable to preserve appearance under complex transform");
QQuickItemPrivate::get(rect.get())->setState("reparented");
QCOMPARE(innerRect->rotation(), qreal(0));
QCOMPARE(innerRect->scale(), qreal(1));
QCOMPARE(innerRect->x(), qreal(5));
QCOMPARE(innerRect->y(), qreal(5));
}
}
void tst_qquickstates::anchorChanges()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("anchorChanges1.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QQuickRectangle *innerRect = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"));
QVERIFY(innerRect != nullptr);
QQmlListReference list(rect.get(), "states");
QQuickState *state = qobject_cast<QQuickState*>(list.at(0));
QVERIFY(state != nullptr);
qmlExecuteDeferred(state);
QQuickAnchorChanges *aChanges = qobject_cast<QQuickAnchorChanges*>(state->operationAt(0));
QVERIFY(aChanges != nullptr);
QCOMPARE(aChanges->anchors()->left().isUndefinedLiteral(), true);
QVERIFY(!aChanges->anchors()->left().isEmpty());
QVERIFY(!aChanges->anchors()->right().isEmpty());
rectPrivate->setState("right");
QCOMPARE(innerRect->x(), qreal(150));
QCOMPARE(aChanges->object(), qobject_cast<QQuickItem*>(innerRect));
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->left().anchorLine, QQuickAnchors::InvalidAnchor); //### was reset (how do we distinguish from not set at all)
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->right().item, rectPrivate->right().item);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->right().anchorLine, rectPrivate->right().anchorLine);
rectPrivate->setState("");
QCOMPARE(innerRect->x(), qreal(5));
}
void tst_qquickstates::anchorChanges2()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("anchorChanges2.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QQuickRectangle *innerRect = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"));
QVERIFY(innerRect != nullptr);
rectPrivate->setState("right");
QCOMPARE(innerRect->x(), qreal(150));
rectPrivate->setState("");
QCOMPARE(innerRect->x(), qreal(5));
}
void tst_qquickstates::anchorChanges3()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("anchorChanges3.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QQuickRectangle *innerRect = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"));
QVERIFY(innerRect != nullptr);
QQuickItem *leftGuideline = qobject_cast<QQuickItem*>(rect->findChild<QQuickItem*>("LeftGuideline"));
QVERIFY(leftGuideline != nullptr);
QQuickItem *bottomGuideline = qobject_cast<QQuickItem*>(rect->findChild<QQuickItem*>("BottomGuideline"));
QVERIFY(bottomGuideline != nullptr);
QQmlListReference list(rect.get(), "states");
QQuickState *state = qobject_cast<QQuickState*>(list.at(0));
QVERIFY(state != nullptr);
qmlExecuteDeferred(state);
QQuickAnchorChanges *aChanges = qobject_cast<QQuickAnchorChanges*>(state->operationAt(0));
QVERIFY(aChanges != nullptr);
QVERIFY(!aChanges->anchors()->top().isEmpty());
QVERIFY(!aChanges->anchors()->bottom().isEmpty());
rectPrivate->setState("reanchored");
QCOMPARE(aChanges->object(), qobject_cast<QQuickItem*>(innerRect));
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->left().item, QQuickItemPrivate::get(leftGuideline)->left().item);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->left().anchorLine, QQuickItemPrivate::get(leftGuideline)->left().anchorLine);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->right().item, rectPrivate->right().item);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->right().anchorLine, rectPrivate->right().anchorLine);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->top().item, rectPrivate->top().item);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->top().anchorLine, rectPrivate->top().anchorLine);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->bottom().item, QQuickItemPrivate::get(bottomGuideline)->bottom().item);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->bottom().anchorLine, QQuickItemPrivate::get(bottomGuideline)->bottom().anchorLine);
QCOMPARE(innerRect->x(), qreal(10));
QCOMPARE(innerRect->y(), qreal(0));
QCOMPARE(innerRect->width(), qreal(190));
QCOMPARE(innerRect->height(), qreal(150));
rectPrivate->setState("");
QCOMPARE(innerRect->x(), qreal(0));
QCOMPARE(innerRect->y(), qreal(10));
QCOMPARE(innerRect->width(), qreal(150));
QCOMPARE(innerRect->height(), qreal(190));
}
void tst_qquickstates::anchorChanges4()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("anchorChanges4.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickRectangle *innerRect = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"));
QVERIFY(innerRect != nullptr);
QQuickItem *leftGuideline = qobject_cast<QQuickItem*>(rect->findChild<QQuickItem*>("LeftGuideline"));
QVERIFY(leftGuideline != nullptr);
QQuickItem *bottomGuideline = qobject_cast<QQuickItem*>(rect->findChild<QQuickItem*>("BottomGuideline"));
QVERIFY(bottomGuideline != nullptr);
QQmlListReference list(rect.get(), "states");
QQuickState *state = qobject_cast<QQuickState*>(list.at(0));
QVERIFY(state != nullptr);
qmlExecuteDeferred(state);
QQuickAnchorChanges *aChanges = qobject_cast<QQuickAnchorChanges*>(state->operationAt(0));
QVERIFY(aChanges != nullptr);
QVERIFY(!aChanges->anchors()->horizontalCenter().isEmpty());
QVERIFY(!aChanges->anchors()->verticalCenter().isEmpty());
QQuickItemPrivate::get(rect.get())->setState("reanchored");
QCOMPARE(aChanges->object(), qobject_cast<QQuickItem*>(innerRect));
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->horizontalCenter().item, QQuickItemPrivate::get(bottomGuideline)->horizontalCenter().item);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->horizontalCenter().anchorLine, QQuickItemPrivate::get(bottomGuideline)->horizontalCenter().anchorLine);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->verticalCenter().item, QQuickItemPrivate::get(leftGuideline)->verticalCenter().item);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->verticalCenter().anchorLine, QQuickItemPrivate::get(leftGuideline)->verticalCenter().anchorLine);
}
void tst_qquickstates::anchorChanges5()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("anchorChanges5.qml"));
QScopedPointer<QQuickRectangle>rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickRectangle *innerRect = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"));
QVERIFY(innerRect != nullptr);
QQuickItem *leftGuideline = qobject_cast<QQuickItem*>(rect->findChild<QQuickItem*>("LeftGuideline"));
QVERIFY(leftGuideline != nullptr);
QQuickItem *bottomGuideline = qobject_cast<QQuickItem*>(rect->findChild<QQuickItem*>("BottomGuideline"));
QVERIFY(bottomGuideline != nullptr);
QQmlListReference list(rect.get(), "states");
QQuickState *state = qobject_cast<QQuickState*>(list.at(0));
QVERIFY(state != nullptr);
qmlExecuteDeferred(state);
QQuickAnchorChanges *aChanges = qobject_cast<QQuickAnchorChanges*>(state->operationAt(0));
QVERIFY(aChanges != nullptr);
QVERIFY(!aChanges->anchors()->baseline().isEmpty());
QQuickItemPrivate::get(rect.get())->setState("reanchored");
QCOMPARE(aChanges->object(), qobject_cast<QQuickItem*>(innerRect));
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->horizontalCenter().item, QQuickItemPrivate::get(bottomGuideline)->horizontalCenter().item);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->horizontalCenter().anchorLine, QQuickItemPrivate::get(bottomGuideline)->horizontalCenter().anchorLine);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->baseline().item, QQuickItemPrivate::get(leftGuideline)->baseline().item);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->baseline().anchorLine, QQuickItemPrivate::get(leftGuideline)->baseline().anchorLine);
}
void mirrorAnchors(QQuickItem *item) {
QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
itemPrivate->setLayoutMirror(true);
}
qreal offsetRTL(QQuickItem *anchorItem, QQuickItem *item) {
return anchorItem->width()+2*anchorItem->x()-item->width();
}
void tst_qquickstates::anchorChangesRTL()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("anchorChanges1.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QQuickRectangle *innerRect = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"));
QVERIFY(innerRect != nullptr);
mirrorAnchors(innerRect);
QQmlListReference list(rect.get(), "states");
QQuickState *state = qobject_cast<QQuickState*>(list.at(0));
QVERIFY(state != nullptr);
qmlExecuteDeferred(state);
QQuickAnchorChanges *aChanges = qobject_cast<QQuickAnchorChanges*>(state->operationAt(0));
QVERIFY(aChanges != nullptr);
rectPrivate->setState("right");
QCOMPARE(innerRect->x(), offsetRTL(rect.get(), innerRect) - qreal(150));
QCOMPARE(aChanges->object(), qobject_cast<QQuickItem*>(innerRect));
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->left().anchorLine, QQuickAnchors::InvalidAnchor); //### was reset (how do we distinguish from not set at all)
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->right().item, rectPrivate->right().item);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->right().anchorLine, rectPrivate->right().anchorLine);
rectPrivate->setState("");
QCOMPARE(innerRect->x(), offsetRTL(rect.get(), innerRect) -qreal(5));
}
void tst_qquickstates::anchorChangesRTL2()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("anchorChanges2.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QQuickRectangle *innerRect = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"));
QVERIFY(innerRect != nullptr);
mirrorAnchors(innerRect);
rectPrivate->setState("right");
QCOMPARE(innerRect->x(), offsetRTL(rect.get(), innerRect) - qreal(150));
rectPrivate->setState("");
QCOMPARE(innerRect->x(), offsetRTL(rect.get(), innerRect) - qreal(5));
}
void tst_qquickstates::anchorChangesRTL3()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("anchorChanges3.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QQuickRectangle *innerRect = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("MyRect"));
QVERIFY(innerRect != nullptr);
mirrorAnchors(innerRect);
QQuickItem *leftGuideline = qobject_cast<QQuickItem*>(rect->findChild<QQuickItem*>("LeftGuideline"));
QVERIFY(leftGuideline != nullptr);
QQuickItem *bottomGuideline = qobject_cast<QQuickItem*>(rect->findChild<QQuickItem*>("BottomGuideline"));
QVERIFY(bottomGuideline != nullptr);
QQmlListReference list(rect.get(), "states");
QQuickState *state = qobject_cast<QQuickState*>(list.at(0));
QVERIFY(state != nullptr);
qmlExecuteDeferred(state);
QQuickAnchorChanges *aChanges = qobject_cast<QQuickAnchorChanges*>(state->operationAt(0));
QVERIFY(aChanges != nullptr);
rectPrivate->setState("reanchored");
QCOMPARE(aChanges->object(), qobject_cast<QQuickItem*>(innerRect));
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->left().item, QQuickItemPrivate::get(leftGuideline)->left().item);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->left().anchorLine, QQuickItemPrivate::get(leftGuideline)->left().anchorLine);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->right().item, rectPrivate->right().item);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->right().anchorLine, rectPrivate->right().anchorLine);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->top().item, rectPrivate->top().item);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->top().anchorLine, rectPrivate->top().anchorLine);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->bottom().item, QQuickItemPrivate::get(bottomGuideline)->bottom().item);
QCOMPARE(QQuickItemPrivate::get(aChanges->object())->anchors()->bottom().anchorLine, QQuickItemPrivate::get(bottomGuideline)->bottom().anchorLine);
QCOMPARE(innerRect->x(), offsetRTL(leftGuideline, innerRect) - qreal(10));
QCOMPARE(innerRect->y(), qreal(0));
// between left side of parent and leftGuideline.x: 10, which has width 0
QCOMPARE(innerRect->width(), qreal(10));
QCOMPARE(innerRect->height(), qreal(150));
rectPrivate->setState("");
QCOMPARE(innerRect->x(), offsetRTL(rect.get(), innerRect) - qreal(0));
QCOMPARE(innerRect->y(), qreal(10));
// between right side of parent and left side of rightGuideline.x: 150, which has width 0
QCOMPARE(innerRect->width(), qreal(50));
QCOMPARE(innerRect->height(), qreal(190));
}
//QTBUG-9609
void tst_qquickstates::anchorChangesCrash()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("anchorChangesCrash.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate::get(rect.get())->setState("reanchored");
}
// QTBUG-12273
void tst_qquickstates::anchorRewindBug()
{
QScopedPointer<QQuickView> view(new QQuickView);
view->setSource(testFileUrl("anchorRewindBug.qml"));
view->show();
QVERIFY(QTest::qWaitForWindowExposed(view.get()));
QQuickRectangle *rect = qobject_cast<QQuickRectangle*>(view->rootObject());
QVERIFY(rect != nullptr);
QQuickItem * column = rect->findChild<QQuickItem*>("column");
QVERIFY(column != nullptr);
QVERIFY(!QQuickItemPrivate::get(column)->heightValid());
QVERIFY(!QQuickItemPrivate::get(column)->widthValid());
QCOMPARE(column->height(), 200.0);
QQuickItemPrivate::get(rect)->setState("reanchored");
// column height and width should stay implicit
// and column's implicit resizing should still work
QVERIFY(!QQuickItemPrivate::get(column)->heightValid());
QVERIFY(!QQuickItemPrivate::get(column)->widthValid());
QTRY_COMPARE(column->height(), 100.0);
QQuickItemPrivate::get(rect)->setState("");
// column height and width should stay implicit
// and column's implicit resizing should still work
QVERIFY(!QQuickItemPrivate::get(column)->heightValid());
QVERIFY(!QQuickItemPrivate::get(column)->widthValid());
QTRY_COMPARE(column->height(), 200.0);
}
// QTBUG-11834
void tst_qquickstates::anchorRewindBug2()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("anchorRewindBug2.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickRectangle *mover = rect->findChild<QQuickRectangle*>("mover");
QVERIFY(mover != nullptr);
QCOMPARE(mover->y(), qreal(0.0));
QCOMPARE(mover->width(), qreal(50.0));
QQuickItemPrivate::get(rect.get())->setState("anchored");
QCOMPARE(mover->y(), qreal(250.0));
QCOMPARE(mover->width(), qreal(200.0));
QQuickItemPrivate::get(rect.get())->setState("");
QCOMPARE(mover->y(), qreal(0.0));
QCOMPARE(mover->width(), qreal(50.0));
}
// QTBUG-126057
void tst_qquickstates::anchorRewind_keepsSize_whenStateResetsDefaultAnchors()
{
// Arrange
QQmlEngine engine;
// NOTE: Contains two nested rectangles, inner is by default anchored to the top left corner of
// its parent. A state is initially "anchored" which removes the default anchoring and anchors
// the inner rectangle to the bottom right corner of the parent. The size of the inner rectangle
// is assigned to 50x50 on Component.onCompleted of outer rectangle.
QQmlComponent rectComponent(&engine, testFileUrl("anchorRewindBug3.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickRectangle *mover = rect->findChild<QQuickRectangle*>("inner");
QVERIFY(mover != nullptr);
// Act
QQuickItemPrivate::get(rect.get())->setState("");
// Assert
QCOMPARE(mover->width(), qreal(50.0));
QCOMPARE(mover->height(), qreal(50.0));
}
void tst_qquickstates::script()
{
QQmlEngine engine;
{
QQmlComponent rectComponent(&engine, testFileUrl("script.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QCOMPARE(rect->color(),QColor("red"));
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
rectPrivate->setState("");
QCOMPARE(rect->color(),QColor("blue")); // a script isn't reverted
}
}
void tst_qquickstates::restoreEntryValues()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("restoreEntryValues.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QCOMPARE(rect->color(),QColor("red"));
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
rectPrivate->setState("");
QCOMPARE(rect->color(),QColor("blue"));
}
void tst_qquickstates::explicitChanges()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("explicit.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QQmlListReference list(rect.get(), "states");
QQuickState *state = qobject_cast<QQuickState*>(list.at(0));
QVERIFY(state != nullptr);
qmlExecuteDeferred(state);
QQuickPropertyChanges *changes = qobject_cast<QQuickPropertyChanges*>(rect->findChild<QQuickPropertyChanges*>("changes"));
QVERIFY(changes != nullptr);
QVERIFY(changes->isExplicit());
QCOMPARE(rect->color(),QColor("red"));
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
rect->setProperty("sourceColor", QColor("green"));
QCOMPARE(rect->color(),QColor("blue"));
rectPrivate->setState("");
QCOMPARE(rect->color(),QColor("red"));
rect->setProperty("sourceColor", QColor("yellow"));
QCOMPARE(rect->color(),QColor("red"));
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("yellow"));
}
void tst_qquickstates::propertyErrors()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("propertyErrors.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QCOMPARE(rect->color(),QColor("red"));
QTest::ignoreMessage(QtWarningMsg, fullDataPath("propertyErrors.qml") + ":8:9: QML PropertyChanges: Cannot assign to non-existent property \"colr\"");
QTest::ignoreMessage(QtWarningMsg, fullDataPath("propertyErrors.qml") + ":8:9: QML PropertyChanges: Cannot assign to read-only property \"activeFocus\"");
QQuickItemPrivate::get(rect.get())->setState("blue");
}
void tst_qquickstates::incorrectRestoreBug()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("basicChanges.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QCOMPARE(rect->color(),QColor("red"));
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
rectPrivate->setState("");
QCOMPARE(rect->color(),QColor("red"));
// make sure if we change the base state value, we then restore to it correctly
rect->setColor(QColor("green"));
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
rectPrivate->setState("");
QCOMPARE(rect->color(),QColor("green"));
}
void tst_qquickstates::autoStateAtStartupRestoreBug()
{
QQmlEngine engine;
QQmlComponent component(&engine, testFileUrl("autoStateAtStartupRestoreBug.qml"));
QScopedPointer<QObject> obj(component.create());
QVERIFY(obj != nullptr);
QCOMPARE(obj->property("test").toInt(), 3);
obj->setProperty("input", 2);
QCOMPARE(obj->property("test").toInt(), 9);
}
void tst_qquickstates::deletingChange()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("deleting.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
QCOMPARE(rect->radius(),qreal(5));
rectPrivate->setState("");
QCOMPARE(rect->color(),QColor("red"));
QCOMPARE(rect->radius(),qreal(0));
QQuickPropertyChanges *pc = rect->findChild<QQuickPropertyChanges*>("pc1");
QVERIFY(pc != nullptr);
delete pc;
QQuickState *state = rect->findChild<QQuickState*>();
QVERIFY(state != nullptr);
qmlExecuteDeferred(state);
QCOMPARE(state->operationCount(), 1);
rectPrivate->setState("blue");
QCOMPARE(rect->color(),QColor("red"));
QCOMPARE(rect->radius(),qreal(5));
}
void tst_qquickstates::deletingState()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("deletingState.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickStateGroup *sg = rect->findChild<QQuickStateGroup*>();
QVERIFY(sg != nullptr);
QVERIFY(sg->findState("blue") != nullptr);
sg->setState("blue");
QCOMPARE(rect->color(),QColor("blue"));
sg->setState("");
QCOMPARE(rect->color(),QColor("red"));
QQuickState *state = rect->findChild<QQuickState*>();
QVERIFY(state != nullptr);
delete state;
QVERIFY(!sg->findState("blue"));
//### should we warn that state doesn't exist
sg->setState("blue");
QCOMPARE(rect->color(),QColor("red"));
}
void tst_qquickstates::tempState()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("legalTempState.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QTest::ignoreMessage(QtDebugMsg, "entering placed");
QTest::ignoreMessage(QtDebugMsg, "entering idle");
rectPrivate->setState("placed");
QCOMPARE(rectPrivate->state(), QLatin1String("idle"));
}
void tst_qquickstates::illegalTempState()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("illegalTempState.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML StateGroup: Can't apply a state change as part of a state definition.");
rectPrivate->setState("placed");
QCOMPARE(rectPrivate->state(), QLatin1String("placed"));
}
void tst_qquickstates::nonExistantProperty()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("nonExistantProp.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QTest::ignoreMessage(QtWarningMsg, fullDataPath("nonExistantProp.qml") + ":9:9: QML PropertyChanges: Cannot assign to non-existent property \"colr\"");
rectPrivate->setState("blue");
QCOMPARE(rectPrivate->state(), QLatin1String("blue"));
}
void tst_qquickstates::reset()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("reset.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));
QVERIFY(rect != nullptr);
QQuickImage *image = rect->findChild<QQuickImage*>();
QVERIFY(image != nullptr);
QCOMPARE(image->width(), qreal(40.));
QCOMPARE(image->height(), qreal(20.));
QQuickItemPrivate::get(rect.get())->setState("state1");
QCOMPARE(image->width(), 20.0);
QCOMPARE(image->height(), qreal(20.));
}
void tst_qquickstates::illegalObjectCreation()
{
QQmlEngine engine;
QQmlComponent component(&engine, testFileUrl("illegalObj.qml"));
QList<QQmlError> errors = component.errors();
QCOMPARE(errors.size(), 1);
const QQmlError &error = errors.at(0);
QCOMPARE(error.line(), 9);
QCOMPARE(error.column(), 23);
QCOMPARE(error.description().toUtf8().constData(), "PropertyChanges does not support creating state-specific objects.");
}
void tst_qquickstates::whenOrdering()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("whenOrdering.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QCOMPARE(rectPrivate->state(), QLatin1String(""));
rect->setProperty("condition2", true);
QCOMPARE(rectPrivate->state(), QLatin1String("state2"));
rect->setProperty("condition1", true);
QCOMPARE(rectPrivate->state(), QLatin1String("state1"));
rect->setProperty("condition2", false);
QCOMPARE(rectPrivate->state(), QLatin1String("state1"));
rect->setProperty("condition2", true);
QCOMPARE(rectPrivate->state(), QLatin1String("state1"));
rect->setProperty("condition1", false);
rect->setProperty("condition2", false);
QCOMPARE(rectPrivate->state(), QLatin1String(""));
}
void tst_qquickstates::urlResolution()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("urlResolution.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));
QVERIFY(rect != nullptr);
QQuickItem *myType = rect->findChild<QQuickItem*>("MyType");
QQuickImage *image1 = rect->findChild<QQuickImage*>("image1");
QQuickImage *image2 = rect->findChild<QQuickImage*>("image2");
QQuickImage *image3 = rect->findChild<QQuickImage*>("image3");
QVERIFY(myType != nullptr && image1 != nullptr && image2 != nullptr && image3 != nullptr);
QQuickItemPrivate::get(myType)->setState("SetImageState");
QUrl resolved = QUrl("images/qt-logo.png");
QCOMPARE(image1->source(), resolved);
QCOMPARE(image2->source(), resolved);
QCOMPARE(image3->source(), resolved);
}
void tst_qquickstates::unnamedWhen()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("unnamedWhen.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QCOMPARE(rectPrivate->state(), QLatin1String(""));
QCOMPARE(rect->property("stateString").toString(), QLatin1String(""));
rect->setProperty("triggerState", true);
QCOMPARE(rectPrivate->state(), QLatin1String("anonymousState1"));
QCOMPARE(rect->property("stateString").toString(), QLatin1String("inState"));
rect->setProperty("triggerState", false);
QCOMPARE(rectPrivate->state(), QLatin1String(""));
QCOMPARE(rect->property("stateString").toString(), QLatin1String(""));
}
void tst_qquickstates::returnToBase()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("returnToBase.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QCOMPARE(rectPrivate->state(), QLatin1String(""));
QCOMPARE(rect->property("stateString").toString(), QLatin1String(""));
rect->setProperty("triggerState", true);
QCOMPARE(rectPrivate->state(), QLatin1String("anonymousState1"));
QCOMPARE(rect->property("stateString").toString(), QLatin1String("inState"));
rect->setProperty("triggerState", false);
QCOMPARE(rectPrivate->state(), QLatin1String(""));
QCOMPARE(rect->property("stateString").toString(), QLatin1String("originalState"));
}
//QTBUG-12559
void tst_qquickstates::extendsBug()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("extendsBug.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QQuickRectangle *greenRect = rect->findChild<QQuickRectangle*>("greenRect");
rectPrivate->setState("b");
QCOMPARE(greenRect->x(), qreal(100));
QCOMPARE(greenRect->y(), qreal(100));
}
void tst_qquickstates::editProperties()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("editProperties.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
QQuickStateGroup *stateGroup = rectPrivate->_states();
QVERIFY(stateGroup != nullptr);
qmlExecuteDeferred(stateGroup);
QQuickState *blueState = stateGroup->findState("blue");
QVERIFY(blueState != nullptr);
qmlExecuteDeferred(blueState);
QQuickPropertyChanges *propertyChangesBlue = qobject_cast<QQuickPropertyChanges*>(blueState->operationAt(0));
QVERIFY(propertyChangesBlue != nullptr);
QQuickState *greenState = stateGroup->findState("green");
QVERIFY(greenState != nullptr);
qmlExecuteDeferred(greenState);
QQuickPropertyChanges *propertyChangesGreen = qobject_cast<QQuickPropertyChanges*>(greenState->operationAt(0));
QVERIFY(propertyChangesGreen != nullptr);
QQuickRectangle *childRect = rect->findChild<QQuickRectangle*>("rect2");
QVERIFY(childRect != nullptr);
QCOMPARE(childRect->width(), qreal(402));
QVERIFY(QQmlAnyBinding::ofProperty(QQmlProperty(childRect, "width")));
QCOMPARE(childRect->height(), qreal(200));
rectPrivate->setState("blue");
QCOMPARE(childRect->width(), qreal(50));
QCOMPARE(childRect->height(), qreal(40));
QVERIFY(!QQmlPropertyPrivate::binding(QQmlProperty(childRect, "width")));
QVERIFY(blueState->bindingInRevertList(childRect, "width"));
rectPrivate->setState("green");
QCOMPARE(childRect->width(), qreal(200));
QCOMPARE(childRect->height(), qreal(100));
QVERIFY(greenState->bindingInRevertList(childRect, "width"));
rectPrivate->setState("");
QCOMPARE(propertyChangesBlue->actions().size(), 2);
QVERIFY(propertyChangesBlue->containsValue("width"));
QVERIFY(!propertyChangesBlue->containsProperty("x"));
QCOMPARE(propertyChangesBlue->value("width").toInt(), 50);
QVERIFY(!propertyChangesBlue->value("x").isValid());
propertyChangesBlue->changeValue("width", 60);
QCOMPARE(propertyChangesBlue->value("width").toInt(), 60);
QCOMPARE(propertyChangesBlue->actions().size(), 2);
propertyChangesBlue->changeExpression("width", "myRectangle.width / 2");
QVERIFY(!propertyChangesBlue->containsValue("width"));
QVERIFY(propertyChangesBlue->containsExpression("width"));
QCOMPARE(propertyChangesBlue->value("width").toInt(), 0);
QCOMPARE(propertyChangesBlue->actions().size(), 2);
propertyChangesBlue->changeValue("width", 50);
QVERIFY(propertyChangesBlue->containsValue("width"));
QVERIFY(!propertyChangesBlue->containsExpression("width"));
QCOMPARE(propertyChangesBlue->value("width").toInt(), 50);
QCOMPARE(propertyChangesBlue->actions().size(), 2);
QVERIFY(QQmlAnyBinding::ofProperty(QQmlProperty(childRect, "width")));
rectPrivate->setState("blue");
QCOMPARE(childRect->width(), qreal(50));
QCOMPARE(childRect->height(), qreal(40));
propertyChangesBlue->changeValue("width", 60);
QCOMPARE(propertyChangesBlue->value("width").toInt(), 60);
QCOMPARE(propertyChangesBlue->actions().size(), 2);
QCOMPARE(childRect->width(), qreal(60));
QVERIFY(!QQmlAnyBinding::ofProperty(QQmlProperty(childRect, "width")));
propertyChangesBlue->changeExpression("width", "myRectangle.width / 2");
QVERIFY(!propertyChangesBlue->containsValue("width"));
QVERIFY(propertyChangesBlue->containsExpression("width"));
QCOMPARE(propertyChangesBlue->value("width").toInt(), 0);
QCOMPARE(propertyChangesBlue->actions().size(), 2);
QVERIFY(QQmlAnyBinding::ofProperty(QQmlProperty(childRect, "width")));
QCOMPARE(childRect->width(), qreal(200));
propertyChangesBlue->changeValue("width", 50);
QCOMPARE(childRect->width(), qreal(50));
rectPrivate->setState("");
QCOMPARE(childRect->width(), qreal(402));
QVERIFY(QQmlAnyBinding::ofProperty(QQmlProperty(childRect, "width")));
QCOMPARE(propertyChangesGreen->actions().size(), 2);
rectPrivate->setState("green");
QCOMPARE(childRect->width(), qreal(200));
QCOMPARE(childRect->height(), qreal(100));
QVERIFY(QQmlAnyBinding::ofProperty(QQmlProperty(childRect, "width")));
QVERIFY(greenState->bindingInRevertList(childRect, "width"));
QCOMPARE(propertyChangesGreen->actions().size(), 2);
propertyChangesGreen->removeProperty("height");
QVERIFY(!QQmlAnyBinding::ofProperty(QQmlProperty(childRect, "height")));
QCOMPARE(childRect->height(), qreal(200));
QVERIFY(greenState->bindingInRevertList(childRect, "width"));
QVERIFY(greenState->containsPropertyInRevertList(childRect, "width"));
propertyChangesGreen->removeProperty("width");
QVERIFY(QQmlAnyBinding::ofProperty(QQmlProperty(childRect, "width")));
QCOMPARE(childRect->width(), qreal(402));
QVERIFY(!greenState->bindingInRevertList(childRect, "width"));
QVERIFY(!greenState->containsPropertyInRevertList(childRect, "width"));
propertyChangesBlue->removeProperty("width");
QCOMPARE(childRect->width(), qreal(402));
rectPrivate->setState("blue");
QCOMPARE(childRect->width(), qreal(402));
QCOMPARE(childRect->height(), qreal(40));
}
void tst_qquickstates::QTBUG_14830()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("QTBUG-14830.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));
QVERIFY(rect != nullptr);
QQuickItem *item = rect->findChild<QQuickItem*>("area");
QCOMPARE(item->width(), qreal(170));
}
void tst_qquickstates::avoidFastForward()
{
QQmlEngine engine;
//shouldn't fast forward if there isn't a transition
QQmlComponent c(&engine, testFileUrl("avoidFastForward.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));
QVERIFY(rect != nullptr);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
rectPrivate->setState("a");
QCOMPARE(rect->property("updateCount").toInt(), 1);
}
//QTBUG-22583
void tst_qquickstates::revertListBug()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("revertListBug.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));
QVERIFY(rect != nullptr);
QQuickRectangle *rect1 = rect->findChild<QQuickRectangle*>("rect1");
QQuickRectangle *rect2 = rect->findChild<QQuickRectangle*>("rect2");
QQuickItem *origParent1 = rect->findChild<QQuickItem*>("originalParent1");
QQuickItem *origParent2 = rect->findChild<QQuickItem*>("originalParent2");
QQuickItem *newParent = rect->findChild<QQuickItem*>("newParent");
QCOMPARE(rect1->parentItem(), origParent1);
QCOMPARE(rect2->parentItem(), origParent2);
QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get());
rectPrivate->setState("reparented");
QCOMPARE(rect1->parentItem(), newParent);
QCOMPARE(rect2->parentItem(), origParent2);
rectPrivate->setState("");
QCOMPARE(rect1->parentItem(), origParent1);
QCOMPARE(rect2->parentItem(), origParent2);
QMetaObject::invokeMethod(rect.get(), "switchTargetItem");
rectPrivate->setState("reparented");
QCOMPARE(rect1->parentItem(), origParent1);
QCOMPARE(rect2->parentItem(), newParent);
rectPrivate->setState("");
QCOMPARE(rect1->parentItem(), origParent1);
QCOMPARE(rect2->parentItem(), origParent2); //QTBUG-22583 causes rect2's parent item to be origParent1
}
void tst_qquickstates::QTBUG_38492()
{
QQmlEngine engine;
QQmlComponent rectComponent(&engine, testFileUrl("QTBUG-38492.qml"));
QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(rectComponent.create()));
QVERIFY(item != nullptr);
QQuickItemPrivate::get(item.get())->setState("apply");
QCOMPARE(item->property("text").toString(), QString("Test"));
}
static int getRefCount(const QQmlAnyBinding &binding)
{
if (binding.isAbstractPropertyBinding()) {
return binding.asAbstractBinding()->ref;
} else {
// this temporarily adds a refcount because we construc a new untypedpropertybinding
// thus -1
return QPropertyBindingPrivate::get(binding.asUntypedPropertyBinding())->refCount() - 1;
}
}
void tst_qquickstates::revertListMemoryLeak()
{
QQmlAnyBinding bindingPtr;
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("revertListMemoryLeak.qml"));
QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem *>(c.create()));
QVERIFY(item);
QQuickState *state = item->findChild<QQuickState*>("testState");
QVERIFY(state);
item->setState("testState");
auto binding = state->bindingInRevertList(item.get(), "height");
QVERIFY(binding);
bindingPtr = binding;
QVERIFY(getRefCount(bindingPtr) > 1);
}
QVERIFY(getRefCount(bindingPtr) == 1);
}
void tst_qquickstates::duplicateStateName()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("duplicateStateName.qml"));
QTest::ignoreMessage(QtWarningMsg, fullDataPath("duplicateStateName.qml") + ":3:1: QML Rectangle: Found duplicate state name: state1");
QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem *>(c.create()));
QVERIFY(!item.isNull());
}
// QTBUG-76838
void tst_qquickstates::trivialWhen()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("trivialWhen.qml"));
QScopedPointer<QObject> root(c.create());
QVERIFY(root);
}
void tst_qquickstates::jsValueWhen_data()
{
QTest::addColumn<QByteArray>("fileName");
QTest::addRow("jsObject") << QByteArray("jsValueWhen.qml");
QTest::addRow("qmlObject") << QByteArray("jsValueWhen2.qml");
}
void tst_qquickstates::jsValueWhen()
{
QFETCH(QByteArray, fileName);
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl(fileName.constData()));
QScopedPointer<QObject> root(c.create());
QVERIFY(root);
QVERIFY(root->property("works").toBool());
}
void tst_qquickstates::noStateOsciallation()
{
QQmlEngine engine;
QQmlComponent component(&engine, testFileUrl("noStateOsciallation.qml"));
QScopedPointer<QObject> root {component.create()};
QVERIFY(root);
// set to 1 on initial transition from "" to "n2"
QCOMPARE(root->property("stateChangeCounter").toInt(), 1);
root->setProperty("number", 1);
// setting number to 1 changes directly from "n2" to "n1"
// without any intermediate transition to ""
QCOMPARE(root->property("stateChangeCounter").toInt(), 2);
}
void tst_qquickstates::parentChangeCorrectReversal()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("parentChangeCorrectReversal.qml"));
QScopedPointer<QObject> root {c.create()};
QVERIFY(root);
QQmlProperty stayingRectX(root.get(), "stayingRectX");
qreal oldX = stayingRectX.read().toDouble();
QQmlProperty switchToRight(root.get(), "switchToRight");
switchToRight.write(true);
qreal newX = stayingRectX.read().toDouble();
QVERIFY(newX != oldX);
switchToRight.write(false);
QCOMPARE(oldX, stayingRectX.read().toDouble());
}
void tst_qquickstates::revertNullObjectBinding()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("revertNullObjectBinding.qml"));
QScopedPointer<QObject> root { c.create() };
QVERIFY(root);
QTest::qWait(10);
QQmlProperty state2Active(root.get(), "state2Active");
state2Active.write(false);
}
void tst_qquickstates::bindableProperties()
{
QQmlEngine engine;
qmlRegisterType<MyBindable>("Qt.test", 1, 0, "MyBindable");
QQmlComponent c(&engine, testFileUrl("bindableProperties.qml"));
QScopedPointer<MyBindable> root { qobject_cast<MyBindable *>(c.create()) };
QVERIFY(root);
{
// initial sanity check
QCOMPARE(root->prop(), 84);
QVERIFY(root->bindableProp().hasBinding());
root->setX(0);
QCOMPARE(root->prop(), 42);
}
{
// When the state changes,
root->setProperty("toggle", true);
// the value gets updated,
QCOMPARE(root->prop(), 5);
// the binding is accessible from C++,
QVERIFY(root->bindableProp().hasBinding());
// and the new binding is active.
root->setHeight(10);
QCOMPARE(root->prop(), 10);
}
{
// After changing the state back,
root->setProperty("toggle", false);
// the old value is restored,
QCOMPARE(root->prop(), 42);
// the binding is accessible from C++,
QVERIFY(root->bindableProp().hasBinding());
// and the old binding is active.
root->setX(42);
QCOMPARE(root->prop(), 84);
}
}
struct Listener : QQuickItemChangeListener
{
// We want to get notified about all the states.
constexpr static const QRectF expectations[] = {
QRectF(40, 40, 400, 400),
QRectF(40, 0, 400, 400),
QRectF(0, 0, 400, 400),
QRectF(0, 0, 800, 400),
QRectF(0, 0, 800, 200),
QRectF(0, 0, 400, 200),
QRectF(0, 20, 400, 200),
QRectF(40, 20, 400, 200),
QRectF(84, 42, 400, 200),
QRectF(84, 42, 86, 43),
QRectF(40, 40, 86, 43),
QRectF(40, 40, 400, 400),
QRectF(40, 20, 400, 400),
QRectF(40, 20, 400, 200),
QRectF(20, 20, 400, 200),
QRectF(20, 20, 200, 200),
QRectF(20, 20, 200, 300),
QRectF(20, 20, 300, 300),
QRectF(20, 30, 300, 300),
QRectF(30, 30, 300, 300),
};
int position = 0;
bool ok = true;
void itemGeometryChanged(QQuickItem *, QQuickGeometryChange, const QRectF &rect) override
{
if (rect != expectations[position]) {
qDebug() << position << rect;
ok = false;
}
++position;
}
};
void tst_qquickstates::parentChangeInvolvingBindings()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("parentChangeInvolvingBindings.qml"));
Listener listener;
QScopedPointer<QQuickItem> root { qobject_cast<QQuickItem *>(c.create()) };
QVERIFY2(root, qPrintable(c.errorString()));
QObject *child = qmlContext(root.data())->objectForName(QStringLiteral("firstChild"));
QVERIFY(child);
QQuickItem *childItem = qobject_cast<QQuickItem *>(child);
QVERIFY(childItem);
QQuickItemPrivate::get(childItem)->addItemChangeListener(&listener, QQuickItemPrivate::Geometry);
QCOMPARE(root->property("childWidth").toInt(), 400);
QCOMPARE(root->property("childX").toInt(), 40);
QCOMPARE(root->property("childRotation").toInt(), 100);
root->setState("reparented");
QCOMPARE(root->property("childWidth").toInt(), 800);
QCOMPARE(root->property("childX").toInt(), 0); // x gets zeroed here, from unrelated place.
QCOMPARE(root->property("childRotation").toInt(), 200);
root->setProperty("myrotation2", 300);
root->setHeight(200);
root->setY(20);
QCOMPARE(root->property("childRotation").toInt(), 300);
QCOMPARE(root->property("childWidth").toInt(), 400);
QCOMPARE(root->property("childX").toInt(), 40);
QObject *inner = qmlContext(root.data())->objectForName(QStringLiteral("inner"));
QVERIFY(inner);
QQuickItem *innerItem = qobject_cast<QQuickItem *>(inner);
QVERIFY(innerItem);
QCOMPARE(innerItem->size(), childItem->size());
// Does not break bindings and does not survive the state change.
// However, since the binding between x and y stays intact, we don't know
// whether x is set another time from the new y. We pass a pair of numbers that
// matches the binding.
childItem->setPosition(QPointF(84, 42));
QCOMPARE(root->property("childX").toInt(), 84);
QVERIFY(listener.ok);
childItem->setSize(QSizeF(86, 43));
QCOMPARE(root->property("childWidth").toInt(), 86);
QVERIFY(listener.ok);
QCOMPARE(innerItem->size(), childItem->size());
QSignalSpy xSpy(childItem, SIGNAL(xChanged()));
QSignalSpy widthSpy(childItem, SIGNAL(widthChanged()));
root->setState("");
QVERIFY(listener.ok);
QCOMPARE(root->property("childRotation").toInt(), 100);
// First change to 40 via reverse(), then to 20 via binding.
QCOMPARE(xSpy.size(), 2);
// First change to 400 via reverse(), then to 200 via binding.
QCOMPARE(widthSpy.size(), 2);
QCOMPARE(root->property("childX").toInt(), 20);
QCOMPARE(root->property("childWidth").toInt(), 200);
QCOMPARE(innerItem->size(), childItem->size());
root->setProperty("myrotation", 50);
root->setHeight(300);
QVERIFY(listener.ok);
root->setY(30);
QVERIFY(listener.ok);
QCOMPARE(root->property("childWidth").toInt(), 300);
QCOMPARE(root->property("childX").toInt(), 30);
QCOMPARE(root->property("childRotation").toInt(), 50);
QCOMPARE(innerItem->size(), childItem->size());
}
void tst_qquickstates::deferredProperties()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("cleanPropertyChange.qml"));
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
QScopedPointer<QQuickRectangle> root(qobject_cast<QQuickRectangle *>(c.create()));
QVERIFY(root);
QCOMPARE(root->color(), QColor(Qt::red));
QCOMPARE(qvariant_cast<QColor>(root->property("extendedColor")), QColor(Qt::cyan));
QCOMPARE(root->width(), 100.0);
QCOMPARE(root->height(), 100.0);
QCOMPARE(root->state(), QString());
root->setState(QStringLiteral("green"));
QCOMPARE(root->color(), QColor(Qt::yellow));
QCOMPARE(qvariant_cast<QColor>(root->property("extendedColor")), QColor(Qt::blue));
QCOMPARE(root->width(), 90.0);
QCOMPARE(root->height(), 90.0);
QMetaObject::invokeMethod(root.get(), "didSomething");
const QColor green = qRgb(0x00, 0x80, 0x00);
QCOMPARE(root->color(), green);
QCOMPARE(qvariant_cast<QColor>(root->property("extendedColor")), green);
QCOMPARE(root->width(), 90.0);
QCOMPARE(root->height(), 90.0);
root->setState(QString());
QCOMPARE(root->color(), QColor(Qt::red));
QCOMPARE(qvariant_cast<QColor>(root->property("extendedColor")), QColor(Qt::cyan));
QCOMPARE(root->width(), 100.0);
QCOMPARE(root->height(), 100.0);
}
void tst_qquickstates::rewindAnchorChange()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("anchorRewind.qml"));
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
QScopedPointer<QObject> root(c.create());
QVERIFY(root);
QQmlContext *context = qmlContext(root.data());
QVERIFY(context);
QObject *inner = context->objectForName(QStringLiteral("inner"));
QVERIFY(inner);
QQuickItem *innerRect = qobject_cast<QQuickItem *>(inner);
QVERIFY(innerRect);
QTRY_COMPARE(innerRect->x(), 0);
QTRY_COMPARE(innerRect->y(), 0);
QTRY_COMPARE(innerRect->width(), 200);
QTRY_COMPARE(innerRect->height(), 200);
}
void tst_qquickstates::rewindAnchorChangeSize()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("anchorRewindSize.qml"));
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
std::unique_ptr<QObject> root(c.create());
QVERIFY(root);
QQmlContext *context = qmlContext(root.get());
QVERIFY(context);
QObject *inner = context->objectForName(QStringLiteral("inner"));
QVERIFY(inner);
QQuickItem *innerRect = qobject_cast<QQuickItem *>(inner);
QVERIFY(innerRect);
QCOMPARE(innerRect->x(), 0);
QCOMPARE(innerRect->y(), 0);
QCOMPARE(innerRect->width(), 100);
QCOMPARE(innerRect->height(), 100);
root->setProperty("changeState", true);
QCOMPARE(innerRect->x(), 0);
QCOMPARE(innerRect->y(), 0);
QCOMPARE(innerRect->width(), 400);
QCOMPARE(innerRect->height(), 400);
root->setProperty("changeState", false);
QCOMPARE(innerRect->x(), 0);
QCOMPARE(innerRect->y(), 0);
QCOMPARE(innerRect->width(), 100);
QCOMPARE(innerRect->height(), 100);
}
void tst_qquickstates::bindingProperlyRemovedWithTransition()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("removeBindingWithTransition.qml"));
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
QScopedPointer<QObject> root(c.create());
QVERIFY(root);
QQuickItem *item = qobject_cast<QQuickItem *>(root.get());
QVERIFY(item);
if (QSysInfo::productType() == "rhel" && QSysInfo::productVersion() == QLatin1String("10.0"))
QEXPECT_FAIL("", "Fails on RHEL 10 - QTBUG-144177", Continue);
item->setProperty("toggle", false);
QTRY_COMPARE(item->width(), 300);
item->setProperty("state1Width", 100);
QCOMPARE(item->width(), 300);
item->setProperty("toggle", true);
QTRY_COMPARE(item->width(), 100);
}
void tst_qquickstates::doNotCrashOnBroken()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("broken.qml"));
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
QScopedPointer<QObject> root(c.create());
QVERIFY(root);
QQuickItem *item = qobject_cast<QQuickItem *>(root.get());
QVERIFY(item);
QQmlListReference states(item, "states");
QCOMPARE(states.size(), 1);
QCOMPARE(states.at(0), nullptr);
}
QTEST_MAIN(tst_qquickstates)
#include "tst_qquickstates.moc"