1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the test suite of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
44 #include <QHostAddress>
48 #include <QtQml/qqmlengine.h>
49 #include <QtQml/qqmlcontext.h>
50 #include <QtQml/qqmlcomponent.h>
51 #include <QtQml/qqmlexpression.h>
52 #include <QtQml/qqmlproperty.h>
53 #include <QtQuick/qquickitem.h>
55 #include <private/qqmlbinding_p.h>
56 #include <private/qqmlboundsignal_p.h>
57 #include <private/qqmldebugservice_p.h>
58 #include <private/qqmlmetatype_p.h>
59 #include <private/qqmlproperty_p.h>
61 #include "debugutil_p.h"
62 #include "qqmlenginedebug_p.h"
64 Q_DECLARE_METATYPE(QQmlDebugWatch::State)
66 class tst_QQmlEngineDebugService : public QObject
71 QQmlDebugObjectReference findRootObject(int context = 0, bool recursive = false);
72 QQmlDebugPropertyReference findProperty(const QList<QQmlDebugPropertyReference> &props, const QString &name) const;
73 void waitForQuery(QQmlDebugQuery *query);
75 void recursiveObjectTest(QObject *o, const QQmlDebugObjectReference &oref, bool recursive) const;
77 void recursiveCompareObjects(const QQmlDebugObjectReference &a, const QQmlDebugObjectReference &b) const;
78 void recursiveCompareContexts(const QQmlDebugContextReference &a, const QQmlDebugContextReference &b) const;
79 void compareProperties(const QQmlDebugPropertyReference &a, const QQmlDebugPropertyReference &b) const;
81 QQmlDebugConnection *m_conn;
82 QQmlEngineDebug *m_dbg;
84 QQuickItem *m_rootItem;
86 QObjectList m_components;
90 void cleanupTestCase();
92 void watch_property();
94 void watch_expression();
95 void watch_expression_data();
99 void queryAvailableEngines();
100 void queryRootContexts();
102 void queryObject_data();
103 void queryExpressionResult();
104 void queryExpressionResult_data();
106 void tst_QQmlDebugFileReference();
107 void tst_QQmlDebugEngineReference();
108 void tst_QQmlDebugObjectReference();
109 void tst_QQmlDebugContextReference();
110 void tst_QQmlDebugPropertyReference();
112 void setBindingForObject();
113 void setMethodBody();
114 void queryObjectTree();
115 void setBindingInStates();
118 class NonScriptProperty : public QObject {
120 Q_PROPERTY(int nonScriptProp READ nonScriptProp WRITE setNonScriptProp NOTIFY nonScriptPropChanged SCRIPTABLE false)
122 int nonScriptProp() const { return 0; }
123 void setNonScriptProp(int) {}
125 void nonScriptPropChanged();
127 QML_DECLARE_TYPE(NonScriptProperty)
130 QQmlDebugObjectReference tst_QQmlEngineDebugService::findRootObject(int context, bool recursive)
132 QQmlDebugEnginesQuery *q_engines = m_dbg->queryAvailableEngines(this);
133 waitForQuery(q_engines);
135 if (q_engines->engines().count() == 0)
136 return QQmlDebugObjectReference();
137 QQmlDebugRootContextQuery *q_context = m_dbg->queryRootContexts(q_engines->engines()[0].debugId(), this);
138 waitForQuery(q_context);
140 if (q_context->rootContext().objects().count() == 0)
141 return QQmlDebugObjectReference();
142 QQmlDebugObjectQuery *q_obj = recursive ?
143 m_dbg->queryObjectRecursive(q_context->rootContext().objects()[context], this) :
144 m_dbg->queryObject(q_context->rootContext().objects()[context], this);
147 QQmlDebugObjectReference result = q_obj->object();
156 QQmlDebugPropertyReference tst_QQmlEngineDebugService::findProperty(const QList<QQmlDebugPropertyReference> &props, const QString &name) const
158 foreach(const QQmlDebugPropertyReference &p, props) {
159 if (p.name() == name)
162 return QQmlDebugPropertyReference();
165 void tst_QQmlEngineDebugService::waitForQuery(QQmlDebugQuery *query)
168 QCOMPARE(query->parent(), qobject_cast<QObject*>(this));
169 QVERIFY(query->state() == QQmlDebugQuery::Waiting);
170 if (!QQmlDebugTest::waitForSignal(query, SIGNAL(stateChanged(QQmlDebugQuery::State))))
171 QFAIL("query timed out");
174 void tst_QQmlEngineDebugService::recursiveObjectTest(QObject *o, const QQmlDebugObjectReference &oref, bool recursive) const
176 const QMetaObject *meta = o->metaObject();
178 QQmlType *type = QQmlMetaType::qmlType(meta);
179 QString className = type ? QString(type->qmlTypeName()) : QString(meta->className());
180 className = className.mid(className.lastIndexOf(QLatin1Char('/'))+1);
182 QCOMPARE(oref.debugId(), QQmlDebugService::idForObject(o));
183 QCOMPARE(oref.name(), o->objectName());
184 QCOMPARE(oref.className(), className);
185 QCOMPARE(oref.contextDebugId(), QQmlDebugService::idForObject(qmlContext(o)));
187 const QObjectList &children = o->children();
188 for (int i=0; i<children.count(); i++) {
189 QObject *child = children[i];
190 if (!qmlContext(child))
192 int debugId = QQmlDebugService::idForObject(child);
193 QVERIFY(debugId >= 0);
195 QQmlDebugObjectReference cref;
196 foreach (const QQmlDebugObjectReference &ref, oref.children()) {
197 if (ref.debugId() == debugId) {
202 QVERIFY(cref.debugId() >= 0);
205 recursiveObjectTest(child, cref, true);
208 foreach (const QQmlDebugPropertyReference &p, oref.properties()) {
209 QCOMPARE(p.objectDebugId(), QQmlDebugService::idForObject(o));
211 // signal properties are fake - they are generated from QQmlBoundSignal children
212 if (p.name().startsWith("on") && p.name().length() > 2 && p.name()[2].isUpper()) {
213 QList<QQmlBoundSignal*> signalHandlers = o->findChildren<QQmlBoundSignal*>();
214 QString signal = p.value().toString();
216 for (int i = 0; i < signalHandlers.count(); ++i)
217 if (signalHandlers.at(i)->expression()->expression() == signal) {
222 QVERIFY(p.valueTypeName().isEmpty());
223 QVERIFY(p.binding().isEmpty());
224 QVERIFY(!p.hasNotifySignal());
228 QMetaProperty pmeta = meta->property(meta->indexOfProperty(p.name().toUtf8().constData()));
230 QCOMPARE(p.name(), QString::fromUtf8(pmeta.name()));
232 if (pmeta.type() < QVariant::UserType && pmeta.userType() != QMetaType::QVariant) // TODO test complex types
233 QCOMPARE(p.value(), pmeta.read(o));
235 if (p.name() == "parent")
236 QVERIFY(p.valueTypeName() == "QGraphicsObject*" || p.valueTypeName() == "QQuickItem*");
238 QCOMPARE(p.valueTypeName(), QString::fromUtf8(pmeta.typeName()));
240 QQmlAbstractBinding *binding =
241 QQmlPropertyPrivate::binding(QQmlProperty(o, p.name()));
243 QCOMPARE(binding->expression(), p.binding());
245 QCOMPARE(p.hasNotifySignal(), pmeta.hasNotifySignal());
247 QVERIFY(pmeta.isValid());
251 void tst_QQmlEngineDebugService::recursiveCompareObjects(const QQmlDebugObjectReference &a, const QQmlDebugObjectReference &b) const
253 QCOMPARE(a.debugId(), b.debugId());
254 QCOMPARE(a.className(), b.className());
255 QCOMPARE(a.name(), b.name());
256 QCOMPARE(a.contextDebugId(), b.contextDebugId());
258 QCOMPARE(a.source().url(), b.source().url());
259 QCOMPARE(a.source().lineNumber(), b.source().lineNumber());
260 QCOMPARE(a.source().columnNumber(), b.source().columnNumber());
262 QCOMPARE(a.properties().count(), b.properties().count());
263 QCOMPARE(a.children().count(), b.children().count());
265 QList<QQmlDebugPropertyReference> aprops = a.properties();
266 QList<QQmlDebugPropertyReference> bprops = b.properties();
268 for (int i=0; i<aprops.count(); i++)
269 compareProperties(aprops[i], bprops[i]);
271 for (int i=0; i<a.children().count(); i++)
272 recursiveCompareObjects(a.children()[i], b.children()[i]);
275 void tst_QQmlEngineDebugService::recursiveCompareContexts(const QQmlDebugContextReference &a, const QQmlDebugContextReference &b) const
277 QCOMPARE(a.debugId(), b.debugId());
278 QCOMPARE(a.name(), b.name());
279 QCOMPARE(a.objects().count(), b.objects().count());
280 QCOMPARE(a.contexts().count(), b.contexts().count());
282 for (int i=0; i<a.objects().count(); i++)
283 recursiveCompareObjects(a.objects()[i], b.objects()[i]);
285 for (int i=0; i<a.contexts().count(); i++)
286 recursiveCompareContexts(a.contexts()[i], b.contexts()[i]);
289 void tst_QQmlEngineDebugService::compareProperties(const QQmlDebugPropertyReference &a, const QQmlDebugPropertyReference &b) const
291 QCOMPARE(a.objectDebugId(), b.objectDebugId());
292 QCOMPARE(a.name(), b.name());
293 QCOMPARE(a.value(), b.value());
294 QCOMPARE(a.valueTypeName(), b.valueTypeName());
295 QCOMPARE(a.binding(), b.binding());
296 QCOMPARE(a.hasNotifySignal(), b.hasNotifySignal());
299 void tst_QQmlEngineDebugService::initTestCase()
301 qRegisterMetaType<QQmlDebugWatch::State>();
302 qmlRegisterType<NonScriptProperty>("Test", 1, 0, "NonScriptPropertyElement");
304 QTest::ignoreMessage(QtWarningMsg, "QML Debugger: Waiting for connection on port 3768...");
305 m_engine = new QQmlEngine(this);
307 QList<QByteArray> qml;
308 qml << "import QtQuick 2.0\n"
312 "width: 10; height: 20; scale: blueRect.scale;"
313 "Rectangle { id: blueRect; width: 500; height: 600; color: \"blue\"; }"
314 "Text { color: blueRect.color; }"
316 "onEntered: { console.log('hello') }"
318 "property variant varObj\n"
319 "property variant varObjList: []\n"
320 "property variant varObjMap\n"
321 "Component.onCompleted: {\n"
322 "varObj = blueRect;\n"
323 "var list = varObjList;\n"
324 "list[0] = blueRect;\n"
325 "varObjList = list;\n"
326 "var map = new Object;\n"
327 "map.rect = blueRect;\n"
330 "NonScriptPropertyElement {\n"
334 // add second component to test multiple root contexts
335 qml << "import QtQuick 2.0\n"
338 // and a third to test methods
339 qml << "import QtQuick 2.0\n"
341 "function myMethodNoArgs() { return 3; }\n"
342 "function myMethod(a) { return a + 9; }\n"
343 "function myMethodIndirect() { myMethod(3); }\n"
346 // and a fourth to test states
347 qml << "import QtQuick 2.0\n"
354 "PropertyChanges {\n"
364 "PropertyAnimation {\n"
366 "property:\"width\"\n"
374 for (int i=0; i<qml.count(); i++) {
375 QQmlComponent component(m_engine);
376 component.setData(qml[i], QUrl::fromLocalFile(""));
377 QVERIFY(component.isReady()); // fails if bad syntax
378 m_components << qobject_cast<QQuickItem*>(component.create());
380 m_rootItem = qobject_cast<QQuickItem*>(m_components.first());
382 // add an extra context to test for multiple contexts
383 QQmlContext *context = new QQmlContext(m_engine->rootContext(), this);
384 context->setObjectName("tst_QQmlDebug_childContext");
386 m_conn = new QQmlDebugConnection(this);
387 m_conn->connectToHost("127.0.0.1", 3768);
389 QTest::ignoreMessage(QtWarningMsg, "QML Debugger: Connection established.");
390 bool ok = m_conn->waitForConnected();
392 QTRY_VERIFY(QQmlDebugService::hasDebuggingClient());
393 m_dbg = new QQmlEngineDebug(m_conn, this);
394 QTRY_VERIFY(m_dbg->state() == QQmlEngineDebug::Enabled);
397 void tst_QQmlEngineDebugService::cleanupTestCase()
401 qDeleteAll(m_components);
405 void tst_QQmlEngineDebugService::setMethodBody()
407 QQmlDebugObjectReference obj = findRootObject(2);
409 QObject *root = m_components.at(2);
413 QVERIFY(QMetaObject::invokeMethod(root, "myMethodNoArgs", Qt::DirectConnection,
414 Q_RETURN_ARG(QVariant, rv)));
415 QVERIFY(rv == QVariant(qreal(3)));
418 QVERIFY(m_dbg->setMethodBody(obj.debugId(), "myMethodNoArgs", "return 7"));
421 QVERIFY(QMetaObject::invokeMethod(root, "myMethodNoArgs", Qt::DirectConnection,
422 Q_RETURN_ARG(QVariant, rv)));
423 QVERIFY(rv == QVariant(qreal(7)));
429 QVERIFY(QMetaObject::invokeMethod(root, "myMethod", Qt::DirectConnection,
430 Q_RETURN_ARG(QVariant, rv), Q_ARG(QVariant, QVariant(19))));
431 QVERIFY(rv == QVariant(qreal(28)));
433 QVERIFY(m_dbg->setMethodBody(obj.debugId(), "myMethod", "return a + 7"));
436 QVERIFY(QMetaObject::invokeMethod(root, "myMethod", Qt::DirectConnection,
437 Q_RETURN_ARG(QVariant, rv), Q_ARG(QVariant, QVariant(19))));
438 QVERIFY(rv == QVariant(qreal(26)));
442 void tst_QQmlEngineDebugService::watch_property()
444 QQmlDebugObjectReference obj = findRootObject();
445 QQmlDebugPropertyReference prop = findProperty(obj.properties(), "width");
447 QQmlDebugPropertyWatch *watch;
449 QQmlEngineDebug *unconnected = new QQmlEngineDebug(0);
450 watch = unconnected->addWatch(prop, this);
451 QCOMPARE(watch->state(), QQmlDebugWatch::Dead);
455 watch = m_dbg->addWatch(QQmlDebugPropertyReference(), this);
456 QVERIFY(QQmlDebugTest::waitForSignal(watch, SIGNAL(stateChanged(QQmlDebugWatch::State))));
457 QCOMPARE(watch->state(), QQmlDebugWatch::Inactive);
460 watch = m_dbg->addWatch(prop, this);
461 QCOMPARE(watch->state(), QQmlDebugWatch::Waiting);
462 QCOMPARE(watch->objectDebugId(), obj.debugId());
463 QCOMPARE(watch->name(), prop.name());
465 QSignalSpy spy(watch, SIGNAL(valueChanged(QByteArray,QVariant)));
467 int origWidth = m_rootItem->property("width").toInt();
468 m_rootItem->setProperty("width", origWidth*2);
470 // stateChanged() is received before valueChanged()
471 QVERIFY(QQmlDebugTest::waitForSignal(watch, SIGNAL(stateChanged(QQmlDebugWatch::State))));
472 QCOMPARE(watch->state(), QQmlDebugWatch::Active);
473 QCOMPARE(spy.count(), 1);
475 m_dbg->removeWatch(watch);
478 // restore original value and verify spy doesn't get additional signal since watch has been removed
479 m_rootItem->setProperty("width", origWidth);
481 QCOMPARE(spy.count(), 1);
483 QCOMPARE(spy.at(0).at(0).value<QByteArray>(), prop.name().toUtf8());
484 QCOMPARE(spy.at(0).at(1).value<QVariant>(), qVariantFromValue(origWidth*2));
487 void tst_QQmlEngineDebugService::watch_object()
489 QQmlDebugEnginesQuery *q_engines = m_dbg->queryAvailableEngines(this);
490 waitForQuery(q_engines);
492 QVERIFY(q_engines->engines().count() > 0);
493 QQmlDebugRootContextQuery *q_context = m_dbg->queryRootContexts(q_engines->engines()[0].debugId(), this);
494 waitForQuery(q_context);
496 QVERIFY(q_context->rootContext().objects().count() > 0);
497 QQmlDebugObjectQuery *q_obj = m_dbg->queryObject(q_context->rootContext().objects()[0], this);
500 QQmlDebugObjectReference obj = q_obj->object();
506 QQmlDebugWatch *watch;
508 QQmlEngineDebug *unconnected = new QQmlEngineDebug(0);
509 watch = unconnected->addWatch(obj, this);
510 QCOMPARE(watch->state(), QQmlDebugWatch::Dead);
514 watch = m_dbg->addWatch(QQmlDebugObjectReference(), this);
515 QVERIFY(QQmlDebugTest::waitForSignal(watch, SIGNAL(stateChanged(QQmlDebugWatch::State))));
516 QCOMPARE(watch->state(), QQmlDebugWatch::Inactive);
519 watch = m_dbg->addWatch(obj, this);
520 QCOMPARE(watch->state(), QQmlDebugWatch::Waiting);
521 QCOMPARE(watch->objectDebugId(), obj.debugId());
523 QSignalSpy spy(watch, SIGNAL(valueChanged(QByteArray,QVariant)));
525 int origWidth = m_rootItem->property("width").toInt();
526 int origHeight = m_rootItem->property("height").toInt();
527 m_rootItem->setProperty("width", origWidth*2);
528 m_rootItem->setProperty("height", origHeight*2);
530 // stateChanged() is received before any valueChanged() signals
531 QVERIFY(QQmlDebugTest::waitForSignal(watch, SIGNAL(stateChanged(QQmlDebugWatch::State))));
532 QCOMPARE(watch->state(), QQmlDebugWatch::Active);
533 QVERIFY(spy.count() > 0);
537 for (int i=0; i<spy.count(); i++) {
538 const QVariantList &values = spy[i];
539 if (values[0].value<QByteArray>() == "width")
540 newWidth = values[1].value<QVariant>().toInt();
541 else if (values[0].value<QByteArray>() == "height")
542 newHeight = values[1].value<QVariant>().toInt();
546 m_dbg->removeWatch(watch);
549 // since watch has been removed, restoring the original values should not trigger a valueChanged()
551 m_rootItem->setProperty("width", origWidth);
552 m_rootItem->setProperty("height", origHeight);
554 QCOMPARE(spy.count(), 0);
556 QCOMPARE(newWidth, origWidth * 2);
557 QCOMPARE(newHeight, origHeight * 2);
560 void tst_QQmlEngineDebugService::watch_expression()
562 QFETCH(QString, expr);
563 QFETCH(int, increment);
564 QFETCH(int, incrementCount);
566 int origWidth = m_rootItem->property("width").toInt();
568 QQmlDebugObjectReference obj = findRootObject();
570 QQmlDebugObjectExpressionWatch *watch;
572 QQmlEngineDebug *unconnected = new QQmlEngineDebug(0);
573 watch = unconnected->addWatch(obj, expr, this);
574 QCOMPARE(watch->state(), QQmlDebugWatch::Dead);
578 watch = m_dbg->addWatch(QQmlDebugObjectReference(), expr, this);
579 QVERIFY(QQmlDebugTest::waitForSignal(watch, SIGNAL(stateChanged(QQmlDebugWatch::State))));
580 QCOMPARE(watch->state(), QQmlDebugWatch::Inactive);
583 watch = m_dbg->addWatch(obj, expr, this);
584 QCOMPARE(watch->state(), QQmlDebugWatch::Waiting);
585 QCOMPARE(watch->objectDebugId(), obj.debugId());
586 QCOMPARE(watch->expression(), expr);
588 QSignalSpy spyState(watch, SIGNAL(stateChanged(QQmlDebugWatch::State)));
590 QSignalSpy spy(watch, SIGNAL(valueChanged(QByteArray,QVariant)));
591 int expectedSpyCount = incrementCount + 1; // should also get signal with expression's initial value
593 int width = origWidth;
594 for (int i=0; i<incrementCount+1; i++) {
597 m_rootItem->setProperty("width", width);
599 if (!QQmlDebugTest::waitForSignal(watch, SIGNAL(valueChanged(QByteArray,QVariant))))
600 QFAIL("Did not receive valueChanged() for expression");
603 if (spyState.count() == 0)
604 QVERIFY(QQmlDebugTest::waitForSignal(watch, SIGNAL(stateChanged(QQmlDebugWatch::State))));
605 QCOMPARE(spyState.count(), 1);
606 QCOMPARE(watch->state(), QQmlDebugWatch::Active);
608 m_dbg->removeWatch(watch);
611 // restore original value and verify spy doesn't get a signal since watch has been removed
612 m_rootItem->setProperty("width", origWidth);
614 QCOMPARE(spy.count(), expectedSpyCount);
616 width = origWidth + increment;
617 for (int i=0; i<spy.count(); i++) {
618 QCOMPARE(spy.at(i).at(1).value<QVariant>().toInt(), width);
623 void tst_QQmlEngineDebugService::watch_expression_data()
625 QTest::addColumn<QString>("expr");
626 QTest::addColumn<int>("increment");
627 QTest::addColumn<int>("incrementCount");
629 QTest::newRow("width") << "width" << 0 << 0;
630 QTest::newRow("width+10") << "width + 10" << 10 << 5;
633 void tst_QQmlEngineDebugService::watch_context()
635 QQmlDebugContextReference c;
636 QTest::ignoreMessage(QtWarningMsg, "QQmlEngineDebug::addWatch(): Not implemented");
637 QVERIFY(!m_dbg->addWatch(c, QString(), this));
640 void tst_QQmlEngineDebugService::watch_file()
642 QQmlDebugFileReference f;
643 QTest::ignoreMessage(QtWarningMsg, "QQmlEngineDebug::addWatch(): Not implemented");
644 QVERIFY(!m_dbg->addWatch(f, this));
647 void tst_QQmlEngineDebugService::queryAvailableEngines()
649 QQmlDebugEnginesQuery *q_engines;
651 QQmlEngineDebug *unconnected = new QQmlEngineDebug(0);
652 q_engines = unconnected->queryAvailableEngines(0);
653 QCOMPARE(q_engines->state(), QQmlDebugQuery::Error);
657 q_engines = m_dbg->queryAvailableEngines(this);
660 q_engines = m_dbg->queryAvailableEngines(this);
661 QVERIFY(q_engines->engines().isEmpty());
662 waitForQuery(q_engines);
664 // TODO test multiple engines
665 QList<QQmlDebugEngineReference> engines = q_engines->engines();
666 QCOMPARE(engines.count(), 1);
668 foreach(const QQmlDebugEngineReference &e, engines) {
669 QCOMPARE(e.debugId(), QQmlDebugService::idForObject(m_engine));
670 QCOMPARE(e.name(), m_engine->objectName());
673 // Make query invalid by deleting client
674 q_engines = m_dbg->queryAvailableEngines(this);
675 QCOMPARE(q_engines->state(), QQmlDebugQuery::Waiting);
677 QCOMPARE(q_engines->state(), QQmlDebugQuery::Error);
679 m_dbg = new QQmlEngineDebug(m_conn, this);
682 void tst_QQmlEngineDebugService::queryRootContexts()
684 QQmlDebugEnginesQuery *q_engines = m_dbg->queryAvailableEngines(this);
685 waitForQuery(q_engines);
686 int engineId = q_engines->engines()[0].debugId();
689 QQmlDebugRootContextQuery *q_context;
691 QQmlEngineDebug *unconnected = new QQmlEngineDebug(0);
692 q_context = unconnected->queryRootContexts(engineId, this);
693 QCOMPARE(q_context->state(), QQmlDebugQuery::Error);
697 q_context = m_dbg->queryRootContexts(engineId, this);
700 q_context = m_dbg->queryRootContexts(engineId, this);
701 waitForQuery(q_context);
703 QQmlContext *actualContext = m_engine->rootContext();
704 QQmlDebugContextReference context = q_context->rootContext();
705 QCOMPARE(context.debugId(), QQmlDebugService::idForObject(actualContext));
706 QCOMPARE(context.name(), actualContext->objectName());
708 QCOMPARE(context.objects().count(), 4); // 4 qml component objects created for context in main()
710 // root context query sends only root object data - it doesn't fill in
711 // the children or property info
712 QCOMPARE(context.objects()[0].properties().count(), 0);
713 QCOMPARE(context.objects()[0].children().count(), 0);
715 QCOMPARE(context.contexts().count(), 5);
716 QVERIFY(context.contexts()[0].debugId() >= 0);
717 QCOMPARE(context.contexts()[0].name(), QString("tst_QQmlDebug_childContext"));
719 // Make query invalid by deleting client
720 q_context = m_dbg->queryRootContexts(engineId, this);
721 QCOMPARE(q_context->state(), QQmlDebugQuery::Waiting);
723 QCOMPARE(q_context->state(), QQmlDebugQuery::Error);
725 m_dbg = new QQmlEngineDebug(m_conn, this);
728 void tst_QQmlEngineDebugService::queryObject()
730 QFETCH(bool, recursive);
732 QQmlDebugEnginesQuery *q_engines = m_dbg->queryAvailableEngines(this);
733 waitForQuery(q_engines);
735 QQmlDebugRootContextQuery *q_context = m_dbg->queryRootContexts(q_engines->engines()[0].debugId(), this);
736 waitForQuery(q_context);
737 QQmlDebugObjectReference rootObject = q_context->rootContext().objects()[0];
739 QQmlDebugObjectQuery *q_obj = 0;
741 QQmlEngineDebug *unconnected = new QQmlEngineDebug(0);
742 q_obj = recursive ? unconnected->queryObjectRecursive(rootObject, this) : unconnected->queryObject(rootObject, this);
743 QCOMPARE(q_obj->state(), QQmlDebugQuery::Error);
747 q_obj = recursive ? m_dbg->queryObjectRecursive(rootObject, this) : m_dbg->queryObject(rootObject, this);
750 q_obj = recursive ? m_dbg->queryObjectRecursive(rootObject, this) : m_dbg->queryObject(rootObject, this);
753 QQmlDebugObjectReference obj = q_obj->object();
758 // Make query invalid by deleting client
759 q_obj = recursive ? m_dbg->queryObjectRecursive(rootObject, this) : m_dbg->queryObject(rootObject, this);
760 QCOMPARE(q_obj->state(), QQmlDebugQuery::Waiting);
762 QCOMPARE(q_obj->state(), QQmlDebugQuery::Error);
764 m_dbg = new QQmlEngineDebug(m_conn, this);
766 // check source as defined in main()
767 QQmlDebugFileReference source = obj.source();
768 QCOMPARE(source.url(), QUrl::fromLocalFile(""));
769 QCOMPARE(source.lineNumber(), 3);
770 QCOMPARE(source.columnNumber(), 1);
772 // generically test all properties, children and childrens' properties
773 recursiveObjectTest(m_rootItem, obj, recursive);
776 foreach(const QQmlDebugObjectReference &child, obj.children())
777 QVERIFY(child.properties().count() > 0);
779 QQmlDebugObjectReference rect;
780 QQmlDebugObjectReference text;
781 foreach (const QQmlDebugObjectReference &child, obj.children()) {
782 if (child.className() == "Rectangle")
784 else if (child.className() == "Text")
788 // test specific property values
789 QCOMPARE(findProperty(rect.properties(), "width").value(), qVariantFromValue(500));
790 QCOMPARE(findProperty(rect.properties(), "height").value(), qVariantFromValue(600));
791 QCOMPARE(findProperty(rect.properties(), "color").value(), qVariantFromValue(QColor("blue")));
793 QCOMPARE(findProperty(text.properties(), "color").value(), qVariantFromValue(QColor("blue")));
795 foreach(const QQmlDebugObjectReference &child, obj.children())
796 QCOMPARE(child.properties().count(), 0);
800 void tst_QQmlEngineDebugService::queryObject_data()
802 QTest::addColumn<bool>("recursive");
804 QTest::newRow("non-recursive") << false;
805 QTest::newRow("recursive") << true;
808 void tst_QQmlEngineDebugService::queryExpressionResult()
810 QFETCH(QString, expr);
811 QFETCH(QVariant, result);
813 QQmlDebugEnginesQuery *q_engines = m_dbg->queryAvailableEngines(this);
814 waitForQuery(q_engines); // check immediate deletion is ok
816 QQmlDebugRootContextQuery *q_context = m_dbg->queryRootContexts(q_engines->engines()[0].debugId(), this);
817 waitForQuery(q_context);
818 int objectId = q_context->rootContext().objects()[0].debugId();
820 QQmlDebugExpressionQuery *q_expr;
822 QQmlEngineDebug *unconnected = new QQmlEngineDebug(0);
823 q_expr = unconnected->queryExpressionResult(objectId, expr, this);
824 QCOMPARE(q_expr->state(), QQmlDebugQuery::Error);
828 q_expr = m_dbg->queryExpressionResult(objectId, expr, this);
831 q_expr = m_dbg->queryExpressionResult(objectId, expr, this);
832 QCOMPARE(q_expr->expression().toString(), expr);
833 waitForQuery(q_expr);
835 QCOMPARE(q_expr->result(), result);
840 // Make query invalid by deleting client
841 q_expr = m_dbg->queryExpressionResult(objectId, expr, this);
842 QCOMPARE(q_expr->state(), QQmlDebugQuery::Waiting);
844 QCOMPARE(q_expr->state(), QQmlDebugQuery::Error);
846 m_dbg = new QQmlEngineDebug(m_conn, this);
849 void tst_QQmlEngineDebugService::queryExpressionResult_data()
851 QTest::addColumn<QString>("expr");
852 QTest::addColumn<QVariant>("result");
854 QTest::newRow("width + 50") << "width + 50" << qVariantFromValue(60);
855 QTest::newRow("blueRect.width") << "blueRect.width" << qVariantFromValue(500);
856 QTest::newRow("bad expr") << "aeaef" << qVariantFromValue(QString("<undefined>"));
857 QTest::newRow("QObject*") << "varObj" << qVariantFromValue(QString("<unnamed object>"));
858 QTest::newRow("list of QObject*") << "varObjList" << qVariantFromValue(QString("<unknown value>"));
860 map.insert(QLatin1String("rect"), QVariant(QLatin1String("<unnamed object>")));
861 QTest::newRow("varObjMap") << "varObjMap" << qVariantFromValue(map);
864 void tst_QQmlEngineDebugService::tst_QQmlDebugFileReference()
866 QQmlDebugFileReference ref;
867 QVERIFY(ref.url().isEmpty());
868 QCOMPARE(ref.lineNumber(), -1);
869 QCOMPARE(ref.columnNumber(), -1);
871 ref.setUrl(QUrl("http://test"));
872 QCOMPARE(ref.url(), QUrl("http://test"));
873 ref.setLineNumber(1);
874 QCOMPARE(ref.lineNumber(), 1);
875 ref.setColumnNumber(1);
876 QCOMPARE(ref.columnNumber(), 1);
878 QQmlDebugFileReference copy(ref);
879 QQmlDebugFileReference copyAssign;
881 foreach (const QQmlDebugFileReference &r, (QList<QQmlDebugFileReference>() << copy << copyAssign)) {
882 QCOMPARE(r.url(), ref.url());
883 QCOMPARE(r.lineNumber(), ref.lineNumber());
884 QCOMPARE(r.columnNumber(), ref.columnNumber());
888 void tst_QQmlEngineDebugService::tst_QQmlDebugEngineReference()
890 QQmlDebugEngineReference ref;
891 QCOMPARE(ref.debugId(), -1);
892 QVERIFY(ref.name().isEmpty());
894 ref = QQmlDebugEngineReference(1);
895 QCOMPARE(ref.debugId(), 1);
896 QVERIFY(ref.name().isEmpty());
898 QQmlDebugEnginesQuery *q_engines = m_dbg->queryAvailableEngines(this);
899 waitForQuery(q_engines);
900 ref = q_engines->engines()[0];
903 QQmlDebugEngineReference copy(ref);
904 QQmlDebugEngineReference copyAssign;
906 foreach (const QQmlDebugEngineReference &r, (QList<QQmlDebugEngineReference>() << copy << copyAssign)) {
907 QCOMPARE(r.debugId(), ref.debugId());
908 QCOMPARE(r.name(), ref.name());
912 void tst_QQmlEngineDebugService::tst_QQmlDebugObjectReference()
914 QQmlDebugObjectReference ref;
915 QCOMPARE(ref.debugId(), -1);
916 QCOMPARE(ref.className(), QString());
917 QCOMPARE(ref.name(), QString());
918 QCOMPARE(ref.contextDebugId(), -1);
919 QVERIFY(ref.properties().isEmpty());
920 QVERIFY(ref.children().isEmpty());
922 QQmlDebugFileReference source = ref.source();
923 QVERIFY(source.url().isEmpty());
924 QVERIFY(source.lineNumber() < 0);
925 QVERIFY(source.columnNumber() < 0);
927 ref = QQmlDebugObjectReference(1);
928 QCOMPARE(ref.debugId(), 1);
930 QQmlDebugObjectReference rootObject = findRootObject();
931 QQmlDebugObjectQuery *query = m_dbg->queryObjectRecursive(rootObject, this);
933 ref = query->object();
936 QVERIFY(ref.debugId() >= 0);
938 QQmlDebugObjectReference copy(ref);
939 QQmlDebugObjectReference copyAssign;
941 foreach (const QQmlDebugObjectReference &r, (QList<QQmlDebugObjectReference>() << copy << copyAssign))
942 recursiveCompareObjects(r, ref);
945 void tst_QQmlEngineDebugService::tst_QQmlDebugContextReference()
947 QQmlDebugContextReference ref;
948 QCOMPARE(ref.debugId(), -1);
949 QVERIFY(ref.name().isEmpty());
950 QVERIFY(ref.objects().isEmpty());
951 QVERIFY(ref.contexts().isEmpty());
953 QQmlDebugEnginesQuery *q_engines = m_dbg->queryAvailableEngines(this);
954 waitForQuery(q_engines);
955 QQmlDebugRootContextQuery *q_context = m_dbg->queryRootContexts(q_engines->engines()[0].debugId(), this);
956 waitForQuery(q_context);
958 ref = q_context->rootContext();
961 QVERIFY(ref.debugId() >= 0);
963 QQmlDebugContextReference copy(ref);
964 QQmlDebugContextReference copyAssign;
966 foreach (const QQmlDebugContextReference &r, (QList<QQmlDebugContextReference>() << copy << copyAssign))
967 recursiveCompareContexts(r, ref);
970 void tst_QQmlEngineDebugService::tst_QQmlDebugPropertyReference()
972 QQmlDebugObjectReference rootObject = findRootObject();
973 QQmlDebugObjectQuery *query = m_dbg->queryObject(rootObject, this);
975 QQmlDebugObjectReference obj = query->object();
978 QQmlDebugPropertyReference ref = findProperty(obj.properties(), "scale");
979 QVERIFY(ref.objectDebugId() > 0);
980 QVERIFY(!ref.name().isEmpty());
981 QVERIFY(!ref.value().isNull());
982 QVERIFY(!ref.valueTypeName().isEmpty());
983 QVERIFY(!ref.binding().isEmpty());
984 QVERIFY(ref.hasNotifySignal());
986 QQmlDebugPropertyReference copy(ref);
987 QQmlDebugPropertyReference copyAssign;
989 foreach (const QQmlDebugPropertyReference &r, (QList<QQmlDebugPropertyReference>() << copy << copyAssign))
990 compareProperties(r, ref);
993 void tst_QQmlEngineDebugService::setBindingForObject()
995 QQmlDebugObjectReference rootObject = findRootObject();
996 QVERIFY(rootObject.debugId() != -1);
997 QQmlDebugPropertyReference widthPropertyRef = findProperty(rootObject.properties(), "width");
999 QCOMPARE(widthPropertyRef.value(), QVariant(10));
1000 QCOMPARE(widthPropertyRef.binding(), QString());
1005 m_dbg->setBindingForObject(rootObject.debugId(), "width", "15", true);
1007 rootObject = findRootObject();
1008 widthPropertyRef = findProperty(rootObject.properties(), "width");
1010 QCOMPARE(widthPropertyRef.value(), QVariant(15));
1011 QCOMPARE(widthPropertyRef.binding(), QString());
1016 m_dbg->setBindingForObject(rootObject.debugId(), "width", "height", false);
1018 rootObject = findRootObject();
1019 widthPropertyRef = findProperty(rootObject.properties(), "width");
1021 QCOMPARE(widthPropertyRef.value(), QVariant(20));
1022 QCOMPARE(widthPropertyRef.binding(), QString("height"));
1027 m_dbg->resetBindingForObject(rootObject.debugId(), "width");
1029 rootObject = findRootObject();
1030 widthPropertyRef = findProperty(rootObject.properties(), "width");
1032 // QCOMPARE(widthPropertyRef.value(), QVariant(0)); // TODO: Shouldn't this work?
1033 QCOMPARE(widthPropertyRef.binding(), QString());
1038 rootObject = findRootObject();
1039 QCOMPARE(rootObject.children().size(), 5); // Rectangle, Text, MouseArea, Component.onCompleted, NonScriptPropertyElement
1040 QQmlDebugObjectReference mouseAreaObject = rootObject.children().at(2);
1041 QQmlDebugObjectQuery *q_obj = m_dbg->queryObjectRecursive(mouseAreaObject, this);
1042 waitForQuery(q_obj);
1043 mouseAreaObject = q_obj->object();
1045 QCOMPARE(mouseAreaObject.className(), QString("MouseArea"));
1047 QQmlDebugPropertyReference onEnteredRef = findProperty(mouseAreaObject.properties(), "onEntered");
1049 QCOMPARE(onEnteredRef.name(), QString("onEntered"));
1050 QCOMPARE(onEnteredRef.value(), QVariant("(function onEntered() { { console.log('hello') } })"));
1052 m_dbg->setBindingForObject(mouseAreaObject.debugId(), "onEntered", "{console.log('hello, world') }", false) ;
1054 rootObject = findRootObject();
1055 mouseAreaObject = rootObject.children().at(2);
1056 q_obj = m_dbg->queryObjectRecursive(mouseAreaObject, this);
1057 waitForQuery(q_obj);
1058 mouseAreaObject = q_obj->object();
1059 onEnteredRef = findProperty(mouseAreaObject.properties(), "onEntered");
1060 QCOMPARE(onEnteredRef.name(), QString("onEntered"));
1061 QCOMPARE(onEnteredRef.value(), QVariant("{console.log('hello, world') }"));
1064 void tst_QQmlEngineDebugService::setBindingInStates()
1066 // Check if changing bindings of propertychanges works
1068 const int sourceIndex = 3;
1070 QQmlDebugObjectReference obj = findRootObject(sourceIndex);
1072 QVERIFY(obj.debugId() != -1);
1073 QVERIFY(obj.children().count() >= 2);
1075 // We are going to switch state a couple of times, we need to get rid of the transition before
1076 QQmlDebugExpressionQuery *q_deleteTransition = m_dbg->queryExpressionResult(obj.debugId(),QString("transitions = []"),this);
1077 waitForQuery(q_deleteTransition);
1078 delete q_deleteTransition;
1081 // check initial value of the property that is changing
1082 QQmlDebugExpressionQuery *q_setState;
1083 q_setState = m_dbg->queryExpressionResult(obj.debugId(),QString("state=\"state1\""),this);
1084 waitForQuery(q_setState);
1087 obj = findRootObject(sourceIndex);
1088 QCOMPARE(findProperty(obj.properties(),"width").value().toInt(),200);
1091 q_setState = m_dbg->queryExpressionResult(obj.debugId(),QString("state=\"\""),this);
1092 waitForQuery(q_setState);
1096 obj = findRootObject(sourceIndex, true);
1097 QCOMPARE(findProperty(obj.properties(),"width").value().toInt(),100);
1100 // change the binding
1101 QQmlDebugObjectReference state = obj.children()[1];
1102 QCOMPARE(state.className(), QString("State"));
1103 QVERIFY(state.children().count() > 0);
1105 QQmlDebugObjectReference propertyChange = state.children()[0];
1106 QVERIFY(propertyChange.debugId() != -1);
1108 QVERIFY( m_dbg->setBindingForObject(propertyChange.debugId(), "width",QVariant(300),true) );
1110 // check properties changed in state
1111 obj = findRootObject(sourceIndex);
1112 QCOMPARE(findProperty(obj.properties(),"width").value().toInt(),100);
1115 q_setState = m_dbg->queryExpressionResult(obj.debugId(),QString("state=\"state1\""),this);
1116 waitForQuery(q_setState);
1119 obj = findRootObject(sourceIndex);
1120 QCOMPARE(findProperty(obj.properties(),"width").value().toInt(),300);
1122 // check changing properties of base state from within a state
1123 QVERIFY(m_dbg->setBindingForObject(obj.debugId(),"width","height*2",false));
1124 QVERIFY(m_dbg->setBindingForObject(obj.debugId(),"height","200",true));
1126 obj = findRootObject(sourceIndex);
1127 QCOMPARE(findProperty(obj.properties(),"width").value().toInt(),300);
1129 q_setState = m_dbg->queryExpressionResult(obj.debugId(),QString("state=\"\""),this);
1130 waitForQuery(q_setState);
1133 obj = findRootObject(sourceIndex);
1134 QCOMPARE(findProperty(obj.properties(),"width").value().toInt(), 400);
1136 // reset binding while in a state
1137 q_setState = m_dbg->queryExpressionResult(obj.debugId(),QString("state=\"state1\""),this);
1138 waitForQuery(q_setState);
1141 obj = findRootObject(sourceIndex);
1142 QCOMPARE(findProperty(obj.properties(),"width").value().toInt(), 300);
1144 m_dbg->resetBindingForObject(propertyChange.debugId(), "width");
1146 obj = findRootObject(sourceIndex);
1147 QCOMPARE(findProperty(obj.properties(),"width").value().toInt(), 400);
1150 m_dbg->setBindingForObject(propertyChange.debugId(), "width", "300", true);
1152 obj = findRootObject(sourceIndex);
1153 QCOMPARE(findProperty(obj.properties(),"width").value().toInt(), 300);
1156 void tst_QQmlEngineDebugService::queryObjectTree()
1158 const int sourceIndex = 3;
1160 // Check if states/transitions are initialized when fetching root item
1161 QQmlDebugEnginesQuery *q_engines = m_dbg->queryAvailableEngines(this);
1162 waitForQuery(q_engines);
1164 QQmlDebugRootContextQuery *q_context = m_dbg->queryRootContexts(q_engines->engines()[0].debugId(), this);
1165 waitForQuery(q_context);
1167 QVERIFY(q_context->rootContext().objects().count() > sourceIndex);
1168 QQmlDebugObjectReference rootObject = q_context->rootContext().objects()[sourceIndex];
1170 QQmlDebugObjectQuery *q_obj = m_dbg->queryObjectRecursive(rootObject, this);
1171 waitForQuery(q_obj);
1173 QQmlDebugObjectReference obj = q_obj->object();
1179 QVERIFY(obj.debugId() != -1);
1180 QVERIFY(obj.children().count() >= 2);
1185 QQmlDebugObjectReference state = obj.children()[1];
1186 QCOMPARE(state.className(), QString("State"));
1187 QVERIFY(state.children().count() > 0);
1189 QQmlDebugObjectReference propertyChange = state.children()[0];
1190 QVERIFY(propertyChange.debugId() != -1);
1192 QQmlDebugPropertyReference propertyChangeTarget = findProperty(propertyChange.properties(),"target");
1193 QCOMPARE(propertyChangeTarget.objectDebugId(), propertyChange.debugId());
1195 QQmlDebugObjectReference targetReference = qvariant_cast<QQmlDebugObjectReference>(propertyChangeTarget.value());
1196 QVERIFY(targetReference.debugId() != -1);
1201 QQmlDebugObjectReference transition = obj.children()[0];
1202 QCOMPARE(transition.className(), QString("Transition"));
1203 QCOMPARE(findProperty(transition.properties(),"from").value().toString(), QString("*"));
1204 QCOMPARE(findProperty(transition.properties(),"to").value(), findProperty(state.properties(),"name").value());
1205 QVERIFY(transition.children().count() > 0);
1207 QQmlDebugObjectReference animation = transition.children()[0];
1208 QVERIFY(animation.debugId() != -1);
1210 QQmlDebugPropertyReference animationTarget = findProperty(animation.properties(),"target");
1211 QCOMPARE(animationTarget.objectDebugId(), animation.debugId());
1213 targetReference = qvariant_cast<QQmlDebugObjectReference>(animationTarget.value());
1214 QVERIFY(targetReference.debugId() != -1);
1216 QCOMPARE(findProperty(animation.properties(),"property").value().toString(), QString("width"));
1217 QCOMPARE(findProperty(animation.properties(),"duration").value().toInt(), 100);
1220 int main(int argc, char *argv[])
1222 int _argc = argc + 1;
1223 char **_argv = new char*[_argc];
1224 for (int i = 0; i < argc; ++i)
1226 char arg[] = "-qmljsdebugger=port:3768";
1227 _argv[_argc - 1] = arg;
1229 QGuiApplication app(_argc, _argv);
1230 tst_QQmlEngineDebugService tc;
1231 return QTest::qExec(&tc, _argc, _argv);
1235 #include "tst_qqmlenginedebugservice.moc"