diff options
Diffstat (limited to 'tests/auto/qml')
26 files changed, 617 insertions, 12 deletions
diff --git a/tests/auto/qml/debugger/qqmlpreview/tst_qqmlpreview.cpp b/tests/auto/qml/debugger/qqmlpreview/tst_qqmlpreview.cpp index 0c4fd568a9..802adaee14 100644 --- a/tests/auto/qml/debugger/qqmlpreview/tst_qqmlpreview.cpp +++ b/tests/auto/qml/debugger/qqmlpreview/tst_qqmlpreview.cpp @@ -26,6 +26,8 @@ public: private: ConnectResult startQmlProcess(const QString &qmlFile); void serveRequest(const QString &path); + void serveFile(const QString &path, const QByteArray &contents); + QList<QQmlDebugClient *> createClients() override; void verifyProcessOutputContains(const QString &string) const; @@ -42,6 +44,7 @@ private slots: void connect(); void load(); + void loadFromQrc(); void rerun(); void blacklist(); void error(); @@ -70,8 +73,7 @@ void tst_QQmlPreview::serveRequest(const QString &path) } else { QFile file(path); if (file.open(QIODevice::ReadOnly)) { - m_files.append(path); - m_client->sendFile(path, file.readAll()); + serveFile(path, file.readAll()); } else { m_filesNotFound.append(path); m_client->sendError(path); @@ -79,6 +81,12 @@ void tst_QQmlPreview::serveRequest(const QString &path) } } +void tst_QQmlPreview::serveFile(const QString &path, const QByteArray &contents) +{ + m_files.append(path); + m_client->sendFile(path, contents); +} + QList<QQmlDebugClient *> tst_QQmlPreview::createClients() { m_client = new QQmlPreviewClient(m_connection); @@ -162,6 +170,34 @@ void tst_QQmlPreview::load() QVERIFY(m_serviceErrors.isEmpty()); } +void tst_QQmlPreview::loadFromQrc() +{ + // One of the configuration files built into the "qml" executable. + const QString fromQrc(":/qt-project.org/imports/QmlRuntime/Config/default.qml"); + + QCOMPARE(QQmlDebugTest::connectTo( + QLibraryInfo::path(QLibraryInfo::BinariesPath) + "/qml", + QStringLiteral("QmlPreview"), fromQrc, true), + ConnectSuccess); + + QVERIFY(m_client); + QTRY_COMPARE(m_client->state(), QQmlDebugClient::Enabled); + + serveFile(fromQrc, R"( + import QtQuick + Item { + Component.onCompleted: console.log("default.qml replaced") + } + )"); + + m_client->triggerLoad(QUrl("qrc" + fromQrc)); + verifyProcessOutputContains("default.qml replaced"); + + m_process->stop(); + QTRY_COMPARE(m_client->state(), QQmlDebugClient::NotConnected); + QVERIFY(m_serviceErrors.isEmpty()); +} + void tst_QQmlPreview::rerun() { const QString file("window.qml"); diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index f9bd5c28aa..00854ccb43 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -319,6 +319,8 @@ private slots: void consoleLogSequence(); + void multiMatchingRegularExpression(); + public: Q_INVOKABLE QJSValue throwingCppMethod1(); Q_INVOKABLE void throwingCppMethod2(); @@ -6415,6 +6417,17 @@ void tst_QJSEngine::consoleLogSequence() QCOMPARE(stringListFetchCount, 1); } +void tst_QJSEngine::multiMatchingRegularExpression() +{ + QJSEngine engine; + const QJSValue result = engine.evaluate(R"( + "33312345.897".replace(/\./g, ",").replace(/\B(?=(\d{3})+(?!\d))/g, ".") + )"); + + QVERIFY(result.isString()); + QCOMPARE(result.toString(), "33.312.345,897"_L1); +} + QTEST_MAIN(tst_QJSEngine) #include "tst_qjsengine.moc" diff --git a/tests/auto/qml/qmllint/data/Enumerei/Main.qml b/tests/auto/qml/qmllint/data/Enumerei/Main.qml new file mode 100644 index 0000000000..61c27e4670 --- /dev/null +++ b/tests/auto/qml/qmllint/data/Enumerei/Main.qml @@ -0,0 +1,22 @@ +import QtQml 2.15 +import qtbug127308 + +QtObject { + Component.onCompleted: { + console.log("Unscoped access:") + try { console.log("EnumTester::Scoped", EnumTester.S1, EnumTester.S2 ); } catch (a) { console.log("EnumTester::Scoped", a) } + try { console.log("EnumTester::Unscoped", EnumTester.U1, EnumTester.U2 ); } catch (b) { console.log("EnumTester::Unscoped", b) } + try { console.log("EnumTesterScoped::Scoped", EnumTesterScoped.S1, EnumTesterScoped.S2 ); } catch (c) { console.log("EnumTesterScoped::Scoped", c) } + try { console.log("EnumTesterScoped::Unscoped", EnumTesterScoped.U1, EnumTesterScoped.U2 ); } catch (d) { console.log("EnumTesterScoped::Unscoped", d) } + try { console.log("EnumTesterUnscoped::Scoped", EnumTesterUnscoped.S1, EnumTesterUnscoped.S2 ); } catch (e) { console.log("EnumTesterUnscoped::Scoped", e) } + try { console.log("EnumTesterUnscoped::Unscoped", EnumTesterUnscoped.U1, EnumTesterUnscoped.U2 ); } catch (f) { console.log("EnumTesterUnscoped::Unscoped", f) } + console.log() + console.log("Scoped access:") + try { console.log("EnumTester::Scoped", EnumTester.Scoped.S1, EnumTester.Scoped.S2 ); } catch (g) { console.log("EnumTester::Scoped", g) } + try { console.log("EnumTester::Unscoped", EnumTester.Unscoped.U1, EnumTester.Unscoped.U2 ); } catch (h) { console.log("EnumTester::Unscoped", h) } + try { console.log("EnumTesterScoped::Scoped", EnumTesterScoped.Scoped.S1, EnumTesterScoped.Scoped.S2 ); } catch (i) { console.log("EnumTesterScoped::Scoped", i) } + try { console.log("EnumTesterScoped::Unscoped", EnumTesterScoped.Unscoped.U1, EnumTesterScoped.Unscoped.U2 ); } catch (j) { console.log("EnumTesterScoped::Unscoped", j) } + try { console.log("EnumTesterUnscoped::Scoped", EnumTesterUnscoped.Scoped.S1, EnumTesterUnscoped.Scoped.S2 ); } catch (k) { console.log("EnumTesterUnscoped::Scoped", k) } + try { console.log("EnumTesterUnscoped::Unscoped", EnumTesterUnscoped.Unscoped.U1, EnumTesterUnscoped.Unscoped.U2 ); } catch (l) { console.log("EnumTesterUnscoped::Unscoped", l) } + } +} diff --git a/tests/auto/qml/qmllint/data/Enumerei/plugins.qmltypes b/tests/auto/qml/qmllint/data/Enumerei/plugins.qmltypes new file mode 100644 index 0000000000..212215362a --- /dev/null +++ b/tests/auto/qml/qmllint/data/Enumerei/plugins.qmltypes @@ -0,0 +1,44 @@ +import QtQuick.tooling 1.2 + +// This file describes the plugin-supplied types contained in the library. +// It is used for QML tooling purposes only. +// +// This file was auto-generated by qmltyperegistrar. + +Module { + Component { + file: "main.h" + name: "EnumTester" + accessSemantics: "reference" + prototype: "QObject" + exports: ["Enumerei/EnumTester 1.0"] + exportMetaObjectRevisions: [256] + Enum { + name: "Unscoped" + values: ["U1", "U2"] + } + Enum { + name: "Scoped" + isScoped: true + values: ["S1", "S2"] + } + } + Component { + file: "main.h" + name: "EnumTesterScoped" + accessSemantics: "reference" + prototype: "QObject" + exports: ["Enumerei/EnumTesterScoped 1.0"] + enforcesScopedEnums: true + exportMetaObjectRevisions: [256] + Enum { + name: "Unscoped" + values: ["U1", "U2"] + } + Enum { + name: "Scoped" + isScoped: true + values: ["S1", "S2"] + } + } +} diff --git a/tests/auto/qml/qmllint/data/Enumerei/qmldir b/tests/auto/qml/qmllint/data/Enumerei/qmldir new file mode 100644 index 0000000000..aa031dd7e8 --- /dev/null +++ b/tests/auto/qml/qmllint/data/Enumerei/qmldir @@ -0,0 +1,3 @@ +module Enumerei +typeinfo plugins.qmltypes + diff --git a/tests/auto/qml/qmllint/data/enumValid.qml b/tests/auto/qml/qmllint/data/enumValid.qml new file mode 100644 index 0000000000..32971df070 --- /dev/null +++ b/tests/auto/qml/qmllint/data/enumValid.qml @@ -0,0 +1,11 @@ +import QtQml +import Enumerei + +QtObject { + property int a: EnumTester.S2 + property int b: EnumTester.U2 + property int c: EnumTesterScoped.U2 + + property int d: EnumTester.Scoped.S2 + property int e: EnumTesterScoped.Scoped.S2 +} diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp index 56e31dba8f..690a3a272a 100644 --- a/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -1288,6 +1288,7 @@ void TestQmllint::cleanQmlCode_data() QTest::newRow("listConversion") << QStringLiteral("listConversion.qml"); QTest::newRow("groupedAttachedLayout") << QStringLiteral("groupedAttachedLayout.qml"); QTest::newRow("constInvokable") << QStringLiteral("useConstInvokable.qml"); + QTest::newRow("scopedAndUnscopedEnums") << QStringLiteral("enumValid.qml"); } void TestQmllint::cleanQmlCode() diff --git a/tests/auto/qml/qmltyperegistrar/CMakeLists.txt b/tests/auto/qml/qmltyperegistrar/CMakeLists.txt index c36bc7bff8..ab89394fc8 100644 --- a/tests/auto/qml/qmltyperegistrar/CMakeLists.txt +++ b/tests/auto/qml/qmltyperegistrar/CMakeLists.txt @@ -91,6 +91,7 @@ qt_internal_add_resource(tst_qmltyperegistrar "resources" PREFIX "/" FILES + brokenEnums.json duplicatedExports.json ) diff --git a/tests/auto/qml/qmltyperegistrar/brokenEnums.json b/tests/auto/qml/qmltyperegistrar/brokenEnums.json new file mode 100644 index 0000000000..e54a58f9b7 --- /dev/null +++ b/tests/auto/qml/qmltyperegistrar/brokenEnums.json @@ -0,0 +1,57 @@ +[ + { + "classes": [ + { + "className": "QObject", + "object": true, + "qualifiedClassName": "QObject" + }, + { + "classInfos": [ + { + "name": "QML.Element", + "value": "auto" + }, + { + "name": "RegisterEnumClassesUnscoped", + "value": "true" + } + ], + "className": "EnumsExplicitlyUnscoped", + "lineNumber": 878, + "object": true, + "qualifiedClassName": "EnumsExplicitlyUnscoped", + "superClasses": [ + { + "access": "public", + "name": "QObject" + } + ] + }, + { + "classInfos": [ + { + "name": "QML.Element", + "value": "auto" + }, + { + "name": "RegisterEnumClassesUnscoped", + "value": "horst" + } + ], + "className": "EnumScopingConfused", + "lineNumber": 885, + "object": true, + "qualifiedClassName": "EnumScopingConfused", + "superClasses": [ + { + "access": "public", + "name": "QObject" + } + ] + } + ], + "inputFile": "tst_qmltyperegistrar.h", + "outputRevision": 68 + } +] diff --git a/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp b/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp index 980e3503a2..c2e4ef0397 100644 --- a/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp +++ b/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp @@ -417,6 +417,47 @@ void tst_qmltyperegistrar::duplicateExportWarnings() r.write(output); } +void tst_qmltyperegistrar::enumWarnings() +{ + QmlTypeRegistrar r; + r.setModuleVersions(QTypeRevision::fromVersion(1, 1), {}, false); + QString moduleName = "tstmodule"; + QString targetNamespace = "tstnamespace"; + r.setModuleNameAndNamespace(moduleName, targetNamespace); + + const auto expectWarning = [](const char *message) { + QTest::ignoreMessage(QtWarningMsg, message); + }; + + expectWarning("Warning: tst_qmltyperegistrar.h:: " + "Unrecognized value for RegisterEnumClassesUnscoped: horst"); + expectWarning("Warning: tst_qmltyperegistrar.h:: " + "Setting RegisterEnumClassesUnscoped to true has no effect."); + + QTest::failOnWarning(QRegularExpression(".*")); + + + MetaTypesJsonProcessor processor(true); + + QVERIFY(processor.processTypes({ ":/brokenEnums.json" })); + processor.postProcessTypes(); + processor.postProcessForeignTypes(); + + QVector<QJsonObject> types = processor.types(); + QVector<QJsonObject> typesforeign = processor.foreignTypes(); + r.setTypes(types, typesforeign); + + QString outputData; + QTextStream output(&outputData, QIODeviceBase::ReadWrite); + + r.write(output); + + QTemporaryFile pluginTypes; + QVERIFY(pluginTypes.open()); + + r.generatePluginTypes(pluginTypes.fileName()); +} + void tst_qmltyperegistrar::clonedSignal() { QVERIFY(qmltypesData.contains(R"(Signal { @@ -671,4 +712,17 @@ void tst_qmltyperegistrar::constReturnType() })")); } +void tst_qmltyperegistrar::enumsExplicitlyScoped() +{ + QVERIFY(qmltypesData.contains(R"(Component { + file: "tst_qmltyperegistrar.h" + name: "EnumsExplicitlyScoped" + accessSemantics: "reference" + prototype: "QObject" + exports: ["QmlTypeRegistrarTest/EnumsExplicitlyScoped 1.0"] + enforcesScopedEnums: true + exportMetaObjectRevisions: [256] + })")); +} + QTEST_MAIN(tst_qmltyperegistrar) diff --git a/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h b/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h index 15d2a92f8e..fdca40632b 100644 --- a/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h +++ b/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h @@ -565,6 +565,13 @@ public: Q_INVOKABLE const QObject *getObject() { return nullptr; } }; +class EnumsExplicitlyScoped : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_CLASSINFO("RegisterEnumClassesUnscoped", "false") +}; + class tst_qmltyperegistrar : public QObject { Q_OBJECT @@ -619,8 +626,11 @@ private slots: void listSignal(); void foreignNamespaceFromGadget(); + void enumWarnings(); void constReturnType(); + void enumsExplicitlyScoped(); + private: QByteArray qmltypesData; }; diff --git a/tests/auto/qml/qqmlcomponent/lifecyclewatcher.h b/tests/auto/qml/qqmlcomponent/lifecyclewatcher.h index 738fd86942..3d3bdfd562 100644 --- a/tests/auto/qml/qqmlcomponent/lifecyclewatcher.h +++ b/tests/auto/qml/qqmlcomponent/lifecyclewatcher.h @@ -15,10 +15,29 @@ class LifeCycleWatcher : public QObject, public QQmlParserStatus, public QQmlFin QML_ELEMENT Q_INTERFACES(QQmlParserStatus) Q_INTERFACES(QQmlFinalizerHook) + Q_PROPERTY(QString text MEMBER text) public: - void classBegin() override {states.push_back(1); } - void componentComplete() override {states.push_back(2);}; - void componentFinalized() override { states.push_back(3); } + void classBegin() override + { + states.push_back(1); + observedTexts.push_back(text); + } + + void componentComplete() override + { + states.push_back(2); + observedTexts.push_back(text); + } + + void componentFinalized() override + { + states.push_back(3); + observedTexts.push_back(text); + } + + QString text; QList<int> states; + QStringList observedTexts; }; + #endif diff --git a/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp b/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp index 1ccf7a6f23..59703d5c36 100644 --- a/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp +++ b/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp @@ -1361,8 +1361,7 @@ void tst_qqmlcomponent::loadFromModule() void tst_qqmlcomponent::loadFromModuleLifecycle() { QQmlEngine engine; - QList<int> loadFromModuleOrder; - QList<int> plainLoadOrder; + const QString text = "text"_L1; const QList<int> expected {1, 2, 3}; { QQmlComponent component(&engine); @@ -1371,19 +1370,58 @@ void tst_qqmlcomponent::loadFromModuleLifecycle() std::unique_ptr<QObject> root{ component.create() }; LifeCycleWatcher *watcher = qobject_cast<LifeCycleWatcher *>(root.get()); QVERIFY(watcher); - loadFromModuleOrder = watcher->states; - QCOMPARE(loadFromModuleOrder, expected); + QCOMPARE(watcher->states, expected); + QCOMPARE(watcher->observedTexts, QStringList(3)); + + const QString loaded = "load from module"_L1; + root.reset(component.createWithInitialProperties(QVariantMap{{text, loaded}})); + watcher = qobject_cast<LifeCycleWatcher *>(root.get()); + QVERIFY(watcher); + QCOMPARE(watcher->states, expected); + QCOMPARE(watcher->observedTexts, QStringList({QString(), loaded, loaded})); } + { QQmlComponent component(&engine); component.setData("import test; LifeCycleWatcher {}", {}); QVERIFY2(component.isReady(), qPrintable(component.errorString())); + std::unique_ptr<QObject> root{ component.create() }; LifeCycleWatcher *watcher = qobject_cast<LifeCycleWatcher *>(root.get()); QVERIFY(watcher); - plainLoadOrder = watcher->states; + QCOMPARE(watcher->states, expected); + QCOMPARE(watcher->observedTexts, QStringList(3)); + + const QString loaded = "load from data"_L1; + root.reset(component.createWithInitialProperties(QVariantMap{{text, loaded}})); + watcher = qobject_cast<LifeCycleWatcher *>(root.get()); + QVERIFY(watcher); + QCOMPARE(watcher->states, expected); + QCOMPARE(watcher->observedTexts, QStringList({QString(), loaded, loaded})); } - QCOMPARE(loadFromModuleOrder, plainLoadOrder); + + { + QQmlComponent component(&engine); + const QString compiled = "inline"_L1; + component.setData("import test; LifeCycleWatcher { text: 'inline' }", {}); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + + std::unique_ptr<QObject> root{ component.create() }; + LifeCycleWatcher *watcher = qobject_cast<LifeCycleWatcher *>(root.get()); + QVERIFY(watcher); + QCOMPARE(watcher->states, expected); + QCOMPARE(watcher->observedTexts, QStringList({QString(), compiled, compiled})); + + const QString loaded = "overridden"_L1; + std::unique_ptr<QObject> withProperties( + component.createWithInitialProperties(QVariantMap{{text, loaded}})); + watcher = qobject_cast<LifeCycleWatcher *>(withProperties.get()); + QVERIFY(watcher); + QCOMPARE(watcher->states, expected); + QCOMPARE(watcher->observedTexts, QStringList({QString(), loaded, loaded})); + } + + } struct CallVerifyingIncubtor : QQmlIncubator diff --git a/tests/auto/qml/qqmldelegatemodel/data/proxyModelWithDelayedSourceModelInListView.qml b/tests/auto/qml/qqmldelegatemodel/data/proxyModelWithDelayedSourceModelInListView.qml new file mode 100644 index 0000000000..b6733bd38c --- /dev/null +++ b/tests/auto/qml/qqmldelegatemodel/data/proxyModelWithDelayedSourceModelInListView.qml @@ -0,0 +1,30 @@ +import QtQuick +import Test + +Window { + id: root + title: listView.count + + property alias listView: listView + property ProxySourceModel connectionModel: null + + Component { + id: modelComponent + ProxySourceModel {} + } + + ListView { + id: listView + anchors.fill: parent + + delegate: Text { + text: model.Name + } + + model: ProxyModel { + sourceModel: root.connectionModel + } + } + + Component.onCompleted: root.connectionModel = modelComponent.createObject(root) +} diff --git a/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp b/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp index e9c22ca1e6..336d0bd679 100644 --- a/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp +++ b/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <QtTest/qtest.h> +#include <QtCore/qsortfilterproxymodel.h> #include <QtCore/QConcatenateTablesProxyModel> #include <QtCore/qtimer.h> #include <QtGui/QStandardItemModel> @@ -44,6 +45,7 @@ private slots: void persistedItemsStayInCache(); void doNotUnrefObjectUnderConstruction(); void clearCacheDuringInsertion(); + void proxyModelWithDelayedSourceModelInListView(); }; class BaseAbstractItemModel : public QAbstractItemModel @@ -501,6 +503,77 @@ void tst_QQmlDelegateModel::clearCacheDuringInsertion() QTRY_COMPARE(object->property("testModel").toInt(), 0); } +class ProxySourceModel : public QAbstractListModel +{ + Q_OBJECT + QML_ELEMENT +public: + explicit ProxySourceModel(QObject *parent = nullptr) + : QAbstractListModel(parent) + { + for (int i = 0; i < rows; ++i) { + beginInsertRows(QModelIndex(), i, i); + endInsertRows(); + } + } + + ~ProxySourceModel() override = default; + + int rowCount(const QModelIndex &) const override + { + return rows; + } + + QVariant data(const QModelIndex &, int ) const override + { + return "Hello"; + } + + QHash<int, QByteArray> roleNames() const override + { + QHash<int, QByteArray> roles = QAbstractListModel::roleNames(); + roles[Qt::UserRole + 1] = "Name"; + + return roles; + } + + static const int rows = 1; +}; + +class ProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(QAbstractItemModel *sourceModel READ sourceModel WRITE setSourceModel) + +public: + explicit ProxyModel(QObject *parent = nullptr) + : QSortFilterProxyModel(parent) + { + } + + ~ProxyModel() override = default; +}; + +// Checks that the correct amount of delegates are created when using a proxy +// model whose source model is set after a delay. +void tst_QQmlDelegateModel::proxyModelWithDelayedSourceModelInListView() +{ + qmlRegisterTypesAndRevisions<ProxySourceModel>("Test", 1); + qmlRegisterTypesAndRevisions<ProxyModel>("Test", 1); + + QQuickApplicationHelper helper(this, "proxyModelWithDelayedSourceModelInListView.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + auto *listView = window->property("listView").value<QQuickListView *>(); + QVERIFY(listView); + const auto delegateModel = QQuickItemViewPrivate::get(listView)->model; + QTRY_COMPARE(listView->count(), 1); +} + QTEST_MAIN(tst_QQmlDelegateModel) #include "tst_qqmldelegatemodel.moc" diff --git a/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp index b82a1f4174..bf68de0d2f 100644 --- a/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp +++ b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp @@ -79,6 +79,7 @@ private slots: void lockedRootObject(); void crossReferencingSingletonsDeletion(); void bindingInstallUseAfterFree(); + void attachedObjectOfUnregistered(); public slots: QObject *createAQObjectForOwnershipTest () @@ -1708,6 +1709,42 @@ void tst_qqmlengine::bindingInstallUseAfterFree() QVERIFY(o); } +class UnregisteredAttached : public QObject +{ + Q_OBJECT +public: + UnregisteredAttached(QObject *parent = nullptr) : QObject(parent) {} +}; + +class Unregistered : public QObject +{ + Q_OBJECT + QML_ATTACHED(UnregisteredAttached) +public: + static UnregisteredAttached *qmlAttachedProperties(QObject *obj) + { + return new UnregisteredAttached(obj); + } +}; + +void tst_qqmlengine::attachedObjectOfUnregistered() +{ + QObject o; + + QObject *a = qmlAttachedPropertiesObject<Unregistered>(&o); + QVERIFY(a); + QVERIFY(qobject_cast<UnregisteredAttached *>(a)); + + QObject *b = qmlAttachedPropertiesObject<Unregistered>(&o); + QCOMPARE(a, b); + + QObject o2; + QObject *c = qmlAttachedPropertiesObject<Unregistered>(&o2); + QVERIFY(c); + QVERIFY(qobject_cast<UnregisteredAttached *>(c)); + QVERIFY(c != a); +} + QTEST_MAIN(tst_qqmlengine) #include "tst_qqmlengine.moc" diff --git a/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp b/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp index 92da003ad4..d57dfe2956 100644 --- a/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp +++ b/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp @@ -40,6 +40,7 @@ private slots: void implicitWithDependencies(); void qualifiedScriptImport(); void invalidImportUrl(); + void sanitizeUNCPath(); }; void tst_QQmlImport::cleanup() @@ -150,6 +151,15 @@ void tst_QQmlImport::invalidImportUrl() ":2 Cannot resolve URL for import \"file://./MyModuleName\"\n")); } +void tst_QQmlImport::sanitizeUNCPath() +{ + QString wildUNCPath = QStringLiteral("//Server2/Sh%re/foO/qmldir"); + QQmlImportDatabase::sanitizeUNCPath(&wildUNCPath); + + // It lowercases the "server" component of the path. The rest is left as-is + QCOMPARE(wildUNCPath, QStringLiteral("//server2/Sh%re/foO/qmldir")); +} + void tst_QQmlImport::testDesignerSupported() { QQuickView *window = new QQuickView(); diff --git a/tests/auto/qml/qqmllanguage/data/TextItem.qml b/tests/auto/qml/qqmllanguage/data/TextItem.qml new file mode 100644 index 0000000000..1f6f171b41 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/TextItem.qml @@ -0,0 +1,7 @@ +import QtQuick + +Text { + property bool testBool: false + font.family: "Ar" + "iallll" + onTestBoolChanged: font.pixelSize = 16; +} diff --git a/tests/auto/qml/qqmllanguage/data/Wrap.qml b/tests/auto/qml/qqmllanguage/data/Wrap.qml new file mode 100644 index 0000000000..365350f16e --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/Wrap.qml @@ -0,0 +1,6 @@ +import QtQuick + +TextItem { + font.pixelSize: height * 0.9 + testBool: true +} diff --git a/tests/auto/qml/qqmllanguage/testtypes.cpp b/tests/auto/qml/qqmllanguage/testtypes.cpp index 12fe042c20..c65cbe329d 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.cpp +++ b/tests/auto/qml/qqmllanguage/testtypes.cpp @@ -4,6 +4,10 @@ #include <private/qv4qmlcontext_p.h> +#include <QtQml/qqmlextensionplugin.h> + +Q_IMPORT_QML_PLUGIN(testhelperPlugin) + static QObject *myTypeObjectSingleton(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine); diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 7992896506..b7dff336af 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -6222,7 +6222,7 @@ class EnumTester : public QObject public: enum Types { - FIRST = 0, + FIRST = 42, SECOND, THIRD }; @@ -6236,13 +6236,18 @@ void tst_qqmllanguage::qualifiedScopeInCustomParser() QQmlEngine engine; QQmlComponent component(&engine); component.setData("import QtQml.Models 2.12\n" + "import QtQml\n" "import scoped.custom.test 1.0 as BACKEND\n" "ListModel {\n" + " id: root\n" + " property int num: -1\n" " ListElement { text: \"a\"; type: BACKEND.EnumTester.FIRST }\n" + " Component.onCompleted: { root.num = root.get(0).type }\n" "}\n", QUrl()); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QScopedPointer<QObject> obj(component.create()); QVERIFY(!obj.isNull()); + QCOMPARE(obj->property("num").toInt(), 42); } void tst_qqmllanguage::checkUncreatableNoReason() @@ -8262,6 +8267,14 @@ void tst_qqmllanguage::overrideInnerBinding() QCOMPARE(o->property("width").toReal(), 20.0); QCOMPARE(o->property("innerWidth").toReal(), 20.0); + + QQmlComponent c2(&e, testFileUrl("Wrap.qml")); + QVERIFY2(c2.isReady(), qPrintable(c2.errorString())); + o.reset(c2.create()); + QVERIFY(!o.isNull()); + + QFont font = qvariant_cast<QFont>(o->property("font")); + QCOMPARE(font.family(), "Ariallll"); } QTEST_MAIN(tst_qqmllanguage) diff --git a/tests/auto/qml/qqmltypeloader/data/SlowImporter/A.qml b/tests/auto/qml/qqmltypeloader/data/SlowImporter/A.qml new file mode 100644 index 0000000000..421a0918ed --- /dev/null +++ b/tests/auto/qml/qqmltypeloader/data/SlowImporter/A.qml @@ -0,0 +1,2 @@ +import Slow +SlowStuff {} diff --git a/tests/auto/qml/qqmltypeloader/data/SlowImporter/qmldir b/tests/auto/qml/qqmltypeloader/data/SlowImporter/qmldir new file mode 100644 index 0000000000..bf2eff032d --- /dev/null +++ b/tests/auto/qml/qqmltypeloader/data/SlowImporter/qmldir @@ -0,0 +1,2 @@ +module SlowImporter +A 1.0 A.qml diff --git a/tests/auto/qml/qqmltypeloader/tst_qqmltypeloader.cpp b/tests/auto/qml/qqmltypeloader/tst_qqmltypeloader.cpp index 717c900c00..cb4610d01b 100644 --- a/tests/auto/qml/qqmltypeloader/tst_qqmltypeloader.cpp +++ b/tests/auto/qml/qqmltypeloader/tst_qqmltypeloader.cpp @@ -34,6 +34,7 @@ private slots: void trimCache3(); void keepSingleton(); void keepRegistrations(); + void importAndDestroy(); void intercept(); void redirect(); void qmlSingletonWithinModule(); @@ -48,6 +49,7 @@ private slots: void declarativeCppAndQmlDir(); void signalHandlersAreCompatible(); void loadTypeOnShutdown(); + void floodTypeLoaderEventQueue(); private: void checkSingleton(const QString & dataDirectory); @@ -418,6 +420,49 @@ public: } }; +void tst_QQMLTypeLoader::importAndDestroy() +{ +#if defined Q_OS_ANDROID || defined Q_OS_IOS + QSKIP("Data directory is not in the host file system on Android and iOS"); +#endif + qmlClearTypeRegistrations(); + + QQmlEngine engine; + NetworkAccessManagerFactory factory; + engine.setNetworkAccessManagerFactory(&factory); + QQmlComponent component(&engine); + + // We redirect the import through the network access manager to make it asynchronous. + // Otherwise the type loader will just directly call back into the main thread and we + // won't get a chance to do mischief before initializeEngine gets called for the "Slow" + // module. Note that the "Slow" module needs to be loaded from a "local" URL since plugins + // can only be loaded locally. + + // Detour through testFileUrl to get the path right on windows ('C:' and things like that) + QUrl url = testFileUrl("SlowImporter"); + url.setScheme(url.scheme() + QLatin1String("+debug")); + + component.setData(QString::fromLatin1(R"( + import '%1' + A {} + )").arg(url.toString()).toUtf8(), QUrl()); + + while (!QQmlMetaType::qmlType( + QStringLiteral("SlowStuff"), QStringLiteral("Slow"), QTypeRevision()) + .isValid()) { + // busy wait for type to be registered + QVERIFY2(!component.isError(), qPrintable(component.errorString())); + } + + // Now the type loader thread is likely waiting for the main thread to process the + // initializeEngine callback. We destroy the engine here to trigger the situation where the main + // thread needs to wake the type loader thread one more time to process the isShutdown flag. + // If it fails to do so, the type loader thread waits indefinitely for the main thread and the + // engine dtor in turn waits indefinitely for the type loader thread to terminate. + + // The point of this test is that it _should not_ deadlock here. +} + void tst_QQMLTypeLoader::intercept() { #ifdef Q_OS_ANDROID @@ -768,6 +813,22 @@ void tst_QQMLTypeLoader::loadTypeOnShutdown() QVERIFY(dead2); } +void tst_QQMLTypeLoader::floodTypeLoaderEventQueue() +{ + QQmlEngine engine; + + // Flood the typeloader with useless messages. + for (int i = 0; i < 1000; ++i) { + QQmlComponent c(&engine); + c.setData(QString::fromLatin1(R"( + import "barf:/not/actually/there%1" + SomeElement {} + )").arg(i).toUtf8(), QUrl::fromLocalFile(QString::fromLatin1("foo%1.qml").arg(i))); + QVERIFY(!c.isReady()); + // Should not crash when destrying the QQmlComponent. + } +} + QTEST_MAIN(tst_QQMLTypeLoader) #include "tst_qqmltypeloader.moc" diff --git a/tests/auto/qml/qv4mm/data/forInOnProxyMarksTarget.qml b/tests/auto/qml/qv4mm/data/forInOnProxyMarksTarget.qml new file mode 100644 index 0000000000..23e3820f2c --- /dev/null +++ b/tests/auto/qml/qv4mm/data/forInOnProxyMarksTarget.qml @@ -0,0 +1,23 @@ +import QtQml + +QtObject { + property bool wasInUseBeforeRevoke: false + property bool wasInUseAfterRevoke: false + + Component.onCompleted: { + let handler = {}; + let target = {prop1: 1, prop2: 2}; + + let proxy = Proxy.revocable(target, handler); + wasInUseBeforeRevoke = __inUse(target) + target = null; + + for (var prop in proxy.proxy) { + prop[4] = 10; + proxy.revoke() + gc() + wasInUseAfterRevoke = __inUse() + break + } + } +} diff --git a/tests/auto/qml/qv4mm/tst_qv4mm.cpp b/tests/auto/qml/qv4mm/tst_qv4mm.cpp index e5f8951825..832abfa4a0 100644 --- a/tests/auto/qml/qv4mm/tst_qv4mm.cpp +++ b/tests/auto/qml/qv4mm/tst_qv4mm.cpp @@ -27,6 +27,7 @@ private slots: void accessParentOnDestruction(); void cleanInternalClasses(); void createObjectsOnDestruction(); + void forInOnProxyMarksTarget(); }; tst_qv4mm::tst_qv4mm() @@ -206,6 +207,33 @@ void tst_qv4mm::createObjectsOnDestruction() QCOMPARE(obj->property("ok").toBool(), true); } + +QV4::ReturnedValue method_in_use(const QV4::FunctionObject *, const QV4::Value *, const QV4::Value *argv, int argc) { + static QV4::Value::HeapBasePtr target = nullptr; + + if (argc == 1) { + target = argv[0].heapObject(); + } + + Q_ASSERT(target); + return QV4::Encode(target->inUse()); +} + +void tst_qv4mm::forInOnProxyMarksTarget() { + QQmlEngine engine; + auto *v4 = engine.handle(); + auto globalObject = v4->globalObject; + globalObject->defineDefaultProperty(QStringLiteral("__inUse"), method_in_use); + + QQmlComponent comp(&engine, testFileUrl("forInOnProxyMarksTarget.qml")); + QVERIFY(comp.isReady()); + std::unique_ptr<QObject> root {comp.create()}; + + QVERIFY(root); + QVERIFY(root->property("wasInUseBeforeRevoke").toBool()); + QVERIFY(root->property("wasInUseAfterRevoke").toBool()); +} + QTEST_MAIN(tst_qv4mm) #include "tst_qv4mm.moc" |
