From 62615291477b6268263fad5c1f767d6291b541c4 Mon Sep 17 00:00:00 2001 From: Warwick Allison Date: Fri, 18 Sep 2009 12:57:15 +1000 Subject: Make SqlDatabase testable and add a basic test. --- src/declarative/qml/qmlengine.cpp | 66 +++++++++++++++++++ src/declarative/qml/qmlengine.h | 4 ++ src/declarative/qml/qmlengine_p.h | 1 + src/declarative/qml/qmlsqldatabase.cpp | 41 +----------- tests/auto/declarative/sql/data/test1.js | 18 ++++++ tests/auto/declarative/sql/tst_sql.cpp | 108 +++++++++++++++++++++++++++++++ 6 files changed, 198 insertions(+), 40 deletions(-) create mode 100644 tests/auto/declarative/sql/data/test1.js create mode 100644 tests/auto/declarative/sql/tst_sql.cpp diff --git a/src/declarative/qml/qmlengine.cpp b/src/declarative/qml/qmlengine.cpp index 1aa2cb9..a00b175 100644 --- a/src/declarative/qml/qmlengine.cpp +++ b/src/declarative/qml/qmlengine.cpp @@ -108,6 +108,45 @@ QScriptValue desktopOpenUrl(QScriptContext *ctxt, QScriptEngine *e) return e->newVariant(QVariant(ret)); } +// XXX Something like this should be exported by Qt. +static QString userLocalDataPath(const QString& app) +{ + QString result; + +#ifdef Q_OS_WIN +#ifndef Q_OS_WINCE + QLibrary library(QLatin1String("shell32")); +#else + QLibrary library(QLatin1String("coredll")); +#endif // Q_OS_WINCE + typedef BOOL (WINAPI*GetSpecialFolderPath)(HWND, LPWSTR, int, BOOL); + GetSpecialFolderPath SHGetSpecialFolderPath = (GetSpecialFolderPath)library.resolve("SHGetSpecialFolderPathW"); + if (SHGetSpecialFolderPath) { + wchar_t path[MAX_PATH]; + SHGetSpecialFolderPath(0, path, CSIDL_APPDATA, FALSE); + result = QString::fromWCharArray(path); + } +#endif // Q_OS_WIN + +#ifdef Q_OS_MAC + result = QLatin1String(qgetenv("HOME")); + result += "/Library/Application Support"; +#else + if (result.isEmpty()) { + // Fallback: UNIX style + result = QLatin1String(qgetenv("XDG_DATA_HOME")); + if (result.isEmpty()) { + result = QLatin1String(qgetenv("HOME")); + result += QLatin1String("/.local/share"); + } + } +#endif + + result += QLatin1Char('/'); + result += app; + return result; +} + QmlEnginePrivate::QmlEnginePrivate(QmlEngine *e) : rootContext(0), currentExpression(0), isDebugging(false), contextClass(0), objectClass(0), valueTypeClass(0), @@ -121,6 +160,7 @@ QmlEnginePrivate::QmlEnginePrivate(QmlEngine *e) qtObject.setProperty(QLatin1String("DesktopServices"), desktopObject); scriptEngine.globalObject().setProperty(QLatin1String("Qt"), qtObject); + offlineStoragePath = userLocalDataPath(QLatin1String("Nokia/Qt/QML/OfflineStorage")); qt_add_qmlxmlhttprequest(&scriptEngine); qt_add_qmlsqldatabase(&scriptEngine); @@ -1601,6 +1641,32 @@ void QmlEngine::addImportPath(const QString& path) } /*! + \property QmlEngine::offlineStoragePath + \brief the directory for storing offline user data + + Returns the directory where SQL and other offline + storage is placed. + + QFxWebView and the SQL databases created with openDatabase() + are stored here. + + The default is Nokia/Qt/QML/Databases/ in the platform-standard + user application data directory. +*/ +void QmlEngine::setOfflineStoragePath(const QString& dir) +{ + Q_D(QmlEngine); + d->offlineStoragePath = dir; +} + +QString QmlEngine::offlineStoragePath() const +{ + Q_D(const QmlEngine); + return d->offlineStoragePath; +} + + +/*! \internal Adds information to \a imports such that subsequent calls to resolveType() diff --git a/src/declarative/qml/qmlengine.h b/src/declarative/qml/qmlengine.h index 8caa505..b4233e4 100644 --- a/src/declarative/qml/qmlengine.h +++ b/src/declarative/qml/qmlengine.h @@ -65,6 +65,7 @@ class QScriptContext; class QNetworkAccessManager; class Q_DECLARATIVE_EXPORT QmlEngine : public QObject { + Q_PROPERTY(QString offlineStoragePath READ offlineStoragePath WRITE setOfflineStoragePath) Q_OBJECT public: QmlEngine(QObject *p = 0); @@ -79,6 +80,9 @@ public: void setNetworkAccessManager(QNetworkAccessManager *); QNetworkAccessManager *networkAccessManager() const; + void setOfflineStoragePath(const QString& dir); + QString offlineStoragePath() const; + QUrl baseUrl() const; void setBaseUrl(const QUrl &); diff --git a/src/declarative/qml/qmlengine_p.h b/src/declarative/qml/qmlengine_p.h index 0eeae0c..cca8355 100644 --- a/src/declarative/qml/qmlengine_p.h +++ b/src/declarative/qml/qmlengine_p.h @@ -201,6 +201,7 @@ public: QmlCompositeTypeManager typeManager; QStringList fileImportPath; + QString offlineStoragePath; mutable quint32 uniqueId; quint32 getUniqueId() const { diff --git a/src/declarative/qml/qmlsqldatabase.cpp b/src/declarative/qml/qmlsqldatabase.cpp index 144a170..fda0342 100644 --- a/src/declarative/qml/qmlsqldatabase.cpp +++ b/src/declarative/qml/qmlsqldatabase.cpp @@ -217,45 +217,6 @@ static QScriptValue qmlsqldatabase_transaction(QScriptContext *context, QScriptE return engine->undefinedValue(); } -// XXX Something like this should be exported by Qt. -static QString userLocalDataPath(const QString& app) -{ - QString result; - -#ifdef Q_OS_WIN -#ifndef Q_OS_WINCE - QLibrary library(QLatin1String("shell32")); -#else - QLibrary library(QLatin1String("coredll")); -#endif // Q_OS_WINCE - typedef BOOL (WINAPI*GetSpecialFolderPath)(HWND, LPWSTR, int, BOOL); - GetSpecialFolderPath SHGetSpecialFolderPath = (GetSpecialFolderPath)library.resolve("SHGetSpecialFolderPathW"); - if (SHGetSpecialFolderPath) { - wchar_t path[MAX_PATH]; - SHGetSpecialFolderPath(0, path, CSIDL_APPDATA, FALSE); - result = QString::fromWCharArray(path); - } -#endif // Q_OS_WIN - -#ifdef Q_OS_MAC - result = QLatin1String(qgetenv("HOME")); - result += "/Library/Application Support"; -#else - if (result.isEmpty()) { - // Fallback: UNIX style - result = QLatin1String(qgetenv("XDG_DATA_HOME")); - if (result.isEmpty()) { - result = QLatin1String(qgetenv("HOME")); - result += QLatin1String("/.local/share"); - } - } -#endif - - result += QLatin1Char('/'); - result += app; - return result; -} - static QScriptValue qmlsqldatabase_open(QScriptContext *context, QScriptEngine *engine) { @@ -279,7 +240,7 @@ static QScriptValue qmlsqldatabase_open(QScriptContext *context, QScriptEngine * database = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), dbid); } if (!database.isOpen()) { - QString basename = userLocalDataPath(QLatin1String("Nokia/Qt/QML/Databases/")); + QString basename = QmlEnginePrivate::get(engine)->offlineStoragePath + "/Databases/"; QDir().mkpath(basename); basename += dbid; database.setDatabaseName(basename+QLatin1String(".sqllite")); diff --git a/tests/auto/declarative/sql/data/test1.js b/tests/auto/declarative/sql/data/test1.js new file mode 100644 index 0000000..2ae9988 --- /dev/null +++ b/tests/auto/declarative/sql/data/test1.js @@ -0,0 +1,18 @@ +var db; +db = openDatabase("QmlTestDB", "", "Test database from Qt autotests", 1000000); +var r="testerror"; + +// Asynchronous in WebKit, so must wait before calling test() +db.transaction(function(tx) { + r = "passed"; + tx.executeSql('CREATE TABLE IF NOT EXISTS Greeting(salutation TEXT, salutee TEXT)', [], + function(tx, rs) { }, function(tx, error) { r="FAILED: "+error.message }); + tx.executeSql('INSERT INTO Greeting VALUES(?, ?)', [ 'hello', 'world' ], + function(tx, rs) { }, function(tx, error) { r="FAILED: "+error.message }); +}, function(tx, error) { r="TXFAILED: "+error.message }, function(tx, result) { if (r=="testerror") r="passed" }); + + +function test() +{ + return r; +} diff --git a/tests/auto/declarative/sql/tst_sql.cpp b/tests/auto/declarative/sql/tst_sql.cpp new file mode 100644 index 0000000..3179c99 --- /dev/null +++ b/tests/auto/declarative/sql/tst_sql.cpp @@ -0,0 +1,108 @@ +#include +#include "../../../shared/util.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class tst_sql : public QObject +{ + Q_OBJECT +public: + tst_sql() {} + +private slots: + void verifyAgainstWebKit_data(); + void verifyAgainstWebKit(); + +private: + QmlEngine engine; +}; + +class QWebPageWithJavaScriptConsoleMessages : public QWebPage { +public: + void javaScriptConsoleMessage(const QString& message, int lineNumber, const QString& sourceID) + { +qDebug() << sourceID << ":" << lineNumber << ":" << message; + } +}; + +void removeRecursive(const QString& dirname) +{ + QDir dir(dirname); + QFileInfoList entries(dir.entryInfoList(QDir::Dirs|QDir::Files|QDir::NoDotAndDotDot)); + for (int i = 0; i < entries.count(); ++i) + if (entries[i].isDir()) + removeRecursive(entries[i].filePath()); + else + dir.remove(entries[i].fileName()); + QDir().rmdir(dirname); +} + + +void tst_sql::verifyAgainstWebKit_data() +{ + QTest::addColumn("jsfile"); // The input file + QTest::addColumn("result"); // The required output from the js test() function + QTest::addColumn("databases"); // The number of databases that should have been created + + QTest::newRow("basic creation") << "data/test1.js" << "passed" << 1; +} + +void tst_sql::verifyAgainstWebKit() +{ + // Tests QML SQL Database support, and tests the same thing against + // WebKit (HTML5) support to validate the test. + // + // WebKit SQL is asynchronous, so tests are divided into code plus a test() + // function which is executed "later" (via QTRY_). + // + QFETCH(QString, jsfile); + QFETCH(QString, result); + QFETCH(int, databases); + + QString tmpdir = QString(SRCDIR)+"/output"; + + // QML... + QString qml= + "import Qt 4.6\n" + "Text { Script { source: \""+jsfile+"\" } text: test() }"; + + // Check default storage path (we can't use it since we don't want to mess with user's data) + QVERIFY(engine.offlineStoragePath().contains("Nokia")); + QVERIFY(engine.offlineStoragePath().contains("OfflineStorage")); + engine.setOfflineStoragePath(tmpdir); + QmlComponent component(&engine, qml.toUtf8(), QUrl::fromLocalFile(SRCDIR "/empty.qml")); // just a file for relative local imports + QFxText *text = qobject_cast(component.create()); + QVERIFY(text != 0); + QCOMPARE(text->text(),result); + QCOMPARE(QDir(tmpdir+"/Databases").entryInfoList(QDir::Files|QDir::NoDotAndDotDot).count(), databases*2); // *2 = .ini file + .sqlite file + + // WebKit... + QFile f(jsfile); + QVERIFY(f.open(QIODevice::ReadOnly)); + QString js=f.readAll(); + + QWebPageWithJavaScriptConsoleMessages webpage; + QDir().mkpath(tmpdir); + webpage.settings()->setOfflineStoragePath(tmpdir); + webpage.mainFrame()->evaluateJavaScript(js); + QTest::qWait(200); // WebKit db access is asynchronous + QTRY_COMPARE(webpage.mainFrame()->evaluateJavaScript("test()").toString(),result); + + QWebSecurityOrigin origin = webpage.mainFrame()->securityOrigin(); + QList dbs = origin.databases(); + QCOMPARE(dbs.count(), databases); + + + removeRecursive(tmpdir); +} + +QTEST_MAIN(tst_sql) + +#include "tst_sql.moc" -- cgit v0.12