Revision control
Copy as Markdown
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <qtest.h>
#include <private/qqmlpropertycache_p.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcontext.h>
#include <QtQml/qqmlcomponent.h>
#include <private/qmetaobjectbuilder_p.h>
#include <private/qqmlcontextdata_p.h>
#include <private/qqmldata_p.h>
#include <private/qqmlpropertycachecreator_p.h>
#include <QCryptographicHash>
#include <QtQuickTestUtils/private/qmlutils_p.h>
class tst_qqmlpropertycache : public QQmlDataTest
{
Q_OBJECT
public:
tst_qqmlpropertycache() : QQmlDataTest(QT_QMLTEST_DATADIR) {}
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();
void derivedGadgetMethod();
void restrictRegistrationVersion();
void rejectOverriddenFinal();
void overriddenSignals();
void duplicateIdsAndGeneralizedGroupedProperties();
void appendPropertyAttr_logging_data();
void appendPropertyAttr_logging();
void appendPropertyAttr_InvalidOverride_data();
void appendPropertyAttr_InvalidOverride();
void appendPropertyAttr_ValidOverride_data();
void appendPropertyAttr_ValidOverride();
void handleOverride_data();
void handleOverride();
void nonExistentGeneralizedGroup();
void append_propertyAttr_data();
void append_propertyAttr();
void isComposite();
void rebase();
void rebaseOntoLargerParent();
void rebaseOntoReorderedParent();
private:
QQmlEngine engine;
};
class BaseGadget
{
Q_GADGET
QML_ANONYMOUS
public:
Q_INVOKABLE QString stringValue() { return QLatin1String("base"); }
};
class DerivedGadget : public BaseGadget
{
Q_GADGET
QML_ANONYMOUS
public:
Q_INVOKABLE QString stringValue() { return QLatin1String("derived"); }
};
class GadgetUser : public QObject
{
Q_OBJECT
Q_PROPERTY(BaseGadget base READ base CONSTANT)
Q_PROPERTY(DerivedGadget derived READ derived CONSTANT)
Q_PROPERTY(QString baseString READ baseString WRITE setBaseString NOTIFY baseStringChanged)
Q_PROPERTY(QString derivedString READ derivedString WRITE setDerivedString NOTIFY derivedStringChanged)
QML_ELEMENT
public:
BaseGadget base() const { return m_base; }
DerivedGadget derived() const { return m_derived; }
QString baseString() const { return m_baseString; }
QString derivedString() const { return m_derivedString; }
public slots:
void setBaseString(QString baseString)
{
if (m_baseString == baseString)
return;
m_baseString = baseString;
emit baseStringChanged(m_baseString);
}
void setDerivedString(QString derivedString)
{
if (m_derivedString == derivedString)
return;
m_derivedString = derivedString;
emit derivedStringChanged(m_derivedString);
}
signals:
void baseStringChanged(QString baseString);
void derivedStringChanged(QString derivedString);
private:
BaseGadget m_base;
DerivedGadget m_derived;
QString m_baseString;
QString m_derivedString;
};
class BaseObject : public QObject
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(int propertyA READ propertyA NOTIFY propertyAChanged)
Q_PROPERTY(QString propertyB READ propertyB NOTIFY propertyBChanged)
Q_PROPERTY(int highVersion READ highVersion WRITE setHighVersion NOTIFY highVersionChanged REVISION(4, 0))
Q_PROPERTY(int finalProp READ finalProp CONSTANT FINAL)
public:
BaseObject(QObject *parent = nullptr) : QObject(parent) {}
int propertyA() const { return 0; }
QString propertyB() const { return QString(); }
int highVersion() const { return m_highVersion; }
int finalProp() const { return 8; }
public Q_SLOTS:
void slotA() {}
void setHighVersion(int highVersion)
{
if (m_highVersion == highVersion)
return;
m_highVersion = highVersion;
emit highVersionChanged();
}
Q_SIGNALS:
void propertyAChanged();
void propertyBChanged();
void signalA();
void highVersionChanged();
private:
int m_highVersion = 0;
};
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)
Q_PROPERTY(int finalProp READ finalProp CONSTANT) // bad!
public:
DerivedObject(QObject *parent = nullptr) : BaseObject(parent) {}
int propertyC() const { return 0; }
QString propertyD() const { return QString(); }
int propertyE() const { return 0; }
int finalProp() const { return 9; }
public Q_SLOTS:
void slotB() {}
Q_SIGNALS:
void propertyCChanged();
void propertyDChanged();
Q_REVISION(1) void propertyEChanged();
void signalB();
};
class OverriddenSignal : public BaseObject
{
Q_OBJECT
public:
OverriddenSignal(QObject *parent = nullptr) : BaseObject(parent) {}
Q_INVOKABLE void propertyAChanged() { ++propertyAChangedCalled; }
int propertyAChangedCalled = 0;
Q_SIGNALS:
void signalA();
};
const QQmlPropertyData *cacheProperty(const QQmlPropertyCache::ConstPtr &cache, const char *name)
{
return cache->property(QLatin1String(name), nullptr, nullptr);
}
void tst_qqmlpropertycache::properties()
{
QQmlEngine engine;
DerivedObject object;
const QMetaObject *metaObject = object.metaObject();
QQmlPropertyCache::ConstPtr cache = QQmlPropertyCache::createStandalone(metaObject);
const 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();
QQmlPropertyCache::ConstPtr parentCache
= QQmlPropertyCache::createStandalone(&BaseObject::staticMetaObject);
QQmlPropertyCache::ConstPtr cache
= parentCache->copyAndAppend(object.metaObject(), QTypeRevision());
const 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();
QQmlPropertyCache::ConstPtr cacheWithoutVersion(
QQmlPropertyCache::createStandalone(metaObject));
QQmlPropertyCache::ConstPtr cacheWithVersion(
QQmlPropertyCache::createStandalone(
metaObject, QTypeRevision::fromMinorVersion(1)));
const 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();
QQmlPropertyCache::ConstPtr cache(QQmlPropertyCache::createStandalone(metaObject));
const 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();
QQmlPropertyCache::ConstPtr parentCache(
QQmlPropertyCache::createStandalone(&BaseObject::staticMetaObject));
QQmlPropertyCache::ConstPtr cache
= parentCache->copyAndAppend(object.metaObject(), QTypeRevision {});
const 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();
QQmlPropertyCache::ConstPtr cache(QQmlPropertyCache::createStandalone(metaObject));
const 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();
QQmlPropertyCache::ConstPtr parentCache(
QQmlPropertyCache::createStandalone(&BaseObject::staticMetaObject));
QQmlPropertyCache::ConstPtr cache
= parentCache->copyAndAppend(object.metaObject(), QTypeRevision{});
const 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(after.typeId(), 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")
};
template <typename T, typename = void>
struct SizeofOffsetsAndSizes_helper
{
static constexpr size_t value = sizeof(T::offsetsAndSize); // old moc
};
template <typename T>
struct SizeofOffsetsAndSizes_helper<T, std::void_t<decltype(T::offsetsAndSizes)>>
{
static constexpr size_t value = sizeof(T::offsetsAndSizes); // new moc
};
template <typename T>
constexpr size_t sizeofOffsetsAndSizes(const T &)
{
return SizeofOffsetsAndSizes_helper<T>::value;
}
// NOTE: ScopeCounter might change new metaobjects are added
// check the moc file and adjust as necessary if tests fails to compile
#define TEST_CLASS(Class, Fields, Strings) \
QTest::newRow(#Class) \
<< &Class::staticMetaObject \
<< Fields \
<< Strings
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, 49, 10);
TEST_CLASS(TestClassWithParameters, 24, 4);
TEST_CLASS(TestClassWithClassInfo, 17, 3);
}
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"));
}
void tst_qqmlpropertycache::derivedGadgetMethod()
{
metaObjectsForRootElements();
qmlRegisterTypesAndRevisions<BaseGadget, DerivedGadget, GadgetUser>("Test.PropertyCache", 1);
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("derivedGadgetMethod.qml"));
QVERIFY(c.isReady());
QScopedPointer<QObject> obj(c.create());
QVERIFY(!obj.isNull());
GadgetUser *gadgetUser = qobject_cast<GadgetUser *>(obj.data());
QVERIFY(gadgetUser);
QCOMPARE(gadgetUser->baseString(), QString::fromLatin1("base"));
QCOMPARE(gadgetUser->derivedString(), QString::fromLatin1("derived"));
}
void tst_qqmlpropertycache::restrictRegistrationVersion()
{
qmlRegisterTypesAndRevisions<BaseObject>("Test.PropertyCache", 3);
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("highVersion.qml"));
QVERIFY(c.isError());
}
void tst_qqmlpropertycache::rejectOverriddenFinal()
{
qmlRegisterTypesAndRevisions<BaseObject>("Test.PropertyCache", 3);
QQmlEngine engine;
QTest::ignoreMessage(QtWarningMsg, QRegularExpression(
"Final member finalProp is overridden in class BaseObject_QML_.* "
"The override won't be used."));
QQmlComponent c(&engine, testFileUrl("finalProp.qml"));
QVERIFY2(!c.isError(), qPrintable(c.errorString()));
QTest::ignoreMessage(QtWarningMsg, QRegularExpression(
"finalProp.qml:15: TypeError: Property 'finalProp' of object "
"BaseObject_QML_.* is not a function"));
QTest::ignoreMessage(QtWarningMsg, QRegularExpression(
"finalProp.qml:11: TypeError: Property 'finalProp' of object "
"BaseObject_QML_.* is not a function"));
QScopedPointer<QObject> o(c.create());
QCOMPARE(o->property("a").toInt(), 8);
DerivedObject *derived = new DerivedObject(o.data());
QTest::ignoreMessage(QtWarningMsg,
"Final member finalProp is overridden in class DerivedObject. "
"The override won't be used.");
o->setProperty("obj", QVariant::fromValue(derived));
QCOMPARE(derived->finalProp(), 9);
// rejects override of final property
QCOMPARE(o->property("a").toInt(), 8);
// Cannot override final property with method, either
QCOMPARE(o->property("b").toInt(), 8);
// Cannot call the method overridding a final property
QCOMPARE(o->property("c").toInt(), 0);
}
void tst_qqmlpropertycache::overriddenSignals()
{
qmlRegisterTypesAndRevisions<BaseObject>("Test.PropertyCache", 3);
QQmlEngine engine;
QQmlComponent c1(&engine, testFileUrl("overriddenSignal.qml"));
QVERIFY2(!c1.isError(), qPrintable(c1.errorString()));
QScopedPointer<QObject> o(c1.create());
// the propertyAChanged _signal_ is sent once (initially).
QCOMPARE(o->property("a").toInt(), 1);
// signalA() is invoked once as signal, and the other time as method since both are C++.
QCOMPARE(o->property("b").toInt(), 1);
OverriddenSignal *derived = new OverriddenSignal(o.data());
// Does call our overridden method, since that is defined in C++
QCOMPARE(derived->propertyAChangedCalled, 0);
o->setProperty("obj", QVariant::fromValue(derived));
QCOMPARE(derived->propertyAChangedCalled, 1);
o->setProperty("obj2", QVariant::fromValue(derived));
// the propertyAChanged _signal_ is sent once (initially).
QCOMPARE(o->property("a").toInt(), 1);
// We get to receive both signalA() signals since we only match by name.
QCOMPARE(o->property("b").toInt(), 2);
// We shouldn't be allowed to define such things in QML, though.
const QUrl c2Url = testFileUrl("qmlOverriddenSignal.qml");
QTest::ignoreMessage(
QtWarningMsg,
qPrintable(c2Url.toString() + QLatin1String(
":6:14: Duplicate method name: "
"invalid override of property change signal or superclass signal")));
QQmlComponent c2(&engine, c2Url);
// Should be an error, but we can't enforce it yet.
const QUrl c3Url = testFileUrl("qmlOverriddenSignal2.qml");
QTest::ignoreMessage(
QtWarningMsg,
qPrintable(c3Url.toString() + QLatin1String(
":5:12: Duplicate signal name: "
"invalid override of property change signal or superclass signal")));
QQmlComponent c3(&engine, c3Url);
// Should be an error, but we can't enforce it yet.
}
void tst_qqmlpropertycache::duplicateIdsAndGeneralizedGroupedProperties()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("duplicateIdsAndGeneralizedGroupedProperties.qml"));
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
QTest::ignoreMessage(QtDebugMsg, "1 true true true");
QTest::ignoreMessage(QtDebugMsg, "2 false true true");
QTest::ignoreMessage(QtDebugMsg, "3 false false true");
QTest::ignoreMessage(QtDebugMsg, "4 false false false");
QScopedPointer<QObject> o(c.create());
}
// TODO consider moving to test utils
static inline auto propertyWithFlags(QQmlPropertyData::Flags &&flags = QQmlPropertyData::Flags())
-> QQmlPropertyData
{
QQmlPropertyData property{};
property.setFlags(std::move(flags));
return property;
};
using namespace OverrideSemantics;
static inline auto newPropertyCache(HandlerRef fakeOverrideHandler) -> QQmlPropertyCache::Ptr
{
return QQmlPropertyCache::Ptr(new QQmlPropertyCache(fakeOverrideHandler),
QQmlPropertyCache::Ptr::Adopt);
}
void tst_qqmlpropertycache::appendPropertyAttr_logging_data()
{
QTest::addColumn<OverrideSemantics::Status>("overrideStatus");
QTest::addColumn<QString>("warningPattern");
QTest::addColumn<QtMsgType>("level");
QTest::newRow("NoOverride") << Status::NoOverride << "" << QtInfoMsg;
QTest::newRow("Valid") << Status::Valid << "" << QtInfoMsg;
QTest::newRow("MissingBase")
<< Status::MissingBase
<< "Member (.*) of the object (.*) does not override anything. Consider "
"removing \"override\"."
<< QtWarningMsg;
QTest::newRow("OverridingFinal")
<< Status::OverridingFinal
<< "Final member (.*) is overridden in class (.*). The override won't be used."
<< QtWarningMsg;
QTest::newRow("OverridingNonVirtual")
<< Status::OverridingNonVirtual
<< "Member (.*) of the object (.*) overrides a non-virtual "
"member. Consider renaming it or mark it virtual in the base object"
<< QtDebugMsg;
QTest::newRow("OverridingNonVirtualError")
<< Status::OverridingNonVirtualError
<< "Member (.*) of the object (.*) overrides a non-virtual "
"member. Consider renaming it or mark it virtual in the base object"
<< QtWarningMsg;
QTest::newRow("MissingOverrideSpecifier")
<< Status::MissingOverrideOrFinalSpecifier
<< "Member (.*) of the object (.*) overrides a member of the base object. "
"Consider renaming it or adding final or override specifier"
<< QtWarningMsg;
}
static QLoggingCategory::CategoryFilter parentFilter;
void logFilter(QLoggingCategory *category)
{
if (qstrcmp(category->categoryName(), "qt.qml.propertyCache.append") == 0)
category->setEnabled(QtDebugMsg, true);
else if (parentFilter)
parentFilter(category);
}
void tst_qqmlpropertycache::appendPropertyAttr_logging()
{
QFETCH(OverrideSemantics::Status, overrideStatus);
QFETCH(QString, warningPattern);
QFETCH(QtMsgType, level);
parentFilter = QLoggingCategory::installFilter(logFilter);
const auto restoreFilter = qScopeGuard([]() { QLoggingCategory::installFilter(parentFilter); });
const auto fakeOverrideHandler = [&overrideStatus](QQmlPropertyData &, QQmlPropertyData *,
CheckMode) -> Status {
return overrideStatus;
};
if (!warningPattern.isEmpty())
QTest::ignoreMessage(level, QRegularExpression(warningPattern.toLatin1()));
newPropertyCache(fakeOverrideHandler)->appendPropertyAttr("p", QQmlPropertyData());
}
void tst_qqmlpropertycache::appendPropertyAttr_InvalidOverride_data()
{
QTest::addColumn<OverrideSemantics::Status>("overrideStatus");
QTest::newRow("MissingBase") << Status::MissingBase;
QTest::newRow("OverridingFinal") << Status::OverridingFinal;
QTest::newRow("OverridingNonVirtualError") << Status::OverridingNonVirtualError;
}
void tst_qqmlpropertycache::appendPropertyAttr_InvalidOverride()
{
QFETCH(OverrideSemantics::Status, overrideStatus);
QVERIFY(!isValidOverride(overrideStatus));
const auto fakeOverrideHandler = [&overrideStatus](QQmlPropertyData &, QQmlPropertyData *,
CheckMode) -> Status {
return overrideStatus;
};
QQmlPropertyCache::Ptr cache = newPropertyCache(fakeOverrideHandler);
QCOMPARE(cache->propertyCount(), 0);
const auto res = cache->appendPropertyAttr("p", QQmlPropertyData());
QVERIFY(!res);
QCOMPARE(res.error(), overrideStatus);
QCOMPARE(cache->propertyCount(), 1);
// since append is invalid the property is not accessible by name
QVERIFY(!cacheProperty(cache, "p"));
}
void tst_qqmlpropertycache::appendPropertyAttr_ValidOverride_data()
{
QTest::addColumn<OverrideSemantics::Status>("overrideStatus");
QTest::newRow("NoOverride") << Status::NoOverride;
QTest::newRow("Valid") << Status::Valid;
QTest::newRow("MissingOverrideSpecifier") << Status::MissingOverrideOrFinalSpecifier;
QTest::newRow("OverridingNonVirtual") << Status::OverridingNonVirtual;
}
void tst_qqmlpropertycache::appendPropertyAttr_ValidOverride()
{
QFETCH(OverrideSemantics::Status, overrideStatus);
QVERIFY(isValidOverride(overrideStatus));
const auto fakeOverrideHandler = [&overrideStatus](QQmlPropertyData &, QQmlPropertyData *,
CheckMode) -> Status {
return overrideStatus;
};
QQmlPropertyCache::Ptr cache = newPropertyCache(fakeOverrideHandler);
auto propCount = 0;
QCOMPARE(cache->propertyCount(), propCount);
const QString propName = "p";
{
QQmlPropertyData data{};
// set just to verify upon query that it's the same data
data.setCoreIndex(6);
const auto res = cache->appendPropertyAttr(propName, QQmlPropertyData(data));
QVERIFY(res);
QCOMPARE(cache->propertyCount(), ++propCount);
const auto *addedProperty = cache->property(propName, nullptr, nullptr);
QVERIFY(addedProperty);
QCOMPARE(*addedProperty, data);
}
// Now override the property
{
QQmlPropertyData data{};
// set just to verify upon query that it's the same data
data.setCoreIndex(3);
data.setWritable(false);
const auto res = cache->appendPropertyAttr(propName, QQmlPropertyData(data));
QVERIFY(res);
QCOMPARE(cache->propertyCount(), ++propCount);
const auto *addedProperty = cache->property(propName, nullptr, nullptr);
QVERIFY(addedProperty);
QCOMPARE(*addedProperty, data);
}
}
void tst_qqmlpropertycache::handleOverride_data()
{
QTest::addColumn<QQmlPropertyData>("overridingProperty");
QTest::addColumn<std::optional<QQmlPropertyData>>("existingProperty");
QTest::addColumn<CheckMode>("checkMode");
QTest::addColumn<Status>("expectedResult");
const auto makeNullopt = []() -> std::optional<QQmlPropertyData> { return std::nullopt; };
// Minimal checks
QTest::newRow("Derived{property var p} Base{}")
<< propertyWithFlags() << makeNullopt() << CheckMode::Minimal << Status::NoOverride;
QQmlPropertyData::Flags final{};
final.setIsFinal(true);
const auto finalProperty = propertyWithFlags(std::move(final));
QTest::newRow("Derived{property var p} Base{final property var p}")
<< propertyWithFlags() << std::make_optional(finalProperty) << CheckMode::Minimal
<< Status::OverridingFinal;
QTest::newRow("Derived{property var p} Base{ property var p}")
<< propertyWithFlags() << std::make_optional(propertyWithFlags()) << CheckMode::Minimal
<< Status::Valid;
// Full
QTest::newRow("Derived{property var p} Base{} full")
<< propertyWithFlags() << makeNullopt() << CheckMode::Full << Status::NoOverride;
QQmlPropertyData::Flags override{};
override.setDoesOverride(true);
const auto propertyWithOverride = propertyWithFlags(std::move(override));
QQmlPropertyData::Flags virtual_{};
virtual_.setIsVirtual(true);
const auto propertyWithVirtual = propertyWithFlags(std::move(virtual_));
QTest::newRow("Derived{override property var p} Base{}")
<< propertyWithOverride << makeNullopt() << CheckMode::Full << Status::MissingBase;
QTest::newRow("Derived{property var p} Base{final property var p} full")
<< propertyWithFlags() << std::make_optional(finalProperty) << CheckMode::Full
<< Status::OverridingFinal;
QTest::newRow("Derived{override property var p} Base{final property var p}")
<< propertyWithOverride << std::make_optional(finalProperty) << CheckMode::Full
<< Status::OverridingFinal;
QTest::newRow("Derived{property var p} Base{property var p}")
<< propertyWithFlags() << std::make_optional(propertyWithFlags()) << CheckMode::Full
<< Status::OverridingNonVirtual;
QTest::newRow("Derived{final property var p} Base{property var p}")
<< finalProperty << std::make_optional(propertyWithFlags()) << CheckMode::Full
<< Status::OverridingNonVirtual;
QTest::newRow("Derived{virtual property var p} Base{property var p}")
<< propertyWithVirtual << std::make_optional(propertyWithFlags()) << CheckMode::Full
<< Status::OverridingNonVirtual;
QTest::newRow("Derived{override property var p} Base{property var p}")
<< propertyWithOverride << std::make_optional(propertyWithFlags()) << CheckMode::Full
<< Status::OverridingNonVirtualError;
QTest::newRow("Derived{property var p} Base{virtual property var p}")
<< propertyWithFlags() << std::make_optional(propertyWithVirtual) << CheckMode::Full
<< Status::MissingOverrideOrFinalSpecifier;
QTest::newRow("Derived{virtual property var p} Base{virtual property var p}")
<< propertyWithVirtual << std::make_optional(propertyWithVirtual) << CheckMode::Full
<< Status::MissingOverrideOrFinalSpecifier;
QTest::newRow("Derived{override property var p} Base{virtual property var p}")
<< propertyWithOverride << std::make_optional(propertyWithVirtual) << CheckMode::Full
<< Status::Valid;
QTest::newRow("Derived{final property var p} Base{virtual property var p}")
<< finalProperty << std::make_optional(propertyWithVirtual) << CheckMode::Full
<< Status::Valid;
QQmlPropertyData::Flags invokable{};
invokable.setType(QQmlPropertyData::Flags::Type::FunctionType);
auto funcProperty = propertyWithFlags(std::move(invokable));
QTest::newRow("Derived{override p()} Base{virtual property var p}")
<< funcProperty << std::make_optional(propertyWithFlags(std::move(virtual_)))
<< CheckMode::Full << Status::InvokabilityMismatch;
QTest::newRow("Derived{override p} Base{p()}")
<< propertyWithOverride << std::make_optional(funcProperty) << CheckMode::Full
<< Status::InvokabilityMismatch;
}
void tst_qqmlpropertycache::handleOverride()
{
QFETCH(QQmlPropertyData, overridingProperty);
QFETCH(std::optional<QQmlPropertyData>, existingProperty);
QFETCH(CheckMode, checkMode);
QFETCH(Status, expectedResult);
auto *existing = existingProperty.has_value() ? &existingProperty.value() : nullptr;
const auto res = ::handleOverride(overridingProperty, existing, checkMode);
QCOMPARE(res, expectedResult);
if (isValidOverride(res) && existing) {
// test markOverrideOf
QCOMPARE(overridingProperty.isVirtual(), existing->isVirtual());
QCOMPARE(overridingProperty.overrideIndexIsProperty(), !existing->isFunction());
QCOMPARE(overridingProperty.overrideIndex(), existing->coreIndex());
QVERIFY(existing->isOverridden());
}
}
void tst_qqmlpropertycache::nonExistentGeneralizedGroup()
{
QQmlEngine engine;
QQmlComponent component(&engine);
component.setData(R"(
import QtQml
QtObject{grid{id:grid}}
)", QUrl());
QVERIFY(component.isError());
QVERIFY(component.errorString().contains("Cannot assign to non-existent property \"grid\""));
}
void tst_qqmlpropertycache::append_propertyAttr_data()
{
appendPropertyAttr_logging_data();
}
/*
* Because of the tightly coupling of the QQmlPropertyCache, this case covers very
* narrowed (far from exhaustive) pieces of "append" method through publicly exposed "update"
* method.
* It verifies that depending on the Override Semantics the corresponding logging takes
* place. It also verifies that the property is being added and accessible depending on the status of
* override semantics.
*/
void tst_qqmlpropertycache::append_propertyAttr()
{
QFETCH(OverrideSemantics::Status, overrideStatus);
QFETCH(QString, warningPattern);
QFETCH(QtMsgType, level);
parentFilter = QLoggingCategory::installFilter(logFilter);
const auto restoreFilter = qScopeGuard([]() { QLoggingCategory::installFilter(parentFilter); });
const auto fakeOverrideHandler = [&overrideStatus](QQmlPropertyData &, QQmlPropertyData *,
CheckMode) -> Status {
return overrideStatus;
};
QQmlPropertyCache::Ptr cache = newPropertyCache(fakeOverrideHandler);
QCOMPARE(cache->propertyCount(), 0);
QMetaObjectBuilder moBuilder;
const QString propName = "p";
moBuilder.addProperty(propName.toUtf8(), "int", -1);
std::unique_ptr<QMetaObject, decltype(&free)> mo(moBuilder.toMetaObject(), &free);
if (!warningPattern.isEmpty())
QTest::ignoreMessage(level, QRegularExpression(warningPattern.toLatin1()));
cache->update(mo.get());
// no matter what the property is added
QCOMPARE(cache->propertyCount(), mo->propertyCount());
const auto *addedProperty = cache->property(propName, nullptr, nullptr);
// however it is accessible by name only in cases of valid override
if (isValidOverride(overrideStatus)) {
QVERIFY(addedProperty);
} else {
QVERIFY(!addedProperty);
}
}
void tst_qqmlpropertycache::isComposite()
{
// A property cache from a C++ QMetaObject should not be composite.
QQmlPropertyCache::Ptr cppCache(
QQmlPropertyCache::createStandalone(&QObject::staticMetaObject));
QVERIFY(!cppCache->isComposite());
// A property cache from a QML component should be composite.
QQmlComponent component(&engine, testFileUrl("Simple.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QScopedPointer<QObject> obj(component.create());
QVERIFY(obj);
QQmlData *ddata = QQmlData::get(obj.data());
QVERIFY(ddata);
QVERIFY(ddata->propertyCache);
QVERIFY(ddata->propertyCache->isComposite());
}
void tst_qqmlpropertycache::rebase()
{
QQmlComponent component(&engine, testFileUrl("Simple.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QScopedPointer<QObject> obj(component.create());
QVERIFY(obj);
QQmlData *ddata = QQmlData::get(obj.data());
QVERIFY(ddata);
QVERIFY(ddata->propertyCache);
const QQmlPropertyCache::ConstPtr original = ddata->propertyCache;
QVERIFY(original->parent());
// Rebase onto the same parent – property counts should match.
const QQmlPropertyCache::Ptr rebased = original->rebased(original->parent());
QCOMPARE(rebased->propertyCount(), original->propertyCount());
QCOMPARE(rebased->methodCount(), original->methodCount());
QCOMPARE(rebased->signalCount(), original->signalCount());
QVERIFY(rebased->parent().data() == original->parent().data());
// Verify that the custom property "testProp" is still reachable.
const QQmlPropertyData *prop = rebased->property(
QLatin1String("testProp"), /*object=*/nullptr, /*context=*/nullptr);
QVERIFY(prop);
QVERIFY(prop->propType() == QMetaType::fromType<int>());
}
// Rebasing onto a parent with a *different* (larger) layout must shift the derived type's own
// member indices and offsets, and rebuild the meta-object to include those members. This is what
// the QML hot-reload path relies on when a composite base type gains members.
void tst_qqmlpropertycache::rebaseOntoLargerParent()
{
QQmlComponent derivedComp(&engine, testFileUrl("RebaseDerived.qml"));
QVERIFY2(derivedComp.isReady(), qPrintable(derivedComp.errorString()));
QScopedPointer<QObject> derived(derivedComp.create());
QVERIFY(derived);
QQmlComponent grownComp(&engine, testFileUrl("RebaseBaseGrown.qml"));
QVERIFY2(grownComp.isReady(), qPrintable(grownComp.errorString()));
QScopedPointer<QObject> grown(grownComp.create());
QVERIFY(grown);
const QQmlPropertyCache::ConstPtr derivedCache = QQmlData::get(derived.data())->propertyCache;
const QQmlPropertyCache::ConstPtr grownCache = QQmlData::get(grown.data())->propertyCache;
QVERIFY(derivedCache && grownCache);
const QQmlPropertyCache::ConstPtr baseCache = derivedCache->parent();
QVERIFY(baseCache);
// The "grown" base really has one more property than the base the derived type was compiled
// against — and thus one more change signal (hence one more method).
const int deltaProperty = grownCache->propertyCount() - baseCache->propertyCount();
const int deltaMethod = grownCache->methodCount() - baseCache->methodCount();
const int deltaSignal = grownCache->signalCount() - baseCache->signalCount();
QCOMPARE(deltaProperty, 1);
QCOMPARE(deltaMethod, 1);
QCOMPARE(deltaSignal, 1);
const auto lookup = [](const QQmlPropertyCache::ConstPtr &cache, const char *name) {
return cache->property(QLatin1String(name), nullptr, nullptr);
};
const QQmlPropertyData *ownBefore = lookup(derivedCache, "own");
const QQmlPropertyData *ownSignalBefore = lookup(derivedCache, "ownSignal");
const QQmlPropertyData *ownMethodBefore = lookup(derivedCache, "ownMethod");
QVERIFY(ownBefore && ownSignalBefore && ownMethodBefore);
// Rebase the derived cache onto the larger parent.
const QQmlPropertyCache::Ptr rebased = derivedCache->rebased(grownCache);
QVERIFY(rebased->parent().data() == grownCache.data());
// The inherited-member counts (which place the derived type's own members) follow the new,
// larger parent — not the old one.
QCOMPARE(rebased->propertyOffset(), grownCache->propertyCount());
QCOMPARE(rebased->methodOffset(), grownCache->methodCount());
QCOMPARE(rebased->signalOffset(), grownCache->signalCount());
// The derived type's own members are still reachable, and their indices shifted by the delta
// in each respective index space.
const QQmlPropertyData *own = lookup(rebased, "own");
const QQmlPropertyData *ownSignal = lookup(rebased, "ownSignal");
const QQmlPropertyData *ownMethod = lookup(rebased, "ownMethod");
QVERIFY(own && ownSignal && ownMethod);
QCOMPARE(own->propType(), QMetaType::fromType<int>());
QCOMPARE(own->coreIndex(), ownBefore->coreIndex() + deltaProperty);
QCOMPARE(own->notifyIndex(), ownBefore->notifyIndex() + deltaSignal);
QCOMPARE(ownSignal->coreIndex(), ownSignalBefore->coreIndex() + deltaMethod);
QCOMPARE(ownMethod->coreIndex(), ownMethodBefore->coreIndex() + deltaMethod);
// The (shifted) notify index still resolves to a signal — the property's own change signal.
const QQmlPropertyData *notify = rebased->signal(own->notifyIndex());
QVERIFY(notify && notify->isSignal());
// The rebuilt meta-object must include the derived type's own members (seeding it from the
// parent would drop them) and agree with the cache on every index.
const QMetaObject *mo = rebased->createMetaObject();
QVERIFY(mo);
const int ownPropIdx = mo->indexOfProperty("own");
QVERIFY(ownPropIdx >= 0);
QCOMPARE(ownPropIdx, own->coreIndex());
const int ownSignalIdx = mo->indexOfSignal("ownSignal()");
QVERIFY(ownSignalIdx >= 0);
QCOMPARE(ownSignalIdx, ownSignal->coreIndex());
const int ownMethodIdx = mo->indexOfMethod("ownMethod()");
QVERIFY(ownMethodIdx >= 0);
QCOMPARE(ownMethodIdx, ownMethod->coreIndex());
const QMetaProperty ownProp = mo->property(ownPropIdx);
QVERIFY(ownProp.hasNotifySignal());
QCOMPARE(ownProp.notifySignal().name(), QByteArray("ownChanged"));
}
// rebased() shifts the derived type's stored indices by a single per-space delta
// (newParentCount - oldParentCount), assuming the parent only *grew at the end*.
// But a reloaded base type can change the index of its *existing* members (e.g. a
// property declared earlier in the file, or reordered members). References that a
// derived type stores *into* the parent's index space -- here the overrideIndex of an
// overriding property -- are then left pointing at the wrong parent member.
void tst_qqmlpropertycache::rebaseOntoReorderedParent()
{
QQmlComponent baseComp(&engine, testFileUrl("OverrideBase.qml"));
QVERIFY2(baseComp.isReady(), qPrintable(baseComp.errorString()));
QScopedPointer<QObject> base(baseComp.create());
QVERIFY(base);
QQmlComponent derivedComp(&engine, testFileUrl("OverrideDerived.qml"));
QVERIFY2(derivedComp.isReady(), qPrintable(derivedComp.errorString()));
QScopedPointer<QObject> derived(derivedComp.create());
QVERIFY(derived);
QQmlComponent reorderedComp(&engine, testFileUrl("OverrideBaseReordered.qml"));
QVERIFY2(reorderedComp.isReady(), qPrintable(reorderedComp.errorString()));
QScopedPointer<QObject> reordered(reorderedComp.create());
QVERIFY(reordered);
const QQmlPropertyCache::ConstPtr baseCache = QQmlData::get(base.data())->propertyCache;
const QQmlPropertyCache::ConstPtr derivedCache = QQmlData::get(derived.data())->propertyCache;
const QQmlPropertyCache::ConstPtr reorderedCache =
QQmlData::get(reordered.data())->propertyCache;
QVERIFY(baseCache && derivedCache && reorderedCache);
const auto lookup = [](const QQmlPropertyCache::ConstPtr &cache, const char *name) {
return cache->property(QLatin1String(name), nullptr, nullptr);
};
// The derived type overrides "a"; the override records the base's "a" index.
const QQmlPropertyData *ownA = lookup(derivedCache, "a");
QVERIFY(ownA);
QVERIFY2(ownA->hasOverride(), "precondition: the derived \"a\" overrides the base's \"a\"");
QCOMPARE(ownA->overrideIndex(), lookup(baseCache, "a")->coreIndex());
// Reordering really moved "a" to a different property index than the base it was
// compiled against, and put "b" where "a" used to be.
QVERIFY(lookup(reorderedCache, "a")->coreIndex() != lookup(baseCache, "a")->coreIndex());
QCOMPARE(lookup(reorderedCache, "b")->coreIndex(), lookup(baseCache, "a")->coreIndex());
// Re-link the derived cache onto the reordered base, as the hot-reload path does.
const QQmlPropertyCache::Ptr rebased = derivedCache->rebased(reorderedCache);
QVERIFY(rebased->parent().data() == reorderedCache.data());
const QQmlPropertyData *reboundA = lookup(rebased, "a");
QVERIFY(reboundA && reboundA->hasOverride());
// The override must now point at "a" in the *new* parent. rebased() leaves the
// parent-range override index unshifted, so it still points at the base's old slot
// -- which in the reordered parent is "b".
QCOMPARE(reboundA->overrideIndex(), lookup(reorderedCache, "a")->coreIndex());
}
QTEST_MAIN(tst_qqmlpropertycache)
#include "tst_qqmlpropertycache.moc"