aboutsummaryrefslogtreecommitdiffstats
path: root/tests/auto/qml/qmltc_manual
diff options
context:
space:
mode:
authorAndrei Golubev <andrei.golubev@qt.io>2021-07-23 16:53:27 +0200
committerAndrei Golubev <andrei.golubev@qt.io>2022-01-26 08:10:08 +0100
commitf03f6eec9ac6467cdf601a8c056e76b3905472f3 (patch)
treeeac49736e8afa364989f65423ff592fad7347771 /tests/auto/qml/qmltc_manual
parent8ec6b990e13c8b41d0415af83c5890ef7a9ee227 (diff)
Prototype private property access in tst_qmltc_manual
In theory, private properties could be supported in the compilers through the QMetaProperty. Since we already know each C++ property's index (and QML properties cannot be private), we can use that index for a fast-ish QMetaProperty lookup. QMetaProperty itself has read and write methods, which should be sufficient for all the use cases: * read a value * write a value * create a binding In fact, binding creation on non-grouped private properties is already supported (or almost supported) since bindings already rely on QMetaProperty information. As a downside, QMetaProperty (even without name lookup) is still an overkill compared to static_cast<ClassPrivate *>(QObjectPrivate::get(this)) and then calling READ/WRITE/etc. of a property directly Task-number: QTBUG-91956 Change-Id: If77d2783ac161cb9bdd0bd9d0b397fe88e9c471d Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'tests/auto/qml/qmltc_manual')
-rw-r--r--tests/auto/qml/qmltc_manual/CMakeLists.txt2
-rw-r--r--tests/auto/qml/qmltc_manual/data/anchors.qml5
-rw-r--r--tests/auto/qml/qmltc_manual/testclasses.h30
-rw-r--r--tests/auto/qml/qmltc_manual/tst_qmltc_manual.cpp110
4 files changed, 146 insertions, 1 deletions
diff --git a/tests/auto/qml/qmltc_manual/CMakeLists.txt b/tests/auto/qml/qmltc_manual/CMakeLists.txt
index 9df9b8ca12..a898a77c26 100644
--- a/tests/auto/qml/qmltc_manual/CMakeLists.txt
+++ b/tests/auto/qml/qmltc_manual/CMakeLists.txt
@@ -11,7 +11,7 @@ qt_internal_add_test(tst_qmltc_manual
LIBRARIES
Qt::CorePrivate
Qt::QmlPrivate
- Qt::Quick
+ Qt::QuickPrivate
Qt::QuickTestUtilsPrivate
)
diff --git a/tests/auto/qml/qmltc_manual/data/anchors.qml b/tests/auto/qml/qmltc_manual/data/anchors.qml
new file mode 100644
index 0000000000..9250ee49cd
--- /dev/null
+++ b/tests/auto/qml/qmltc_manual/data/anchors.qml
@@ -0,0 +1,5 @@
+import QtQuick 2.0
+Item {
+ property int value: 42
+ anchors.topMargin: value // binding on anchors (private property)
+}
diff --git a/tests/auto/qml/qmltc_manual/testclasses.h b/tests/auto/qml/qmltc_manual/testclasses.h
index eecc9287ae..c91f82c3f2 100644
--- a/tests/auto/qml/qmltc_manual/testclasses.h
+++ b/tests/auto/qml/qmltc_manual/testclasses.h
@@ -475,4 +475,34 @@ public:
void finalize(QQmlEngine *e);
};
+class ANON_anchors : public QQuickItem
+{
+ Q_OBJECT
+ QML_ANONYMOUS
+ Q_PROPERTY(int value READ getValue WRITE setValue NOTIFY valueChanged)
+protected:
+ ANON_anchors(QObject *parent = nullptr);
+
+public:
+ // test workaround: the url is resolved by the test base class, so use
+ // member variable to store the resolved url used as argument in engine
+ // evaluation of runtime functions
+ static QUrl url;
+
+ ANON_anchors(QQmlEngine *e, QObject *parent = nullptr);
+ QQmlRefPointer<QQmlContextData> init(QQmlEngine *e,
+ const QQmlRefPointer<QQmlContextData> &parentContext);
+ void finalize(QQmlEngine *e);
+
+ QProperty<int> value;
+ int getValue() { return value; }
+ void setValue(int v)
+ {
+ value = v;
+ Q_EMIT valueChanged();
+ }
+Q_SIGNALS:
+ void valueChanged();
+};
+
#endif // TESTCLASSES_H
diff --git a/tests/auto/qml/qmltc_manual/tst_qmltc_manual.cpp b/tests/auto/qml/qmltc_manual/tst_qmltc_manual.cpp
index 5628b6e1d4..c90e51ba61 100644
--- a/tests/auto/qml/qmltc_manual/tst_qmltc_manual.cpp
+++ b/tests/auto/qml/qmltc_manual/tst_qmltc_manual.cpp
@@ -39,9 +39,16 @@
#include <QtCore/qproperty.h>
#include <QtQuickTestUtils/private/qmlutils_p.h>
+#include <QtCore/qobjectdefs.h>
+#include <QtCore/qmetaobject.h>
+
#include <private/qqmlengine_p.h>
#include <private/qqmltypedata_p.h>
#include <private/qqmlvmemetaobject_p.h>
+#include <private/qqmlanybinding_p.h>
+#include <private/qquickitem_p.h>
+#include <private/qv4qmlcontext_p.h>
+#include <private/qqmlproperty_p.h>
#include <array>
#include <memory>
@@ -65,6 +72,7 @@ private slots:
void locallyImported();
void localImport();
void neighbors();
+ void anchors();
private:
void signalHandlers_impl(const QUrl &url);
@@ -496,6 +504,19 @@ void tst_qmltc_manual::neighbors()
}
}
+void tst_qmltc_manual::anchors()
+{
+ QQmlEngine e;
+ ANON_anchors::url = testFileUrl("anchors.qml");
+ ANON_anchors created(&e);
+
+ QQuickAnchors *anchors =
+ static_cast<QQuickItemPrivate *>(QObjectPrivate::get(&created))->anchors();
+ QCOMPARE(anchors->topMargin(), 42);
+ created.setValue(7);
+ QCOMPARE(anchors->topMargin(), 7);
+}
+
// test workaround: hardcode runtime function indices. because they could be
// rather unexpected and passing wrong ones leads to UB and flakiness.
//
@@ -541,6 +562,8 @@ static constexpr int LOCAL_IMPORT_LOCAL_GET_MAGIC_VALUE = 1;
static constexpr int NEIGHBOUR_IDS_CHILD1_P2_BINDING = 0;
static constexpr int NEIGHBOUR_IDS_CHILD2_P_BINDING = 1;
+
+static constexpr int ANCHORS_ANCHORS_TOP_MARGIN_BINDING = 0;
};
// test utility function for type erasure. the "real" code would be
@@ -565,6 +588,35 @@ static void typeEraseArguments(std::array<void *, Size> &a, std::array<QMetaType
t = { /* types */ QMetaType::fromType<std::decay_t<IOArgs>>()... };
}
+// test utility that fetches a QV4::Function from the engine
+inline QV4::Function *getRuntimeFunction(QQmlEngine *engine, const QUrl &url, qsizetype index)
+{
+ QQmlEnginePrivate *priv = QQmlEnginePrivate::get(engine);
+ Q_ASSERT(priv);
+ const auto unit = priv->compilationUnitFromUrl(url);
+ return unit->runtimeFunctions.value(index, nullptr);
+}
+
+// test utility that sets up the binding call arguments
+template<typename CreateBinding>
+inline decltype(auto) createBindingInScope(QQmlEngine *qmlengine, QObject *thisObject,
+ CreateBinding create)
+{
+ QV4::ExecutionEngine *v4 = qmlengine->handle();
+ Q_ASSERT(v4);
+
+ QQmlContext *ctx = qmlengine->contextForObject(thisObject);
+ if (!ctx)
+ ctx = qmlengine->rootContext();
+ QV4::Scope scope(v4);
+ QV4::ExecutionContext *executionCtx = v4->scriptContext();
+ QQmlRefPointer<QQmlContextData> ctxtdata = QQmlContextData::get(ctx);
+ QV4::Scoped<QV4::QmlContext> qmlContext(
+ scope, QV4::QmlContext::create(executionCtx, ctxtdata, thisObject));
+
+ return create(ctxtdata, qmlContext);
+}
+
ContextRegistrator::ContextRegistrator(QQmlEngine *engine, QObject *This)
{
Q_ASSERT(engine && This);
@@ -1172,6 +1224,64 @@ void ANON_neighbors::finalize(QQmlEngine *e)
QUrl ANON_neighbors::url = QUrl(); // workaround
+ANON_anchors::ANON_anchors(QObject *parent) : QQuickItem()
+{
+ setParent(parent);
+}
+
+ANON_anchors::ANON_anchors(QQmlEngine *e, QObject *parent) : ANON_anchors(parent)
+{
+ // NB: use e->rootContext() as this object is document root
+ init(e, QQmlContextData::get(e->rootContext()));
+}
+
+QQmlRefPointer<QQmlContextData>
+ANON_anchors::init(QQmlEngine *e, const QQmlRefPointer<QQmlContextData> &parentContext)
+{
+ constexpr int componentIndex = 0; // root index
+ auto context = ContextRegistrator::create(e, url, parentContext, componentIndex);
+ ContextRegistrator::set(this, context, QQmlContextData::DocumentRoot);
+ finalize(e); // call here because it's document root
+ return context;
+}
+
+void ANON_anchors::finalize(QQmlEngine *e)
+{
+ Q_UNUSED(e);
+
+ this->value = 42;
+
+ // create binding (in a proper way) on Item's anchors through
+ // QQmlAnyBinding. if this is achievable through C++, then compiler could do
+ // it as well (by generating roughly the same code).
+ const QMetaObject *mo = this->metaObject();
+ QVERIFY(mo);
+ // fetching QMetaProperty is possible through the compiler
+ QMetaProperty anchorsProperty = mo->property(mo->indexOfProperty("anchors"));
+ QQuickAnchors *anchors = qvariant_cast<QQuickAnchors *>(anchorsProperty.read(this));
+ QVERIFY(anchors);
+
+ // below is binding-specific code that is part of a special qmltc library
+ auto v4Func = getRuntimeFunction(qmlEngine(this), url,
+ FunctionIndices::ANCHORS_ANCHORS_TOP_MARGIN_BINDING);
+ QVERIFY(v4Func);
+ const QMetaObject *anchorsMo = anchors->metaObject();
+ QVERIFY(anchorsMo);
+ QMetaProperty topMarginProperty = anchorsMo->property(anchorsMo->indexOfProperty("topMargin"));
+ QVERIFY(QByteArray(topMarginProperty.name()) == "topMargin");
+
+ createBindingInScope(
+ qmlEngine(this), this,
+ [&](const QQmlRefPointer<QQmlContextData> &ctxt, QV4::ExecutionContext *scope) {
+ QQmlBinding *binding = QQmlBinding::create(topMarginProperty.metaType(), v4Func,
+ this, ctxt, scope);
+ binding->setTarget(anchors, topMarginProperty.propertyIndex(), false, -1);
+ QQmlPropertyPrivate::setBinding(binding);
+ });
+}
+
+QUrl ANON_anchors::url = QUrl(); // workaround
+
QTEST_MAIN(tst_qmltc_manual)
#include "tst_qmltc_manual.moc"