qtdeclarative/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp

680 lines
22 KiB
C++
Raw Normal View History

/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** 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 Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "../../shared/util.h"
#include <QQmlEngine>
#include <QQmlContext>
#include <QNetworkAccessManager>
#include <QPointer>
#include <QDir>
#include <QStandardPaths>
#include <QSignalSpy>
#include <QDebug>
#include <QBuffer>
#include <QQmlComponent>
#include <QQmlNetworkAccessManagerFactory>
#include <QQmlExpression>
#include <QQmlIncubationController>
#include <private/qqmlengine_p.h>
class tst_qqmlengine : public QQmlDataTest
{
Q_OBJECT
public:
tst_qqmlengine() {}
private slots:
void rootContext();
void networkAccessManager();
void synchronousNetworkAccessManager();
void baseUrl();
void contextForObject();
void offlineStoragePath();
void clearComponentCache();
void trimComponentCache();
void trimComponentCache_data();
void repeatedCompilation();
void failedCompilation();
void failedCompilation_data();
void outputWarningsToStandardError();
void objectOwnership();
void multipleEngines();
void qtqmlModule_data();
void qtqmlModule();
public slots:
QObject *createAQObjectForOwnershipTest ()
{
static QObject *ptr = new QObject();
return ptr;
}
};
void tst_qqmlengine::rootContext()
{
QQmlEngine engine;
QVERIFY(engine.rootContext());
QCOMPARE(engine.rootContext()->engine(), &engine);
QVERIFY(engine.rootContext()->parentContext() == 0);
}
class NetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory
{
public:
NetworkAccessManagerFactory() : manager(0) {}
QNetworkAccessManager *create(QObject *parent) {
manager = new QNetworkAccessManager(parent);
return manager;
}
QNetworkAccessManager *manager;
};
void tst_qqmlengine::networkAccessManager()
{
QQmlEngine *engine = new QQmlEngine;
// Test QQmlEngine created manager
QPointer<QNetworkAccessManager> manager = engine->networkAccessManager();
QVERIFY(manager != 0);
delete engine;
// Test factory created manager
engine = new QQmlEngine;
NetworkAccessManagerFactory factory;
engine->setNetworkAccessManagerFactory(&factory);
QVERIFY(engine->networkAccessManagerFactory() == &factory);
QVERIFY(engine->networkAccessManager() == factory.manager);
delete engine;
}
class ImmediateReply : public QNetworkReply {
Q_OBJECT
public:
ImmediateReply() {
setFinished(true);
}
virtual qint64 readData(char* , qint64 ) {
return 0;
}
virtual void abort() { }
};
class ImmediateManager : public QNetworkAccessManager {
Q_OBJECT
public:
ImmediateManager(QObject *parent = 0) : QNetworkAccessManager(parent) {
}
QNetworkReply *createRequest(Operation, const QNetworkRequest & , QIODevice * outgoingData = 0) {
Q_UNUSED(outgoingData);
return new ImmediateReply;
}
};
class ImmediateFactory : public QQmlNetworkAccessManagerFactory {
public:
QNetworkAccessManager *create(QObject *) {
return new ImmediateManager;
}
};
void tst_qqmlengine::synchronousNetworkAccessManager()
{
ImmediateFactory factory;
QQmlEngine engine;
engine.setNetworkAccessManagerFactory(&factory);
QQmlComponent c(&engine, QUrl("myScheme://test.qml"));
// reply is finished, so should not be in loading state.
QVERIFY(!c.isLoading());
}
void tst_qqmlengine::baseUrl()
{
QQmlEngine engine;
QUrl cwd = QUrl::fromLocalFile(QDir::currentPath() + QDir::separator());
QCOMPARE(engine.baseUrl(), cwd);
QCOMPARE(engine.rootContext()->resolvedUrl(QUrl("main.qml")), cwd.resolved(QUrl("main.qml")));
QDir dir = QDir::current();
dir.cdUp();
QVERIFY(dir != QDir::current());
QDir::setCurrent(dir.path());
QVERIFY(QDir::current() == dir);
QUrl cwd2 = QUrl::fromLocalFile(QDir::currentPath() + QDir::separator());
QCOMPARE(engine.baseUrl(), cwd2);
QCOMPARE(engine.rootContext()->resolvedUrl(QUrl("main.qml")), cwd2.resolved(QUrl("main.qml")));
engine.setBaseUrl(cwd);
QCOMPARE(engine.baseUrl(), cwd);
QCOMPARE(engine.rootContext()->resolvedUrl(QUrl("main.qml")), cwd.resolved(QUrl("main.qml")));
}
void tst_qqmlengine::contextForObject()
{
QQmlEngine *engine = new QQmlEngine;
// Test null-object
QVERIFY(QQmlEngine::contextForObject(0) == 0);
// Test an object with no context
QObject object;
QVERIFY(QQmlEngine::contextForObject(&object) == 0);
// Test setting null-object
QQmlEngine::setContextForObject(0, engine->rootContext());
// Test setting null-context
QQmlEngine::setContextForObject(&object, 0);
// Test setting context
QQmlEngine::setContextForObject(&object, engine->rootContext());
QVERIFY(QQmlEngine::contextForObject(&object) == engine->rootContext());
QQmlContext context(engine->rootContext());
// Try changing context
QTest::ignoreMessage(QtWarningMsg, "QQmlEngine::setContextForObject(): Object already has a QQmlContext");
QQmlEngine::setContextForObject(&object, &context);
QVERIFY(QQmlEngine::contextForObject(&object) == engine->rootContext());
// Delete context
delete engine; engine = 0;
QVERIFY(QQmlEngine::contextForObject(&object) == 0);
}
void tst_qqmlengine::offlineStoragePath()
{
// Without these set, QDesktopServices::storageLocation returns
// strings with extra "//" at the end. We set them to ignore this problem.
qApp->setApplicationName("tst_qqmlengine");
qApp->setOrganizationName("Nokia");
qApp->setOrganizationDomain("nokia.com");
QQmlEngine engine;
QString dataLocation = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
QCOMPARE(dataLocation.isEmpty(), engine.offlineStoragePath().isEmpty());
QDir dir(dataLocation);
dir.mkpath("QML");
dir.cd("QML");
dir.mkpath("OfflineStorage");
dir.cd("OfflineStorage");
QCOMPARE(QDir::fromNativeSeparators(engine.offlineStoragePath()), dir.path());
engine.setOfflineStoragePath(QDir::homePath());
QCOMPARE(engine.offlineStoragePath(), QDir::homePath());
}
void tst_qqmlengine::clearComponentCache()
{
QQmlEngine engine;
// Create original qml file
{
QFile file("temp.qml");
QVERIFY(file.open(QIODevice::WriteOnly));
file.write("import QtQuick 2.0\nQtObject {\nproperty int test: 10\n}\n");
file.close();
}
// Test "test" property
{
QQmlComponent component(&engine, "temp.qml");
QObject *obj = component.create();
QVERIFY(obj != 0);
QCOMPARE(obj->property("test").toInt(), 10);
delete obj;
}
// Modify qml file
{
QFile file("temp.qml");
QVERIFY(file.open(QIODevice::WriteOnly));
file.write("import QtQuick 2.0\nQtObject {\nproperty int test: 11\n}\n");
file.close();
}
// Test cache hit
{
QQmlComponent component(&engine, "temp.qml");
QObject *obj = component.create();
QVERIFY(obj != 0);
QCOMPARE(obj->property("test").toInt(), 10);
delete obj;
}
// Clear cache
engine.clearComponentCache();
// Test cache refresh
{
QQmlComponent component(&engine, "temp.qml");
QObject *obj = component.create();
QVERIFY(obj != 0);
QCOMPARE(obj->property("test").toInt(), 11);
delete obj;
}
}
struct ComponentCacheFunctions : public QObject, public QQmlIncubationController
{
Q_OBJECT
public:
QQmlEngine *engine;
ComponentCacheFunctions(QQmlEngine &e) : engine(&e) {}
Q_INVOKABLE void trim()
{
// Wait for any pending deletions to occur
QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
QCoreApplication::processEvents();
engine->trimComponentCache();
}
Q_INVOKABLE bool isTypeLoaded(QString file)
{
return QQmlEnginePrivate::get(engine)->isTypeLoaded(tst_qqmlengine::instance()->testFileUrl(file));
}
Q_INVOKABLE bool isScriptLoaded(QString file)
{
return QQmlEnginePrivate::get(engine)->isScriptLoaded(tst_qqmlengine::instance()->testFileUrl(file));
}
Q_INVOKABLE void beginIncubation()
{
startTimer(0);
}
Q_INVOKABLE void waitForIncubation()
{
while (incubatingObjectCount() > 0) {
QCoreApplication::processEvents();
}
}
private:
virtual void timerEvent(QTimerEvent *)
{
incubateFor(1000);
}
};
void tst_qqmlengine::trimComponentCache()
{
QFETCH(QString, file);
QQmlEngine engine;
ComponentCacheFunctions componentCache(engine);
engine.rootContext()->setContextProperty("componentCache", &componentCache);
engine.setIncubationController(&componentCache);
QQmlComponent component(&engine, testFileUrl(file));
QVERIFY(component.isReady());
QScopedPointer<QObject> object(component.create());
QVERIFY(object != 0);
QCOMPARE(object->property("success").toBool(), true);
}
void tst_qqmlengine::trimComponentCache_data()
{
QTest::addColumn<QString>("file");
// The various tests here are for two types of components: those that are
// empty apart from their inherited elements, and those that define new properties.
// For each there are five types of composition: extension, aggregation,
// aggregation via component, property and object-created-via-transient-component.
foreach (const QString &test, (QStringList() << "EmptyComponent"
<< "VMEComponent"
<< "EmptyExtendEmptyComponent"
<< "VMEExtendEmptyComponent"
<< "EmptyExtendVMEComponent"
<< "VMEExtendVMEComponent"
<< "EmptyAggregateEmptyComponent"
<< "VMEAggregateEmptyComponent"
<< "EmptyAggregateVMEComponent"
<< "VMEAggregateVMEComponent"
<< "EmptyPropertyEmptyComponent"
<< "VMEPropertyEmptyComponent"
<< "EmptyPropertyVMEComponent"
<< "VMEPropertyVMEComponent"
<< "VMETransientEmptyComponent"
<< "VMETransientVMEComponent")) {
// For these cases, we first test that the component instance keeps the components
// referenced, and then that the instantiated object keeps the components referenced
for (int i = 1; i <= 2; ++i) {
QString name(QString("%1-%2").arg(test).arg(i));
QString file(QString("test%1.%2.qml").arg(test).arg(i));
QTest::newRow(name.toLatin1().constData()) << file;
}
}
// Test that a transient component is correctly referenced
QTest::newRow("TransientComponent-1") << "testTransientComponent.1.qml";
QTest::newRow("TransientComponent-2") << "testTransientComponent.2.qml";
// Test that components can be reloaded after unloading
QTest::newRow("ReloadComponent") << "testReloadComponent.qml";
// Test that components are correctly referenced when dynamically loaded
QTest::newRow("LoaderComponent") << "testLoaderComponent.qml";
// Test that components are correctly referenced when incubated
QTest::newRow("IncubatedComponent") << "testIncubatedComponent.qml";
// Test that a top-level omponents is correctly referenced
QTest::newRow("TopLevelComponent") << "testTopLevelComponent.qml";
// TODO:
// Test that scripts are unloaded when no longer referenced
QTest::newRow("ScriptComponent") << "testScriptComponent.qml";
}
void tst_qqmlengine::repeatedCompilation()
{
QQmlEngine engine;
for (int i = 0; i < 100; ++i) {
engine.collectGarbage();
engine.trimComponentCache();
QQmlComponent component(&engine, testFileUrl("repeatedCompilation.qml"));
QVERIFY(component.isReady());
QScopedPointer<QObject> object(component.create());
QVERIFY(object != 0);
QCOMPARE(object->property("success").toBool(), true);
}
}
void tst_qqmlengine::failedCompilation()
{
QFETCH(QString, file);
QQmlEngine engine;
QQmlComponent component(&engine, testFileUrl(file));
QVERIFY(!component.isReady());
QScopedPointer<QObject> object(component.create());
QVERIFY(object == 0);
engine.collectGarbage();
engine.trimComponentCache();
engine.clearComponentCache();
}
void tst_qqmlengine::failedCompilation_data()
{
QTest::addColumn<QString>("file");
QTest::newRow("Invalid URL") << "failedCompilation.does.not.exist.qml";
QTest::newRow("Invalid content") << "failedCompilation.1.qml";
}
void tst_qqmlengine::outputWarningsToStandardError()
{
QQmlEngine engine;
QCOMPARE(engine.outputWarningsToStandardError(), true);
QQmlComponent c(&engine);
c.setData("import QtQuick 2.0; QtObject { property int a: undefined }", QUrl());
QVERIFY(c.isReady() == true);
QQmlTestMessageHandler messageHandler;
QObject *o = c.create();
QVERIFY(o != 0);
delete o;
QCOMPARE(messageHandler.messages().count(), 1);
QCOMPARE(messageHandler.messages().at(0), QLatin1String("<Unknown File>: Unable to assign [undefined] to int"));
messageHandler.clear();
engine.setOutputWarningsToStandardError(false);
QCOMPARE(engine.outputWarningsToStandardError(), false);
o = c.create();
QVERIFY(o != 0);
delete o;
QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString()));
}
void tst_qqmlengine::objectOwnership()
{
{
QCOMPARE(QQmlEngine::objectOwnership(0), QQmlEngine::CppOwnership);
QQmlEngine::setObjectOwnership(0, QQmlEngine::JavaScriptOwnership);
QCOMPARE(QQmlEngine::objectOwnership(0), QQmlEngine::CppOwnership);
}
{
QObject o;
QCOMPARE(QQmlEngine::objectOwnership(&o), QQmlEngine::CppOwnership);
QQmlEngine::setObjectOwnership(&o, QQmlEngine::CppOwnership);
QCOMPARE(QQmlEngine::objectOwnership(&o), QQmlEngine::CppOwnership);
QQmlEngine::setObjectOwnership(&o, QQmlEngine::JavaScriptOwnership);
QCOMPARE(QQmlEngine::objectOwnership(&o), QQmlEngine::JavaScriptOwnership);
QQmlEngine::setObjectOwnership(&o, QQmlEngine::CppOwnership);
QCOMPARE(QQmlEngine::objectOwnership(&o), QQmlEngine::CppOwnership);
}
{
QQmlEngine engine;
QQmlComponent c(&engine);
c.setData("import QtQuick 2.0; QtObject { property QtObject object: QtObject {} }", QUrl());
QObject *o = c.create();
QVERIFY(o != 0);
QCOMPARE(QQmlEngine::objectOwnership(o), QQmlEngine::CppOwnership);
QObject *o2 = qvariant_cast<QObject *>(o->property("object"));
QCOMPARE(QQmlEngine::objectOwnership(o2), QQmlEngine::JavaScriptOwnership);
delete o;
}
{
QObject *ptr = createAQObjectForOwnershipTest();
QSignalSpy spy(ptr, SIGNAL(destroyed()));
{
QQmlEngine engine;
QQmlComponent c(&engine);
engine.rootContext()->setContextProperty("test", this);
QQmlEngine::setObjectOwnership(ptr, QQmlEngine::JavaScriptOwnership);
c.setData("import QtQuick 2.0; Item { property int data: test.createAQObjectForOwnershipTest() ? 0 : 1 }", QUrl());
QVERIFY(c.isReady());
QObject *o = c.create();
QVERIFY(o != 0);
}
QTRY_VERIFY(spy.count());
}
{
QObject *ptr = new QObject();
QSignalSpy spy(ptr, SIGNAL(destroyed()));
{
QQmlEngine engine;
QQmlComponent c(&engine);
engine.rootContext()->setContextProperty("test", ptr);
QQmlEngine::setObjectOwnership(ptr, QQmlEngine::JavaScriptOwnership);
c.setData("import QtQuick 2.0; QtObject { property var object: { var i = test; test ? 0 : 1 } }", QUrl());
QVERIFY(c.isReady());
QObject *o = c.create();
QVERIFY(o != 0);
engine.rootContext()->setContextProperty("test", 0);
}
QTRY_VERIFY(spy.count());
}
}
// Test an object can be accessed by multiple engines
void tst_qqmlengine::multipleEngines()
{
QObject o;
o.setObjectName("TestName");
// Simultaneous engines
{
QQmlEngine engine1;
QQmlEngine engine2;
engine1.rootContext()->setContextProperty("object", &o);
engine2.rootContext()->setContextProperty("object", &o);
QQmlExpression expr1(engine1.rootContext(), 0, QString("object.objectName"));
QQmlExpression expr2(engine2.rootContext(), 0, QString("object.objectName"));
QCOMPARE(expr1.evaluate().toString(), QString("TestName"));
QCOMPARE(expr2.evaluate().toString(), QString("TestName"));
}
// Serial engines
{
QQmlEngine engine1;
engine1.rootContext()->setContextProperty("object", &o);
QQmlExpression expr1(engine1.rootContext(), 0, QString("object.objectName"));
QCOMPARE(expr1.evaluate().toString(), QString("TestName"));
}
{
QQmlEngine engine1;
engine1.rootContext()->setContextProperty("object", &o);
QQmlExpression expr1(engine1.rootContext(), 0, QString("object.objectName"));
QCOMPARE(expr1.evaluate().toString(), QString("TestName"));
}
}
void tst_qqmlengine::qtqmlModule_data()
{
QTest::addColumn<QUrl>("testFile");
QTest::addColumn<QString>("expectedError");
QTest::addColumn<QStringList>("expectedWarnings");
QTest::newRow("import QtQml of correct version (2.0)")
<< testFileUrl("qtqmlModule.1.qml")
<< QString()
<< QStringList();
QTest::newRow("import QtQml of incorrect version (3.0)")
<< testFileUrl("qtqmlModule.2.qml")
<< QString(testFileUrl("qtqmlModule.2.qml").toString() + QLatin1String(":1 module \"QtQml\" version 3.0 is not installed\n"))
<< QStringList();
QTest::newRow("import QtQml of incorrect version (1.0)")
<< testFileUrl("qtqmlModule.3.qml")
<< QString(testFileUrl("qtqmlModule.3.qml").toString() + QLatin1String(":1 module \"QtQml\" version 1.0 is not installed\n"))
<< QStringList();
QTest::newRow("import QtQml of incorrect version (2.5)")
<< testFileUrl("qtqmlModule.4.qml")
<< QString(testFileUrl("qtqmlModule.4.qml").toString() + QLatin1String(":1 module \"QtQml\" version 2.5 is not installed\n"))
<< QStringList();
QTest::newRow("QtQml 2.0 module provides Component, QtObject, Connections, Binding and Timer")
<< testFileUrl("qtqmlModule.5.qml")
<< QString()
<< QStringList();
QTest::newRow("can import QtQml then QtQuick")
<< testFileUrl("qtqmlModule.6.qml")
<< QString()
<< QStringList();
QTest::newRow("can import QtQuick then QtQml")
<< testFileUrl("qtqmlModule.7.qml")
<< QString()
<< QStringList();
QTest::newRow("no import results in no QtObject availability")
<< testFileUrl("qtqmlModule.8.qml")
<< QString(testFileUrl("qtqmlModule.8.qml").toString() + QLatin1String(":4 QtObject is not a type\n"))
<< QStringList();
QTest::newRow("importing QtQml only results in no Item availability")
<< testFileUrl("qtqmlModule.9.qml")
<< QString(testFileUrl("qtqmlModule.9.qml").toString() + QLatin1String(":4 Item is not a type\n"))
<< QStringList();
}
// Test that the engine registers the QtQml module
void tst_qqmlengine::qtqmlModule()
{
QFETCH(QUrl, testFile);
QFETCH(QString, expectedError);
QFETCH(QStringList, expectedWarnings);
foreach (const QString &w, expectedWarnings)
QTest::ignoreMessage(QtWarningMsg, qPrintable(w));
QQmlEngine e;
QQmlComponent c(&e, testFile);
if (expectedError.isEmpty()) {
QObject *o = c.create();
QVERIFY(o);
delete o;
} else {
QCOMPARE(c.errorString(), expectedError);
}
}
QTEST_MAIN(tst_qqmlengine)
#include "tst_qqmlengine.moc"