/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 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) {} 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 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 noStateOsciallation(); void parentChangeCorrectReversal(); void revertNullObjectBinding(); void bindableProperties(); void parentChangeInvolvingBindings(); void deferredProperties(); void rewindAnchorChange(); }; void tst_qquickstates::initTestCase() { QQmlDataTest::initTestCase(); qmlRegisterType("Qt.test", 1, 0, "MyRectangle"); qmlRegisterSingletonType( "Qt.test", 1, 0, "ContainingObj", [](QQmlEngine *engine, QJSEngine *) { static ContainingObj instance; engine->setObjectOwnership(&instance, QQmlEngine::CppOwnership); return &instance; }); qmlRegisterUncreatableType("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 rect(qobject_cast(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 rect(qobject_cast(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 rect(qobject_cast(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 rect(qobject_cast(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 item(qobject_cast(component.create())); QVERIFY(item != nullptr); QCOMPARE(item->width(), 50.0); // Ensure attached property has been changed QObject *attObj = qmlAttachedPropertiesObject(item.get(), false); QVERIFY(attObj); MyAttached *att = qobject_cast(attObj); QVERIFY(att); QCOMPARE(att->foo(), 1); } void tst_qquickstates::basicExtension() { QQmlEngine engine; { QQmlComponent rectComponent(&engine, testFileUrl("basicExtension.qml")); QScopedPointer rect(qobject_cast(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 rect(qobject_cast(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 rect(qobject_cast(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 rect(qobject_cast(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 rect(qobject_cast(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 rect(qobject_cast(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 rect(qobject_cast(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")); QScopedPointerrect(qobject_cast(rectComponent.create())); QVERIFY(rect != nullptr); QCOMPARE(rect->color(),QColor("white")); rect->doSomething(); QCOMPARE(rect->color(),QColor("blue")); QQuickRectangle *innerRect = qobject_cast(rect->findChild("extendedRect")); QQuickItemPrivate::get(innerRect)->setState("green"); rect->doSomething(); QCOMPARE(rect->color(),QColor("blue")); QCOMPARE(innerRect->color(),QColor("green")); QCOMPARE(innerRect->property("extendedColor").value(),QColor("green")); } } void tst_qquickstates::signalOverrideCrash() { QQmlEngine engine; QQmlComponent rectComponent(&engine, testFileUrl("signalOverrideCrash.qml")); QScopedPointer rect(qobject_cast(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 rect(qobject_cast(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 rect(qobject_cast(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 rect(qobject_cast(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 rect(qobject_cast(rectComponent.create())); QVERIFY(rect != nullptr); QQuickRectangle *innerRect = qobject_cast(rect->findChild("MyRect")); QVERIFY(innerRect != nullptr); QQmlListReference list(rect.get(), "states"); QQuickState *state = qobject_cast(list.at(0)); QVERIFY(state != nullptr); qmlExecuteDeferred(state); QQuickParentChange *pChange = qobject_cast(state->operationAt(0)); QVERIFY(pChange != nullptr); QQuickItem *nParent = qobject_cast(rect->findChild("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 rect(qobject_cast(rectComponent.create())); QVERIFY(rect != nullptr); QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get()); QQuickRectangle *innerRect = qobject_cast(rect->findChild("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 rect(qobject_cast(rectComponent.create())); QVERIFY(rect != nullptr); QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get()); QQuickRectangle *innerRect = qobject_cast(rect->findChild("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 rect(qobject_cast(rectComponent.create())); QVERIFY(rect != nullptr); QQuickRectangle *innerRect = qobject_cast(rect->findChild("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 rect(qobject_cast(rectComponent.create())); QVERIFY(rect != nullptr); QQuickRectangle *innerRect = qobject_cast(rect->findChild("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 rect(qobject_cast(rectComponent.create())); QVERIFY(rect != nullptr); QQuickRectangle *innerRect = qobject_cast(rect->findChild("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 rect(qobject_cast(rectComponent.create())); QVERIFY(rect != nullptr); QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get()); QQuickRectangle *innerRect = qobject_cast(rect->findChild("MyRect")); QVERIFY(innerRect != nullptr); QQmlListReference list(rect.get(), "states"); QQuickState *state = qobject_cast(list.at(0)); QVERIFY(state != nullptr); qmlExecuteDeferred(state); QQuickAnchorChanges *aChanges = qobject_cast(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(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 rect(qobject_cast(rectComponent.create())); QVERIFY(rect != nullptr); QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get()); QQuickRectangle *innerRect = qobject_cast(rect->findChild("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 rect(qobject_cast(rectComponent.create())); QVERIFY(rect != nullptr); QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get()); QQuickRectangle *innerRect = qobject_cast(rect->findChild("MyRect")); QVERIFY(innerRect != nullptr); QQuickItem *leftGuideline = qobject_cast(rect->findChild("LeftGuideline")); QVERIFY(leftGuideline != nullptr); QQuickItem *bottomGuideline = qobject_cast(rect->findChild("BottomGuideline")); QVERIFY(bottomGuideline != nullptr); QQmlListReference list(rect.get(), "states"); QQuickState *state = qobject_cast(list.at(0)); QVERIFY(state != nullptr); qmlExecuteDeferred(state); QQuickAnchorChanges *aChanges = qobject_cast(state->operationAt(0)); QVERIFY(aChanges != nullptr); QVERIFY(!aChanges->anchors()->top().isEmpty()); QVERIFY(!aChanges->anchors()->bottom().isEmpty()); rectPrivate->setState("reanchored"); QCOMPARE(aChanges->object(), qobject_cast(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 rect(qobject_cast(rectComponent.create())); QVERIFY(rect != nullptr); QQuickRectangle *innerRect = qobject_cast(rect->findChild("MyRect")); QVERIFY(innerRect != nullptr); QQuickItem *leftGuideline = qobject_cast(rect->findChild("LeftGuideline")); QVERIFY(leftGuideline != nullptr); QQuickItem *bottomGuideline = qobject_cast(rect->findChild("BottomGuideline")); QVERIFY(bottomGuideline != nullptr); QQmlListReference list(rect.get(), "states"); QQuickState *state = qobject_cast(list.at(0)); QVERIFY(state != nullptr); qmlExecuteDeferred(state); QQuickAnchorChanges *aChanges = qobject_cast(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(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")); QScopedPointerrect(qobject_cast(rectComponent.create())); QVERIFY(rect != nullptr); QQuickRectangle *innerRect = qobject_cast(rect->findChild("MyRect")); QVERIFY(innerRect != nullptr); QQuickItem *leftGuideline = qobject_cast(rect->findChild("LeftGuideline")); QVERIFY(leftGuideline != nullptr); QQuickItem *bottomGuideline = qobject_cast(rect->findChild("BottomGuideline")); QVERIFY(bottomGuideline != nullptr); QQmlListReference list(rect.get(), "states"); QQuickState *state = qobject_cast(list.at(0)); QVERIFY(state != nullptr); qmlExecuteDeferred(state); QQuickAnchorChanges *aChanges = qobject_cast(state->operationAt(0)); QVERIFY(aChanges != nullptr); QVERIFY(!aChanges->anchors()->baseline().isEmpty()); QQuickItemPrivate::get(rect.get())->setState("reanchored"); QCOMPARE(aChanges->object(), qobject_cast(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 rect(qobject_cast(rectComponent.create())); QVERIFY(rect != nullptr); QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get()); QQuickRectangle *innerRect = qobject_cast(rect->findChild("MyRect")); QVERIFY(innerRect != nullptr); mirrorAnchors(innerRect); QQmlListReference list(rect.get(), "states"); QQuickState *state = qobject_cast(list.at(0)); QVERIFY(state != nullptr); qmlExecuteDeferred(state); QQuickAnchorChanges *aChanges = qobject_cast(state->operationAt(0)); QVERIFY(aChanges != nullptr); rectPrivate->setState("right"); QCOMPARE(innerRect->x(), offsetRTL(rect.get(), innerRect) - qreal(150)); QCOMPARE(aChanges->object(), qobject_cast(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 rect(qobject_cast(rectComponent.create())); QVERIFY(rect != nullptr); QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get()); QQuickRectangle *innerRect = qobject_cast(rect->findChild("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 rect(qobject_cast(rectComponent.create())); QVERIFY(rect != nullptr); QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get()); QQuickRectangle *innerRect = qobject_cast(rect->findChild("MyRect")); QVERIFY(innerRect != nullptr); mirrorAnchors(innerRect); QQuickItem *leftGuideline = qobject_cast(rect->findChild("LeftGuideline")); QVERIFY(leftGuideline != nullptr); QQuickItem *bottomGuideline = qobject_cast(rect->findChild("BottomGuideline")); QVERIFY(bottomGuideline != nullptr); QQmlListReference list(rect.get(), "states"); QQuickState *state = qobject_cast(list.at(0)); QVERIFY(state != nullptr); qmlExecuteDeferred(state); QQuickAnchorChanges *aChanges = qobject_cast(state->operationAt(0)); QVERIFY(aChanges != nullptr); rectPrivate->setState("reanchored"); QCOMPARE(aChanges->object(), qobject_cast(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 rect(qobject_cast(rectComponent.create())); QVERIFY(rect != nullptr); QQuickItemPrivate::get(rect.get())->setState("reanchored"); } // QTBUG-12273 void tst_qquickstates::anchorRewindBug() { QScopedPointer view(new QQuickView); view->setSource(testFileUrl("anchorRewindBug.qml")); view->show(); QVERIFY(QTest::qWaitForWindowExposed(view.get())); QQuickRectangle *rect = qobject_cast(view->rootObject()); QVERIFY(rect != nullptr); QQuickItem * column = rect->findChild("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 rect(qobject_cast(rectComponent.create())); QVERIFY(rect != nullptr); QQuickRectangle *mover = rect->findChild("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)); } void tst_qquickstates::script() { QQmlEngine engine; { QQmlComponent rectComponent(&engine, testFileUrl("script.qml")); QScopedPointer rect(qobject_cast(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 rect(qobject_cast(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 rect(qobject_cast(rectComponent.create())); QVERIFY(rect != nullptr); QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get()); QQmlListReference list(rect.get(), "states"); QQuickState *state = qobject_cast(list.at(0)); QVERIFY(state != nullptr); qmlExecuteDeferred(state); QQuickPropertyChanges *changes = qobject_cast(rect->findChild("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 rect(qobject_cast(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 rect(qobject_cast(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 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 rect(qobject_cast(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("pc1"); QVERIFY(pc != nullptr); delete pc; QQuickState *state = rect->findChild(); 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 rect(qobject_cast(rectComponent.create())); QVERIFY(rect != nullptr); QQuickStateGroup *sg = rect->findChild(); 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(); 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 rect(qobject_cast(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 rect(qobject_cast(rectComponent.create())); QVERIFY(rect != nullptr); QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get()); QTest::ignoreMessage(QtWarningMsg, ": 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 rect(qobject_cast(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 rect(qobject_cast(c.create())); QVERIFY(rect != nullptr); QQuickImage *image = rect->findChild(); 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 errors = component.errors(); QCOMPARE(errors.count(), 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 rect(qobject_cast(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 rect(qobject_cast(c.create())); QVERIFY(rect != nullptr); QQuickItem *myType = rect->findChild("MyType"); QQuickImage *image1 = rect->findChild("image1"); QQuickImage *image2 = rect->findChild("image2"); QQuickImage *image3 = rect->findChild("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 rect(qobject_cast(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 rect(qobject_cast(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 rect(qobject_cast(c.create())); QVERIFY(rect != nullptr); QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect.get()); QQuickRectangle *greenRect = rect->findChild("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 rect(qobject_cast(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(blueState->operationAt(0)); QVERIFY(propertyChangesBlue != nullptr); QQuickState *greenState = stateGroup->findState("green"); QVERIFY(greenState != nullptr); qmlExecuteDeferred(greenState); QQuickPropertyChanges *propertyChangesGreen = qobject_cast(greenState->operationAt(0)); QVERIFY(propertyChangesGreen != nullptr); QQuickRectangle *childRect = rect->findChild("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().length(), 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().length(), 2); propertyChangesBlue->changeExpression("width", "myRectangle.width / 2"); QVERIFY(!propertyChangesBlue->containsValue("width")); QVERIFY(propertyChangesBlue->containsExpression("width")); QCOMPARE(propertyChangesBlue->value("width").toInt(), 0); QCOMPARE(propertyChangesBlue->actions().length(), 2); propertyChangesBlue->changeValue("width", 50); QVERIFY(propertyChangesBlue->containsValue("width")); QVERIFY(!propertyChangesBlue->containsExpression("width")); QCOMPARE(propertyChangesBlue->value("width").toInt(), 50); QCOMPARE(propertyChangesBlue->actions().length(), 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().length(), 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().length(), 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().length(), 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().length(), 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 rect(qobject_cast(c.create())); QVERIFY(rect != nullptr); QQuickItem *item = rect->findChild("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 rect(qobject_cast(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 rect(qobject_cast(c.create())); QVERIFY(rect != nullptr); QQuickRectangle *rect1 = rect->findChild("rect1"); QQuickRectangle *rect2 = rect->findChild("rect2"); QQuickItem *origParent1 = rect->findChild("originalParent1"); QQuickItem *origParent2 = rect->findChild("originalParent2"); QQuickItem *newParent = rect->findChild("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 item(qobject_cast(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())->ref - 1; } } void tst_qquickstates::revertListMemoryLeak() { QQmlAnyBinding bindingPtr; { QQmlEngine engine; QQmlComponent c(&engine, testFileUrl("revertListMemoryLeak.qml")); QScopedPointer item(qobject_cast(c.create())); QVERIFY(item); QQuickState *state = item->findChild("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 item(qobject_cast(c.create())); QVERIFY(!item.isNull()); } // QTBUG-76838 void tst_qquickstates::trivialWhen() { QQmlEngine engine; QQmlComponent c(&engine, testFileUrl("trivialWhen.qml")); QScopedPointer root(c.create()); QVERIFY(root); } void tst_qquickstates::noStateOsciallation() { QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("noStateOsciallation.qml")); QScopedPointer 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 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 root { c.create() }; QVERIFY(root); QTest::qWait(10); QQmlProperty state2Active(root.get(), "state2Active"); state2Active.write(false); } void tst_qquickstates::bindableProperties() { QQmlEngine engine; qmlRegisterType("Qt.test", 1, 0, "MyBindable"); QQmlComponent c(&engine, testFileUrl("bindableProperties.qml")); QScopedPointer root { qobject_cast(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 root { qobject_cast(c.create()) }; QVERIFY2(root, qPrintable(c.errorString())); QObject *child = qmlContext(root.data())->objectForName(QStringLiteral("firstChild")); QVERIFY(child); QQuickItem *childItem = qobject_cast(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(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.count(), 2); // First change to 400 via reverse(), then to 200 via binding. QCOMPARE(widthSpy.count(), 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 root(qobject_cast(c.create())); QVERIFY(root); QCOMPARE(root->color(), QColor(Qt::red)); QCOMPARE(qvariant_cast(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(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(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(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 root(c.create()); QVERIFY(root); QQmlContext *context = qmlContext(root.data()); QVERIFY(context); QObject *inner = context->objectForName(QStringLiteral("inner")); QVERIFY(inner); QQuickItem *innerRect = qobject_cast(inner); QVERIFY(innerRect); QTRY_COMPARE(innerRect->x(), 0); QTRY_COMPARE(innerRect->y(), 0); QTRY_COMPARE(innerRect->width(), 200); QTRY_COMPARE(innerRect->height(), 200); } QTEST_MAIN(tst_qquickstates) #include "tst_qquickstates.moc"