1/****************************************************************************
2**
3** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>
4** Copyright (C) 2019 Menlo Systems GmbH, author Arno Rehn <a.rehn@menlosystems.com>
5** Contact: https://www.qt.io/licensing/
6**
7** This file is part of the QtWebChannel module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:GPL-EXCEPT$
10** Commercial License Usage
11** Licensees holding valid commercial Qt licenses may use this file in
12** accordance with the commercial license agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and The Qt Company. For licensing terms
15** and conditions see https://www.qt.io/terms-conditions. For further
16** information use the contact form at https://www.qt.io/contact-us.
17**
18** GNU General Public License Usage
19** Alternatively, this file may be used under the terms of the GNU
20** General Public License version 3 as published by the Free Software
21** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
22** included in the packaging of this file. Please review the following
23** information to ensure the GNU General Public License requirements will
24** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25**
26** $QT_END_LICENSE$
27**
28****************************************************************************/
29
30#include "tst_webchannel.h"
31
32#include <qwebchannel.h>
33#include <qwebchannel_p.h>
34#include <qmetaobjectpublisher_p.h>
35
36#include <QtTest>
37#ifdef WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE
38#include <QJSEngine>
39#endif
40
41QT_USE_NAMESPACE
42
43#ifdef WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE
44class TestJSEngine;
45
46class TestEngineTransport : public QWebChannelAbstractTransport
47{
48 Q_OBJECT
49public:
50 TestEngineTransport(TestJSEngine *);
51 void sendMessage(const QJsonObject &message) override;
52
53 Q_INVOKABLE void channelSetupReady();
54 Q_INVOKABLE void send(const QByteArray &message);
55private:
56 TestJSEngine *m_testEngine;
57};
58
59class ConsoleLogger : public QObject
60{
61 Q_OBJECT
62public:
63 ConsoleLogger(QObject *parent = 0);
64
65 Q_INVOKABLE void log(const QString &text);
66 Q_INVOKABLE void error(const QString &text);
67
68 int errorCount() const { return m_errCount; }
69 int logCount() const { return m_logCount; }
70 QString lastError() const { return m_lastError; }
71
72private:
73 int m_errCount;
74 int m_logCount;
75 QString m_lastError;
76
77};
78
79
80
81ConsoleLogger::ConsoleLogger(QObject *parent)
82 : QObject(parent)
83 , m_errCount(0)
84 , m_logCount(0)
85{
86}
87
88void ConsoleLogger::log(const QString &text)
89{
90 m_logCount++;
91 qDebug(msg: "LOG: %s", qPrintable(text));
92}
93
94void ConsoleLogger::error(const QString &text)
95{
96 m_errCount++;
97 m_lastError = text;
98 qWarning(msg: "ERROR: %s", qPrintable(text));
99}
100
101
102// A test JS engine with convenience integration with WebChannel.
103class TestJSEngine : public QJSEngine
104{
105 Q_OBJECT
106public:
107 TestJSEngine();
108
109 TestEngineTransport *transport() const;
110 ConsoleLogger *logger() const;
111 void initWebChannelJS();
112
113signals:
114 void channelSetupReady(TestEngineTransport *transport);
115
116private:
117 TestEngineTransport *m_transport;
118 ConsoleLogger *m_logger;
119};
120
121TestEngineTransport::TestEngineTransport(TestJSEngine *engine)
122 : QWebChannelAbstractTransport(engine)
123 , m_testEngine(engine)
124{
125}
126
127void TestEngineTransport::sendMessage(const QJsonObject &message)
128{
129 QByteArray json = QJsonDocument(message).toJson(format: QJsonDocument::Compact);
130 QJSValue callback = m_testEngine->evaluate(QStringLiteral("transport.onmessage"));
131 Q_ASSERT(callback.isCallable());
132 QJSValue arg = m_testEngine->newObject();
133 QJSValue data = m_testEngine->evaluate(program: QString::fromLatin1(str: "JSON.parse('%1');").arg(a: QString::fromUtf8(str: json)));
134 Q_ASSERT(!data.isError());
135 arg.setProperty(QStringLiteral("data"), value: data);
136 QJSValue val = callback.call(args: (QJSValueList() << arg));
137 Q_ASSERT(!val.isError());
138}
139
140void TestEngineTransport::channelSetupReady()
141{
142 emit m_testEngine->channelSetupReady(transport: m_testEngine->transport());
143}
144
145void TestEngineTransport::send(const QByteArray &message)
146{
147 QJsonDocument doc(QJsonDocument::fromJson(json: message));
148 emit messageReceived(message: doc.object(), transport: this);
149}
150
151
152TestJSEngine::TestJSEngine()
153 : m_transport(new TestEngineTransport(this))
154 , m_logger(new ConsoleLogger(this))
155{
156 globalObject().setProperty(name: "transport", value: newQObject(object: m_transport));
157 globalObject().setProperty(name: "console", value: newQObject(object: m_logger));
158
159 QString webChannelJSPath(QStringLiteral(":/qtwebchannel/qwebchannel.js"));
160 QFile webChannelJS(webChannelJSPath);
161 if (!webChannelJS.open(flags: QFile::ReadOnly))
162 qFatal(msg: "Error opening qwebchannel.js");
163 QString source(QString::fromUtf8(str: webChannelJS.readAll()));
164 evaluate(program: source, fileName: webChannelJSPath);
165}
166
167TestEngineTransport *TestJSEngine::transport() const
168{
169 return m_transport;
170}
171
172ConsoleLogger *TestJSEngine::logger() const
173{
174 return m_logger;
175}
176
177void TestJSEngine::initWebChannelJS()
178{
179 globalObject().setProperty(QStringLiteral("channel"), value: newObject());
180 QJSValue channel = evaluate(QStringLiteral("channel = new QWebChannel(transport, function(channel) { transport.channelSetupReady();});"));
181 Q_ASSERT(!channel.isError());
182}
183
184#endif // WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE
185
186namespace {
187QVariantList convert_to_js(const TestStructVector &list)
188{
189 QVariantList ret;
190 ret.reserve(alloc: list.size());
191 std::transform(first: list.begin(), last: list.end(), result: std::back_inserter(x&: ret), unary_op: [](const TestStruct &value) -> QVariant {
192 QVariantMap map;
193 map["foo"] = value.foo;
194 map["bar"] = value.bar;
195 return map;
196 });
197 return ret;
198}
199}
200
201TestWebChannel::TestWebChannel(QObject *parent)
202 : QObject(parent)
203 , m_dummyTransport(new DummyTransport(this))
204 , m_lastInt(0)
205 , m_lastBool(false)
206 , m_lastDouble(0)
207{
208 qRegisterMetaType<TestStruct>();
209 qRegisterMetaType<TestStructVector>();
210 QMetaType::registerConverter<TestStructVector, QVariantList>(function: convert_to_js);
211}
212
213TestWebChannel::~TestWebChannel()
214{
215
216}
217
218int TestWebChannel::readInt() const
219{
220 return m_lastInt;
221}
222
223void TestWebChannel::setInt(int i)
224{
225 m_lastInt = i;
226 emit lastIntChanged();
227}
228
229bool TestWebChannel::readBool() const
230{
231 return m_lastBool;
232}
233
234void TestWebChannel::setBool(bool b)
235{
236 m_lastBool = b;
237 emit lastBoolChanged();
238}
239
240double TestWebChannel::readDouble() const
241{
242 return m_lastDouble;
243}
244
245void TestWebChannel::setDouble(double d)
246{
247 m_lastDouble = d;
248 emit lastDoubleChanged();
249}
250
251QVariant TestWebChannel::readVariant() const
252{
253 return m_lastVariant;
254}
255
256void TestWebChannel::setVariant(const QVariant &v)
257{
258 m_lastVariant = v;
259 emit lastVariantChanged();
260}
261
262QJsonValue TestWebChannel::readJsonValue() const
263{
264 return m_lastJsonValue;
265}
266
267void TestWebChannel::setJsonValue(const QJsonValue& v)
268{
269 m_lastJsonValue = v;
270 emit lastJsonValueChanged();
271}
272
273QJsonObject TestWebChannel::readJsonObject() const
274{
275 return m_lastJsonObject;
276}
277
278void TestWebChannel::setJsonObject(const QJsonObject& v)
279{
280 m_lastJsonObject = v;
281 emit lastJsonObjectChanged();
282}
283
284QJsonArray TestWebChannel::readJsonArray() const
285{
286 return m_lastJsonArray;
287}
288
289void TestWebChannel::setJsonArray(const QJsonArray& v)
290{
291 m_lastJsonArray = v;
292 emit lastJsonArrayChanged();
293}
294
295int TestWebChannel::readOverload(int i)
296{
297 return i + 1;
298}
299
300QString TestWebChannel::readOverload(const QString &arg)
301{
302 return arg.toUpper();
303}
304
305QString TestWebChannel::readOverload(const QString &arg, int i)
306{
307 return arg.toUpper() + QString::number(i + 1);
308}
309
310void TestWebChannel::testRegisterObjects()
311{
312 QWebChannel channel;
313 QObject plain;
314
315 QHash<QString, QObject*> objects;
316 objects[QStringLiteral("plain")] = &plain;
317 objects[QStringLiteral("channel")] = &channel;
318 objects[QStringLiteral("publisher")] = channel.d_func()->publisher;
319 objects[QStringLiteral("test")] = this;
320
321 channel.registerObjects(objects);
322}
323
324void TestWebChannel::testDeregisterObjects()
325{
326 QWebChannel channel;
327 TestObject testObject;
328 testObject.setObjectName("myTestObject");
329
330
331 channel.registerObject(id: testObject.objectName(), object: &testObject);
332
333 channel.connectTo(transport: m_dummyTransport);
334 channel.d_func()->publisher->initializeClient(transport: m_dummyTransport);
335
336 QJsonObject connectMessage =
337 QJsonDocument::fromJson(json: ("{\"type\": 7,"
338 "\"object\": \"myTestObject\","
339 "\"signal\": " + QString::number(testObject.metaObject()->indexOfSignal(signal: "sig1()"))
340 + "}").toLatin1()).object();
341 channel.d_func()->publisher->handleMessage(message: connectMessage, transport: m_dummyTransport);
342
343 emit testObject.sig1();
344 channel.deregisterObject(object: &testObject);
345 emit testObject.sig1();
346}
347
348void TestWebChannel::testDeregisterObjectAtStart()
349{
350 QWebChannel channel;
351 QVERIFY(channel.registeredObjects().isEmpty());
352
353 TestObject testObject;
354 testObject.setObjectName("myTestObject");
355
356 channel.registerObject(id: testObject.objectName(), object: &testObject);
357 QCOMPARE(channel.registeredObjects().size(), 1);
358
359 channel.deregisterObject(object: &testObject);
360 QVERIFY(channel.registeredObjects().isEmpty());
361}
362
363void TestWebChannel::testInfoForObject()
364{
365 TestObject obj;
366 obj.setObjectName("myTestObject");
367
368 QWebChannel channel;
369 const QJsonObject info = channel.d_func()->publisher->classInfoForObject(object: &obj, transport: m_dummyTransport);
370
371 QCOMPARE(info.keys(), QStringList() << "enums" << "methods" << "properties" << "signals");
372
373 { // enums
374 QJsonObject fooEnum;
375 fooEnum["Asdf"] = TestObject::Asdf;
376 fooEnum["Bar"] = TestObject::Bar;
377 QJsonObject testFlags;
378 testFlags["FirstFlag"] = static_cast<int>(TestObject::FirstFlag);
379 testFlags["SecondFlag"] = static_cast<int>(TestObject::SecondFlag);
380 QJsonObject expected;
381 expected["Foo"] = fooEnum;
382 expected["TestFlags"] = testFlags;
383 QCOMPARE(info["enums"].toObject(), expected);
384 }
385
386 QJsonArray expected;
387 auto addMethod = [&expected, &obj](const QString &name, const char *signature, bool addName = true) {
388 const auto index = obj.metaObject()->indexOfMethod(method: signature);
389 QVERIFY2(index != -1, signature);
390 if (addName)
391 expected.append(value: QJsonArray{name, index});
392 expected.append(value: QJsonArray{QString::fromUtf8(str: signature), index});
393 };
394 { // methods & slots
395 expected = {};
396 addMethod(QStringLiteral("deleteLater"), "deleteLater()");
397 addMethod(QStringLiteral("slot1"), "slot1()");
398 addMethod(QStringLiteral("slot2"), "slot2(QString)");
399 addMethod(QStringLiteral("setReturnedObject"), "setReturnedObject(TestObject*)");
400 addMethod(QStringLiteral("setObjectProperty"), "setObjectProperty(QObject*)");
401 addMethod(QStringLiteral("setProp"), "setProp(QString)");
402 addMethod(QStringLiteral("fire"), "fire()");
403 addMethod(QStringLiteral("overload"), "overload(double)");
404 addMethod(QStringLiteral("overload"), "overload(int)", false);
405 addMethod(QStringLiteral("overload"), "overload(QObject*)", false);
406 addMethod(QStringLiteral("overload"), "overload(QString)", false);
407 addMethod(QStringLiteral("overload"), "overload(QString,int)", false);
408 addMethod(QStringLiteral("overload"), "overload(QJsonArray)", false);
409 addMethod(QStringLiteral("method1"), "method1()");
410 QCOMPARE(info["methods"].toArray(), expected);
411 }
412
413 { // signals
414 expected = {};
415 addMethod(QStringLiteral("destroyed"), "destroyed(QObject*)");
416 addMethod(QStringLiteral("destroyed"), "destroyed()", false);
417 addMethod(QStringLiteral("sig1"), "sig1()");
418 addMethod(QStringLiteral("sig2"), "sig2(QString)");
419 addMethod(QStringLiteral("replay"), "replay()");
420 addMethod(QStringLiteral("overloadSignal"), "overloadSignal(int)");
421 addMethod(QStringLiteral("overloadSignal"), "overloadSignal(float)", false);
422 QCOMPARE(info["signals"].toArray(), expected);
423 }
424
425 { // properties
426 QJsonArray expected;
427 {
428 QJsonArray property;
429 property.append(value: obj.metaObject()->indexOfProperty(name: "objectName"));
430 property.append(QStringLiteral("objectName"));
431 {
432 QJsonArray signal;
433 signal.append(value: 1);
434 signal.append(value: obj.metaObject()->indexOfMethod(method: "objectNameChanged(QString)"));
435 property.append(value: signal);
436 }
437 property.append(value: obj.objectName());
438 expected.append(value: property);
439 }
440 {
441 QJsonArray property;
442 property.append(value: obj.metaObject()->indexOfProperty(name: "foo"));
443 property.append(QStringLiteral("foo"));
444 {
445 QJsonArray signal;
446 property.append(value: signal);
447 }
448 property.append(value: obj.foo());
449 expected.append(value: property);
450 }
451 {
452 QJsonArray property;
453 property.append(value: obj.metaObject()->indexOfProperty(name: "asdf"));
454 property.append(QStringLiteral("asdf"));
455 {
456 QJsonArray signal;
457 signal.append(value: 1);
458 signal.append(value: obj.metaObject()->indexOfMethod(method: "asdfChanged()"));
459 property.append(value: signal);
460 }
461 property.append(value: obj.asdf());
462 expected.append(value: property);
463 }
464 {
465 QJsonArray property;
466 property.append(value: obj.metaObject()->indexOfProperty(name: "bar"));
467 property.append(QStringLiteral("bar"));
468 {
469 QJsonArray signal;
470 signal.append(QStringLiteral("theBarHasChanged"));
471 signal.append(value: obj.metaObject()->indexOfMethod(method: "theBarHasChanged()"));
472 property.append(value: signal);
473 }
474 property.append(value: obj.bar());
475 expected.append(value: property);
476 }
477 {
478 QJsonArray property;
479 property.append(value: obj.metaObject()->indexOfProperty(name: "objectProperty"));
480 property.append(QStringLiteral("objectProperty"));
481 {
482 QJsonArray signal;
483 signal.append(value: 1);
484 signal.append(value: obj.metaObject()->indexOfMethod(method: "objectPropertyChanged()"));
485 property.append(value: signal);
486 }
487 property.append(value: QJsonValue::fromVariant(variant: QVariant::fromValue(value: obj.objectProperty())));
488 expected.append(value: property);
489 }
490 {
491 QJsonArray property;
492 property.append(value: obj.metaObject()->indexOfProperty(name: "returnedObject"));
493 property.append(QStringLiteral("returnedObject"));
494 {
495 QJsonArray signal;
496 signal.append(value: 1);
497 signal.append(value: obj.metaObject()->indexOfMethod(method: "returnedObjectChanged()"));
498 property.append(value: signal);
499 }
500 property.append(value: QJsonValue::fromVariant(variant: QVariant::fromValue(value: obj.returnedObject())));
501 expected.append(value: property);
502 }
503 {
504 QJsonArray property;
505 property.append(value: obj.metaObject()->indexOfProperty(name: "prop"));
506 property.append(QStringLiteral("prop"));
507 {
508 QJsonArray signal;
509 signal.append(value: 1);
510 signal.append(value: obj.metaObject()->indexOfMethod(method: "propChanged(QString)"));
511 property.append(value: signal);
512 }
513 property.append(value: QJsonValue::fromVariant(variant: QVariant::fromValue(value: obj.prop())));
514 expected.append(value: property);
515 }
516 QCOMPARE(info["properties"].toArray(), expected);
517 }
518}
519
520void TestWebChannel::testInvokeMethodConversion()
521{
522 QWebChannel channel;
523 channel.connectTo(transport: m_dummyTransport);
524
525 QJsonArray args;
526 args.append(value: QJsonValue(1000));
527
528 {
529 channel.d_func()->publisher->invokeMethod(object: this, methodName: "setInt", args);
530 QCOMPARE(m_lastInt, args.at(0).toInt());
531 int getterMethod = metaObject()->indexOfMethod(method: "readInt()");
532 QVERIFY(getterMethod != -1);
533 auto retVal = channel.d_func()->publisher->invokeMethod(object: this, methodIndex: getterMethod, args: {});
534 QCOMPARE(retVal, args.at(0).toVariant());
535 }
536 {
537 QJsonArray args;
538 args.append(value: QJsonValue(!m_lastBool));
539 channel.d_func()->publisher->invokeMethod(object: this, methodName: "setBool", args);
540 QCOMPARE(m_lastBool, args.at(0).toBool());
541 int getterMethod = metaObject()->indexOfMethod(method: "readBool()");
542 QVERIFY(getterMethod != -1);
543 auto retVal = channel.d_func()->publisher->invokeMethod(object: this, methodIndex: getterMethod, args: {});
544 QCOMPARE(retVal, args.at(0).toVariant());
545 }
546 {
547 channel.d_func()->publisher->invokeMethod(object: this, methodName: "setDouble", args);
548 QCOMPARE(m_lastDouble, args.at(0).toDouble());
549 int getterMethod = metaObject()->indexOfMethod(method: "readDouble()");
550 QVERIFY(getterMethod != -1);
551 auto retVal = channel.d_func()->publisher->invokeMethod(object: this, methodIndex: getterMethod, args: {});
552 QCOMPARE(retVal, args.at(0).toVariant());
553 }
554 {
555 channel.d_func()->publisher->invokeMethod(object: this, methodName: "setVariant", args);
556 QCOMPARE(m_lastVariant, args.at(0).toVariant());
557 int getterMethod = metaObject()->indexOfMethod(method: "readVariant()");
558 QVERIFY(getterMethod != -1);
559 auto retVal = channel.d_func()->publisher->invokeMethod(object: this, methodIndex: getterMethod, args: {});
560 QCOMPARE(retVal, args.at(0).toVariant());
561 }
562 {
563 channel.d_func()->publisher->invokeMethod(object: this, methodName: "setJsonValue", args);
564 QCOMPARE(m_lastJsonValue, args.at(0));
565 int getterMethod = metaObject()->indexOfMethod(method: "readJsonValue()");
566 QVERIFY(getterMethod != -1);
567 auto retVal = channel.d_func()->publisher->invokeMethod(object: this, methodIndex: getterMethod, args: {});
568 QCOMPARE(retVal, args.at(0).toVariant());
569 }
570 {
571 QJsonObject object;
572 object["foo"] = QJsonValue(123);
573 object["bar"] = QJsonValue(4.2);
574 args[0] = object;
575 channel.d_func()->publisher->invokeMethod(object: this, methodName: "setJsonObject", args);
576 QCOMPARE(m_lastJsonObject, object);
577 int getterMethod = metaObject()->indexOfMethod(method: "readJsonObject()");
578 QVERIFY(getterMethod != -1);
579 auto retVal = channel.d_func()->publisher->invokeMethod(object: this, methodIndex: getterMethod, args: {});
580 QCOMPARE(retVal, QVariant::fromValue(object));
581 }
582 {
583 QJsonArray array;
584 array << QJsonValue(123);
585 array << QJsonValue(4.2);
586 args[0] = array;
587 channel.d_func()->publisher->invokeMethod(object: this, methodName: "setJsonArray", args);
588 QCOMPARE(m_lastJsonArray, array);
589 int getterMethod = metaObject()->indexOfMethod(method: "readJsonArray()");
590 QVERIFY(getterMethod != -1);
591 auto retVal = channel.d_func()->publisher->invokeMethod(object: this, methodIndex: getterMethod, args: {});
592 QCOMPARE(retVal, QVariant::fromValue(array));
593 }
594}
595
596void TestWebChannel::testFunctionOverloading()
597{
598 QWebChannel channel;
599 channel.connectTo(transport: m_dummyTransport);
600
601 // all method calls will use the first method's index
602 const auto method1 = metaObject()->indexOfMethod(method: "readOverload(int)");
603 QVERIFY(method1 != -1);
604 const auto method2 = metaObject()->indexOfMethod(method: "readOverload(QString)");
605 QVERIFY(method2 != -1);
606 QVERIFY(method1 < method2);
607 const auto method3 = metaObject()->indexOfMethod(method: "readOverload(QString,int)");
608 QVERIFY(method3 != -1);
609 QVERIFY(method2 < method3);
610
611 { // int
612 const auto retVal = channel.d_func()->publisher->invokeMethod(object: this, methodIndex: method1, args: QJsonArray{1000});
613 QCOMPARE(retVal.toInt(), 1001);
614 }
615 { // QString
616 const auto retVal = channel.d_func()->publisher->invokeMethod(object: this, methodIndex: method2, args: QJsonArray{QStringLiteral("hello world")});
617 QCOMPARE(retVal.toString(), QStringLiteral("HELLO WORLD"));
618 }
619 { // QString, int
620 const auto retVal = channel.d_func()->publisher->invokeMethod(object: this, methodIndex: method3, args: QJsonArray{QStringLiteral("the answer is "), 41});
621 QCOMPARE(retVal.toString(), QStringLiteral("THE ANSWER IS 42"));
622 }
623}
624
625void TestWebChannel::testSetPropertyConversion()
626{
627 QWebChannel channel;
628 channel.connectTo(transport: m_dummyTransport);
629
630 {
631 int property = metaObject()->indexOfProperty(name: "lastInt");
632 QVERIFY(property != -1);
633 channel.d_func()->publisher->setProperty(object: this, propertyIndex: property, value: QJsonValue(42));
634 QCOMPARE(m_lastInt, 42);
635 }
636 {
637 int property = metaObject()->indexOfProperty(name: "lastBool");
638 QVERIFY(property != -1);
639 bool newValue = !m_lastBool;
640 channel.d_func()->publisher->setProperty(object: this, propertyIndex: property, value: QJsonValue(newValue));
641 QCOMPARE(m_lastBool, newValue);
642 }
643 {
644 int property = metaObject()->indexOfProperty(name: "lastDouble");
645 QVERIFY(property != -1);
646 channel.d_func()->publisher->setProperty(object: this, propertyIndex: property, value: QJsonValue(-4.2));
647 QCOMPARE(m_lastDouble, -4.2);
648 }
649 {
650 int property = metaObject()->indexOfProperty(name: "lastVariant");
651 QVERIFY(property != -1);
652 QVariant variant("foo bar asdf");
653 channel.d_func()->publisher->setProperty(object: this, propertyIndex: property, value: QJsonValue::fromVariant(variant));
654 QCOMPARE(m_lastVariant, variant);
655 }
656 {
657 int property = metaObject()->indexOfProperty(name: "lastJsonValue");
658 QVERIFY(property != -1);
659 QJsonValue value("asdf asdf");
660 channel.d_func()->publisher->setProperty(object: this, propertyIndex: property, value);
661 QCOMPARE(m_lastJsonValue, value);
662 }
663 {
664 int property = metaObject()->indexOfProperty(name: "lastJsonArray");
665 QVERIFY(property != -1);
666 QJsonArray array;
667 array << QJsonValue(-123);
668 array << QJsonValue(-42);
669 channel.d_func()->publisher->setProperty(object: this, propertyIndex: property, value: array);
670 QCOMPARE(m_lastJsonArray, array);
671 }
672 {
673 int property = metaObject()->indexOfProperty(name: "lastJsonObject");
674 QVERIFY(property != -1);
675 QJsonObject object;
676 object["foo"] = QJsonValue(-123);
677 object["bar"] = QJsonValue(-4.2);
678 channel.d_func()->publisher->setProperty(object: this, propertyIndex: property, value: object);
679 QCOMPARE(m_lastJsonObject, object);
680 }
681}
682
683void TestWebChannel::testInvokeMethodOverloadResolution()
684{
685 QWebChannel channel;
686 TestObject testObject;
687 TestObject exportedObject;
688 channel.registerObject(id: "test", object: &exportedObject);
689 channel.connectTo(transport: m_dummyTransport);
690
691 QVariant result;
692 QMetaObjectPublisher *publisher = channel.d_func()->publisher;
693
694 {
695 result = publisher->invokeMethod(object: &testObject, methodName: "overload", args: { 41.0 });
696 QVERIFY(result.userType() == QMetaType::Double);
697 QCOMPARE(result.toDouble(), 42.0);
698 }
699 {
700 // In JavaScript, there's only 'double', so this should always invoke the 'double' overload
701 result = publisher->invokeMethod(object: &testObject, methodName: "overload", args: { 41 });
702 QVERIFY(result.userType() == QMetaType::Double);
703 QCOMPARE(result.toDouble(), 42);
704 }
705 {
706 QJsonObject wrappedObject { {"id", "test"} };
707 result = publisher->invokeMethod(object: &testObject, methodName: "overload", args: { wrappedObject });
708 QCOMPARE(result.value<TestObject*>(), &exportedObject);
709 }
710 {
711 result = publisher->invokeMethod(object: &testObject, methodName: "overload", args: { "hello world" });
712 QCOMPARE(result.toString(), QStringLiteral("HELLO WORLD"));
713 }
714 {
715 result = publisher->invokeMethod(object: &testObject, methodName: "overload", args: { "the answer is ", 41 });
716 QCOMPARE(result.toString(), QStringLiteral("THE ANSWER IS 42"));
717 }
718 {
719 QJsonArray args;
720 args.append(value: QJsonArray { "foobar", 42 });
721 result = publisher->invokeMethod(object: &testObject, methodName: "overload", args);
722 QCOMPARE(result.toString(), QStringLiteral("42foobar"));
723 }
724}
725
726void TestWebChannel::testDisconnect()
727{
728 QWebChannel channel;
729 channel.connectTo(transport: m_dummyTransport);
730 channel.disconnectFrom(transport: m_dummyTransport);
731 m_dummyTransport->emitMessageReceived(message: QJsonObject());
732}
733
734void TestWebChannel::testWrapRegisteredObject()
735{
736 QWebChannel channel;
737 TestObject obj;
738 obj.setObjectName("myTestObject");
739
740 channel.registerObject(id: obj.objectName(), object: &obj);
741 channel.connectTo(transport: m_dummyTransport);
742 channel.d_func()->publisher->initializeClient(transport: m_dummyTransport);
743
744 QJsonObject objectInfo = channel.d_func()->publisher->wrapResult(result: QVariant::fromValue(value: &obj), transport: m_dummyTransport).toObject();
745
746 QCOMPARE(2, objectInfo.length());
747 QVERIFY(objectInfo.contains("id"));
748 QVERIFY(objectInfo.contains("__QObject*__"));
749 QVERIFY(objectInfo.value("__QObject*__").isBool() && objectInfo.value("__QObject*__").toBool());
750
751 QString returnedId = objectInfo.value(key: "id").toString();
752
753 QCOMPARE(&obj, channel.d_func()->publisher->registeredObjects.value(obj.objectName()));
754 QCOMPARE(obj.objectName(), channel.d_func()->publisher->registeredObjectIds.value(&obj));
755 QCOMPARE(obj.objectName(), returnedId);
756}
757
758void TestWebChannel::testUnwrapObject()
759{
760 QWebChannel channel;
761
762 {
763 TestObject obj;
764 obj.setObjectName("testObject");
765 channel.registerObject(id: obj.objectName(), object: &obj);
766 QObject *unwrapped = channel.d_func()->publisher->unwrapObject(objectId: obj.objectName());
767 QCOMPARE(unwrapped, &obj);
768 }
769 {
770 TestObject obj;
771 QJsonObject objectInfo = channel.d_func()->publisher->wrapResult(result: QVariant::fromValue(value: &obj), transport: m_dummyTransport).toObject();
772 QObject *unwrapped = channel.d_func()->publisher->unwrapObject(objectId: objectInfo["id"].toString());
773 QCOMPARE(unwrapped, &obj);
774 }
775}
776
777void TestWebChannel::testTransportWrapObjectProperties()
778{
779 QWebChannel channel;
780
781 TestObject obj;
782 obj.setObjectName("testObject");
783 channel.registerObject(id: obj.objectName(), object: &obj);
784
785 DummyTransport *dummyTransport = new DummyTransport(this);
786 channel.connectTo(transport: dummyTransport);
787 channel.d_func()->publisher->initializeClient(transport: dummyTransport);
788 channel.d_func()->publisher->setClientIsIdle(true);
789
790 QCOMPARE(channel.d_func()->publisher->transportedWrappedObjects.size(), 0);
791
792 QObject objPropObject;
793 objPropObject.setObjectName("foobar");
794
795 obj.setObjectProperty(&objPropObject);
796
797 channel.d_func()->publisher->sendPendingPropertyUpdates();
798
799 QCOMPARE(channel.d_func()->publisher->wrappedObjects.size(), 1);
800 const QString wrappedObjId = channel.d_func()->publisher->wrappedObjects.keys()[0];
801
802 QCOMPARE(channel.d_func()->publisher->transportedWrappedObjects.size(), 1);
803 QCOMPARE(channel.d_func()->publisher->transportedWrappedObjects.keys()[0], dummyTransport);
804 QCOMPARE(channel.d_func()->publisher->transportedWrappedObjects.values()[0], wrappedObjId);
805}
806
807void TestWebChannel::testRemoveUnusedTransports()
808{
809 QWebChannel channel;
810 DummyTransport *dummyTransport = new DummyTransport(this);
811 TestObject obj;
812
813 channel.connectTo(transport: dummyTransport);
814 channel.d_func()->publisher->initializeClient(transport: dummyTransport);
815
816 QMetaObjectPublisher *pub = channel.d_func()->publisher;
817 pub->wrapResult(result: QVariant::fromValue(value: &obj), transport: dummyTransport);
818
819 QCOMPARE(pub->wrappedObjects.size(), 1);
820 QCOMPARE(pub->registeredObjectIds.size(), 1);
821
822 channel.disconnectFrom(transport: dummyTransport);
823 delete dummyTransport;
824
825 QCOMPARE(pub->wrappedObjects.size(), 0);
826 QCOMPARE(pub->registeredObjectIds.size(), 0);
827}
828
829void TestWebChannel::testPassWrappedObjectBack()
830{
831 QWebChannel channel;
832 TestObject registeredObj;
833 TestObject returnedObjMethod;
834 TestObject returnedObjProperty;
835
836 registeredObj.setObjectName("registeredObject");
837
838 channel.registerObject(id: registeredObj.objectName(), object: &registeredObj);
839 channel.connectTo(transport: m_dummyTransport);
840 channel.d_func()->publisher->initializeClient(transport: m_dummyTransport);
841
842 QMetaObjectPublisher *pub = channel.d_func()->publisher;
843 QJsonObject returnedObjMethodInfo = pub->wrapResult(result: QVariant::fromValue(value: &returnedObjMethod), transport: m_dummyTransport).toObject();
844 QJsonObject returnedObjPropertyInfo = pub->wrapResult(result: QVariant::fromValue(value: &returnedObjProperty), transport: m_dummyTransport).toObject();
845
846 QJsonArray argsMethod;
847 QJsonObject argMethod0;
848 argMethod0["id"] = returnedObjMethodInfo["id"];
849 argsMethod << argMethod0;
850 QJsonObject argProperty;
851 argProperty["id"] = returnedObjPropertyInfo["id"];
852
853 pub->invokeMethod(object: &registeredObj, methodName: "setReturnedObject", args: argsMethod);
854 QCOMPARE(registeredObj.mReturnedObject, &returnedObjMethod);
855 pub->setProperty(object: &registeredObj, propertyIndex: registeredObj.metaObject()->indexOfProperty(name: "returnedObject"), value: argProperty);
856 QCOMPARE(registeredObj.mReturnedObject, &returnedObjProperty);
857}
858
859void TestWebChannel::testWrapValues()
860{
861 QWebChannel channel;
862 channel.connectTo(transport: m_dummyTransport);
863
864 {
865 QVariant variant = QVariant::fromValue(value: TestObject::Asdf);
866 QJsonValue value = channel.d_func()->publisher->wrapResult(result: variant, transport: m_dummyTransport);
867 QVERIFY(value.isDouble());
868 QCOMPARE(value.toInt(), (int) TestObject::Asdf);
869 }
870 {
871 TestObject::TestFlags flags = TestObject::FirstFlag | TestObject::SecondFlag;
872 QVariant variant = QVariant::fromValue(value: flags);
873 QJsonValue value = channel.d_func()->publisher->wrapResult(result: variant, transport: m_dummyTransport);
874 QVERIFY(value.isDouble());
875 QCOMPARE(value.toInt(), (int) flags);
876 }
877 {
878 QVector<int> vec{1, 2, 3};
879 QVariant variant = QVariant::fromValue(value: vec);
880 QJsonValue value = channel.d_func()->publisher->wrapResult(result: variant, transport: m_dummyTransport);
881 QVERIFY(value.isArray());
882 QCOMPARE(value.toArray(), QJsonArray({1, 2, 3}));
883 }
884 {
885 TestStructVector vec{{1, 2}, {3, 4}};
886 QVariant variant = QVariant::fromValue(value: vec);
887 QJsonValue value = channel.d_func()->publisher->wrapResult(result: variant, transport: m_dummyTransport);
888 QVERIFY(value.isArray());
889 QCOMPARE(value.toArray(), QJsonArray({QJsonObject{{"foo", 1}, {"bar", 2}},
890 QJsonObject{{"foo", 3}, {"bar", 4}}}));
891 }
892}
893
894void TestWebChannel::testWrapObjectWithMultipleTransports()
895{
896 QWebChannel channel;
897 QMetaObjectPublisher *pub = channel.d_func()->publisher;
898
899 DummyTransport *dummyTransport = new DummyTransport(this);
900 DummyTransport *dummyTransport2 = new DummyTransport(this);
901
902 TestObject obj;
903
904 pub->wrapResult(result: QVariant::fromValue(value: &obj), transport: dummyTransport);
905 pub->wrapResult(result: QVariant::fromValue(value: &obj), transport: dummyTransport2);
906
907 QCOMPARE(pub->transportedWrappedObjects.count(), 2);
908}
909
910void TestWebChannel::testJsonToVariant()
911{
912 QWebChannel channel;
913 channel.connectTo(transport: m_dummyTransport);
914
915 {
916 QVariant variant = QVariant::fromValue(value: TestObject::Asdf);
917 QVariant convertedValue = channel.d_func()->publisher->toVariant(value: static_cast<int>(TestObject::Asdf), targetType: variant.userType());
918 QCOMPARE(convertedValue, variant);
919 }
920 {
921 TestObject::TestFlags flags = TestObject::FirstFlag | TestObject::SecondFlag;
922 QVariant variant = QVariant::fromValue(value: flags);
923 QVariant convertedValue = channel.d_func()->publisher->toVariant(value: static_cast<int>(flags), targetType: variant.userType());
924 QCOMPARE(convertedValue, variant);
925 }
926}
927
928void TestWebChannel::testInfiniteRecursion()
929{
930 QWebChannel channel;
931 TestObject obj;
932 obj.setObjectProperty(&obj);
933 obj.setObjectName("myTestObject");
934
935 channel.connectTo(transport: m_dummyTransport);
936 channel.d_func()->publisher->initializeClient(transport: m_dummyTransport);
937
938 QJsonObject objectInfo = channel.d_func()->publisher->wrapResult(result: QVariant::fromValue(value: &obj), transport: m_dummyTransport).toObject();
939
940 // Wrap the result twice to test for QTBUG-84007. A single result wrap will not trigger all recursion paths.
941 objectInfo = channel.d_func()->publisher->wrapResult(result: QVariant::fromValue(value: &obj), transport: m_dummyTransport).toObject();
942}
943
944void TestWebChannel::testAsyncObject()
945{
946 QSKIP("This test is broken. See QTBUG-80729");
947
948 QWebChannel channel;
949 channel.connectTo(transport: m_dummyTransport);
950
951 QThread thread;
952 thread.start();
953
954 TestObject obj;
955 obj.moveToThread(thread: &thread);
956
957 QJsonArray args;
958 args.append(value: QJsonValue("message"));
959
960 {
961 int received = 0;
962 connect(sender: &obj, signal: &TestObject::propChanged, context: this, slot: [&](const QString &arg) {
963 QCOMPARE(arg, args.at(0).toString());
964 ++received;
965 });
966 channel.d_func()->publisher->invokeMethod(object: &obj, methodName: "setProp", args);
967 QTRY_COMPARE(received, 1);
968 }
969
970 channel.registerObject(id: "myObj", object: &obj);
971 channel.d_func()->publisher->initializeClient(transport: m_dummyTransport);
972
973 QJsonObject connectMessage;
974 connectMessage["type"] = 7;
975 connectMessage["object"] = "myObj";
976 connectMessage["signal"] = obj.metaObject()->indexOfSignal(signal: "replay()");
977 channel.d_func()->publisher->handleMessage(message: connectMessage, transport: m_dummyTransport);
978
979 {
980 int received = 0;
981 connect(sender: &obj, signal: &TestObject::replay, context: this, slot: [&]() { ++received; });
982 QMetaObject::invokeMethod(obj: &obj, member: "fire");
983 QTRY_COMPARE(received, 1);
984 channel.deregisterObject(object: &obj);
985 QMetaObject::invokeMethod(obj: &obj, member: "fire");
986 QTRY_COMPARE(received, 2);
987 }
988
989 thread.quit();
990 thread.wait();
991}
992
993class FunctionWrapper : public QObject
994{
995 Q_OBJECT
996 std::function<void()> m_fun;
997public:
998 FunctionWrapper(std::function<void()> fun) : m_fun(std::move(fun)) {}
999public slots:
1000 void invoke()
1001 {
1002 m_fun();
1003 }
1004};
1005
1006void TestWebChannel::testDeletionDuringMethodInvocation_data()
1007{
1008 QTest::addColumn<bool>(name: "deleteChannel");
1009 QTest::addColumn<bool>(name: "deleteTransport");
1010 QTest::newRow(dataTag: "delete neither") << false << false;
1011 QTest::newRow(dataTag: "delete channel") << true << false;
1012 QTest::newRow(dataTag: "delete transport") << false << true;
1013 QTest::newRow(dataTag: "delete both") << true << true;
1014}
1015
1016void TestWebChannel::testDeletionDuringMethodInvocation()
1017{
1018 QFETCH(bool, deleteChannel);
1019 QFETCH(bool, deleteTransport);
1020
1021 QScopedPointer<QWebChannel> channel(new QWebChannel);
1022 QScopedPointer<DummyTransport> transport(new DummyTransport(nullptr));
1023 FunctionWrapper deleter([&](){
1024 if (deleteChannel)
1025 channel.reset();
1026 if (deleteTransport)
1027 transport.reset();
1028 });
1029 channel->registerObject(id: "deleter", object: &deleter);
1030 channel->connectTo(transport: transport.data());
1031
1032 transport->emitMessageReceived(message: {
1033 {"type", TypeInvokeMethod},
1034 {"object", "deleter"},
1035 {"method", deleter.metaObject()->indexOfMethod(method: "invoke()")},
1036 {"id", 42}
1037 });
1038
1039 QCOMPARE(deleteChannel, !channel);
1040 QCOMPARE(deleteTransport, !transport);
1041 if (!deleteTransport)
1042 QCOMPARE(transport->messagesSent().size(), deleteChannel ? 0 : 1);
1043}
1044
1045static QHash<QString, QObject*> createObjects(QObject *parent)
1046{
1047 const int num = 100;
1048 QHash<QString, QObject*> objects;
1049 objects.reserve(asize: num);
1050 for (int i = 0; i < num; ++i) {
1051 objects[QStringLiteral("obj%1").arg(a: i)] = new BenchObject(parent);
1052 }
1053 return objects;
1054}
1055
1056void TestWebChannel::benchClassInfo()
1057{
1058 QWebChannel channel;
1059 channel.connectTo(transport: m_dummyTransport);
1060
1061 QObject parent;
1062 const QHash<QString, QObject*> objects = createObjects(parent: &parent);
1063
1064 QBENCHMARK {
1065 foreach (const QObject *object, objects) {
1066 channel.d_func()->publisher->classInfoForObject(object, transport: m_dummyTransport);
1067 }
1068 }
1069}
1070
1071void TestWebChannel::benchInitializeClients()
1072{
1073 QWebChannel channel;
1074 channel.connectTo(transport: m_dummyTransport);
1075
1076 QObject parent;
1077 channel.registerObjects(objects: createObjects(parent: &parent));
1078
1079 QMetaObjectPublisher *publisher = channel.d_func()->publisher;
1080 QBENCHMARK {
1081 publisher->initializeClient(transport: m_dummyTransport);
1082
1083 publisher->propertyUpdatesInitialized = false;
1084 publisher->signalToPropertyMap.clear();
1085 publisher->signalHandler.clear();
1086 }
1087}
1088
1089void TestWebChannel::benchPropertyUpdates()
1090{
1091 QWebChannel channel;
1092 channel.connectTo(transport: m_dummyTransport);
1093
1094 QObject parent;
1095 const QHash<QString, QObject*> objects = createObjects(parent: &parent);
1096 QVector<BenchObject*> objectList;
1097 objectList.reserve(asize: objects.size());
1098 foreach (QObject *obj, objects) {
1099 objectList << qobject_cast<BenchObject*>(object: obj);
1100 }
1101
1102 channel.registerObjects(objects);
1103 channel.d_func()->publisher->initializeClient(transport: m_dummyTransport);
1104
1105 QBENCHMARK {
1106 foreach (BenchObject *obj, objectList) {
1107 obj->change();
1108 }
1109
1110 channel.d_func()->publisher->clientIsIdle = true;
1111 channel.d_func()->publisher->sendPendingPropertyUpdates();
1112 }
1113}
1114
1115void TestWebChannel::benchRegisterObjects()
1116{
1117 QWebChannel channel;
1118 channel.connectTo(transport: m_dummyTransport);
1119
1120 QObject parent;
1121 const QHash<QString, QObject*> objects = createObjects(parent: &parent);
1122
1123 QBENCHMARK {
1124 channel.registerObjects(objects);
1125 }
1126}
1127
1128void TestWebChannel::benchRemoveTransport()
1129{
1130 QWebChannel channel;
1131 QList<DummyTransport*> dummyTransports;
1132 for (int i = 500; i > 0; i--)
1133 dummyTransports.append(t: new DummyTransport(this));
1134
1135 QList<QSharedPointer<TestObject>> objs;
1136 QMetaObjectPublisher *pub = channel.d_func()->publisher;
1137
1138 foreach (DummyTransport *transport, dummyTransports) {
1139 channel.connectTo(transport);
1140 channel.d_func()->publisher->initializeClient(transport);
1141
1142 /* 30 objects per transport */
1143 for (int i = 30; i > 0; i--) {
1144 QSharedPointer<TestObject> obj = QSharedPointer<TestObject>::create();
1145 objs.append(t: obj);
1146 pub->wrapResult(result: QVariant::fromValue(value: obj.data()), transport);
1147 }
1148 }
1149
1150 QBENCHMARK_ONCE {
1151 for (auto transport : dummyTransports)
1152 pub->transportRemoved(transport);
1153 }
1154
1155 qDeleteAll(c: dummyTransports);
1156}
1157
1158#ifdef WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE
1159
1160class SubclassedTestObject : public TestObject
1161{
1162 Q_OBJECT
1163 Q_PROPERTY(QString bar READ bar WRITE setBar NOTIFY theBarHasChanged)
1164public:
1165 void setBar(const QString &newBar);
1166signals:
1167 void theBarHasChanged();
1168};
1169
1170void SubclassedTestObject::setBar(const QString &newBar)
1171{
1172 if (!newBar.isNull())
1173 emit theBarHasChanged();
1174}
1175
1176class TestSubclassedFunctor {
1177public:
1178 TestSubclassedFunctor(TestJSEngine *engine)
1179 : m_engine(engine)
1180 {
1181 }
1182
1183 void operator()() {
1184 QCOMPARE(m_engine->logger()->errorCount(), 0);
1185 }
1186
1187private:
1188 TestJSEngine *m_engine;
1189};
1190#endif // WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE
1191
1192void TestWebChannel::qtbug46548_overriddenProperties()
1193{
1194#ifndef WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE
1195 QSKIP("A JS engine is required for this test to make sense.");
1196#else
1197 SubclassedTestObject obj;
1198 obj.setObjectName("subclassedTestObject");
1199
1200 QWebChannel webChannel;
1201 webChannel.registerObject(id: obj.objectName(), object: &obj);
1202 TestJSEngine engine;
1203 webChannel.connectTo(transport: engine.transport());
1204 QSignalSpy spy(&engine, &TestJSEngine::channelSetupReady);
1205 connect(sender: &engine, signal: &TestJSEngine::channelSetupReady, slot: TestSubclassedFunctor(&engine));
1206 engine.initWebChannelJS();
1207 if (!spy.count())
1208 spy.wait();
1209 QCOMPARE(spy.count(), 1);
1210 QJSValue subclassedTestObject = engine.evaluate(program: "channel.objects[\"subclassedTestObject\"]");
1211 QVERIFY(subclassedTestObject.isObject());
1212
1213#endif // WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE
1214}
1215
1216void TestWebChannel::qtbug62388_wrapObjectMultipleTransports()
1217{
1218 QWebChannel channel;
1219 TestObject obj;
1220
1221 auto initTransport = [&channel](QWebChannelAbstractTransport *transport) {
1222 channel.connectTo(transport);
1223 channel.d_func()->publisher->initializeClient(transport);
1224 };
1225 initTransport(m_dummyTransport);
1226
1227 auto queryObjectInfo = [&channel](QObject *obj, QWebChannelAbstractTransport *transport) {
1228 return channel.d_func()->publisher->wrapResult(result: QVariant::fromValue(value: obj), transport).toObject();
1229 };
1230
1231 auto verifyObjectInfo = [&obj](const QJsonObject &objectInfo) {
1232
1233 QCOMPARE(objectInfo.length(), 3);
1234 QVERIFY(objectInfo.contains("id"));
1235 QVERIFY(objectInfo.contains("__QObject*__"));
1236 QVERIFY(objectInfo.contains("data"));
1237 QVERIFY(objectInfo.value("__QObject*__").isBool() && objectInfo.value("__QObject*__").toBool());
1238
1239 const auto propIndex = obj.metaObject()->indexOfProperty(name: "prop");
1240 const auto prop = objectInfo["data"].toObject()["properties"].toArray()[propIndex].toArray()[3].toString();
1241 QCOMPARE(prop, obj.prop());
1242 };
1243
1244 const auto objectInfo = queryObjectInfo(&obj, m_dummyTransport);
1245 verifyObjectInfo(objectInfo);
1246
1247 const auto id = objectInfo.value(key: "id").toString();
1248
1249 QCOMPARE(channel.d_func()->publisher->unwrapObject(id), &obj);
1250
1251 DummyTransport transport;
1252 initTransport(&transport);
1253 QCOMPARE(queryObjectInfo(&obj, &transport), objectInfo);
1254
1255 obj.setProp("asdf");
1256
1257 const auto objectInfo2 = queryObjectInfo(&obj, m_dummyTransport);
1258 QVERIFY(objectInfo2 != objectInfo);
1259 verifyObjectInfo(objectInfo2);
1260
1261 DummyTransport transport2;
1262 initTransport(&transport2);
1263 QCOMPARE(queryObjectInfo(&obj, &transport2), objectInfo2);
1264
1265 // don't crash when the transports are destroyed
1266}
1267
1268QTEST_MAIN(TestWebChannel)
1269
1270#include "tst_webchannel.moc"
1271

source code of qtwebchannel/tests/auto/webchannel/tst_webchannel.cpp