From 81449c0c804735b71ba2f225005482c7c0123dad Mon Sep 17 00:00:00 2001 From: Pierre Rossi Date: Tue, 7 Jul 2015 13:02:09 +0200 Subject: Support subclass property getters and setters. QDialog for instance adds a setter for the modal property already exposed by QWidget. Object.defineProperty requires configurable set to true in order to add that setter at a later stage. Adds some reusable autotest logic with a soft dependency on QJSEngine to test some of the C++/JS integration aspects. Task-number: QTBUG-46548 Change-Id: Ibd49274f7d334c068c4006fb09417abf911c24e9 Reviewed-by: Milian Wolff --- src/webchannel/qwebchannel.js | 1 + tests/auto/webchannel/tst_webchannel.cpp | 205 +++++++++++++++++++++++++++++++ tests/auto/webchannel/tst_webchannel.h | 2 + tests/auto/webchannel/webchannel.pro | 5 + 4 files changed, 213 insertions(+) diff --git a/src/webchannel/qwebchannel.js b/src/webchannel/qwebchannel.js index f114e77..d8c28bc 100644 --- a/src/webchannel/qwebchannel.js +++ b/src/webchannel/qwebchannel.js @@ -365,6 +365,7 @@ function QObject(name, data, webChannel) } Object.defineProperty(object, propertyName, { + configurable: true, get: function () { var propertyValue = object.__propertyCache__[propertyIndex]; if (propertyValue === undefined) { diff --git a/tests/auto/webchannel/tst_webchannel.cpp b/tests/auto/webchannel/tst_webchannel.cpp index e36a1b3..f1911e5 100644 --- a/tests/auto/webchannel/tst_webchannel.cpp +++ b/tests/auto/webchannel/tst_webchannel.cpp @@ -38,9 +38,156 @@ #include #include +#ifdef WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE +#include +#endif QT_USE_NAMESPACE +#ifdef WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE +class TestJSEngine; + +class TestEngineTransport : public QWebChannelAbstractTransport +{ + Q_OBJECT +public: + TestEngineTransport(TestJSEngine *); + void sendMessage(const QJsonObject &message) Q_DECL_OVERRIDE; + + Q_INVOKABLE void channelSetupReady(); + Q_INVOKABLE void send(const QByteArray &message); +private: + TestJSEngine *m_testEngine; +}; + +class ConsoleLogger : public QObject +{ + Q_OBJECT +public: + ConsoleLogger(QObject *parent = 0); + + Q_INVOKABLE void log(const QString &text); + Q_INVOKABLE void error(const QString &text); + + int errorCount() const { return m_errCount; } + int logCount() const { return m_logCount; } + QString lastError() const { return m_lastError; } + +private: + int m_errCount; + int m_logCount; + QString m_lastError; + +}; + + + +ConsoleLogger::ConsoleLogger(QObject *parent) + : QObject(parent) + , m_errCount(0) + , m_logCount(0) +{ +} + +void ConsoleLogger::log(const QString &text) +{ + m_logCount++; + qDebug("LOG: %s", qPrintable(text)); +} + +void ConsoleLogger::error(const QString &text) +{ + m_errCount++; + m_lastError = text; + qWarning("ERROR: %s", qPrintable(text)); +} + + +// A test JS engine with convenience integration with WebChannel. +class TestJSEngine : public QJSEngine +{ + Q_OBJECT +public: + TestJSEngine(); + + TestEngineTransport *transport() const; + ConsoleLogger *logger() const; + void initWebChannelJS(); + +signals: + void channelSetupReady(TestEngineTransport *transport); + +private: + TestEngineTransport *m_transport; + ConsoleLogger *m_logger; +}; + +TestEngineTransport::TestEngineTransport(TestJSEngine *engine) + : QWebChannelAbstractTransport(engine) + , m_testEngine(engine) +{ +} + +void TestEngineTransport::sendMessage(const QJsonObject &message) +{ + QByteArray json = QJsonDocument(message).toJson(QJsonDocument::Compact); + QJSValue callback = m_testEngine->evaluate(QStringLiteral("transport.onmessage")); + Q_ASSERT(callback.isCallable()); + QJSValue arg = m_testEngine->newObject(); + QJSValue data = m_testEngine->evaluate(QString::fromLatin1("JSON.parse('%1');").arg(QString::fromUtf8(json))); + Q_ASSERT(!data.isError()); + arg.setProperty(QStringLiteral("data"), data); + QJSValue val = callback.call((QJSValueList() << arg)); + Q_ASSERT(!val.isError()); +} + +void TestEngineTransport::channelSetupReady() +{ + emit m_testEngine->channelSetupReady(m_testEngine->transport()); +} + +void TestEngineTransport::send(const QByteArray &message) +{ + QJsonDocument doc(QJsonDocument::fromJson(message)); + emit messageReceived(doc.object(), this); +} + + +TestJSEngine::TestJSEngine() + : m_transport(new TestEngineTransport(this)) + , m_logger(new ConsoleLogger(this)) +{ + globalObject().setProperty("transport", newQObject(m_transport)); + globalObject().setProperty("console", newQObject(m_logger)); + + QString webChannelJSPath(QStringLiteral(":/qtwebchannel/qwebchannel.js")); + QFile webChannelJS(webChannelJSPath); + if (!webChannelJS.open(QFile::ReadOnly)) + qFatal("Error opening qwebchannel.js"); + QString source(QString::fromUtf8(webChannelJS.readAll())); + evaluate(source, webChannelJSPath); +} + +TestEngineTransport *TestJSEngine::transport() const +{ + return m_transport; +} + +ConsoleLogger *TestJSEngine::logger() const +{ + return m_logger; +} + +void TestJSEngine::initWebChannelJS() +{ + globalObject().setProperty(QStringLiteral("channel"), newObject()); + QJSValue channel = evaluate(QStringLiteral("channel = new QWebChannel(transport, function(channel) { transport.channelSetupReady();});")); + Q_ASSERT(!channel.isError()); +} + +#endif // WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE + + TestWebChannel::TestWebChannel(QObject *parent) : QObject(parent) , m_dummyTransport(new DummyTransport(this)) @@ -408,5 +555,63 @@ void TestWebChannel::benchRegisterObjects() channel.registerObjects(objects); } } +#ifdef WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE + +class SubclassedTestObject : public TestObject +{ + Q_OBJECT + Q_PROPERTY(QString bar READ bar WRITE setBar NOTIFY theBarHasChanged) +public: + void setBar(const QString &newBar); +signals: + void theBarHasChanged(); +}; + +void SubclassedTestObject::setBar(const QString &newBar) +{ + if (!newBar.isNull()) + emit theBarHasChanged(); +} + +class TestSubclassedFunctor { +public: + TestSubclassedFunctor(TestJSEngine *engine) + : m_engine(engine) + { + } + + void operator()() { + QCOMPARE(m_engine->logger()->errorCount(), 0); + } +private: + TestJSEngine *m_engine; +}; +#endif // WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE + +void TestWebChannel::qtbug46548_overriddenProperties() +{ +#ifndef WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE + QSKIP("A JS engine is required for this test to make sense."); +#else + SubclassedTestObject obj; + obj.setObjectName("subclassedTestObject"); + + QWebChannel webChannel; + webChannel.registerObject(obj.objectName(), &obj); + TestJSEngine engine; + webChannel.connectTo(engine.transport()); + QSignalSpy spy(&engine, &TestJSEngine::channelSetupReady); + connect(&engine, &TestJSEngine::channelSetupReady, TestSubclassedFunctor(&engine)); + engine.initWebChannelJS(); + if (!spy.count()) + spy.wait(); + QCOMPARE(spy.count(), 1); + QJSValue subclassedTestObject = engine.evaluate("channel.objects[\"subclassedTestObject\"]"); + QVERIFY(subclassedTestObject.isObject()); + +#endif // WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE +} QTEST_MAIN(TestWebChannel) + +#include "tst_webchannel.moc" diff --git a/tests/auto/webchannel/tst_webchannel.h b/tests/auto/webchannel/tst_webchannel.h index 649e61f..b2a1040 100644 --- a/tests/auto/webchannel/tst_webchannel.h +++ b/tests/auto/webchannel/tst_webchannel.h @@ -244,6 +244,8 @@ private slots: void benchPropertyUpdates(); void benchRegisterObjects(); + void qtbug46548_overriddenProperties(); + private: DummyTransport *m_dummyTransport; diff --git a/tests/auto/webchannel/webchannel.pro b/tests/auto/webchannel/webchannel.pro index 40b324e..2dcc610 100644 --- a/tests/auto/webchannel/webchannel.pro +++ b/tests/auto/webchannel/webchannel.pro @@ -12,3 +12,8 @@ SOURCES += \ HEADERS += \ tst_webchannel.h + +qtHaveModule(qml) { + DEFINES += WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE + QT += qml +} -- cgit v1.2.1