qmltc: test alias on properties with attributes

Add tests to qmltc to see if it behaves well for aliases to different
kinds of properties, and compares if the engine shares the same
behavior. Same for the attributes in the QMetaProperties.

Changes:
* add some more MOC information to aliases
** always add NOTIFY to aliases (like the engine does)
** always set DESIGNABLE to false for aliases (like the engine does)
** always set CONSTANT to false for aliases (like the engine does)
** always set STORED to false for aliases (like the engine does)

Test if:
* default aliases works when compiled via qmltc
* attributes of aliases are set correctly in QMetaProperty and compare
  it to the attributes of the QMetaProperty obtained from the engine.
* aliases can read/written/reset/notified

Fixes: QTBUG-105708
Change-Id: I66b9c43c8c8de3dbd2b33d5ce15cd42ffb377ce7
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
Sami Shalayel 2022-08-22 11:13:03 +02:00
parent b6463590fa
commit 8120ec1d3d
9 changed files with 380 additions and 11 deletions

View File

@ -17,6 +17,8 @@ set(cpp_sources
cpptypes/extensiontypes.h cpptypes/extensiontypes.cpp
cpptypes/typewithspecialproperties.h
cpptypes/typewithmanyproperties.h
)
set(qml_sources
@ -50,6 +52,7 @@ set(qml_sources
javaScriptFunctions.qml
changingBindings.qml
propertyAlias.qml
propertyAliasAttributes.qml
propertyAlias_external.qml
propertyChangeHandler.qml
NestedHelloWorld.qml
@ -58,7 +61,7 @@ set(qml_sources
listPropertySameName.qml
defaultProperty.qml
defaultPropertyCorrectSelection.qml
# defaultAlias.qml
defaultAlias.qml
propertyReturningFunction.qml
AttachedProperty.qml
attachedPropertyDerived.qml
@ -99,6 +102,7 @@ set(qml_sources
# support types:
DefaultPropertySingleChild.qml
DefaultPropertyAliasChild.qml
DefaultPropertyManyChildren.qml
LocallyImported.qml
LocalWithOnCompleted.qml

View File

@ -0,0 +1,8 @@
import QtQml 2.0
QtObject {
id: self
property QtObject someObject
default property alias child: self.someObject
}

View File

@ -0,0 +1,90 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#ifndef PROPERTYALIASATTRIBUTES_H
#define PROPERTYALIASATTRIBUTES_H
#include <QtCore/qobject.h>
#include <QtCore/QBindable>
#include <QtQml/qqmlregistration.h>
using namespace Qt::Literals;
class TypeWithManyProperties : public QObject
{
Q_OBJECT
QML_ELEMENT
const QString m_readOnly = u"Hello World!"_s;
QString m_readAndWrite;
QString m_resettable;
QString m_unresettable;
QString m_hasAllAttributes;
QString m_hasAllAttributes2;
public:
TypeWithManyProperties()
{
hasAllAttributesBindable().setBinding(
[&] { return u"From the bindable: "_s + readOnly(); });
}
Q_PROPERTY(QString readOnly READ readOnly);
Q_PROPERTY(QString readAndWrite READ readAndWrite WRITE setReadAndWrite);
Q_PROPERTY(QString readAndWriteMember MEMBER m_readAndWrite);
Q_PROPERTY(QString resettable READ resettable WRITE setReadAndWrite RESET resetResettable);
Q_PROPERTY(QString unresettable READ unresettable WRITE setUnresettable);
Q_PROPERTY(QString notifiable READ readAndWrite WRITE setReadAndWriteAndNotify NOTIFY
notifiableChanged);
Q_PROPERTY(QString notifiableMember MEMBER m_readAndWrite NOTIFY notifiableChanged);
Q_PROPERTY(QString latestReadAndWrite MEMBER m_readAndWrite REVISION(1, 0));
Q_PROPERTY(QString notExisting MEMBER m_readAndWrite REVISION(6, 0));
Q_PROPERTY(QString hasAllAttributes READ hasAllAttributes WRITE setHasAllAttributes RESET
resetHasAllAttributes NOTIFY hasAllAttributesChanged REVISION(1, 0)
BINDABLE hasAllAttributesBindable
DESIGNABLE false SCRIPTABLE true STORED false USER true FINAL
REQUIRED);
Q_OBJECT_BINDABLE_PROPERTY(TypeWithManyProperties, QString, hasAllAttributesProperty);
QBindable<QString> hasAllAttributesBindable()
{
return QBindable<QString>(&hasAllAttributesProperty);
}
Q_PROPERTY(QString hasAllAttributes2 READ hasAllAttributes2
DESIGNABLE true SCRIPTABLE true STORED true USER false CONSTANT);
QString readOnly() { return m_readOnly; }
QString readAndWrite() { return m_readAndWrite; }
void setReadAndWrite(const QString &s) { m_readAndWrite = s; }
void setReadAndWriteAndNotify(const QString &s)
{
if (s != readAndWrite()) {
setReadAndWrite(s);
emit notifiableChanged(s);
}
}
void resetResettable() { m_resettable = u"Reset!"_s; }
QString resettable() { return m_resettable; }
QString unresettable() { return m_unresettable; }
void setResettable(const QString &s) { m_resettable = s; }
void setUnresettable(const QString &s) { m_unresettable = s; }
QString hasAllAttributes2() { return u"Some Constant string"_s; }
QString hasAllAttributes() { return m_hasAllAttributes; }
void setHasAllAttributes(const QString &s) { m_hasAllAttributes = s; }
void resetHasAllAttributes() { m_hasAllAttributes = "This value has been reset."; }
signals:
void notifiableChanged(const QString &newValue);
void hasAllAttributesChanged(const QString &newValue);
};
#endif // PROPERTYALIASATTRIBUTES_H

View File

@ -1,9 +1,8 @@
import QtQml 2.0
import QtQml
QtObject {
DefaultPropertyAliasChild {
id: self
property string hello: "Hello from parent"
property QtObject origin
default property alias child: origin
QtObject {
property string hello: "Hello from parent.child (alias)"

View File

@ -0,0 +1,37 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQml
import QmltcTests
TypeWithManyProperties {
id: self
property alias hasAllAttributesAlias: self.hasAllAttributes
//hasAllAttributes: "The string."
hasAllAttributesAlias: "The string." // when this is missing then qmltc does not emit any error...
property alias hasAllAttributes2Alias: self.hasAllAttributes2
property alias readOnlyAlias: self.readOnly
property alias readAndWriteMemberAlias: self.readAndWriteMember
property alias resettableAlias: self.resettable
property alias unresettableAlias: self.unresettable
property alias notifiableAlias: self.notifiable
property alias notifiableMemberAlias: self.notifiableMember
property alias latestReadAndWriteAlias: self.latestReadAndWrite
// aliases cannot be readonly, they inherit it from the property pointed to
// readonly default property alias readOnlyAlias2: self.readAndWriteMember // not valid
default property alias defaultAlias: self.readAndWriteMember
// cannot be compiled by qmlcachegen it seems: required property cannot have initializer
// required property alias requiredAlias: self.hasAllAttributes
function assignUndefinedToResettableAlias() {
resettableAlias = undefined
}
function assignUndefinedToUnresettableAlias() {
unresettableAlias = undefined
}
}

View File

@ -33,6 +33,7 @@
#include "changingbindings.h"
#include "propertyalias.h"
#include "propertyalias_external.h"
#include "propertyaliasattributes.h"
#include "complexaliases.h"
#include "propertychangehandler.h"
#include "nestedhelloworld.h"
@ -72,6 +73,7 @@
#include "valuetypelistproperty.h"
#include "translations.h"
#include "translationsbyid.h"
#include "defaultalias.h"
#include "testprivateproperty.h"
@ -894,8 +896,19 @@ void tst_qmltc::specialProperties()
// alias attributes:
const QMetaObject *mo = created.metaObject();
QMetaProperty xxAlias = mo->property(mo->indexOfProperty("xxAlias"));
QQmlComponent c(&e);
c.loadUrl(QUrl("qrc:/qt/qml/QmltcTests/specialProperties.qml"));
QScopedPointer<QObject> _fromEngine(c.create());
QVERIFY2(_fromEngine, qPrintable(c.errorString()));
QObject &fromEngine = *_fromEngine;
const QMetaObject *fromEngineMetaObject = fromEngine.metaObject();
QMetaProperty xxAliasFromEngine =
fromEngineMetaObject->property(mo->indexOfProperty("xxAlias"));
QVERIFY(xxAlias.isValid());
QVERIFY(xxAlias.isConstant());
QVERIFY(xxAliasFromEngine.isValid());
QCOMPARE(xxAlias.isConstant(), xxAliasFromEngine.isConstant());
QCOMPARE(created.xyAlias(), u"reset");
}
@ -1091,6 +1104,192 @@ void tst_qmltc::propertyAlias_external()
QCOMPARE(heightAliasChangedSpy.count(), 1);
}
void tst_qmltc::propertyAliasAttribute()
{
QQmlEngine e;
PREPEND_NAMESPACE(propertyAliasAttributes) fromQmltc(&e);
QQmlComponent c(&e);
c.loadUrl(QUrl("qrc:/qt/qml/QmltcTests/propertyAliasAttributes.qml"));
QScopedPointer<QObject> _fromEngine(c.create());
QVERIFY2(_fromEngine, qPrintable(c.errorString()));
QObject &fromEngine = *_fromEngine;
const QMetaObject *fromEngineMetaObject = fromEngine.metaObject();
const QString stringA = u"The quick brown fox"_s;
const QString stringB = u"jumps over the lazy dog."_s;
QCOMPARE(fromQmltc.readOnlyAlias(), u"Hello World!"_s);
QCOMPARE(fromEngine.property("readOnlyAlias"), u"Hello World!"_s);
QVERIFY(!fromQmltc.setProperty("readOnlyAlias", u"Some string"_s));
QVERIFY(!fromEngine.setProperty("readOnlyAlias", u"Some string"_s));
// reading and writing from alias is already covered in the alias test
// check if it works on properties with the MEMBERS attribute
fromQmltc.setReadAndWriteMemberAlias(stringA);
fromEngine.setProperty("readAndWriteMemberAlias", stringA);
QCOMPARE(fromQmltc.property("readAndWriteMember"), stringA);
QCOMPARE(fromEngine.property("readAndWriteMember"), stringA);
fromQmltc.setReadAndWriteMemberAlias(stringB);
fromEngine.setProperty("readAndWriteMemberAlias", stringB);
QCOMPARE(fromQmltc.readAndWriteMemberAlias(), stringB);
QCOMPARE(fromQmltc.property("readAndWriteMember"), stringB);
QCOMPARE(fromEngine.property("readAndWriteMemberAlias"), stringB);
QCOMPARE(fromEngine.property("readAndWriteMember"), stringB);
// check if alias can be reset through property
fromQmltc.setResettableAlias(stringA);
fromEngine.setProperty("resettableAlias", stringB);
fromQmltc.resetResettable();
const int resettableIdx = fromEngineMetaObject->indexOfProperty("resettable");
QVERIFY(fromEngineMetaObject->property(resettableIdx).reset(&fromEngine));
QCOMPARE(fromQmltc.resettable(), u"Reset!"_s);
QCOMPARE(fromQmltc.resettableAlias(), u"Reset!"_s);
QCOMPARE(fromEngine.property("resettable"), u"Reset!"_s);
QCOMPARE(fromEngine.property("resettableAlias"), u"Reset!"_s);
// check if property can be reset through alias
fromQmltc.setResettableAlias(stringA);
fromEngine.setProperty("resettableAlias", stringA);
fromQmltc.resetResettableAlias();
QMetaMethod resetResettableAlias = fromEngineMetaObject->method(
fromEngineMetaObject->indexOfMethod("resetResettableAlias"));
resetResettableAlias.invoke(&fromEngine);
QCOMPARE(fromQmltc.resettable(), u"Reset!"_s);
QCOMPARE(fromQmltc.resettableAlias(), u"Reset!"_s);
QCOMPARE(fromEngine.property("resettable"), u"Reset!"_s);
QCOMPARE(fromEngine.property("resettableAlias"), u"Reset!"_s);
// check if property can be reset by assigning undefined to alias
fromQmltc.setResettableAlias(stringA);
fromEngine.setProperty("resettableAlias", stringA);
fromQmltc.assignUndefinedToResettableAlias();
QMetaMethod assignUndefinedToResettableAlias = fromEngineMetaObject->method(
fromEngineMetaObject->indexOfMethod("assignUndefinedToResettableAlias"));
assignUndefinedToResettableAlias.invoke(&fromEngine);
QCOMPARE(fromQmltc.resettableAlias(), u"Reset!"_s);
QCOMPARE(fromQmltc.resettable(), u"Reset!"_s);
QCOMPARE(fromEngine.property("resettableAlias"), u"Reset!"_s);
QCOMPARE(fromEngine.property("resettable"), u"Reset!"_s);
// check if property can be reset by assigning undefined to alias of
// non-resettable prop which should not happen: instead, nothing should happen
fromQmltc.setUnresettableAlias(stringA);
fromEngine.setProperty("unresettableAlias", stringA);
fromQmltc.assignUndefinedToUnresettableAlias();
QMetaMethod assignUndefinedToUnresettableAlias = fromEngineMetaObject->method(
fromEngineMetaObject->indexOfMethod("assignUndefinedToUnresettableAlias"));
assignUndefinedToUnresettableAlias.invoke(&fromEngine);
QCOMPARE(fromQmltc.unresettableAlias(), stringA);
QCOMPARE(fromQmltc.property("unresettable"), stringA);
QCOMPARE(fromEngine.property("unresettableAlias"), stringA);
QCOMPARE(fromEngine.property("unresettable"), stringA);
// check if notify arrives!
fromQmltc.setReadAndWrite(stringB);
fromEngine.setProperty("readAndWrite", stringB);
qsizetype calls = 0;
QSignalSpy spyQmltc(&fromQmltc, SIGNAL(notifiableChanged(QString)));
QSignalSpy spyEngine(&fromEngine, SIGNAL(notifiableChanged(QString)));
// write through alias
fromQmltc.setNotifiableAlias(stringA);
QVERIFY(fromEngine.setProperty("notifiableAlias", stringA));
QCOMPARE(spyQmltc.count(), ++calls);
QCOMPARE(spyEngine.count(), calls);
// write through property
fromQmltc.setReadAndWriteAndNotify(stringB);
QVERIFY(fromEngine.setProperty("notifiable", stringB));
QCOMPARE(spyQmltc.count(), ++calls);
QCOMPARE(spyEngine.count(), calls);
fromQmltc.setNotifiableMemberAlias(stringA);
QVERIFY(fromEngine.setProperty("notifiableMemberAlias", stringA));
QCOMPARE(spyQmltc.count(), ++calls);
QCOMPARE(spyEngine.count(), calls);
fromQmltc.setProperty("notifiableMember", stringB);
QVERIFY(fromEngine.setProperty("notifiableMember", stringB));
QCOMPARE(spyQmltc.count(), ++calls);
QCOMPARE(spyEngine.count(), calls);
// check that the alias to a revisioned property works
fromQmltc.setLatestReadAndWriteAlias(stringA);
QVERIFY(fromEngine.setProperty("latestReadAndWriteAlias", stringA));
QCOMPARE(fromQmltc.latestReadAndWriteAlias(), stringA);
QCOMPARE(fromQmltc.property("latestReadAndWrite"), stringA);
QCOMPARE(fromEngine.property("latestReadAndWriteAlias"), stringA);
QCOMPARE(fromEngine.property("latestReadAndWrite"), stringA);
QVERIFY(fromQmltc.setProperty("latestReadAndWrite", stringB));
QVERIFY(fromEngine.setProperty("latestReadAndWrite", stringB));
QCOMPARE(fromQmltc.latestReadAndWriteAlias(), stringB);
QCOMPARE(fromQmltc.property("latestReadAndWrite"), stringB);
QCOMPARE(fromEngine.property("latestReadAndWriteAlias"), stringB);
QCOMPARE(fromEngine.property("latestReadAndWrite"), stringB);
// check if metaobject of alias is correct
const QVector<const QMetaObject *> metaObjects = {
fromQmltc.metaObject(),
fromEngine.metaObject(),
};
QVERIFY(metaObjects[0]);
QVERIFY(metaObjects[1]);
QVector<QHash<QString, QMetaProperty>> metaProperties(2);
for (int j = 0; j < metaObjects.size(); j++) {
const QMetaObject *metaObject = metaObjects[j];
for (int i = metaObject->propertyOffset(); i < metaObject->propertyCount(); ++i) {
metaProperties[j][QString::fromLatin1(metaObject->property(i).name())] =
metaObject->property(i);
}
}
{
QVERIFY(metaProperties[0].contains("hasAllAttributesAlias"));
QVERIFY(metaProperties[1].contains("hasAllAttributesAlias"));
QMetaProperty mpQmltc = metaProperties[0].value("hasAllAttributesAlias");
QMetaProperty mpEngine = metaProperties[1].value("hasAllAttributesAlias");
QCOMPARE(mpQmltc.isReadable(), mpEngine.isReadable());
QCOMPARE(mpQmltc.isWritable(), mpEngine.isWritable());
QCOMPARE(mpQmltc.isResettable(), mpEngine.isResettable());
QCOMPARE(mpQmltc.hasNotifySignal(), mpEngine.hasNotifySignal());
QCOMPARE(mpQmltc.revision(), mpEngine.revision());
QCOMPARE(mpQmltc.isDesignable(), mpEngine.isDesignable());
QCOMPARE(mpQmltc.isScriptable(), mpEngine.isScriptable());
QCOMPARE(mpQmltc.isStored(), mpEngine.isStored());
QCOMPARE(mpQmltc.isUser(), mpEngine.isUser());
QCOMPARE(mpQmltc.isBindable(), mpEngine.isBindable());
QCOMPARE(mpQmltc.isConstant(), mpEngine.isConstant());
QCOMPARE(mpQmltc.isFinal(), mpEngine.isFinal());
QCOMPARE(mpQmltc.isRequired(), mpEngine.isRequired());
}
{
QVERIFY(metaProperties[0].contains("hasAllAttributes2Alias"));
QVERIFY(metaProperties[1].contains("hasAllAttributes2Alias"));
QMetaProperty mpQmltc = metaProperties[0].value("hasAllAttributes2Alias");
QMetaProperty mpEngine = metaProperties[1].value("hasAllAttributes2Alias");
QCOMPARE(mpQmltc.isReadable(), mpEngine.isReadable());
QCOMPARE(mpQmltc.isWritable(), mpEngine.isWritable());
QCOMPARE(mpQmltc.isResettable(), mpEngine.isResettable());
QCOMPARE(mpQmltc.hasNotifySignal(), mpEngine.hasNotifySignal());
QCOMPARE(mpQmltc.revision(), mpEngine.revision());
QCOMPARE(mpQmltc.isDesignable(), mpEngine.isDesignable());
QCOMPARE(mpQmltc.isScriptable(), mpEngine.isScriptable());
QCOMPARE(mpQmltc.isStored(), mpEngine.isStored());
QCOMPARE(mpQmltc.isUser(), mpEngine.isUser());
QCOMPARE(mpQmltc.isBindable(), mpEngine.isBindable());
QCOMPARE(mpQmltc.isConstant(), mpEngine.isConstant());
QCOMPARE(mpQmltc.isFinal(), mpEngine.isFinal());
QCOMPARE(mpQmltc.isRequired(), mpEngine.isRequired());
}
}
// TODO: we need to support RESET in aliases as well? (does it make sense?)
void tst_qmltc::complexAliases()
{
@ -1455,7 +1654,19 @@ void tst_qmltc::defaultPropertyCorrectSelection()
void tst_qmltc::defaultAlias()
{
QSKIP("Not implemented - not supported");
QQmlEngine e;
PREPEND_NAMESPACE(defaultAlias) created(&e);
QQmlComponent c(&e);
c.loadUrl(QUrl("qrc:/qt/qml/QmltcTests/defaultAlias.qml"));
QScopedPointer<QObject> fromEngine(c.create());
QVERIFY2(fromEngine, qPrintable(c.errorString()));
auto *child = static_cast<QmltcTest::defaultAlias_QtObject *>(created.child());
QVERIFY(fromEngine->property("child").canConvert<QObject *>());
QObject *childFromEngine = fromEngine->property("child").value<QObject *>();
QVERIFY(childFromEngine);
QCOMPARE(child->hello(), childFromEngine->property("hello"));
}
void tst_qmltc::attachedProperty()

View File

@ -45,6 +45,7 @@ private slots:
void changingBindings();
void propertyAlias();
void propertyAlias_external();
void propertyAliasAttribute();
void complexAliases();
void propertyChangeHandler();
void nestedHelloWorld();

View File

@ -733,10 +733,20 @@ void QmltcCompiler::compileAlias(QmltcType &current, const QQmlJSMetaProperty &a
current.functions.emplaceBack(bindable);
mocLines << u"BINDABLE"_s << bindable.name;
}
// 3. add notify - which is pretty special
if (QString notifyName = result.property.notify(); !notifyName.isEmpty()) {
// step 1: generate the moc instructions
// mimic the engines behavior: do it even if the notify will never be emitted
if (const QString aliasNotifyName = alias.notify(); !aliasNotifyName.isEmpty()) {
Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise
mocLines << u"NOTIFY"_s << aliasNotifyName;
}
// step 2: connect the notifier to the aliased property notifier, if this latter exists
// otherwise, mimic the engines behavior and generate a useless notify
if (const QString notifyName = result.property.notify(); !notifyName.isEmpty()) {
auto notifyFrames = frames;
notifyFrames.pop(); // we don't need the last frame at all in this case
@ -762,6 +772,7 @@ void QmltcCompiler::compileAlias(QmltcType &current, const QQmlJSMetaProperty &a
current.endInit.body += notifyEpilogue;
current.endInit.body << u"}"_s;
}
if (QString resetName = result.property.reset(); !resetName.isEmpty()) {
Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise
QmltcMethod reset {};
@ -775,8 +786,12 @@ void QmltcCompiler::compileAlias(QmltcType &current, const QQmlJSMetaProperty &a
mocLines << u"RESET"_s << reset.name;
}
if (result.property.isConstant())
mocLines << u"CONSTANT"_s;
// mimic the engines behavior: aliases are never constants
// mocLines << u"CONSTANT"_s;
// mimic the engines behavior: aliases are never stored
mocLines << u"STORED"_s << u"false"_s;
// mimic the engines behavior: aliases are never designable
mocLines << u"DESIGNABLE"_s << u"false"_s;
// 4. add moc entry
// Q_PROPERTY(QString text READ text WRITE setText BINDABLE bindableText NOTIFY textChanged)

View File

@ -562,8 +562,12 @@ static void setAliasData(QQmlJSMetaProperty *alias, const QQmlJSUtils::ResolvedA
return;
if (origin.property.isWritable() && alias->write().isEmpty())
alias->setWrite(compiledData.write);
if (!origin.property.notify().isEmpty() && alias->notify().isEmpty())
// the engine always compiles a notify for properties/aliases defined in qml code
// Yes, this generated notify will never be emitted.
if (alias->notify().isEmpty())
alias->setNotify(compiledData.notify);
if (!origin.property.bindable().isEmpty() && alias->bindable().isEmpty())
alias->setBindable(compiledData.bindable);
}