blob: b44977bd5acc4a4376949a0bfd26e71fae2ea80c [file] [log] [blame]
/****************************************************************************
**
** 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 <qtest.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQuick/qquickview.h>
#include <private/qquickdesignersupportitems_p.h>
#include <private/qquickdesignersupportmetainfo_p.h>
#include <private/qquickdesignersupportproperties_p.h>
#include <private/qquickdesignersupportstates_p.h>
#include <private/qquickdesignersupportpropertychanges_p.h>
#include <private/qquickitem_p.h>
#include <private/qquickstate_p.h>
#include <private/qquickstate_p_p.h>
#include <private/qquickstategroup_p.h>
#include <private/qquickpropertychanges_p.h>
#include <private/qquickrectangle_p.h>
#include "../../shared/util.h"
#include "../shared/visualtestutil.h"
using namespace QQuickVisualTestUtil;
class tst_qquickdesignersupport : public QQmlDataTest
{
Q_OBJECT
public:
tst_qquickdesignersupport() {}
private slots:
void customData();
void customDataBindings();
void objectProperties();
void dynamicProperty();
void createComponent();
void basicStates();
void statesPropertyChanges();
void testNotifyPropertyChangeCallBack();
void testFixResourcePathsForObjectCallBack();
void testComponentOnCompleteSignal();
};
void tst_qquickdesignersupport::customData()
{
QScopedPointer<QQuickView> view(new QQuickView);
view->engine()->setOutputWarningsToStandardError(false);
view->setSource(testFileUrl("test.qml"));
QVERIFY(view->errors().isEmpty());
QQuickItem *rootItem = view->rootObject();
QVERIFY(rootItem);
QScopedPointer<QObject> newItemScopedPointer(QQuickDesignerSupportItems::createPrimitive(QLatin1String("QtQuick/Item"), 2, 6, view->rootContext()));
QObject *newItem = newItemScopedPointer.data();
QVERIFY(newItem);
QVERIFY(qobject_cast<QQuickItem*>(newItem));
QQuickDesignerSupportProperties::registerCustomData(newItem);
QVERIFY(QQuickDesignerSupportProperties::getResetValue(newItem, "width").isValid());
int defaultWidth = QQuickDesignerSupportProperties::getResetValue(newItem, "width").toInt();
QCOMPARE(defaultWidth, 0);
newItem->setProperty("width", 200);
QCOMPARE(newItem->property("width").toInt(), 200);
//Check if reseting property does work
QQuickDesignerSupportProperties::doResetProperty(newItem, view->rootContext(), "width");
QCOMPARE(newItem->property("width").toInt(), 0);
//Setting a binding on width
QQuickDesignerSupportProperties::setPropertyBinding(newItem,
view->engine()->contextForObject(newItem),
"width",
QLatin1String("Math.max(0, 200)"));
QCOMPARE(newItem->property("width").toInt(), 200);
QVERIFY(QQuickDesignerSupportProperties::hasBindingForProperty(newItem,
view->engine()->contextForObject(newItem),
"width",
nullptr));
//Check if reseting property does work after setting binding
QQuickDesignerSupportProperties::doResetProperty(newItem, view->rootContext(), "width");
QCOMPARE(newItem->property("width").toInt(), 0);
//No custom data available for the rootItem, because not registered by QQuickDesignerSupportProperties::registerCustomData
QVERIFY(!QQuickDesignerSupportProperties::getResetValue(rootItem, "width").isValid());
newItemScopedPointer.reset(); //Delete the item and check if item gets removed from the hash and an invalid QVariant is returned.
QVERIFY(!QQuickDesignerSupportProperties::getResetValue(newItem, "width").isValid());
}
void tst_qquickdesignersupport::customDataBindings()
{
QScopedPointer<QQuickView> view(new QQuickView);
view->engine()->setOutputWarningsToStandardError(false);
view->setSource(testFileUrl("test.qml"));
QVERIFY(view->errors().isEmpty());
QQuickItem *rootItem = view->rootObject();
QVERIFY(rootItem);
QQuickItem *testComponent = findItem<QQuickItem>(view->rootObject(), QLatin1String("testComponent"));
QVERIFY(testComponent);
QQuickDesignerSupportProperties::registerCustomData(testComponent);
QQuickDesignerSupportProperties::hasValidResetBinding(testComponent, "x");
QVERIFY(QQuickDesignerSupportProperties::hasBindingForProperty(testComponent,
view->engine()->contextForObject(testComponent),
"x",
nullptr));
QCOMPARE(testComponent->property("x").toInt(), 200);
//Set property to 100 and ovveride the default binding
QQmlProperty property(testComponent, "x", view->engine()->contextForObject(testComponent));
QVERIFY(property.write(100));
QCOMPARE(testComponent->property("x").toInt(), 100);
QVERIFY(!QQuickDesignerSupportProperties::hasBindingForProperty(testComponent,
view->engine()->contextForObject(testComponent),
"x",
nullptr));
//Reset the binding to the default
QQuickDesignerSupportProperties::doResetProperty(testComponent,
view->engine()->contextForObject(testComponent),
"x");
QVERIFY(QQuickDesignerSupportProperties::hasBindingForProperty(testComponent,
view->engine()->contextForObject(testComponent),
"x",
nullptr));
QCOMPARE(testComponent->property("x").toInt(), 200);
//Set a different binding/expression
QQuickDesignerSupportProperties::setPropertyBinding(testComponent,
view->engine()->contextForObject(testComponent),
"x",
QLatin1String("Math.max(0, 300)"));
QVERIFY(QQuickDesignerSupportProperties::hasBindingForProperty(testComponent,
view->engine()->contextForObject(testComponent),
"x",
nullptr));
QCOMPARE(testComponent->property("x").toInt(), 300);
//Reset the binding to the default
QQuickDesignerSupportProperties::doResetProperty(testComponent,
view->engine()->contextForObject(testComponent),
"x");
QVERIFY(QQuickDesignerSupportProperties::hasBindingForProperty(testComponent,
view->engine()->contextForObject(testComponent),
"x",
nullptr));
QCOMPARE(testComponent->property("x").toInt(), 200);
}
void tst_qquickdesignersupport::objectProperties()
{
QScopedPointer<QQuickView> view(new QQuickView);
view->engine()->setOutputWarningsToStandardError(false);
view->setSource(testFileUrl("test.qml"));
QVERIFY(view->errors().isEmpty());
QQuickItem *rootItem = view->rootObject();
QVERIFY(rootItem);
QQuickItem *rectangleItem = findItem<QQuickItem>(view->rootObject(), QLatin1String("rectangleItem"));
QVERIFY(rectangleItem);
//Read gradient property as QObject
int propertyIndex = rectangleItem->metaObject()->indexOfProperty("containmentMask");
QVERIFY(propertyIndex > 0);
QMetaProperty metaProperty = rectangleItem->metaObject()->property(propertyIndex);
QVERIFY(metaProperty.isValid());
QVERIFY(QQuickDesignerSupportProperties::isPropertyQObject(metaProperty));
QObject *containmentItem = QQuickDesignerSupportProperties::readQObjectProperty(metaProperty, rectangleItem);
QVERIFY(containmentItem);
//The width property is not a QObject
propertyIndex = rectangleItem->metaObject()->indexOfProperty("width");
QVERIFY(propertyIndex > 0);
metaProperty = rectangleItem->metaObject()->property(propertyIndex);
QVERIFY(metaProperty.isValid());
QVERIFY(!QQuickDesignerSupportProperties::isPropertyQObject(metaProperty));
}
void tst_qquickdesignersupport::dynamicProperty()
{
QScopedPointer<QQuickView> view(new QQuickView);
view->engine()->setOutputWarningsToStandardError(false);
view->setSource(testFileUrl("test.qml"));
QVERIFY(view->errors().isEmpty());
QQuickItem *rootItem = view->rootObject();
QVERIFY(rootItem);
QQuickItem *simpleItem = findItem<QQuickItem>(view->rootObject(), QLatin1String("simpleItem"));
QVERIFY(simpleItem);
QQuickDesignerSupportProperties::registerNodeInstanceMetaObject(simpleItem, view->engine());
QQuickDesignerSupportProperties::getPropertyCache(simpleItem, view->engine());
QQuickDesignerSupportProperties::createNewDynamicProperty(simpleItem, view->engine(), QLatin1String("dynamicProperty"));
QQmlProperty property(simpleItem, "dynamicProperty", view->engine()->contextForObject(simpleItem));
QVERIFY(property.isValid());
QVERIFY(property.write(QLatin1String("test")));
QCOMPARE(property.read().toString(), QLatin1String("test"));
//Force evalutation of all bindings
QQuickDesignerSupport::refreshExpressions(view->rootContext());
//Check if expression to dynamic property gets properly resolved
property = QQmlProperty(simpleItem, "testProperty", view->engine()->contextForObject(simpleItem));
QVERIFY(property.isValid());
QCOMPARE(property.read().toString(), QLatin1String("test"));
}
void tst_qquickdesignersupport::createComponent()
{
QScopedPointer<QQuickView> view(new QQuickView);
view->engine()->setOutputWarningsToStandardError(false);
view->setSource(testFileUrl("test.qml"));
QVERIFY(view->errors().isEmpty());
QQuickItem *rootItem = view->rootObject();
QVERIFY(rootItem);
QObject *testComponentObject = QQuickDesignerSupportItems::createComponent(testFileUrl("TestComponent.qml"), view->rootContext());
QVERIFY(testComponentObject);
QVERIFY(QQuickDesignerSupportMetaInfo::isSubclassOf(testComponentObject, "QtQuick/Item"));
}
void tst_qquickdesignersupport::basicStates()
{
QScopedPointer<QQuickView> view(new QQuickView);
view->engine()->setOutputWarningsToStandardError(false);
view->setSource(testFileUrl("test.qml"));
QVERIFY(view->errors().isEmpty());
QQuickItem *rootItem = view->rootObject();
QVERIFY(rootItem);
QQuickStateGroup *stateGroup = QQuickItemPrivate::get(rootItem)->_states();
QVERIFY(stateGroup);
QCOMPARE(stateGroup->states().count(), 2 );
QQuickState *state01 = stateGroup->states().first();
QQuickState *state02 = stateGroup->states().last();
QVERIFY(state01);
QVERIFY(state02);
QCOMPARE(state01->property("name").toString(), QLatin1String("state01"));
QCOMPARE(state02->property("name").toString(), QLatin1String("state02"));
QVERIFY(!QQuickDesignerSupportStates::isStateActive(state01, view->rootContext()));
QVERIFY(!QQuickDesignerSupportStates::isStateActive(state01, view->rootContext()));
QQuickDesignerSupportStates::activateState(state01, view->rootContext());
QVERIFY(QQuickDesignerSupportStates::isStateActive(state01, view->rootContext()));
QQuickDesignerSupportStates::activateState(state02, view->rootContext());
QVERIFY(QQuickDesignerSupportStates::isStateActive(state02, view->rootContext()));
QVERIFY(!QQuickDesignerSupportStates::isStateActive(state01, view->rootContext()));
QQuickDesignerSupportStates::deactivateState(state02);
QVERIFY(!QQuickDesignerSupportStates::isStateActive(state01, view->rootContext()));
QVERIFY(!QQuickDesignerSupportStates::isStateActive(state01, view->rootContext()));
}
void tst_qquickdesignersupport::statesPropertyChanges()
{
QScopedPointer<QQuickView> view(new QQuickView);
view->engine()->setOutputWarningsToStandardError(false);
view->setSource(testFileUrl("test.qml"));
QVERIFY(view->errors().isEmpty());
QQuickItem *rootItem = view->rootObject();
QVERIFY(rootItem);
QQuickItem *simpleItem = findItem<QQuickItem>(view->rootObject(), QLatin1String("simpleItem"));
QVERIFY(simpleItem);
QQuickStateGroup *stateGroup = QQuickItemPrivate::get(rootItem)->_states();
QVERIFY(stateGroup);
QCOMPARE(stateGroup->states().count(), 2 );
QQuickState *state01 = stateGroup->states().first();
QQuickState *state02 = stateGroup->states().last();
QVERIFY(state01);
QVERIFY(state02);
QCOMPARE(state01->property("name").toString(), QLatin1String("state01"));
QCOMPARE(state02->property("name").toString(), QLatin1String("state02"));
//PropertyChanges are parsed lazily
QQuickDesignerSupportStates::activateState(state01, view->rootContext());
QQuickDesignerSupportStates::deactivateState(state01);
QQuickStatePrivate *statePrivate01 = static_cast<QQuickStatePrivate *>(QQuickStatePrivate::get(state01));
QCOMPARE(state01->operationCount(), 1);
QCOMPARE(statePrivate01->operations.count(), 1);
QQuickStateOperation *propertyChange = statePrivate01->operations.at(0).data();
QCOMPARE(QQuickDesignerSupportPropertyChanges::stateObject(propertyChange), state01);
QQuickDesignerSupportPropertyChanges::changeValue(propertyChange, "width", 300);
QCOMPARE(simpleItem->property("width").toInt(), 0);
QQuickDesignerSupportStates::activateState(state01, view->rootContext());
QCOMPARE(simpleItem->property("width").toInt(), 300);
QQuickDesignerSupportStates::deactivateState(state01);
QCOMPARE(simpleItem->property("width").toInt(), 0);
//Set "base state value" in state1 using the revert list
QQuickDesignerSupportStates::activateState(state01, view->rootContext());
QQuickDesignerSupportStates::changeValueInRevertList(state01, simpleItem, "width", 200);
QCOMPARE(simpleItem->property("width").toInt(), 300);
QQuickDesignerSupportStates::deactivateState(state01);
QCOMPARE(simpleItem->property("width").toInt(), 200);
//Create new PropertyChanges
QQuickPropertyChanges *newPropertyChange = new QQuickPropertyChanges();
newPropertyChange->setParent(state01);
QQmlListProperty<QQuickStateOperation> changes = state01->changes();
QQuickStatePrivate::operations_append(&changes, newPropertyChange);
newPropertyChange->setObject(rootItem);
QQuickDesignerSupportPropertyChanges::attachToState(newPropertyChange);
QCOMPARE(rootItem, QQuickDesignerSupportPropertyChanges::targetObject(newPropertyChange));
QCOMPARE(state01->operationCount(), 2);
QCOMPARE(statePrivate01->operations.count(), 2);
QCOMPARE(QQuickDesignerSupportPropertyChanges::stateObject(newPropertyChange), state01);
//Set color for rootItem in state1
QQuickDesignerSupportPropertyChanges::changeValue(newPropertyChange, "color", QColor(Qt::red));
QQuickDesignerSupportStates::activateState(state01, view->rootContext());
QCOMPARE(rootItem->property("color").value<QColor>(), QColor(Qt::red));
QQuickDesignerSupportStates::deactivateState(state01);
QCOMPARE(rootItem->property("color").value<QColor>(), QColor(Qt::white));
QQuickDesignerSupportPropertyChanges::removeProperty(newPropertyChange, "color");
QQuickDesignerSupportStates::activateState(state01, view->rootContext());
QCOMPARE(rootItem->property("color").value<QColor>(), QColor(Qt::white));
}
static QObject * s_object = nullptr;
static QQuickDesignerSupport::PropertyName s_propertyName;
static void notifyPropertyChangeCallBackFunction(QObject *object, const QQuickDesignerSupport::PropertyName &propertyName)
{
s_object = object;
s_propertyName = propertyName;
}
static void (*notifyPropertyChangeCallBackPointer)(QObject *, const QQuickDesignerSupport::PropertyName &) = &notifyPropertyChangeCallBackFunction;
void tst_qquickdesignersupport::testNotifyPropertyChangeCallBack()
{
QScopedPointer<QQuickView> view(new QQuickView);
view->engine()->setOutputWarningsToStandardError(false);
view->setSource(testFileUrl("test.qml"));
QVERIFY(view->errors().isEmpty());
QQuickItem *rootItem = view->rootObject();
QVERIFY(rootItem);
QQuickRectangle *rectangle = static_cast<QQuickRectangle *>(rootItem);
QVERIFY(rectangle);
QQuickDesignerSupportProperties::registerNodeInstanceMetaObject(rectangle, view->engine());
QQuickGradient *gradient = new QQuickGradient(rectangle);
QQuickDesignerSupportMetaInfo::registerNotifyPropertyChangeCallBack(notifyPropertyChangeCallBackPointer);
rectangle->setProperty("gradient", QVariant::fromValue<QJSValue>(view->engine()->newQObject(gradient)));
QVERIFY(s_object);
QCOMPARE(s_object, rootItem);
QCOMPARE(s_propertyName, QQuickDesignerSupport::PropertyName("gradient"));
}
static void fixResourcePathsForObjectCallBackFunction(QObject *object)
{
s_object = object;
}
static void (*fixResourcePathsForObjectCallBackPointer)(QObject *) = &fixResourcePathsForObjectCallBackFunction;
void tst_qquickdesignersupport::testFixResourcePathsForObjectCallBack()
{
QScopedPointer<QQuickView> view(new QQuickView);
view->engine()->setOutputWarningsToStandardError(false);
view->setSource(testFileUrl("test.qml"));
QVERIFY(view->errors().isEmpty());
QQuickItem *rootItem = view->rootObject();
QVERIFY(rootItem);
s_object = nullptr;
QQuickDesignerSupportItems::registerFixResourcePathsForObjectCallBack(fixResourcePathsForObjectCallBackPointer);
QQuickItem *simpleItem = findItem<QQuickItem>(view->rootObject(), QLatin1String("simpleItem"));
QVERIFY(simpleItem);
QQuickDesignerSupportItems::tweakObjects(simpleItem);
//Check that the fixResourcePathsForObjectCallBack was called on simpleItem
QCOMPARE(simpleItem , s_object);
}
void doComponentCompleteRecursive(QObject *object)
{
if (object) {
QQuickItem *item = qobject_cast<QQuickItem*>(object);
if (item && DesignerSupport::isComponentComplete(item))
return;
DesignerSupport::emitComponentCompleteSignalForAttachedProperty(object);
QList<QObject*> childList = object->children();
if (item) {
foreach (QQuickItem *childItem, item->childItems()) {
if (!childList.contains(childItem))
childList.append(childItem);
}
}
foreach (QObject *child, childList)
doComponentCompleteRecursive(child);
if (item) {
static_cast<QQmlParserStatus*>(item)->componentComplete();
} else {
QQmlParserStatus *qmlParserStatus = dynamic_cast< QQmlParserStatus*>(object);
if (qmlParserStatus)
qmlParserStatus->componentComplete();
}
}
}
void tst_qquickdesignersupport::testComponentOnCompleteSignal()
{
{
QScopedPointer<QQuickView> view(new QQuickView);
view->engine()->setOutputWarningsToStandardError(false);
view->setSource(testFileUrl("componentTest.qml"));
QVERIFY(view->errors().isEmpty());
QQuickItem *rootItem = view->rootObject();
QVERIFY(rootItem);
QQuickItem *item = findItem<QQuickItem>(view->rootObject(), QLatin1String("topLevelComplete"));
QVERIFY(item);
QCOMPARE(item->property("color").value<QColor>(), QColor("red"));
item = findItem<QQuickItem>(view->rootObject(), QLatin1String("implemented"));
QVERIFY(item);
QCOMPARE(item->property("color").value<QColor>(), QColor("blue"));
item = findItem<QQuickItem>(view->rootObject(), QLatin1String("most inner"));
QVERIFY(item);
QCOMPARE(item->property("color").value<QColor>(), QColor("green"));
}
{
ComponentCompleteDisabler disableComponentComplete;
QScopedPointer<QQuickView> view(new QQuickView);
view->engine()->setOutputWarningsToStandardError(false);
view->setSource(testFileUrl("componentTest.qml"));
QVERIFY(view->errors().isEmpty());
QQuickItem *rootItem = view->rootObject();
QVERIFY(rootItem);
QQuickItem *item = findItem<QQuickItem>(view->rootObject(), QLatin1String("topLevelComplete"));
QVERIFY(item);
QCOMPARE(item->property("color").value<QColor>(), QColor("white"));
item = findItem<QQuickItem>(view->rootObject(), QLatin1String("implemented"));
QVERIFY(item);
QCOMPARE(item->property("color").value<QColor>(), QColor("white"));
item = findItem<QQuickItem>(view->rootObject(), QLatin1String("most inner"));
QVERIFY(item);
QCOMPARE(item->property("color").value<QColor>(), QColor("white"));
doComponentCompleteRecursive(rootItem);
item = findItem<QQuickItem>(view->rootObject(), QLatin1String("topLevelComplete"));
QVERIFY(item);
QCOMPARE(item->property("color").value<QColor>(), QColor("red"));
item = findItem<QQuickItem>(view->rootObject(), QLatin1String("implemented"));
QVERIFY(item);
QCOMPARE(item->property("color").value<QColor>(), QColor("blue"));
item = findItem<QQuickItem>(view->rootObject(), QLatin1String("most inner"));
QVERIFY(item);
QCOMPARE(item->property("color").value<QColor>(), QColor("green"));
}
}
QTEST_MAIN(tst_qquickdesignersupport)
#include "tst_qquickdesignersupport.moc"