/**************************************************************************** ** ** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtWebChannel module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ import QtQuick 2.0 import QtTest 1.0 import QtWebChannel 1.0 import QtWebChannel.Tests 1.0 import "qrc:///qtwebchannel/qwebchannel.js" as JSClient TestCase { name: "WebChannel" Client { id: client } property var lastMethodArg QtObject { id: myObj property int myProperty: 1 signal mySignal(var arg) function myMethod(arg) { lastMethodArg = arg; } WebChannel.id: "myObj" } QtObject { id: myOtherObj property var foo: 1 property var bar: 1 WebChannel.id: "myOtherObj" } property var lastFactoryObj QtObject{ id: bar; objectName: "bar" } QtObject{ id: baz; objectName: "baz" } QtObject { id: myFactory function create(id) { lastFactoryObj = component.createObject(myFactory, {objectName: id}); return lastFactoryObj; } property var objectInProperty: QtObject { objectName: "foo" } property var otherObject: myObj property var objects: [ bar, baz ]; WebChannel.id: "myFactory" } Component { id: component QtObject { property var myProperty : 0 function myMethod(arg) { lastMethodArg = arg; } signal mySignal(var arg1, var arg2) } } TestWebChannel { id: webChannel transports: [client.serverTransport] registeredObjects: [myObj, myOtherObj, myFactory] } function initChannel() { client.serverTransport.receiveMessage(JSON.stringify({type: JSClient.QWebChannelMessageTypes.idle})); webChannel.blockUpdates = true; webChannel.blockUpdates = false; } function init() { myObj.myProperty = 1 // immediately send pending updates // to avoid property changed signals // during run of test case initChannel(); client.cleanup(); } function cleanup() { // make tests a bit more strict and predictable // by assuming that a test consumes all messages initChannel(); compare(client.clientMessages.length, 0); } function test_property() { compare(myObj.myProperty, 1); var initialValue; var changedValue; var channel = client.createChannel(function(channel) { initialValue = channel.objects.myObj.myProperty; channel.objects.myObj.myPropertyChanged.connect(function() { changedValue = channel.objects.myObj.myProperty; }); // now trigger a write from the client side channel.objects.myObj.myProperty = 3; }); client.awaitInit(); var msg = client.awaitMessage(); compare(initialValue, 1); compare(myObj.myProperty, 3); client.awaitIdle(); // init // change property, should be propagated to HTML client and a message be send there myObj.myProperty = 2; compare(myObj.myProperty, 2); client.awaitIdle(); // property update compare(changedValue, 2); } function test_method() { var channel = client.createChannel(function (channel) { channel.objects.myObj.myMethod("test"); }); client.awaitInit(); var msg = client.awaitMessage(); compare(msg.type, JSClient.QWebChannelMessageTypes.invokeMethod); compare(msg.object, "myObj"); compare(msg.args, ["test"]); compare(lastMethodArg, "test") client.awaitIdle(); } function test_signal() { var signalReceivedArg; var channel = client.createChannel(function(channel) { channel.objects.myObj.mySignal.connect(function(arg) { signalReceivedArg = arg; }); }); client.awaitInit(); var msg = client.awaitMessage(); compare(msg.type, JSClient.QWebChannelMessageTypes.connectToSignal); compare(msg.object, "myObj"); client.awaitIdle(); // initialization myObj.mySignal("test"); compare(signalReceivedArg, "test"); } function test_grouping() { var receivedPropertyUpdates = 0; var properties = 0; var channel = client.createChannel(function(channel) { var originalHandler = channel.handlePropertyUpdate; channel.handlePropertyUpdate = function(message) { originalHandler(message); receivedPropertyUpdates++; properties = [channel.objects.myObj.myProperty, channel.objects.myOtherObj.foo, channel.objects.myOtherObj.bar]; }; }); client.awaitInit(); client.awaitIdle(); // change properties a lot, we expect this to be grouped into a single update notification for (var i = 0; i < 10; ++i) { myObj.myProperty = i; myOtherObj.foo = i; myOtherObj.bar = i; } client.awaitIdle(); compare(receivedPropertyUpdates, 1); compare(properties, [myObj.myProperty, myOtherObj.foo, myOtherObj.bar]); verify(!client.awaitMessage()); } function test_wrapper() { var signalArgs; var testObjBeforeDeletion; var testObjAfterDeletion; var testObjId; var channel = client.createChannel(function(channel) { channel.objects.myFactory.create("testObj", function(obj) { testObjId = obj.__id__; compare(channel.objects[testObjId], obj); obj.mySignal.connect(function() { signalArgs = arguments; testObjBeforeDeletion = obj; obj.deleteLater(); testObjAfterDeletion = obj; }); obj.myProperty = 42; obj.myMethod("foobar"); }); }); client.awaitInit(); client.awaitResponse(); // create testObj var msg = client.awaitMessage(); compare(msg.type, JSClient.QWebChannelMessageTypes.invokeMethod); compare(msg.object, "myFactory"); client.awaitResponse(); verify(lastFactoryObj); compare(lastFactoryObj.objectName, "testObj"); compare(channel.objects[testObjId].objectName, "testObj"); // mySignal connection msg = client.awaitMessage(); compare(msg.type, JSClient.QWebChannelMessageTypes.connectToSignal); compare(msg.object, testObjId); // set myProperty msg = client.awaitMessage(); compare(msg.type, JSClient.QWebChannelMessageTypes.setProperty); compare(msg.object, testObjId); compare(lastFactoryObj.myProperty, 42); // call myMethod msg = client.awaitMessage(); compare(msg.type, JSClient.QWebChannelMessageTypes.invokeMethod); compare(msg.object, testObjId); compare(msg.args, ["foobar"]); client.awaitResponse(); compare(lastMethodArg, "foobar"); client.awaitIdle(); // the server should eventually notify the client about the property update client.awaitPropertyUpdate(); client.awaitIdle(); // trigger a signal and ensure it gets transmitted lastFactoryObj.mySignal("foobar", 42); client.awaitSignal(); // property should be wrapped compare(channel.objects.myFactory.objectInProperty.objectName, "foo"); // list property as well compare(channel.objects.myFactory.objects.length, 2); compare(channel.objects.myFactory.objects[0].objectName, "bar"); compare(channel.objects.myFactory.objects[1].objectName, "baz"); // also works with properties that reference other registered objects compare(channel.objects.myFactory.otherObject, channel.objects.myObj); // deleteLater call msg = client.awaitMessage(); compare(msg.type, JSClient.QWebChannelMessageTypes.invokeMethod); compare(msg.object, testObjId); client.awaitResponse(); // now the signalArgs should also be set compare(signalArgs, {"0": "foobar", "1": 42}); // and also a destroyed signal client.awaitSignal(); compare(JSON.stringify(testObjBeforeDeletion), JSON.stringify({})); compare(JSON.stringify(testObjAfterDeletion), JSON.stringify({})); compare(typeof channel.objects[testObjId], "undefined"); } // test if returned QObjects get inserted into list of // objects even if no callback function is set function test_wrapper_wrapEveryQObject() { var testObj; var channel = client.createChannel(function(channel) { channel.objects.myFactory.create("testObj", function(obj) { testObj = obj; }); }); client.awaitInit(); client.awaitResponse(); // call to myFactory.create() var msg = client.awaitMessage(); compare(msg.type, JSClient.QWebChannelMessageTypes.invokeMethod); client.awaitResponse(); client.awaitIdle(); verify(testObj); var testObjId = testObj.__id__; testObj.deleteLater(); msg = client.awaitMessage(); compare(msg.type, JSClient.QWebChannelMessageTypes.invokeMethod); compare(msg.object, testObjId); client.awaitResponse(); // destroyed signal client.awaitSignal(); compare(lastFactoryObj, null); compare(typeof channel.objects[testObjId], "undefined"); } function test_wrapper_propertyUpdateOfWrappedObjects() { var testObj; var testObjId; var channel = client.createChannel(function(channel) { channel.objects.myFactory.create("testObj", function(obj) { testObj = lastFactoryObj; testObjId = obj.__id__; }); }); client.awaitInit(); client.awaitResponse(); // call to myFactory.create() var msg = client.awaitMessage(); compare(msg.type, JSClient.QWebChannelMessageTypes.invokeMethod); client.awaitResponse(); client.awaitIdle(); testObj.myProperty = 42; client.awaitPropertyUpdate(); client.awaitIdle(); compare(channel.objects[testObjId].myProperty, 42); channel.objects[testObjId].deleteLater(); msg = client.awaitMessage(); compare(msg.type, JSClient.QWebChannelMessageTypes.invokeMethod); client.awaitResponse(); // destroyed signal client.awaitSignal(); } function test_disconnect() { var signalArg; var channel = client.createChannel(function(channel) { channel.objects.myObj.mySignal.connect(function(arg) { signalArg = arg; channel.objects.myObj.mySignal.disconnect(this); }); }); client.awaitInit(); var msg = client.awaitMessage(); compare(msg.type, JSClient.QWebChannelMessageTypes.connectToSignal); compare(msg.object, "myObj"); client.awaitIdle(); myObj.mySignal(42); compare(signalArg, 42); msg = client.awaitMessage(); compare(msg.type, JSClient.QWebChannelMessageTypes.disconnectFromSignal); compare(msg.object, "myObj"); myObj.mySignal(0); compare(signalArg, 42); } }