Trim trailing whitespace.
[profile/ivi/qtdeclarative.git] / tests / auto / qml / qqmlcontext / tst_qqmlcontext.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the test suite of the Qt Toolkit.
7 **
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.
16 **
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.
20 **
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.
28 **
29 ** Other Usage
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.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include <qtest.h>
43 #include <QDebug>
44 #include <QQmlEngine>
45 #include <QQmlContext>
46 #include <QQmlComponent>
47 #include <QQmlExpression>
48 #include <private/qqmlcontext_p.h>
49 #include "../../shared/util.h"
50
51 class tst_qqmlcontext : public QQmlDataTest
52 {
53     Q_OBJECT
54 public:
55     tst_qqmlcontext() {}
56
57 private slots:
58     void baseUrl();
59     void resolvedUrl();
60     void engineMethod();
61     void parentContext();
62     void setContextProperty();
63     void setContextObject();
64     void destruction();
65     void idAsContextProperty();
66     void readOnlyContexts();
67     void nameForObject();
68
69     void refreshExpressions();
70     void refreshExpressionsCrash();
71     void refreshExpressionsRootContext();
72
73     void qtbug_22535();
74     void evalAfterInvalidate();
75
76 private:
77     QQmlEngine engine;
78 };
79
80 void tst_qqmlcontext::baseUrl()
81 {
82     QQmlContext ctxt(&engine);
83
84     QCOMPARE(ctxt.baseUrl(), QUrl());
85
86     ctxt.setBaseUrl(QUrl("http://www.nokia.com/"));
87
88     QCOMPARE(ctxt.baseUrl(), QUrl("http://www.nokia.com/"));
89 }
90
91 void tst_qqmlcontext::resolvedUrl()
92 {
93     // Relative to the component
94     {
95         QQmlContext ctxt(&engine);
96         ctxt.setBaseUrl(QUrl("http://www.nokia.com/"));
97
98         QCOMPARE(ctxt.resolvedUrl(QUrl("main.qml")), QUrl("http://www.nokia.com/main.qml"));
99     }
100
101     // Relative to a parent
102     {
103         QQmlContext ctxt(&engine);
104         ctxt.setBaseUrl(QUrl("http://www.nokia.com/"));
105
106         QQmlContext ctxt2(&ctxt);
107         QCOMPARE(ctxt2.resolvedUrl(QUrl("main2.qml")), QUrl("http://www.nokia.com/main2.qml"));
108     }
109
110     // Relative to the engine
111     {
112         QQmlContext ctxt(&engine);
113         QCOMPARE(ctxt.resolvedUrl(QUrl("main.qml")), engine.baseUrl().resolved(QUrl("main.qml")));
114     }
115
116     // Relative to a deleted parent
117     {
118         QQmlContext *ctxt = new QQmlContext(&engine);
119         ctxt->setBaseUrl(QUrl("http://www.nokia.com/"));
120
121         QQmlContext ctxt2(ctxt);
122         QCOMPARE(ctxt2.resolvedUrl(QUrl("main2.qml")), QUrl("http://www.nokia.com/main2.qml"));
123
124         delete ctxt; ctxt = 0;
125
126         QCOMPARE(ctxt2.resolvedUrl(QUrl("main2.qml")), QUrl());
127     }
128
129     // Absolute
130     {
131         QQmlContext ctxt(&engine);
132
133         QCOMPARE(ctxt.resolvedUrl(QUrl("http://www.nokia.com/main2.qml")), QUrl("http://www.nokia.com/main2.qml"));
134         QCOMPARE(ctxt.resolvedUrl(QUrl("file:///main2.qml")), QUrl("file:///main2.qml"));
135     }
136 }
137
138 void tst_qqmlcontext::engineMethod()
139 {
140     QQmlEngine *engine = new QQmlEngine;
141
142     QQmlContext ctxt(engine);
143     QQmlContext ctxt2(&ctxt);
144     QQmlContext ctxt3(&ctxt2);
145     QQmlContext ctxt4(&ctxt2);
146
147     QCOMPARE(ctxt.engine(), engine);
148     QCOMPARE(ctxt2.engine(), engine);
149     QCOMPARE(ctxt3.engine(), engine);
150     QCOMPARE(ctxt4.engine(), engine);
151
152     delete engine; engine = 0;
153
154     QCOMPARE(ctxt.engine(), engine);
155     QCOMPARE(ctxt2.engine(), engine);
156     QCOMPARE(ctxt3.engine(), engine);
157     QCOMPARE(ctxt4.engine(), engine);
158 }
159
160 void tst_qqmlcontext::parentContext()
161 {
162     QQmlEngine *engine = new QQmlEngine;
163
164     QCOMPARE(engine->rootContext()->parentContext(), (QQmlContext *)0);
165
166     QQmlContext *ctxt = new QQmlContext(engine);
167     QQmlContext *ctxt2 = new QQmlContext(ctxt);
168     QQmlContext *ctxt3 = new QQmlContext(ctxt2);
169     QQmlContext *ctxt4 = new QQmlContext(ctxt2);
170     QQmlContext *ctxt5 = new QQmlContext(ctxt);
171     QQmlContext *ctxt6 = new QQmlContext(engine);
172     QQmlContext *ctxt7 = new QQmlContext(engine->rootContext());
173
174     QCOMPARE(ctxt->parentContext(), engine->rootContext());
175     QCOMPARE(ctxt2->parentContext(), ctxt);
176     QCOMPARE(ctxt3->parentContext(), ctxt2);
177     QCOMPARE(ctxt4->parentContext(), ctxt2);
178     QCOMPARE(ctxt5->parentContext(), ctxt);
179     QCOMPARE(ctxt6->parentContext(), engine->rootContext());
180     QCOMPARE(ctxt7->parentContext(), engine->rootContext());
181
182     delete ctxt2; ctxt2 = 0;
183
184     QCOMPARE(ctxt->parentContext(), engine->rootContext());
185     QCOMPARE(ctxt3->parentContext(), (QQmlContext *)0);
186     QCOMPARE(ctxt4->parentContext(), (QQmlContext *)0);
187     QCOMPARE(ctxt5->parentContext(), ctxt);
188     QCOMPARE(ctxt6->parentContext(), engine->rootContext());
189     QCOMPARE(ctxt7->parentContext(), engine->rootContext());
190
191     delete engine; engine = 0;
192
193     QCOMPARE(ctxt->parentContext(), (QQmlContext *)0);
194     QCOMPARE(ctxt3->parentContext(), (QQmlContext *)0);
195     QCOMPARE(ctxt4->parentContext(), (QQmlContext *)0);
196     QCOMPARE(ctxt5->parentContext(), (QQmlContext *)0);
197     QCOMPARE(ctxt6->parentContext(), (QQmlContext *)0);
198     QCOMPARE(ctxt7->parentContext(), (QQmlContext *)0);
199
200     delete ctxt7;
201     delete ctxt6;
202     delete ctxt5;
203     delete ctxt4;
204     delete ctxt3;
205     delete ctxt;
206 }
207
208 class TestObject : public QObject
209 {
210     Q_OBJECT
211     Q_PROPERTY(int a READ a NOTIFY aChanged)
212     Q_PROPERTY(int b READ b NOTIFY bChanged)
213     Q_PROPERTY(int c READ c NOTIFY cChanged)
214
215 public:
216     TestObject() : _a(10), _b(10), _c(10) {}
217
218     int a() const { return _a; }
219     void setA(int a) { _a = a; emit aChanged(); }
220
221     int b() const { return _b; }
222     void setB(int b) { _b = b; emit bChanged(); }
223
224     int c() const { return _c; }
225     void setC(int c) { _c = c; emit cChanged(); }
226
227 signals:
228     void aChanged();
229     void bChanged();
230     void cChanged();
231
232 private:
233     int _a;
234     int _b;
235     int _c;
236 };
237
238 #define TEST_CONTEXT_PROPERTY(ctxt, name, value) \
239 { \
240     QQmlComponent component(&engine); \
241     component.setData("import QtQuick 2.0; QtObject { property variant test: " #name " }", QUrl()); \
242 \
243     QObject *obj = component.create(ctxt); \
244 \
245     QCOMPARE(obj->property("test"), value); \
246 \
247     delete obj; \
248 }
249
250 void tst_qqmlcontext::setContextProperty()
251 {
252     QQmlContext ctxt(&engine);
253     QQmlContext ctxt2(&ctxt);
254
255     TestObject obj1;
256     obj1.setA(3345);
257     TestObject obj2;
258     obj2.setA(-19);
259
260     // Static context properties
261     ctxt.setContextProperty("a", QVariant(10));
262     ctxt.setContextProperty("b", QVariant(9));
263     ctxt2.setContextProperty("d", &obj2);
264     ctxt2.setContextProperty("b", QVariant(19));
265     ctxt2.setContextProperty("c", QVariant(QString("Hello World!")));
266     ctxt.setContextProperty("d", &obj1);
267     ctxt.setContextProperty("e", &obj1);
268
269     TEST_CONTEXT_PROPERTY(&ctxt2, a, QVariant(10));
270     TEST_CONTEXT_PROPERTY(&ctxt2, b, QVariant(19));
271     TEST_CONTEXT_PROPERTY(&ctxt2, c, QVariant(QString("Hello World!")));
272     TEST_CONTEXT_PROPERTY(&ctxt2, d.a, QVariant(-19));
273     TEST_CONTEXT_PROPERTY(&ctxt2, e.a, QVariant(3345));
274
275     ctxt.setContextProperty("a", QVariant(13));
276     ctxt.setContextProperty("b", QVariant(4));
277     ctxt2.setContextProperty("b", QVariant(8));
278     ctxt2.setContextProperty("c", QVariant(QString("Hi World!")));
279     ctxt2.setContextProperty("d", &obj1);
280     obj1.setA(12);
281
282     TEST_CONTEXT_PROPERTY(&ctxt2, a, QVariant(13));
283     TEST_CONTEXT_PROPERTY(&ctxt2, b, QVariant(8));
284     TEST_CONTEXT_PROPERTY(&ctxt2, c, QVariant(QString("Hi World!")));
285     TEST_CONTEXT_PROPERTY(&ctxt2, d.a, QVariant(12));
286     TEST_CONTEXT_PROPERTY(&ctxt2, e.a, QVariant(12));
287
288     // Changes in context properties
289     {
290         QQmlComponent component(&engine);
291         component.setData("import QtQuick 2.0; QtObject { property variant test: a }", QUrl());
292
293         QObject *obj = component.create(&ctxt2);
294
295         QCOMPARE(obj->property("test"), QVariant(13));
296         ctxt.setContextProperty("a", QVariant(19));
297         QCOMPARE(obj->property("test"), QVariant(19));
298
299         delete obj;
300     }
301     {
302         QQmlComponent component(&engine);
303         component.setData("import QtQuick 2.0; QtObject { property variant test: b }", QUrl());
304
305         QObject *obj = component.create(&ctxt2);
306
307         QCOMPARE(obj->property("test"), QVariant(8));
308         ctxt.setContextProperty("b", QVariant(5));
309         QCOMPARE(obj->property("test"), QVariant(8));
310         ctxt2.setContextProperty("b", QVariant(1912));
311         QCOMPARE(obj->property("test"), QVariant(1912));
312
313         delete obj;
314     }
315     {
316         QQmlComponent component(&engine);
317         component.setData("import QtQuick 2.0; QtObject { property variant test: e.a }", QUrl());
318
319         QObject *obj = component.create(&ctxt2);
320
321         QCOMPARE(obj->property("test"), QVariant(12));
322         obj1.setA(13);
323         QCOMPARE(obj->property("test"), QVariant(13));
324
325         delete obj;
326     }
327
328     // New context properties
329     {
330         QQmlComponent component(&engine);
331         component.setData("import QtQuick 2.0; QtObject { property variant test: a }", QUrl());
332
333         QObject *obj = component.create(&ctxt2);
334
335         QCOMPARE(obj->property("test"), QVariant(19));
336         ctxt2.setContextProperty("a", QVariant(1945));
337         QCOMPARE(obj->property("test"), QVariant(1945));
338
339         delete obj;
340     }
341
342     // Setting an object-variant context property
343     {
344         QQmlComponent component(&engine);
345         component.setData("import QtQuick 2.0; QtObject { id: root; property int a: 10; property int test: ctxtProp.a; property variant obj: root; }", QUrl());
346
347         QQmlContext ctxt(engine.rootContext());
348         ctxt.setContextProperty("ctxtProp", QVariant());
349
350         QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: TypeError: Cannot read property 'a' of undefined");
351         QObject *obj = component.create(&ctxt);
352
353         QVariant v = obj->property("obj");
354
355         ctxt.setContextProperty("ctxtProp", v);
356
357         QCOMPARE(obj->property("test"), QVariant(10));
358
359         delete obj;
360     }
361 }
362
363 void tst_qqmlcontext::setContextObject()
364 {
365     QQmlContext ctxt(&engine);
366
367     TestObject to;
368
369     to.setA(2);
370     to.setB(192);
371     to.setC(18);
372
373     ctxt.setContextObject(&to);
374     ctxt.setContextProperty("c", QVariant(9));
375
376     // Static context properties
377     TEST_CONTEXT_PROPERTY(&ctxt, a, QVariant(2));
378     TEST_CONTEXT_PROPERTY(&ctxt, b, QVariant(192));
379     TEST_CONTEXT_PROPERTY(&ctxt, c, QVariant(9));
380
381     to.setA(12);
382     to.setB(100);
383     to.setC(7);
384     ctxt.setContextProperty("c", QVariant(3));
385
386     TEST_CONTEXT_PROPERTY(&ctxt, a, QVariant(12));
387     TEST_CONTEXT_PROPERTY(&ctxt, b, QVariant(100));
388     TEST_CONTEXT_PROPERTY(&ctxt, c, QVariant(3));
389
390     // Changes in context properties
391     {
392         QQmlComponent component(&engine);
393         component.setData("import QtQuick 2.0; QtObject { property variant test: a }", QUrl());
394
395         QObject *obj = component.create(&ctxt);
396
397         QCOMPARE(obj->property("test"), QVariant(12));
398         to.setA(14);
399         QCOMPARE(obj->property("test"), QVariant(14));
400
401         delete obj;
402     }
403 }
404
405 void tst_qqmlcontext::destruction()
406 {
407     QQmlContext *ctxt = new QQmlContext(&engine);
408
409     QObject obj;
410     QQmlEngine::setContextForObject(&obj, ctxt);
411     QQmlExpression expr(ctxt, 0, "a");
412
413     QCOMPARE(ctxt, QQmlEngine::contextForObject(&obj));
414     QCOMPARE(ctxt, expr.context());
415
416     delete ctxt; ctxt = 0;
417
418     QCOMPARE(ctxt, QQmlEngine::contextForObject(&obj));
419     QCOMPARE(ctxt, expr.context());
420 }
421
422 void tst_qqmlcontext::idAsContextProperty()
423 {
424     QQmlComponent component(&engine);
425     component.setData("import QtQuick 2.0; QtObject { property variant a; a: QtObject { id: myObject } }", QUrl());
426
427     QObject *obj = component.create();
428     QVERIFY(obj);
429
430     QVariant a = obj->property("a");
431     QVERIFY(a.userType() == QMetaType::QObjectStar);
432
433     QVariant ctxt = qmlContext(obj)->contextProperty("myObject");
434     QVERIFY(ctxt.userType() == QMetaType::QObjectStar);
435
436     QVERIFY(a == ctxt);
437
438     delete obj;
439 }
440
441 // Internal contexts should be read-only
442 void tst_qqmlcontext::readOnlyContexts()
443 {
444     QQmlComponent component(&engine);
445     component.setData("import QtQuick 2.0; QtObject { id: me }", QUrl());
446
447     QObject *obj = component.create();
448     QVERIFY(obj);
449
450     QQmlContext *context = qmlContext(obj);
451     QVERIFY(context);
452
453     QVERIFY(qvariant_cast<QObject*>(context->contextProperty("me")) == obj);
454     QVERIFY(context->contextObject() == obj);
455
456     QTest::ignoreMessage(QtWarningMsg, "QQmlContext: Cannot set property on internal context.");
457     context->setContextProperty("hello", 12);
458     QVERIFY(context->contextProperty("hello") == QVariant());
459
460     QTest::ignoreMessage(QtWarningMsg, "QQmlContext: Cannot set property on internal context.");
461     context->setContextProperty("hello", obj);
462     QVERIFY(context->contextProperty("hello") == QVariant());
463
464     QTest::ignoreMessage(QtWarningMsg, "QQmlContext: Cannot set context object for internal context.");
465     context->setContextObject(0);
466     QVERIFY(context->contextObject() == obj);
467
468     delete obj;
469 }
470
471 void tst_qqmlcontext::nameForObject()
472 {
473     QObject o1;
474     QObject o2;
475     QObject o3;
476
477     QQmlEngine engine;
478
479     // As a context property
480     engine.rootContext()->setContextProperty("o1", &o1);
481     engine.rootContext()->setContextProperty("o2", &o2);
482     engine.rootContext()->setContextProperty("o1_2", &o1);
483
484     QCOMPARE(engine.rootContext()->nameForObject(&o1), QString("o1"));
485     QCOMPARE(engine.rootContext()->nameForObject(&o2), QString("o2"));
486     QCOMPARE(engine.rootContext()->nameForObject(&o3), QString());
487
488     // As an id
489     QQmlComponent component(&engine);
490     component.setData("import QtQuick 2.0; QtObject { id: root; property QtObject o: QtObject { id: nested } }", QUrl());
491
492     QObject *o = component.create();
493     QVERIFY(o != 0);
494
495     QCOMPARE(qmlContext(o)->nameForObject(o), QString("root"));
496     QCOMPARE(qmlContext(o)->nameForObject(qvariant_cast<QObject*>(o->property("o"))), QString("nested"));
497     QCOMPARE(qmlContext(o)->nameForObject(&o1), QString());
498
499     delete o;
500 }
501
502 class DeleteCommand : public QObject
503 {
504 Q_OBJECT
505 public:
506     DeleteCommand() : object(0) {}
507
508     QObject *object;
509
510 public slots:
511     void doCommand() { if (object) delete object; object = 0; }
512 };
513
514 // Calling refresh expressions would crash if an expression or context was deleted during
515 // the refreshing
516 void tst_qqmlcontext::refreshExpressionsCrash()
517 {
518     {
519     QQmlEngine engine;
520
521     DeleteCommand command;
522     engine.rootContext()->setContextProperty("deleteCommand", &command);
523     // We use a fresh context here to bypass any root-context optimizations in
524     // the engine
525     QQmlContext ctxt(engine.rootContext());
526
527     QQmlComponent component(&engine);
528     component.setData("import QtQuick 2.0; QtObject { property var binding: deleteCommand.doCommand() }", QUrl());
529     QVERIFY(component.isReady());
530
531     QObject *o1 = component.create(&ctxt);
532     QObject *o2 = component.create(&ctxt);
533
534     command.object = o2;
535
536     QQmlContextData::get(&ctxt)->refreshExpressions();
537
538     delete o1;
539     }
540     {
541     QQmlEngine engine;
542
543     DeleteCommand command;
544     engine.rootContext()->setContextProperty("deleteCommand", &command);
545     // We use a fresh context here to bypass any root-context optimizations in
546     // the engine
547     QQmlContext ctxt(engine.rootContext());
548
549     QQmlComponent component(&engine);
550     component.setData("import QtQuick 2.0; QtObject { property var binding: deleteCommand.doCommand() }", QUrl());
551     QVERIFY(component.isReady());
552
553     QObject *o1 = component.create(&ctxt);
554     QObject *o2 = component.create(&ctxt);
555
556     command.object = o1;
557
558     QQmlContextData::get(&ctxt)->refreshExpressions();
559
560     delete o2;
561     }
562 }
563
564 class CountCommand : public QObject
565 {
566 Q_OBJECT
567 public:
568     CountCommand() : count(0) {}
569
570     int count;
571
572 public slots:
573     void doCommand() { ++count; }
574 };
575
576
577 // Test that calling refresh expressions causes all the expressions to refresh
578 void tst_qqmlcontext::refreshExpressions()
579 {
580     QQmlEngine engine;
581     QQmlComponent component(&engine, testFileUrl("refreshExpressions.qml"));
582     QQmlComponent component2(&engine, testFileUrl("RefreshExpressionsType.qml"));
583
584     CountCommand command;
585     engine.rootContext()->setContextProperty("countCommand", &command);
586
587     // We use a fresh context here to bypass any root-context optimizations in
588     // the engine
589     QQmlContext context(engine.rootContext());
590     QQmlContext context2(&context);
591
592     QObject *o1 = component.create(&context);
593     QObject *o2 = component.create(&context2);
594     QObject *o3 = component2.create(&context);
595
596     QCOMPARE(command.count, 5);
597
598     QQmlContextData::get(&context)->refreshExpressions();
599
600     QCOMPARE(command.count, 10);
601
602     delete o3;
603     delete o2;
604     delete o1;
605 }
606
607 // Test that updating the root context, only causes expressions in contexts with an
608 // unresolved name to reevaluate
609 void tst_qqmlcontext::refreshExpressionsRootContext()
610 {
611     QQmlEngine engine;
612
613     CountCommand command;
614     engine.rootContext()->setContextProperty("countCommand", &command);
615
616     QQmlComponent component(&engine, testFileUrl("refreshExpressions.qml"));
617     QQmlComponent component2(&engine, testFileUrl("refreshExpressionsRootContext.qml"));
618
619     QQmlContext context(engine.rootContext());
620     QQmlContext context2(engine.rootContext());
621
622     QString warning = component2.url().toString() + QLatin1String(":4: ReferenceError: unresolvedName is not defined");
623
624     QObject *o1 = component.create(&context);
625
626     QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));
627     QObject *o2 = component2.create(&context2);
628
629     QCOMPARE(command.count, 3);
630
631     QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));
632     QQmlContextData::get(engine.rootContext())->refreshExpressions();
633
634     QCOMPARE(command.count, 4);
635
636     delete o2;
637     delete o1;
638 }
639
640 void tst_qqmlcontext::qtbug_22535()
641 {
642     QQmlEngine engine;
643     QQmlComponent component(&engine, testFileUrl("qtbug_22535.qml"));
644     QQmlContext context(engine.rootContext());
645
646     QObject *o = component.create(&context);
647
648     // Don't crash!
649     delete o;
650 }
651
652 void tst_qqmlcontext::evalAfterInvalidate()
653 {
654     QQmlEngine engine;
655     QQmlComponent component(&engine, testFileUrl("evalAfterInvalidate.qml"));
656     QScopedPointer<QObject> o(component.create());
657
658     QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
659     QCoreApplication::processEvents();
660 }
661
662 QTEST_MAIN(tst_qqmlcontext)
663
664 #include "tst_qqmlcontext.moc"