blob: c9e92cd3c9b8cc88192566256ade641fadab71b9 [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 <private/qqmlpropertycache_p.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcontext.h>
#include <QtQml/qqmlcomponent.h>
#include <private/qmetaobjectbuilder_p.h>
#include <QCryptographicHash>
#include "../../shared/util.h"
class tst_qqmlpropertycache : public QQmlDataTest
{
Q_OBJECT
public:
tst_qqmlpropertycache() {}
private slots:
void properties();
void propertiesDerived();
void revisionedProperties();
void methods();
void methodsDerived();
void signalHandlers();
void signalHandlersDerived();
void passForeignEnums();
void passQGadget();
void metaObjectSize_data();
void metaObjectSize();
void metaObjectChecksum();
void metaObjectsForRootElements();
private:
QQmlEngine engine;
};
class BaseObject : public QObject
{
Q_OBJECT
Q_PROPERTY(int propertyA READ propertyA NOTIFY propertyAChanged)
Q_PROPERTY(QString propertyB READ propertyB NOTIFY propertyBChanged)
public:
BaseObject(QObject *parent = nullptr) : QObject(parent) {}
int propertyA() const { return 0; }
QString propertyB() const { return QString(); }
public Q_SLOTS:
void slotA() {}
Q_SIGNALS:
void propertyAChanged();
void propertyBChanged();
void signalA();
};
class DerivedObject : public BaseObject
{
Q_OBJECT
Q_PROPERTY(int propertyC READ propertyC NOTIFY propertyCChanged)
Q_PROPERTY(QString propertyD READ propertyD NOTIFY propertyDChanged)
Q_PROPERTY(int propertyE READ propertyE NOTIFY propertyEChanged REVISION 1)
public:
DerivedObject(QObject *parent = nullptr) : BaseObject(parent) {}
int propertyC() const { return 0; }
QString propertyD() const { return QString(); }
int propertyE() const { return 0; }
public Q_SLOTS:
void slotB() {}
Q_SIGNALS:
void propertyCChanged();
void propertyDChanged();
Q_REVISION(1) void propertyEChanged();
void signalB();
};
QQmlPropertyData *cacheProperty(const QQmlRefPointer<QQmlPropertyCache> &cache, const char *name)
{
return cache->property(QLatin1String(name), nullptr, nullptr);
}
void tst_qqmlpropertycache::properties()
{
QQmlEngine engine;
DerivedObject object;
const QMetaObject *metaObject = object.metaObject();
QQmlRefPointer<QQmlPropertyCache> cache(new QQmlPropertyCache(metaObject));
QQmlPropertyData *data;
QVERIFY((data = cacheProperty(cache, "propertyA")));
QCOMPARE(data->coreIndex(), metaObject->indexOfProperty("propertyA"));
QVERIFY((data = cacheProperty(cache, "propertyB")));
QCOMPARE(data->coreIndex(), metaObject->indexOfProperty("propertyB"));
QVERIFY((data = cacheProperty(cache, "propertyC")));
QCOMPARE(data->coreIndex(), metaObject->indexOfProperty("propertyC"));
QVERIFY((data = cacheProperty(cache, "propertyD")));
QCOMPARE(data->coreIndex(), metaObject->indexOfProperty("propertyD"));
}
void tst_qqmlpropertycache::propertiesDerived()
{
QQmlEngine engine;
DerivedObject object;
const QMetaObject *metaObject = object.metaObject();
QQmlRefPointer<QQmlPropertyCache> parentCache(new QQmlPropertyCache(&BaseObject::staticMetaObject));
QQmlRefPointer<QQmlPropertyCache> cache(parentCache->copyAndAppend(object.metaObject()));
QQmlPropertyData *data;
QVERIFY((data = cacheProperty(cache, "propertyA")));
QCOMPARE(data->coreIndex(), metaObject->indexOfProperty("propertyA"));
QVERIFY((data = cacheProperty(cache, "propertyB")));
QCOMPARE(data->coreIndex(), metaObject->indexOfProperty("propertyB"));
QVERIFY((data = cacheProperty(cache, "propertyC")));
QCOMPARE(data->coreIndex(), metaObject->indexOfProperty("propertyC"));
QVERIFY((data = cacheProperty(cache, "propertyD")));
QCOMPARE(data->coreIndex(), metaObject->indexOfProperty("propertyD"));
}
void tst_qqmlpropertycache::revisionedProperties()
{
// Check that if you create a QQmlPropertyCache from a QMetaObject together
// with an explicit revision, the cache will then, and only then, report a
// property with a matching revision as available.
DerivedObject object;
const QMetaObject *metaObject = object.metaObject();
QQmlRefPointer<QQmlPropertyCache> cacheWithoutVersion(new QQmlPropertyCache(metaObject));
QQmlRefPointer<QQmlPropertyCache> cacheWithVersion(new QQmlPropertyCache(metaObject, 1));
QQmlPropertyData *data;
QVERIFY((data = cacheProperty(cacheWithoutVersion, "propertyE")));
QCOMPARE(cacheWithoutVersion->isAllowedInRevision(data), false);
QCOMPARE(cacheWithVersion->isAllowedInRevision(data), true);
}
void tst_qqmlpropertycache::methods()
{
QQmlEngine engine;
DerivedObject object;
const QMetaObject *metaObject = object.metaObject();
QQmlRefPointer<QQmlPropertyCache> cache(new QQmlPropertyCache(metaObject));
QQmlPropertyData *data;
QVERIFY((data = cacheProperty(cache, "slotA")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("slotA()"));
QVERIFY((data = cacheProperty(cache, "slotB")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("slotB()"));
QVERIFY((data = cacheProperty(cache, "signalA")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("signalA()"));
QVERIFY((data = cacheProperty(cache, "signalB")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("signalB()"));
QVERIFY((data = cacheProperty(cache, "propertyAChanged")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("propertyAChanged()"));
QVERIFY((data = cacheProperty(cache, "propertyBChanged")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("propertyBChanged()"));
QVERIFY((data = cacheProperty(cache, "propertyCChanged")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("propertyCChanged()"));
QVERIFY((data = cacheProperty(cache, "propertyDChanged")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("propertyDChanged()"));
}
void tst_qqmlpropertycache::methodsDerived()
{
QQmlEngine engine;
DerivedObject object;
const QMetaObject *metaObject = object.metaObject();
QQmlRefPointer<QQmlPropertyCache> parentCache(new QQmlPropertyCache(&BaseObject::staticMetaObject));
QQmlRefPointer<QQmlPropertyCache> cache(parentCache->copyAndAppend(object.metaObject()));
QQmlPropertyData *data;
QVERIFY((data = cacheProperty(cache, "slotA")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("slotA()"));
QVERIFY((data = cacheProperty(cache, "slotB")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("slotB()"));
QVERIFY((data = cacheProperty(cache, "signalA")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("signalA()"));
QVERIFY((data = cacheProperty(cache, "signalB")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("signalB()"));
QVERIFY((data = cacheProperty(cache, "propertyAChanged")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("propertyAChanged()"));
QVERIFY((data = cacheProperty(cache, "propertyBChanged")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("propertyBChanged()"));
QVERIFY((data = cacheProperty(cache, "propertyCChanged")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("propertyCChanged()"));
QVERIFY((data = cacheProperty(cache, "propertyDChanged")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("propertyDChanged()"));
}
void tst_qqmlpropertycache::signalHandlers()
{
QQmlEngine engine;
DerivedObject object;
const QMetaObject *metaObject = object.metaObject();
QQmlRefPointer<QQmlPropertyCache> cache(new QQmlPropertyCache(metaObject));
QQmlPropertyData *data;
QVERIFY((data = cacheProperty(cache, "onSignalA")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("signalA()"));
QVERIFY((data = cacheProperty(cache, "onSignalB")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("signalB()"));
QVERIFY((data = cacheProperty(cache, "onPropertyAChanged")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("propertyAChanged()"));
QVERIFY((data = cacheProperty(cache, "onPropertyBChanged")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("propertyBChanged()"));
QVERIFY((data = cacheProperty(cache, "onPropertyCChanged")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("propertyCChanged()"));
QVERIFY((data = cacheProperty(cache, "onPropertyDChanged")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("propertyDChanged()"));
}
void tst_qqmlpropertycache::signalHandlersDerived()
{
QQmlEngine engine;
DerivedObject object;
const QMetaObject *metaObject = object.metaObject();
QQmlRefPointer<QQmlPropertyCache> parentCache(new QQmlPropertyCache(&BaseObject::staticMetaObject));
QQmlRefPointer<QQmlPropertyCache> cache(parentCache->copyAndAppend(object.metaObject()));
QQmlPropertyData *data;
QVERIFY((data = cacheProperty(cache, "onSignalA")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("signalA()"));
QVERIFY((data = cacheProperty(cache, "onSignalB")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("signalB()"));
QVERIFY((data = cacheProperty(cache, "onPropertyAChanged")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("propertyAChanged()"));
QVERIFY((data = cacheProperty(cache, "onPropertyBChanged")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("propertyBChanged()"));
QVERIFY((data = cacheProperty(cache, "onPropertyCChanged")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("propertyCChanged()"));
QVERIFY((data = cacheProperty(cache, "onPropertyDChanged")));
QCOMPARE(data->coreIndex(), metaObject->indexOfMethod("propertyDChanged()"));
}
class MyEnum : public QObject
{
Q_OBJECT
public:
enum Option1Flag {
Option10 = 0,
Option1A = 1,
Option1B = 2,
Option1C = 4,
Option1D = 8,
Option1E = 16,
Option1F = 32,
Option1AD = Option1A | Option1D,
};
Q_DECLARE_FLAGS(Option1, Option1Flag)
Q_FLAG(Option1)
enum ShortEnum: quint16 {
Short0 = 0,
Short8 = 0xff,
Short16 = 0xffff
};
Q_ENUM(ShortEnum);
};
class MyData : public QObject
{
Q_OBJECT
Q_PROPERTY(MyEnum::Option1 opt1 READ opt1 WRITE setOpt1 NOTIFY opt1Changed)
Q_PROPERTY(MyEnum::ShortEnum opt2 READ opt2 WRITE setOpt2 NOTIFY opt2Changed)
public:
MyEnum::Option1 opt1() const { return m_opt1; }
MyEnum::ShortEnum opt2() const { return m_opt2; }
signals:
void opt1Changed(MyEnum::Option1 opt1);
void opt2Changed(MyEnum::ShortEnum opt2);
public slots:
void setOpt1(MyEnum::Option1 opt1)
{
QCOMPARE(opt1, MyEnum::Option1AD);
if (opt1 != m_opt1) {
m_opt1 = opt1;
emit opt1Changed(opt1);
}
}
void setOpt2(MyEnum::ShortEnum opt2)
{
QCOMPARE(opt2, MyEnum::Short16);
if (opt2 != m_opt2) {
m_opt2 = opt2;
emit opt2Changed(opt2);
}
}
void setOption1(MyEnum::Option1 opt1) { setOpt1(opt1); }
void setOption2(MyEnum::ShortEnum opt2) { setOpt2(opt2); }
private:
MyEnum::Option1 m_opt1 = MyEnum::Option10;
MyEnum::ShortEnum m_opt2 = MyEnum::Short8;
};
void tst_qqmlpropertycache::passForeignEnums()
{
qmlRegisterType<MyEnum>("example", 1, 0, "MyEnum");
qmlRegisterType<MyData>("example", 1, 0, "MyData");
MyEnum myenum;
MyData data;
engine.rootContext()->setContextProperty("myenum", &myenum);
engine.rootContext()->setContextProperty("mydata", &data);
QQmlComponent component(&engine, testFile("foreignEnums.qml"));
QVERIFY(component.isReady());
QScopedPointer<QObject> obj(component.create(engine.rootContext()));
QVERIFY(!obj.isNull());
QCOMPARE(data.opt1(), MyEnum::Option1AD);
QCOMPARE(data.opt2(), MyEnum::Short16);
}
Q_DECLARE_METATYPE(MyEnum::Option1)
Q_DECLARE_METATYPE(MyEnum::ShortEnum)
QT_BEGIN_NAMESPACE
class SimpleGadget
{
Q_GADGET
Q_PROPERTY(bool someProperty READ someProperty)
public:
bool someProperty() const { return true; }
};
// Avoids NeedsCreation and NeedsDestruction flags
Q_DECLARE_TYPEINFO(SimpleGadget, Q_PRIMITIVE_TYPE);
QT_END_NAMESPACE
class GadgetEmitter : public QObject
{
Q_OBJECT
signals:
void emitGadget(SimpleGadget);
};
void tst_qqmlpropertycache::passQGadget()
{
qRegisterMetaType<SimpleGadget>();
GadgetEmitter emitter;
engine.rootContext()->setContextProperty("emitter", &emitter);
QQmlComponent component(&engine, testFile("passQGadget.qml"));
QVERIFY(component.isReady());
QScopedPointer<QObject> obj(component.create(engine.rootContext()));
QVariant before = obj->property("result");
QVERIFY(before.isNull());
emit emitter.emitGadget(SimpleGadget());
QVariant after = obj->property("result");
QCOMPARE(QMetaType::Type(after.type()), QMetaType::Bool);
QVERIFY(after.toBool());
}
class TestClass : public QObject
{
Q_OBJECT
Q_PROPERTY(int prop READ prop WRITE setProp NOTIFY propChanged)
int m_prop;
public:
enum MyEnum {
First, Second
};
Q_ENUM(MyEnum)
Q_CLASSINFO("Foo", "Bar")
TestClass() {}
int prop() const
{
return m_prop;
}
public slots:
void setProp(int prop)
{
if (m_prop == prop)
return;
m_prop = prop;
emit propChanged(prop);
}
signals:
void propChanged(int prop);
};
class TestClassWithParameters : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE void slotWithArguments(int firstArg) {
Q_UNUSED(firstArg);
}
};
class TestClassWithClassInfo : public QObject
{
Q_OBJECT
Q_CLASSINFO("Key", "Value")
};
#include "tst_qqmlpropertycache.moc"
#define ARRAY_SIZE(arr) \
int(sizeof(arr) / sizeof(arr[0]))
#define TEST_CLASS(Class) \
QTest::newRow(#Class) << &Class::staticMetaObject << ARRAY_SIZE(qt_meta_data_##Class) << ARRAY_SIZE(qt_meta_stringdata_##Class.data)
Q_DECLARE_METATYPE(const QMetaObject*);
void tst_qqmlpropertycache::metaObjectSize_data()
{
QTest::addColumn<const QMetaObject*>("metaObject");
QTest::addColumn<int>("expectedFieldCount");
QTest::addColumn<int>("expectedStringCount");
TEST_CLASS(TestClass);
TEST_CLASS(TestClassWithParameters);
TEST_CLASS(TestClassWithClassInfo);
}
void tst_qqmlpropertycache::metaObjectSize()
{
QFETCH(const QMetaObject *, metaObject);
QFETCH(int, expectedFieldCount);
QFETCH(int, expectedStringCount);
int size = 0;
int stringDataSize = 0;
bool valid = QQmlPropertyCache::determineMetaObjectSizes(*metaObject, &size, &stringDataSize);
QVERIFY(valid);
QCOMPARE(size, expectedFieldCount - 1); // Remove trailing zero field until fixed in moc.
QCOMPARE(stringDataSize, expectedStringCount);
}
void tst_qqmlpropertycache::metaObjectChecksum()
{
QMetaObjectBuilder builder;
builder.setClassName("Test");
builder.addClassInfo("foo", "bar");
QCryptographicHash hash(QCryptographicHash::Md5);
QScopedPointer<QMetaObject, QScopedPointerPodDeleter> mo(builder.toMetaObject());
QVERIFY(!mo.isNull());
QVERIFY(QQmlPropertyCache::addToHash(hash, *mo.data()));
QByteArray initialHash = hash.result();
QVERIFY(!initialHash.isEmpty());
hash.reset();
{
QVERIFY(QQmlPropertyCache::addToHash(hash, *mo.data()));
QByteArray nextHash = hash.result();
QVERIFY(!nextHash.isEmpty());
hash.reset();
QCOMPARE(initialHash, nextHash);
}
builder.addProperty("testProperty", "int", -1);
mo.reset(builder.toMetaObject());
{
QVERIFY(QQmlPropertyCache::addToHash(hash, *mo.data()));
QByteArray nextHash = hash.result();
QVERIFY(!nextHash.isEmpty());
hash.reset();
QVERIFY(initialHash != nextHash);
}
}
void tst_qqmlpropertycache::metaObjectsForRootElements()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("noDuckType.qml"));
QVERIFY(c.isReady());
QScopedPointer<QObject> obj(c.create());
QVERIFY(!obj.isNull());
QCOMPARE(obj->property("result").toString(), QString::fromLatin1("good"));
}
QTEST_MAIN(tst_qqmlpropertycache)