diff options
Diffstat (limited to 'tests/auto')
153 files changed, 4808 insertions, 706 deletions
diff --git a/tests/auto/qml/debugger/CMakeLists.txt b/tests/auto/qml/debugger/CMakeLists.txt index edd268cb94..a8bb089e15 100644 --- a/tests/auto/qml/debugger/CMakeLists.txt +++ b/tests/auto/qml/debugger/CMakeLists.txt @@ -5,7 +5,7 @@ add_subdirectory(qqmldebugtranslationservice) add_subdirectory(qpacketprotocol) add_subdirectory(qqmlnativeconnector) -if(NOT CMAKE_CROSSCOMPILING) +if(NOT (CMAKE_CROSSCOMPILING OR (MACOS AND TEST_architecture_arch STREQUAL "x86_64"))) add_subdirectory(qdebugmessageservice) add_subdirectory(qqmldebugtranslationclient) add_subdirectory(qqmlenginedebugservice) @@ -23,7 +23,7 @@ if(QT_FEATURE_private_tests) add_subdirectory(qqmldebugclient) add_subdirectory(qqmldebuglocal) add_subdirectory(qv4debugger) - if(NOT CMAKE_CROSSCOMPILING) + if(NOT (CMAKE_CROSSCOMPILING OR (MACOS AND TEST_architecture_arch STREQUAL "x86_64"))) add_subdirectory(qqmldebugservice) endif() endif() diff --git a/tests/auto/qml/debugger/qqmldebugtranslationclient/tst_qqmldebugtranslationclient.cpp b/tests/auto/qml/debugger/qqmldebugtranslationclient/tst_qqmldebugtranslationclient.cpp index 2cdf0a5029..28dad81be6 100644 --- a/tests/auto/qml/debugger/qqmldebugtranslationclient/tst_qqmldebugtranslationclient.cpp +++ b/tests/auto/qml/debugger/qqmldebugtranslationclient/tst_qqmldebugtranslationclient.cpp @@ -60,9 +60,10 @@ public: } private slots: - void init() + void init() override { QQmlDebugTest::initTestCase(); + QQmlDebugTest::init(); initDebugTranslationConnection(); QVersionedPacket<QQmlDebugConnector> packet; diff --git a/tests/auto/qml/debugger/qqmldebugtranslationservice/tst_qqmldebugtranslationservice.cpp b/tests/auto/qml/debugger/qqmldebugtranslationservice/tst_qqmldebugtranslationservice.cpp index 7519dd3043..85d4c77dd6 100644 --- a/tests/auto/qml/debugger/qqmldebugtranslationservice/tst_qqmldebugtranslationservice.cpp +++ b/tests/auto/qml/debugger/qqmldebugtranslationservice/tst_qqmldebugtranslationservice.cpp @@ -76,8 +76,9 @@ private slots: QVERIFY(hooks->qt_qmlDebugEnableService(qPrintable(QQmlDebugTranslationServiceImpl::s_key))); } - void init() + void init() override { + QQmlDebugTest::init(); hooks->qt_qmlDebugClearBuffer(); QVERIFY(currentDebugServiceMessage().isEmpty()); } diff --git a/tests/auto/qml/debugger/qqmlenginedebugservice/data/debuggerCrashOnAttach.qml b/tests/auto/qml/debugger/qqmlenginedebugservice/data/debuggerCrashOnAttach.qml new file mode 100644 index 0000000000..ea99518425 --- /dev/null +++ b/tests/auto/qml/debugger/qqmlenginedebugservice/data/debuggerCrashOnAttach.qml @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite 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 QtQuick.Controls 2.5 + +Text { + id:text + font.weight: Font.Bold +} + diff --git a/tests/auto/qml/debugger/qqmlenginedebugservice/tst_qqmlenginedebugservice.cpp b/tests/auto/qml/debugger/qqmlenginedebugservice/tst_qqmlenginedebugservice.cpp index bed8cb9c46..e33b9fd0e8 100644 --- a/tests/auto/qml/debugger/qqmlenginedebugservice/tst_qqmlenginedebugservice.cpp +++ b/tests/auto/qml/debugger/qqmlenginedebugservice/tst_qqmlenginedebugservice.cpp @@ -160,6 +160,7 @@ private slots: void watch_expression_data(); void watch_context(); void watch_file(); + void debuggerCrashOnAttach(); void queryAvailableEngines(); void queryRootContexts(); @@ -355,7 +356,8 @@ void tst_QQmlEngineDebugService::initTestCase() "itemWithFunctions.qml", "rectangleWithTransitions.qml", "customTypes.qml", - "jsonTest.qml" + "jsonTest.qml", + "debuggerCrashOnAttach.qml" }; for (auto file : fileNames) { @@ -503,6 +505,7 @@ void tst_QQmlEngineDebugService::watch_object() int origWidth = m_rootItem->property("width").toInt(); int origHeight = m_rootItem->property("height").toInt(); + m_rootItem->setProperty("width", origWidth*2); QVERIFY(QQmlDebugTest::waitForSignal(m_dbg, SIGNAL(valueChanged(QByteArray,QVariant)))); m_rootItem->setProperty("height", origHeight*2); @@ -670,7 +673,7 @@ void tst_QQmlEngineDebugService::queryRootContexts() // root context query sends only root object data - it doesn't fill in // the children or property info QCOMPARE(context.objects.count(), 0); - QCOMPARE(context.contexts.count(), 7); + QCOMPARE(context.contexts.count(), 8); QVERIFY(context.contexts[0].debugId >= 0); QCOMPARE(context.contexts[0].name, QString("tst_QQmlDebug_childContext")); } @@ -1380,6 +1383,18 @@ void tst_QQmlEngineDebugService::createObjectOnDestruction() QCOMPARE(spy.count(), 2); } +void tst_QQmlEngineDebugService::debuggerCrashOnAttach() { + QQmlEngineDebugObjectReference obj = findRootObject(6); + QVERIFY(!obj.className.isEmpty()); + + bool success; + + m_dbg->addWatch(obj, &success); + QVERIFY(success); + QVERIFY(QQmlDebugTest::waitForSignal(m_dbg, SIGNAL(result()))); + QCOMPARE(m_dbg->valid(), true); +} + int main(int argc, char *argv[]) { int _argc = argc + 1; diff --git a/tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp b/tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp index beaee6c61a..d4f205ea18 100644 --- a/tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp +++ b/tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp @@ -298,7 +298,7 @@ public: tst_qv4debugger(); private slots: - void init(); + void init() override; void cleanup(); // breakpoints: diff --git a/tests/auto/qml/debugger/shared/debugutil.cpp b/tests/auto/qml/debugger/shared/debugutil.cpp index 4598e288c7..a4a7c2168b 100644 --- a/tests/auto/qml/debugger/shared/debugutil.cpp +++ b/tests/auto/qml/debugger/shared/debugutil.cpp @@ -34,8 +34,8 @@ #include <QtCore/qeventloop.h> #include <QtCore/qtimer.h> -QQmlDebugTest::QQmlDebugTest(const char *qmlTestDataDir) - : QQmlDataTest(qmlTestDataDir) +QQmlDebugTest::QQmlDebugTest(const char *qmlTestDataDir, FailOnWarningsPolicy failOnWarningsPolicy) + : QQmlDataTest(qmlTestDataDir, failOnWarningsPolicy) { } diff --git a/tests/auto/qml/debugger/shared/debugutil_p.h b/tests/auto/qml/debugger/shared/debugutil_p.h index 3ef2c56414..e3d46135f3 100644 --- a/tests/auto/qml/debugger/shared/debugutil_p.h +++ b/tests/auto/qml/debugger/shared/debugutil_p.h @@ -48,7 +48,8 @@ class QQmlDebugTest : public QQmlDataTest { Q_OBJECT public: - QQmlDebugTest(const char *qmlTestDataDir); + QQmlDebugTest(const char *qmlTestDataDir, + FailOnWarningsPolicy failOnWarningsPolicy = FailOnWarningsPolicy::DoNotFailOnWarnings); public: static bool waitForSignal(QObject *receiver, const char *member, int timeout = 5000); diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index 4982085172..f08d247148 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -5421,7 +5421,7 @@ void tst_QJSEngine::urlObject() check(QStringLiteral("href"), url.toString()); check(QStringLiteral("origin"), QStringLiteral("http://example.com:777")); - check(QStringLiteral("protocol"), url.scheme()); + check(QStringLiteral("protocol"), url.scheme() + QLatin1Char(':')); check(QStringLiteral("username"), url.userName()); check(QStringLiteral("password"), url.password()); check(QStringLiteral("host"), url.host() + u':' + QString::number(url.port())); diff --git a/tests/auto/qml/qjsonbinding/tst_qjsonbinding.cpp b/tests/auto/qml/qjsonbinding/tst_qjsonbinding.cpp index b0e2c288b8..4c12f6d44c 100644 --- a/tests/auto/qml/qjsonbinding/tst_qjsonbinding.cpp +++ b/tests/auto/qml/qjsonbinding/tst_qjsonbinding.cpp @@ -77,6 +77,9 @@ private slots: void writeProperty_javascriptExpression_data(); void writeProperty_javascriptExpression(); + void cyclicStringify(); + void recursiveStringify(); + private: QByteArray readAsUtf8(const QString &fileName); static QJsonValue valueFromJson(const QByteArray &json); @@ -514,6 +517,43 @@ void tst_qjsonbinding::writeProperty_javascriptExpression() QCOMPARE(ret.toString(), expectedJson); } +void tst_qjsonbinding::cyclicStringify() +{ + QJSEngine e; + const QString program = QStringLiteral(R"( + var a = {}; + a.a = a; + JSON.stringify(a); + )"); + + QJSValue result = e.evaluate(program); + QVERIFY(result.isError()); + QCOMPARE(result.errorType(), QJSValue::TypeError); + QVERIFY(result.toString().contains(QLatin1String("Cannot convert circular structure to JSON"))); +} + +void tst_qjsonbinding::recursiveStringify() +{ + QJSEngine e; + const QString program = QStringLiteral(R"( + function create() { + var a = {} + Object.defineProperty(a, "a", { + enumerable: true, + get: function() { return create(); } + }); + return a; + } + + JSON.stringify(create()); + )"); + + QJSValue result = e.evaluate(program); + QVERIFY(result.isError()); + QCOMPARE(result.errorType(), QJSValue::RangeError); + QVERIFY(result.toString().contains(QLatin1String("Maximum call stack size exceeded"))); +} + QTEST_MAIN(tst_qjsonbinding) #include "tst_qjsonbinding.moc" diff --git a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt index e1ac1ca22d..5f7dbba8d7 100644 --- a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt +++ b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt @@ -3,6 +3,7 @@ set(cpp_sources birthdayparty.cpp birthdayparty.h cppbaseclass.h dynamicmeta.h + gadgetwithenum.h invisible.h objectwithmethod.h person.cpp person.h @@ -52,6 +53,7 @@ set(qml_files compositesingleton.qml construct.qml contextParam.qml + conversionDecrement.qml conversions.qml conversions2.qml curlygrouped.qml @@ -74,6 +76,7 @@ set(qml_files functionLookup.qml funcWithParams.qml functionReturningVoid.qml + functionTakingVar.qml globals.qml idAccess.qml immediateQuit.qml @@ -87,12 +90,14 @@ set(qml_files interactive.qml interceptor.qml isnan.qml + javaScriptArgument.qml jsMathObject.qml jsimport.qml jsmoduleimport.qml layouts.qml library.js listIndices.qml + listPropertyAsModel.qml listlength.qml math.qml methods.qml @@ -128,6 +133,7 @@ set(qml_files text.qml themerbad.qml themergood.qml + throwObjectName.qml undefinedResets.qml unknownAttached.qml unknownParameter.qml diff --git a/tests/auto/qml/qmlcppcodegen/data/conversionDecrement.qml b/tests/auto/qml/qmlcppcodegen/data/conversionDecrement.qml new file mode 100644 index 0000000000..fdce0fe65c --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/conversionDecrement.qml @@ -0,0 +1,18 @@ +pragma Strict +import QtQml + +QtObject { + id: panelGrid + property var pages: 4 + property int currentPageIndex: 0 + + onPagesChanged: { + if (panelGrid.currentPageIndex === 0) { + panelGrid.currentPageIndex = panelGrid.pages - 2 + } else if (panelGrid.currentPageIndex === panelGrid.pages - 1) { + panelGrid.currentPageIndex = 0 + } else { + panelGrid.currentPageIndex -= 1 + } + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/failures.qml b/tests/auto/qml/qmlcppcodegen/data/failures.qml index 6b8c62d1b3..b8331c0ad4 100644 --- a/tests/auto/qml/qmlcppcodegen/data/failures.qml +++ b/tests/auto/qml/qmlcppcodegen/data/failures.qml @@ -1,5 +1,6 @@ import QtQml import TestTypes +import TestTypes as TT2 import Ambiguous 1.2 QtObject { @@ -27,4 +28,12 @@ QtObject { } Component.onCompleted: doesNotExist() + + signal foo() + signal bar() + // Cannot assign potential undefined + onFoo: objectName = self.bar() + + property int enumFromGadget1: GadgetWithEnum.CONNECTED + 1 + property int enumFromGadget2: TT2.GadgetWithEnum.CONNECTED + 1 } diff --git a/tests/auto/qml/qmlcppcodegen/data/functionTakingVar.qml b/tests/auto/qml/qmlcppcodegen/data/functionTakingVar.qml new file mode 100644 index 0000000000..1765bfcab9 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/functionTakingVar.qml @@ -0,0 +1,11 @@ +pragma Strict +import QtQml + +QtObject { + property var c; + + function a(b: var) { + c = b; + } + +} diff --git a/tests/auto/qml/qmlcppcodegen/data/gadgetwithenum.h b/tests/auto/qml/qmlcppcodegen/data/gadgetwithenum.h new file mode 100644 index 0000000000..d146b9f654 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/gadgetwithenum.h @@ -0,0 +1,23 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef GADGETWITHENUM_H +#define GADGETWITHENUM_H + +#include <QtCore/qobject.h> +#include <QtQmlIntegration/qqmlintegration.h> + +class GadgetWithEnum : public QObject { + Q_GADGET + QML_ELEMENT + +public: + enum State { + DISCONNECTED, + CONNECTING, + CONNECTED + }; + Q_ENUM(State) +}; + +#endif // GADGETWITHENUM_H diff --git a/tests/auto/qml/qmlcppcodegen/data/javaScriptArgument.qml b/tests/auto/qml/qmlcppcodegen/data/javaScriptArgument.qml new file mode 100644 index 0000000000..f52325e915 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/javaScriptArgument.qml @@ -0,0 +1,33 @@ +import QtQml + +QtObject { + function absMinusOne(amount: real) : real { + // Access it before the condition below, to make sure we still get the original + var minusOne = amount !== 0 ? -1 : 0; + + // The condition causes the original arguemnt to be overwritten rather than a new + // register to be allocated + if (amount < 0) + amount = -amount; + + return amount + minusOne; + } + + property real a: absMinusOne(-5) + property real b: absMinusOne(10) + + function stringMinusOne(amount: real) : string { + // Access it before the condition below, to make sure we still get the original + var minusOne = amount !== 0 ? -1 : 0; + + // The condition causes the original arguemnt to be overwritten rather than a new + // register to be allocated + if (amount < 0) + amount = -amount + "t"; + + return amount + minusOne; + } + + property string c: stringMinusOne(-5) + property string d: stringMinusOne(10) +} diff --git a/tests/auto/qml/qmlcppcodegen/data/listIndices.qml b/tests/auto/qml/qmlcppcodegen/data/listIndices.qml index b5fda4ef0d..9df172b2e6 100644 --- a/tests/auto/qml/qmlcppcodegen/data/listIndices.qml +++ b/tests/auto/qml/qmlcppcodegen/data/listIndices.qml @@ -5,10 +5,18 @@ QtObject { id: self property list<QtObject> items property int numItems: items.length + property QtObject fractional: items[2.25] + property QtObject negativeZero: items[-1 * 0] + property QtObject infinity: items[1 / 0] + property QtObject nan: items[1 - "a"] Component.onCompleted: { items.length = 3 for (var i = 0; i < 3; ++i) items[i] = self + + items[2.25] = null + items[1 / 0] = self + items[1 - "a"] = self } } diff --git a/tests/auto/qml/qmlcppcodegen/data/listPropertyAsModel.qml b/tests/auto/qml/qmlcppcodegen/data/listPropertyAsModel.qml new file mode 100644 index 0000000000..77a284d179 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/listPropertyAsModel.qml @@ -0,0 +1,16 @@ +pragma Strict +import QtQuick + +Item { + Item { + id: child + Item {} + Item {} + Item {} + } + Repeater { + id: self + Item {} + Component.onCompleted: self.model = child.children + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/throwObjectName.qml b/tests/auto/qml/qmlcppcodegen/data/throwObjectName.qml new file mode 100644 index 0000000000..98282998e9 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/throwObjectName.qml @@ -0,0 +1,6 @@ +pragma Strict +import QtQml + +QtObject { + objectName: { throw "ouch" } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/undefinedResets.qml b/tests/auto/qml/qmlcppcodegen/data/undefinedResets.qml index fae040a1a5..60302c64a5 100644 --- a/tests/auto/qml/qmlcppcodegen/data/undefinedResets.qml +++ b/tests/auto/qml/qmlcppcodegen/data/undefinedResets.qml @@ -1,6 +1,7 @@ -pragma Strict import TestTypes Person { name: shoeSize === 11 ? undefined : "Marge" + + onObjectNameChanged: name = undefined } diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp index bd6fb37677..6bae57a1c7 100644 --- a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp +++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp @@ -129,6 +129,11 @@ private slots: void evadingAmbiguity(); void fromBoolValue(); void invisibleTypes(); + void functionTakingVar(); + void javaScriptArgument(); + void throwObjectName(); + void conversionDecrement(); + void listPropertyAsModel(); }; void tst_QmlCppCodegen::simpleBinding() @@ -1447,6 +1452,11 @@ void tst_QmlCppCodegen::undefinedResets() person->setShoeSize(10); QCOMPARE(person->shoeSize(), 10); QCOMPARE(person->name(), u"Marge"_qs); + + person->setName(u"no one"_qs); + QCOMPARE(person->name(), u"no one"_qs); + person->setObjectName(u"the one"_qs); + QCOMPARE(person->name(), u"Bart"_qs); } void tst_QmlCppCodegen::innerObjectNonShadowable() @@ -1553,6 +1563,10 @@ void tst_QmlCppCodegen::listIndices() for (int i = 0; i < 3; ++i) QCOMPARE(list.at(i), o.data()); QCOMPARE(o->property("numItems").toInt(), 3); + QCOMPARE(qvariant_cast<QObject *>(o->property("fractional")), nullptr); + QCOMPARE(qvariant_cast<QObject *>(o->property("negativeZero")), o.data()); + QCOMPARE(qvariant_cast<QObject *>(o->property("infinity")), nullptr); + QCOMPARE(qvariant_cast<QObject *>(o->property("nan")), nullptr); } void tst_QmlCppCodegen::jsMathObject() @@ -1977,6 +1991,84 @@ void tst_QmlCppCodegen::invisibleTypes() // QCOMPARE(meta->className(), "DerivedFromInvisible"); } +void tst_QmlCppCodegen::functionTakingVar() +{ + QQmlEngine engine; + const QUrl document(u"qrc:/TestTypes/functionTakingVar.qml"_qs); + QQmlComponent c(&engine, document); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + + QVERIFY(!o->property("c").isValid()); + + int value = 11; + QQmlEnginePrivate *e = QQmlEnginePrivate::get(&engine); + void *args[] = { nullptr, reinterpret_cast<void *>(std::addressof(value)) }; + QMetaType types[] = { QMetaType::fromType<void>(), QMetaType::fromType<std::decay_t<int>>() }; + e->executeRuntimeFunction(document, 0, o.data(), 1, args, types); + + QCOMPARE(o->property("c"), QVariant::fromValue<int>(11)); +} + +void tst_QmlCppCodegen::javaScriptArgument() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/TestTypes/javaScriptArgument.qml"_qs)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("a").toDouble(), 4.0); + QCOMPARE(o->property("b").toDouble(), 9.0); + QCOMPARE(o->property("c").toString(), u"5t-1"_qs); + QCOMPARE(o->property("d").toString(), u"9"_qs); +} + +void tst_QmlCppCodegen::throwObjectName() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/TestTypes/throwObjectName.qml"_qs)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QTest::ignoreMessage(QtWarningMsg, "qrc:/TestTypes/throwObjectName.qml:5:5: ouch"); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QVERIFY(o->objectName().isEmpty()); +} + +void tst_QmlCppCodegen::conversionDecrement() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/TestTypes/conversionDecrement.qml"_qs)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("currentPageIndex").toInt(), 0); + o->setProperty("pages", 5); + QCOMPARE(o->property("currentPageIndex").toInt(), 3); + o->setProperty("pages", 4); + QCOMPARE(o->property("currentPageIndex").toInt(), 0); + o->setProperty("pages", 6); + QCOMPARE(o->property("currentPageIndex").toInt(), 4); + o->setProperty("pages", 60); + QCOMPARE(o->property("currentPageIndex").toInt(), 3); +} + +void tst_QmlCppCodegen::listPropertyAsModel() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/TestTypes/listPropertyAsModel.qml"_qs)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + + QQmlListReference children(o.data(), "children"); + QCOMPARE(children.count(), 5); +} + void tst_QmlCppCodegen::runInterpreted() { #ifdef Q_OS_ANDROID diff --git a/tests/auto/qml/qmlformat/data/forWithLet.formatted.qml b/tests/auto/qml/qmlformat/data/forWithLet.formatted.qml new file mode 100644 index 0000000000..5fecc1d180 --- /dev/null +++ b/tests/auto/qml/qmlformat/data/forWithLet.formatted.qml @@ -0,0 +1,8 @@ +import QtQml + +QtObject { + function foo() { + for (let i = 0; i < 5; ++i) + console.log(i); + } +} diff --git a/tests/auto/qml/qmlformat/data/forWithLet.qml b/tests/auto/qml/qmlformat/data/forWithLet.qml new file mode 100644 index 0000000000..afed76ebce --- /dev/null +++ b/tests/auto/qml/qmlformat/data/forWithLet.qml @@ -0,0 +1,8 @@ +import QtQml + +QtObject { +function foo() { +for (let i = 0; i < 5; ++i) +console.log(i); +} +} diff --git a/tests/auto/qml/qmlformat/tst_qmlformat.cpp b/tests/auto/qml/qmlformat/tst_qmlformat.cpp index 6f35282164..4cc19e5a9c 100644 --- a/tests/auto/qml/qmlformat/tst_qmlformat.cpp +++ b/tests/auto/qml/qmlformat/tst_qmlformat.cpp @@ -80,8 +80,9 @@ private: bool isInvalidFile(const QFileInfo &fileName) const; }; +// Don't fail on warnings because we read a lot of QML files that might intentionally be malformed. TestQmlformat::TestQmlformat() - : QQmlDataTest(QT_QMLTEST_DATADIR) + : QQmlDataTest(QT_QMLTEST_DATADIR, FailOnWarningsPolicy::DoNotFailOnWarnings) { } @@ -297,6 +298,9 @@ void TestQmlformat::testFormat_data() QTest::newRow("settings") << "settings/Example1.qml" << "settings/Example1.formatted_mac_cr.qml" << QStringList {} << RunOption::OrigToCopy; + QTest::newRow("forWithLet") + << "forWithLet.qml" + << "forWithLet.formatted.qml" << QStringList {} << RunOption::OnCopy; } void TestQmlformat::testFormat() diff --git a/tests/auto/qml/qmltyperegistrar/CMakeLists.txt b/tests/auto/qml/qmltyperegistrar/CMakeLists.txt index 5f6f8e83b2..d1ad5858a5 100644 --- a/tests/auto/qml/qmltyperegistrar/CMakeLists.txt +++ b/tests/auto/qml/qmltyperegistrar/CMakeLists.txt @@ -74,3 +74,5 @@ qt_add_qml_module(tst-qmltyperegistrar-with-dashes foo.cpp foo.h ) qt_autogen_tools_initial_setup(tst-qmltyperegistrar-with-dashesplugin) + +add_subdirectory(VersionZero) diff --git a/tests/auto/qml/qmltyperegistrar/VersionZero/CMakeLists.txt b/tests/auto/qml/qmltyperegistrar/VersionZero/CMakeLists.txt new file mode 100644 index 0000000000..39dfccebd1 --- /dev/null +++ b/tests/auto/qml/qmltyperegistrar/VersionZero/CMakeLists.txt @@ -0,0 +1,20 @@ +qt_add_library(tst_qmltyperegistrar_major_version_zero) +qt_autogen_tools_initial_setup(tst_qmltyperegistrar_major_version_zero) +target_link_libraries(tst_qmltyperegistrar_major_version_zero PRIVATE Qt::Core Qt::Qml) +qt_enable_autogen_tool(tst_qmltyperegistrar_major_version_zero "moc" ON) +qt_add_qml_module(tst_qmltyperegistrar_major_version_zero + URI VersionZero + VERSION 0.1 + SOURCES + version_zero_type.h +) +qt_autogen_tools_initial_setup(tst_qmltyperegistrar_major_version_zeroplugin) + +# Make sure the backing library is found on Windows next to the executable +set_target_properties( + tst_qmltyperegistrar_major_version_zero + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/.. + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/.. + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/.. +) diff --git a/tests/auto/qml/qmltyperegistrar/VersionZero/version_zero_type.h b/tests/auto/qml/qmltyperegistrar/VersionZero/version_zero_type.h new file mode 100644 index 0000000000..a3277e6ab3 --- /dev/null +++ b/tests/auto/qml/qmltyperegistrar/VersionZero/version_zero_type.h @@ -0,0 +1,17 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef VERSION_ZERO_TYPE_H +#define VERSION_ZERO_TYPE_H + +#include <QtQml/qqml.h> + +class TypeInModuleMajorVersionZero : public QObject +{ + Q_OBJECT + QML_ELEMENT +public: + TypeInModuleMajorVersionZero(QObject *parent = nullptr) : QObject(parent) {} +}; + +#endif // VERSION_ZERO_TYPE_H diff --git a/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp b/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp index 3fc746552e..68a21d8db8 100644 --- a/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp +++ b/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp @@ -396,4 +396,14 @@ void tst_qmltyperegistrar::foreignRevisionedProperty() } #endif +void tst_qmltyperegistrar::typeInModuleMajorVersionZero() +{ + QQmlEngine engine; + QQmlComponent c(&engine); + c.setData(QStringLiteral("import VersionZero\n" + "TypeInModuleMajorVersionZero {}\n").toUtf8(), + QUrl(QTest::currentDataTag())); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); +} + QTEST_MAIN(tst_qmltyperegistrar) diff --git a/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h b/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h index 385d3b6666..f2ccaba618 100644 --- a/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h +++ b/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h @@ -532,6 +532,7 @@ private slots: void addRemoveVersion_data(); void addRemoveVersion(); + void typeInModuleMajorVersionZero(); private: QByteArray qmltypesData; diff --git a/tests/auto/qml/qqmlapplicationengine/CMakeLists.txt b/tests/auto/qml/qqmlapplicationengine/CMakeLists.txt index d9fbf6517d..caf1e2edaa 100644 --- a/tests/auto/qml/qqmlapplicationengine/CMakeLists.txt +++ b/tests/auto/qml/qqmlapplicationengine/CMakeLists.txt @@ -55,3 +55,4 @@ qt_internal_extend_target(tst_qqmlapplicationengine CONDITION NOT ANDROID AND NO QT_QMLTEST_DATADIR=\\\"${CMAKE_CURRENT_SOURCE_DIR}/data\\\" ) add_subdirectory(testapp) +add_subdirectory(androidassets) diff --git a/tests/auto/qml/qqmlapplicationengine/androidassets/CMakeLists.txt b/tests/auto/qml/qqmlapplicationengine/androidassets/CMakeLists.txt new file mode 100644 index 0000000000..1c0d305311 --- /dev/null +++ b/tests/auto/qml/qqmlapplicationengine/androidassets/CMakeLists.txt @@ -0,0 +1,18 @@ +qt_internal_add_test(tst_androidassets + SOURCES + tst_androidassets.cpp + PUBLIC_LIBRARIES + Qt::Gui + Qt::Qml + Qt::Quick +) + +# add qml/*.qml files as assets instead of resources + +file( + COPY qml/main.qml + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/android-build/assets/qml/") + +file( + COPY qml/pages/MainPage.qml + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/android-build/assets/qml/pages/") diff --git a/tests/auto/qml/qqmlapplicationengine/androidassets/qml/main.qml b/tests/auto/qml/qqmlapplicationengine/androidassets/qml/main.qml new file mode 100644 index 0000000000..6901572b00 --- /dev/null +++ b/tests/auto/qml/qqmlapplicationengine/androidassets/qml/main.qml @@ -0,0 +1,13 @@ +import QtQuick + +// relative import: has to work when loading from android assets by path or by URL +import "pages" + +Window { + width: 640 + height: 480 + visible: true + title: qsTr("Hello World") + + MainPage { } +} diff --git a/tests/auto/qml/qqmlapplicationengine/androidassets/qml/pages/MainPage.qml b/tests/auto/qml/qqmlapplicationengine/androidassets/qml/pages/MainPage.qml new file mode 100644 index 0000000000..c9549a928a --- /dev/null +++ b/tests/auto/qml/qqmlapplicationengine/androidassets/qml/pages/MainPage.qml @@ -0,0 +1,11 @@ +import QtQuick + +Rectangle { + anchors.fill: parent + color: "#ddd" + + Text { + anchors.centerIn: parent + text: "Qt 6" + } +} diff --git a/tests/auto/qml/qqmlapplicationengine/androidassets/tst_androidassets.cpp b/tests/auto/qml/qqmlapplicationengine/androidassets/tst_androidassets.cpp new file mode 100644 index 0000000000..0751e23f45 --- /dev/null +++ b/tests/auto/qml/qqmlapplicationengine/androidassets/tst_androidassets.cpp @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite 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$ +** +****************************************************************************/ + +#include <QtQml/qqmlapplicationengine.h> +#include <QtTest/qsignalspy.h> +#include <QtTest/qtest.h> + +class tst_AndroidAssets : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void loadsFromAssetsPath(); + void loadsFromAssetsUrl(); + +private: + + static QString pathPrefix() + { +#ifdef Q_OS_ANDROID + return QStringLiteral("assets:"); +#else + // Even when not running on android we can check that the copying to build dir worked. + return QCoreApplication::applicationDirPath() + QStringLiteral("/android-build/assets"); +#endif + } + + static QString urlPrefix() { +#ifdef Q_OS_ANDROID + return pathPrefix(); +#else + return QStringLiteral("file:") + pathPrefix(); +#endif + } +}; + + +void tst_AndroidAssets::loadsFromAssetsPath() +{ + QQmlApplicationEngine engine; + + // load QML file from assets, by path: + engine.load(pathPrefix() + QStringLiteral("/qml/main.qml")); + QTRY_VERIFY(engine.rootObjects().length() == 1); +} + +void tst_AndroidAssets::loadsFromAssetsUrl() +{ + QQmlApplicationEngine engine; + + // load QML file from assets, by URL: + engine.load(QUrl(urlPrefix() + QStringLiteral("/qml/main.qml"))); + QTRY_VERIFY(engine.rootObjects().length() == 1); +} + +QTEST_MAIN(tst_AndroidAssets) + +#include "tst_androidassets.moc" diff --git a/tests/auto/qml/qqmlecmascript/BLACKLIST b/tests/auto/qml/qqmlecmascript/BLACKLIST deleted file mode 100644 index bd25566eef..0000000000 --- a/tests/auto/qml/qqmlecmascript/BLACKLIST +++ /dev/null @@ -1,2 +0,0 @@ -[gcCrashRegressionTest] -macos arm diff --git a/tests/auto/qml/qqmlecmascript/data/aliasPropertyToIC.qml b/tests/auto/qml/qqmlecmascript/data/aliasPropertyToIC.qml new file mode 100644 index 0000000000..17116bb091 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/aliasPropertyToIC.qml @@ -0,0 +1,11 @@ +import QtQml + + +QtObject { + id: root + + component Test : QtObject {} + + property alias myalias: other + property var direct: Test { id: other } +} diff --git a/tests/auto/qml/qqmlecmascript/data/internalClassParentGc.js b/tests/auto/qml/qqmlecmascript/data/internalClassParentGc.js new file mode 100644 index 0000000000..f51ab662ab --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/internalClassParentGc.js @@ -0,0 +1,29 @@ +function init() { + Array.prototype.doPush = Array.prototype.push +} + +function nasty() { + var sc_Vector = Array; + var push = sc_Vector.prototype.doPush; + + // Change the memberData to hold something nasty on the current internalClass + sc_Vector.prototype.doPush = 5; + + // Trigger a re-allocation of memberData + for (var i = 0; i < 256; ++i) + sc_Vector.prototype[i + "string"] = function() { return 98; } + + // Change the (new) memberData back, to hold our doPush function again. + // This should propagate a protoId change all the way up to the lookup. + sc_Vector.prototype.doPush = push; +} + +function func() { + var b = []; + + // This becomes a lookup internally, which stores protoId and a pointer + // into the memberData. It should get invalidated when memberData is re-allocated. + b.doPush(3); + + return b; +} diff --git a/tests/auto/qml/qqmlecmascript/data/internalClassParentGc.qml b/tests/auto/qml/qqmlecmascript/data/internalClassParentGc.qml new file mode 100644 index 0000000000..e313770bf5 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/internalClassParentGc.qml @@ -0,0 +1,13 @@ +import QtQml + +import "internalClassParentGc.js" as Foo + +QtObject { + Component.onCompleted: { + gc(); + Foo.init(); + Foo.func(); + Foo.nasty(); + objectName = Foo.func()[0]; + } +} diff --git a/tests/auto/qml/qqmlecmascript/data/qmlTypeWrapperArgs.qml b/tests/auto/qml/qqmlecmascript/data/qmlTypeWrapperArgs.qml new file mode 100644 index 0000000000..7f1b5b0317 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/qmlTypeWrapperArgs.qml @@ -0,0 +1,8 @@ +import QtQml + +QtObject { + id: root + required property QtObject invokableObject + + Component.onCompleted: root.invokableObject.method_QObject(Component) +} diff --git a/tests/auto/qml/qqmlecmascript/data/qmlTypeWrapperArgs2.qml b/tests/auto/qml/qqmlecmascript/data/qmlTypeWrapperArgs2.qml new file mode 100644 index 0000000000..1904740b26 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/qmlTypeWrapperArgs2.qml @@ -0,0 +1,9 @@ +import QtQml +import QtQml as NS + +QtObject { + id: root + required property QtObject invokableObject + + Component.onCompleted: root.invokableObject.method_QObject(NS) +} diff --git a/tests/auto/qml/qqmlecmascript/data/qpropertyBindingNoQPropertyCapture.qml b/tests/auto/qml/qqmlecmascript/data/qpropertyBindingNoQPropertyCapture.qml new file mode 100644 index 0000000000..d3a151efe3 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/qpropertyBindingNoQPropertyCapture.qml @@ -0,0 +1,19 @@ +import QtQuick + +Item { + objectName: "redRectangle" + id: redRectangle + + property bool b: false + function toggle() { b = !b } + width: b ? 600 : 500 + + Item { + id: blueRectangle + objectName: "blueRectangle" + // width: b ? (100 + redRectangle.width / 2) : 25 + width: b ? redRectangle.width : 25 + } + + property int blueRectangleWidth: blueRectangle.width +} diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index 7ea5ef6e4b..4beedc4e88 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -119,6 +119,7 @@ private slots: void outerBindingOverridesInnerBinding(); void aliasPropertyAndBinding(); void aliasPropertyReset(); + void aliasPropertyToIC(); void nonExistentAttachedObject(); void scope(); void importScope(); @@ -315,6 +316,7 @@ private slots: void bindingBoundFunctions(); void qpropertyAndQtBinding(); void qpropertyBindingReplacement(); + void qpropertyBindingNoQPropertyCapture(); void deleteRootObjectInCreation(); void onDestruction(); void onDestructionViaGC(); @@ -409,6 +411,7 @@ private slots: void urlConstruction(); void urlPropertyInvalid(); void urlPropertySet(); + void colonAfterProtocol(); void urlSearchParamsConstruction(); void urlSearchParamsMethods(); void variantConversionMethod(); @@ -430,6 +433,8 @@ private slots: void functionNameInFunctionScope(); void functionAsDefaultArgument(); + void internalClassParentGc(); + private: // static void propertyVarWeakRefCallback(v8::Persistent<v8::Value> object, void* parameter); static void verifyContextLifetime(const QQmlRefPointer<QQmlContextData> &ctxt); @@ -1932,6 +1937,24 @@ void tst_qqmlecmascript::aliasPropertyReset() QCOMPARE(object->property("aliasedIntIsUndefined"), QVariant(false)); } +void tst_qqmlecmascript::aliasPropertyToIC() +{ + QQmlEngine engine; + std::unique_ptr<QObject> root; + + // test that a manual write (of undefined) to a resettable aliased property succeeds + QQmlComponent c(&engine, testFileUrl("aliasPropertyToIC.qml")); + root.reset(c.create()); + QVERIFY(root); + auto mo = root->metaObject(); + int aliasIndex = mo->indexOfProperty("myalias"); + auto prop = mo->property(aliasIndex); + QVERIFY(prop.isAlias()); + auto fromAlias = prop.read(root.get()).value<QObject *>(); + auto direct = root->property("direct").value<QObject *>(); + QCOMPARE(fromAlias, direct); +} + void tst_qqmlecmascript::componentCreation_data() { QTest::addColumn<QString>("method"); @@ -3144,6 +3167,26 @@ void tst_qqmlecmascript::callQtInvokables() QCOMPARE(o->actuals().count(), 1); QCOMPARE(o->actuals().at(0), QVariant::fromValue((QObject *)nullptr)); + { + o->reset(); + QQmlComponent comp(&qmlengine, testFileUrl("qmlTypeWrapperArgs.qml")); + QScopedPointer<QObject> root {comp.createWithInitialProperties({{"invokableObject", QVariant::fromValue(o)}}) }; + QVERIFY(root); + QCOMPARE(o->error(), false); + QCOMPARE(o->invoked(), 13); + QCOMPARE(o->actuals().count(), 1); + QCOMPARE(o->actuals().at(0).value<QObject *>()->metaObject()->className(), "QQmlComponentAttached"); + } + + { + o->reset(); + QQmlComponent comp(&qmlengine, testFileUrl("qmlTypeWrapperArgs2.qml")); + QScopedPointer<QObject> root {comp.createWithInitialProperties({{"invokableObject", QVariant::fromValue(o)}}) }; + QVERIFY(root); + QCOMPARE(o->error(), false); + QCOMPARE(o->invoked(), -1); // no function got called due to incompatible arguments + } + o->reset(); QVERIFY(EVALUATE_VALUE("object.method_QObject(undefined)", QV4::Primitive::undefinedValue())); QCOMPARE(o->error(), false); @@ -7741,6 +7784,28 @@ void tst_qqmlecmascript::qpropertyBindingReplacement() QCOMPARE(root->objectName(), u"overwritten"_qs); } +void tst_qqmlecmascript::qpropertyBindingNoQPropertyCapture() +{ + + QQmlEngine engine; + QQmlComponent comp(&engine, testFileUrl("qpropertyBindingNoQPropertyCapture.qml")); + std::unique_ptr<QObject> root(comp.create()); + QVERIFY2(root, qPrintable(comp.errorString())); + auto redRectangle = root.get(); + + QQmlProperty blueRectangleWidth(redRectangle, "blueRectangleWidth", &engine); + + auto toggle = [&](){ + QMetaObject::invokeMethod(root.get(), "toggle"); + }; + + QCOMPARE(blueRectangleWidth.read().toInt(), 25); + toggle(); + QCOMPARE(blueRectangleWidth.read().toInt(), 600); + toggle(); + QCOMPARE(blueRectangleWidth.read().toInt(), 25); +} + void tst_qqmlecmascript::deleteRootObjectInCreation() { QQmlEngine engine; @@ -9565,7 +9630,7 @@ void tst_qqmlecmascript::urlConstruction() QV4::UrlObject *validUrl = ret->as<QV4::UrlObject>(); QVERIFY(validUrl != nullptr); - QCOMPARE(validUrl->protocol(), "https"); + QCOMPARE(validUrl->protocol(), "https:"); QCOMPARE(validUrl->hostname(), "example.com"); QCOMPARE(validUrl->username(), "username"); QCOMPARE(validUrl->password(), "password"); @@ -9585,7 +9650,7 @@ void tst_qqmlecmascript::urlConstruction() QV4::UrlObject *validRelativeUrl = retRel->as<QV4::UrlObject>(); QVERIFY(validRelativeUrl != nullptr); - QCOMPARE(validRelativeUrl->protocol(), "https"); + QCOMPARE(validRelativeUrl->protocol(), "https:"); QCOMPARE(validRelativeUrl->hostname(), "example.com"); QCOMPARE(validRelativeUrl->username(), "username"); QCOMPARE(validRelativeUrl->password(), "password"); @@ -9645,7 +9710,7 @@ void tst_qqmlecmascript::urlPropertySet() // protocol QVERIFY(EVALUATE("this.url.protocol = 'https';")); - QCOMPARE(url->protocol(), "https"); + QCOMPARE(url->protocol(), "https:"); QCOMPARE(url->href(), "https://localhost/a/b/c"); QCOMPARE(url->origin(), "https://localhost"); @@ -9708,7 +9773,7 @@ void tst_qqmlecmascript::urlPropertySet() "this.url.href = " "'https://username:password@example.com:1234/path/to/something?search=value#hash';")); - QCOMPARE(url->protocol(), "https"); + QCOMPARE(url->protocol(), "https:"); QCOMPARE(url->hostname(), "example.com"); QCOMPARE(url->username(), "username"); QCOMPARE(url->password(), "password"); @@ -9722,6 +9787,57 @@ void tst_qqmlecmascript::urlPropertySet() QCOMPARE(url->hash(), "#hash"); } +void tst_qqmlecmascript::colonAfterProtocol() +{ + QQmlEngine qmlengine; + + QObject *o = new QObject(&qmlengine); + + QV4::ExecutionEngine *engine = qmlengine.handle(); + QV4::Scope scope(engine); + + QV4::ScopedValue object(scope, QV4::QObjectWrapper::wrap(engine, o)); + + QV4::ScopedValue ret(scope, EVALUATE("this.url = new URL('http://localhost/a/b/c');")); + QV4::UrlObject *url = ret->as<QV4::UrlObject>(); + QVERIFY(url != nullptr); + + // https without colon + QVERIFY(EVALUATE("this.url.protocol = 'https';")); + QCOMPARE(url->protocol(), "https:"); + QCOMPARE(url->href(), "https://localhost/a/b/c"); + QCOMPARE(url->origin(), "https://localhost"); + + QV4::ScopedValue retHttps(scope, EVALUATE("this.url = new URL('https://localhost/a/b/c');")); + QV4::UrlObject *urlHttps = retHttps->as<QV4::UrlObject>(); + QVERIFY(urlHttps != nullptr); + + // ftp with a colon + QVERIFY(EVALUATE("this.url.protocol = 'ftp:';")); + QCOMPARE(urlHttps->protocol(), "ftp:"); + QCOMPARE(urlHttps->href(), "ftp://localhost/a/b/c"); + QCOMPARE(urlHttps->origin(), "ftp://localhost"); + + QV4::ScopedValue retHttp(scope, EVALUATE("this.url = new URL('http://localhost/a/b/c');")); + QV4::UrlObject *ftpHttps = retHttp->as<QV4::UrlObject>(); + QVERIFY(ftpHttps != nullptr); + + // ftp with three colons + QVERIFY(EVALUATE("this.url.protocol = 'ftp:::';")); + QCOMPARE(ftpHttps->protocol(), "ftp:"); + QCOMPARE(ftpHttps->href(), "ftp://localhost/a/b/c"); + QCOMPARE(ftpHttps->origin(), "ftp://localhost"); + + QV4::ScopedValue retWss(scope, EVALUATE("this.url = new URL('wss://localhost/a/b/c');")); + QV4::UrlObject *urlFtpHttp = retWss->as<QV4::UrlObject>(); + QVERIFY(urlFtpHttp != nullptr); + + // ftp and http with a colon inbetween + QVERIFY(EVALUATE("this.url.protocol = 'ftp:http:';")); + QCOMPARE(urlFtpHttp->protocol(), "ftp:"); + QCOMPARE(urlFtpHttp->href(), "ftp://localhost/a/b/c"); + QCOMPARE(urlFtpHttp->origin(), "ftp://localhost"); +} void tst_qqmlecmascript::urlSearchParamsConstruction() { @@ -10090,6 +10206,15 @@ void tst_qqmlecmascript::functionAsDefaultArgument() QCOMPARE(root->objectName(), "didRun"); } +void tst_qqmlecmascript::internalClassParentGc() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("internalClassParentGc.qml")); + QScopedPointer root(component.create()); + QVERIFY(root); + QCOMPARE(root->objectName(), "3"); +} + QTEST_MAIN(tst_qqmlecmascript) #include "tst_qqmlecmascript.moc" diff --git a/tests/auto/qml/qqmlfile/tst_qqmlfile.cpp b/tests/auto/qml/qqmlfile/tst_qqmlfile.cpp index a1c8daddcf..9f3bfe368d 100644 --- a/tests/auto/qml/qqmlfile/tst_qqmlfile.cpp +++ b/tests/auto/qml/qqmlfile/tst_qqmlfile.cpp @@ -38,19 +38,166 @@ public: tst_qqmlfile() {} private Q_SLOTS: + void isLocalFile_data(); + void isLocalFile(); + + void urlToLocalFileOrQrcOverloads_data(); void urlToLocalFileOrQrcOverloads(); + +private: + void urlData(); }; +void tst_qqmlfile::urlData() +{ + QTest::addColumn<QString>("urlString"); + QTest::addColumn<bool>("isLocal"); + QTest::addColumn<QString>("localPath"); + + const QString invalid; + const QString relative = QStringLiteral("foo/bar"); + const QString absolute = QStringLiteral("/foo/bar"); + + QTest::addRow("plain empty") << QStringLiteral("") << false << invalid; + QTest::addRow("plain no slash") << QStringLiteral("foo/bar") << false << invalid; + QTest::addRow("plain 1 slash") << QStringLiteral("/foo/bar") << false << invalid; + QTest::addRow("plain 2 slashes") << QStringLiteral("//foo/bar") << false << invalid; + QTest::addRow("plain 3 slashes") << QStringLiteral("///foo/bar") << false << invalid; + + QTest::addRow(": empty") << QStringLiteral(":") << false << invalid; + QTest::addRow(": no slash") << QStringLiteral(":foo/bar") << false << invalid; + QTest::addRow(": 1 slash") << QStringLiteral(":/foo/bar") << false << invalid; + QTest::addRow(": 2 slashes") << QStringLiteral("://foo/bar") << false << invalid; + QTest::addRow(": 3 slashes") << QStringLiteral(":///foo/bar") << false << invalid; + + QTest::addRow("C empty") << QStringLiteral("C:") << false << invalid; + QTest::addRow("C no slash") << QStringLiteral("C:foo/bar") << false << invalid; + QTest::addRow("C 1 slash") << QStringLiteral("C:/foo/bar") << false << invalid; + QTest::addRow("C 2 slashes") << QStringLiteral("C://foo/bar") << false << invalid; + QTest::addRow("C 3 slashes") << QStringLiteral("C:///foo/bar") << false << invalid; + + QTest::addRow("file empty") << QStringLiteral("file:") << true << QString(); + QTest::addRow("file no slash") << QStringLiteral("file:foo/bar") << true << relative; + QTest::addRow("file 1 slash") << QStringLiteral("file:/foo/bar") << true << absolute; + QTest::addRow("file 2 slashes") << QStringLiteral("file://foo/bar") << true << QStringLiteral("//foo/bar"); + QTest::addRow("file 3 slashes") << QStringLiteral("file:///foo/bar") << true << absolute; + + QTest::addRow("qrc empty") << QStringLiteral("qrc:") << true << QStringLiteral(":"); + QTest::addRow("qrc no slash") << QStringLiteral("qrc:foo/bar") << true << u':' + relative; + QTest::addRow("qrc 1 slash") << QStringLiteral("qrc:/foo/bar") << true << u':' + absolute; + QTest::addRow("qrc 2 slashes") << QStringLiteral("qrc://foo/bar") << false << invalid; + QTest::addRow("qrc 3 slashes") << QStringLiteral("qrc:///foo/bar") << true << u':' + absolute; + + QTest::addRow("file+stuff empty") << QStringLiteral("file+stuff:") << false << invalid; + QTest::addRow("file+stuff no slash") << QStringLiteral("file+stuff:foo/bar") << false << invalid; + QTest::addRow("file+stuff 1 slash") << QStringLiteral("file+stuff:/foo/bar") << false << invalid; + QTest::addRow("file+stuff 2 slashes") << QStringLiteral("file+stuff://foo/bar") << false << invalid; + QTest::addRow("file+stuff 3 slashes") << QStringLiteral("file+stuff:///foo/bar") << false << invalid; + + // "assets:" and "content:" URLs are only treated as local files on android. In contrast to + // "qrc:" and "file:" we're not trying to be clever about multiple slashes. Two slashes are + // prohibited as that says part of what we would recognize as path is actually a URL authority. + // Everything else is android's problem. + +#ifdef Q_OS_ANDROID + const bool hasAssetsAndContent = true; +#else + const bool hasAssetsAndContent = false; +#endif + + const QString assetsEmpty = hasAssetsAndContent ? QStringLiteral("assets:") : invalid; + const QString assetsRelative = hasAssetsAndContent ? (QStringLiteral("assets:") + relative) : invalid; + const QString assetsAbsolute = hasAssetsAndContent ? (QStringLiteral("assets:") + absolute) : invalid; + const QString assetsThreeSlashes = hasAssetsAndContent ? (QStringLiteral("assets://") + absolute) : invalid; + + QTest::addRow("assets empty") << QStringLiteral("assets:") << hasAssetsAndContent << assetsEmpty; + QTest::addRow("assets no slash") << QStringLiteral("assets:foo/bar") << hasAssetsAndContent << assetsRelative; + QTest::addRow("assets 1 slash") << QStringLiteral("assets:/foo/bar") << hasAssetsAndContent << assetsAbsolute; + QTest::addRow("assets 2 slashes") << QStringLiteral("assets://foo/bar") << false << invalid; + QTest::addRow("assets 3 slashes") << QStringLiteral("assets:///foo/bar") << hasAssetsAndContent << assetsThreeSlashes; + + const QString contentEmpty = hasAssetsAndContent ? QStringLiteral("content:") : invalid; + const QString contentRelative = hasAssetsAndContent ? (QStringLiteral("content:") + relative) : invalid; + const QString contentAbsolute = hasAssetsAndContent ? (QStringLiteral("content:") + absolute) : invalid; + const QString contentThreeSlashes = hasAssetsAndContent ? (QStringLiteral("content://") + absolute) : invalid; + + QTest::addRow("content empty") << QStringLiteral("content:") << hasAssetsAndContent << contentEmpty; + QTest::addRow("content no slash") << QStringLiteral("content:foo/bar") << hasAssetsAndContent << contentRelative; + QTest::addRow("content 1 slash") << QStringLiteral("content:/foo/bar") << hasAssetsAndContent << contentAbsolute; + QTest::addRow("content 2 slashes") << QStringLiteral("content://foo/bar") << false << invalid; + QTest::addRow("content 3 slashes") << QStringLiteral("content:///foo/bar") << hasAssetsAndContent << contentThreeSlashes; + + + // These are local files everywhere. Their paths are only meaningful on android, though. + // The inner slashes of the path do not influence the URL parsing. + + QTest::addRow("file:assets empty") << QStringLiteral("file:assets:") << true << QStringLiteral("assets:"); + QTest::addRow("file:assets no slash") << QStringLiteral("file:assets:foo/bar") << true << QStringLiteral("assets:foo/bar"); + QTest::addRow("file:assets 1 slash") << QStringLiteral("file:assets:/foo/bar") << true << QStringLiteral("assets:/foo/bar"); + QTest::addRow("file:assets 2 slashes") << QStringLiteral("file:assets://foo/bar") << true << QStringLiteral("assets://foo/bar"); + QTest::addRow("file:assets 3 slashes") << QStringLiteral("file:assets:///foo/bar") << true << QStringLiteral("assets:///foo/bar"); + + QTest::addRow("file:content empty") << QStringLiteral("file:content:") << true << QStringLiteral("content:"); + QTest::addRow("file:content no slash") << QStringLiteral("file:content:foo/bar") << true << QStringLiteral("content:foo/bar"); + QTest::addRow("file:content 1 slash") << QStringLiteral("file:content:/foo/bar") << true << QStringLiteral("content:/foo/bar"); + QTest::addRow("file:content 2 slashes") << QStringLiteral("file:content://foo/bar") << true << QStringLiteral("content://foo/bar"); + QTest::addRow("file:content 3 slashes") << QStringLiteral("file:content:///foo/bar") << true << QStringLiteral("content:///foo/bar"); + + const QString contentExternalstoragePath = hasAssetsAndContent ? + QStringLiteral("content://com.android.externalstorage.documents/foo") : invalid; + const QString contentDownloadsPath = hasAssetsAndContent ? + QStringLiteral("content://com.android.providers.downloads.documents/foo") : invalid; + const QString contentMediaPath = hasAssetsAndContent ? + QStringLiteral("content://com.android.providers.media.documents") : invalid; + + QTest::addRow("content externalstorage") << QStringLiteral("content://com.android.externalstorage.documents/foo") + << hasAssetsAndContent << contentExternalstoragePath; + QTest::addRow("content downloads documents") << QStringLiteral("content://com.android.providers.downloads.documents/foo") + << hasAssetsAndContent << contentDownloadsPath; + QTest::addRow("content media documents") << QStringLiteral("content://com.android.providers.media.documents") + << hasAssetsAndContent << contentMediaPath; + + QTest::addRow("assets externalstorage") << QStringLiteral("assets://com.android.externalstorage.documents/foo") + << false << invalid; + QTest::addRow("assets downloads documents") << QStringLiteral("assets://com.android.providers.downloads.documents/foo") + << false << invalid; + QTest::addRow("assets media documents") << QStringLiteral("assets://com.android.providers.media.documents") + << false << invalid; +} + +void tst_qqmlfile::isLocalFile_data() +{ + urlData(); +} + +void tst_qqmlfile::isLocalFile() +{ + QFETCH(QString, urlString); + QFETCH(bool, isLocal); + + const QUrl url(urlString); + + QCOMPARE(QQmlFile::isLocalFile(urlString), isLocal); + QCOMPARE(QQmlFile::isLocalFile(url), isLocal); +} + +void tst_qqmlfile::urlToLocalFileOrQrcOverloads_data() +{ + urlData(); +} + void tst_qqmlfile::urlToLocalFileOrQrcOverloads() { - const QString urlString = QStringLiteral("qrc:///example.qml"); + QFETCH(QString, urlString); + QFETCH(QString, localPath); + const QUrl url(urlString); const QString pathForUrlString = QQmlFile::urlToLocalFileOrQrc(urlString); const QString pathForUrl = QQmlFile::urlToLocalFileOrQrc(url); - QCOMPARE(pathForUrlString, pathForUrl); - QCOMPARE(pathForUrlString, QStringLiteral(":/example.qml")); + QCOMPARE(pathForUrlString, localPath); + QCOMPARE(pathForUrl, localPath); } QTEST_GUILESS_MAIN(tst_qqmlfile) diff --git a/tests/auto/qml/qqmlfileselector/tst_qqmlfileselector.cpp b/tests/auto/qml/qqmlfileselector/tst_qqmlfileselector.cpp index 35c17ce588..4c99bcf9c8 100644 --- a/tests/auto/qml/qqmlfileselector/tst_qqmlfileselector.cpp +++ b/tests/auto/qml/qqmlfileselector/tst_qqmlfileselector.cpp @@ -86,19 +86,13 @@ void tst_qqmlfileselector::basicTestCached() void tst_qqmlfileselector::applicationEngineTest() { QQmlApplicationEngine engine; -QT_WARNING_PUSH -QT_WARNING_DISABLE_DEPRECATED - QQmlFileSelector* selector = QQmlFileSelector::get(&engine); -QT_WARNING_POP - QVERIFY(selector != nullptr); - selector->setExtraSelectors(QStringList() << "basic"); + engine.setExtraFileSelectors(QStringList() << "basic"); + engine.load(testFileUrl("basicTest.qml")); - QQmlComponent component(&engine, testFileUrl("basicTest.qml")); - QObject *object = component.create(); + QVERIFY(!engine.rootObjects().isEmpty()); + QObject *object = engine.rootObjects().at(0); QVERIFY(object != nullptr); QCOMPARE(object->property("value").toString(), QString("selected")); - - delete object; } QTEST_MAIN(tst_qqmlfileselector) diff --git a/tests/auto/qml/qqmljsscope/CMakeLists.txt b/tests/auto/qml/qqmljsscope/CMakeLists.txt index 53cfda55cf..4ea4b3fa8c 100644 --- a/tests/auto/qml/qqmljsscope/CMakeLists.txt +++ b/tests/auto/qml/qqmljsscope/CMakeLists.txt @@ -1,3 +1,9 @@ +# Collect test data +file(GLOB_RECURSE test_data_glob + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + data/*) +list(APPEND test_data ${test_data_glob}) + qt_internal_add_test(tst_qqmljsscope SOURCES tst_qqmljsscope.cpp @@ -5,8 +11,12 @@ qt_internal_add_test(tst_qqmljsscope Qt::QmlPrivate Qt::QmlCompilerPrivate Qt::QuickTestUtilsPrivate + + qqmljsscope_test_module + qqmljsscope_test_moduleplugin + TESTDATA - data/orderedBindings.qml + ${test_data} ) add_dependencies(tst_qqmljsscope Qt::Quick) # we need QtQuick QML module @@ -20,3 +30,5 @@ qt_internal_extend_target(tst_qqmljsscope CONDITION NOT ANDROID AND NOT IOS DEFINES QT_QMLTEST_DATADIR=\\\"${CMAKE_CURRENT_SOURCE_DIR}/data\\\" ) + +add_subdirectory(QQmlJSScopeTests) diff --git a/tests/auto/qml/qqmljsscope/QQmlJSScopeTests/CMakeLists.txt b/tests/auto/qml/qqmljsscope/QQmlJSScopeTests/CMakeLists.txt new file mode 100644 index 0000000000..60156139c8 --- /dev/null +++ b/tests/auto/qml/qqmljsscope/QQmlJSScopeTests/CMakeLists.txt @@ -0,0 +1,15 @@ +qt_add_library(qqmljsscope_test_module STATIC) +qt_autogen_tools_initial_setup(qqmljsscope_test_module) + +# use PUBLIC everywhere to simplify the build of the test binary +target_include_directories(qqmljsscope_test_module PUBLIC cpptypes/) +target_link_libraries(qqmljsscope_test_module PUBLIC Qt::Core Qt::Qml Qt::Gui) + +qt6_add_qml_module(qqmljsscope_test_module + VERSION 1.0 + URI QQmlJSScopeTests + SOURCES + extensiontypes.h +) + +qt_autogen_tools_initial_setup(qqmljsscope_test_moduleplugin) diff --git a/tests/auto/qml/qqmljsscope/QQmlJSScopeTests/extensiontypes.h b/tests/auto/qml/qqmljsscope/QQmlJSScopeTests/extensiontypes.h new file mode 100644 index 0000000000..01712e3251 --- /dev/null +++ b/tests/auto/qml/qqmljsscope/QQmlJSScopeTests/extensiontypes.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite 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$ +** +****************************************************************************/ + +#ifndef EXTENSIONTYPES_H +#define EXTENSIONTYPES_H + +#include <QtCore/qobject.h> +#include <qqml.h> + +class Extension : public QObject +{ + Q_OBJECT + QML_ANONYMOUS + Q_PROPERTY(int count READ getCount WRITE setCount NOTIFY countChanged) + +public: + Extension(QObject *parent = nullptr) : QObject(parent) { } + int getCount() const { return 42; } + void setCount(int) { } +Q_SIGNALS: + void countChanged(); +}; + +class IndirectExtension : public Extension +{ + Q_OBJECT + QML_ANONYMOUS +public: + IndirectExtension(QObject *parent = nullptr) : Extension(parent) { } +}; + +class Extended : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_EXTENDED(Extension) + Q_PROPERTY(double count READ getCount WRITE setCount NOTIFY countChanged) + +public: + Extended(QObject *parent = nullptr) : QObject(parent) { } + double getCount() const { return 0.0; } + void setCount(double) { } +Q_SIGNALS: + void countChanged(); +}; + +class ExtendedIndirect : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_EXTENDED(IndirectExtension) + Q_PROPERTY(double count READ getCount WRITE setCount NOTIFY countChanged) + +public: + ExtendedIndirect(QObject *parent = nullptr) : QObject(parent) { } + double getCount() const { return 0; } + void setCount(double) { } +Q_SIGNALS: + void countChanged(); +}; + +class Extension2 : public QObject +{ + Q_OBJECT + QML_ANONYMOUS + Q_PROPERTY(QString str READ getStr WRITE setStr NOTIFY strChanged) + +public: + Extension2(QObject *parent = nullptr) : QObject(parent) { } + QString getStr() const { return QStringLiteral("42"); } + void setStr(QString) { } +Q_SIGNALS: + void strChanged(); +}; + +class ExtendedTwice : public Extended +{ + Q_OBJECT + QML_ELEMENT + QML_EXTENDED(Extension2) + Q_PROPERTY(QByteArray str READ getStr WRITE setStr) + +public: + ExtendedTwice(QObject *parent = nullptr) : Extended(parent) { } + QByteArray getStr() const { return QByteArray(); } + void setStr(QByteArray) { } +}; + +#endif // EXTENSIONTYPES_H diff --git a/tests/auto/qml/qqmljsscope/data/extensions.qml b/tests/auto/qml/qqmljsscope/data/extensions.qml new file mode 100644 index 0000000000..0f4a3fbfec --- /dev/null +++ b/tests/auto/qml/qqmljsscope/data/extensions.qml @@ -0,0 +1,8 @@ +import QtQuick +import QQmlJSScopeTests 1.0 + +Item { + Extended { } + ExtendedIndirect { } + ExtendedTwice { } +} diff --git a/tests/auto/qml/qqmljsscope/tst_qqmljsscope.cpp b/tests/auto/qml/qqmljsscope/tst_qqmljsscope.cpp index bac60d4a54..20375e9f31 100644 --- a/tests/auto/qml/qqmljsscope/tst_qqmljsscope.cpp +++ b/tests/auto/qml/qqmljsscope/tst_qqmljsscope.cpp @@ -77,19 +77,13 @@ class tst_qqmljsscope : public QQmlDataTest if (!error.message.isEmpty()) return QQmlJSScope::ConstPtr(); - const QStringList importPaths = { - QLibraryInfo::path(QLibraryInfo::QmlImportsPath), - dataDirectory(), - }; - - QQmlJSImporter importer { importPaths, /* resource file mapper */ nullptr }; QQmlJSLogger logger; logger.setFileName(url); logger.setCode(sourceCode); logger.setSilent(true); QQmlJSScope::Ptr target = QQmlJSScope::create(); - QQmlJSImportVisitor visitor(target, &importer, &logger, dataDirectory()); - QQmlJSTypeResolver typeResolver { &importer }; + QQmlJSImportVisitor visitor(target, &m_importer, &logger, dataDirectory()); + QQmlJSTypeResolver typeResolver { &m_importer }; typeResolver.init(&visitor, document.program); return visitor.result(); } @@ -100,9 +94,26 @@ private Q_SLOTS: void orderedBindings(); void signalCreationDifferences(); void descriptiveNameOfNull(); + void extensions(); public: - tst_qqmljsscope() : QQmlDataTest(QT_QMLTEST_DATADIR) { } + tst_qqmljsscope() + : QQmlDataTest(QT_QMLTEST_DATADIR) + , m_importer( + { + QLibraryInfo::path(QLibraryInfo::QmlImportsPath), + dataDirectory(), + // Note: to be able to import the QQmlJSScopeTests + // correctly, we need an additional import path. Use + // this application's binary directory as done by + // QQmlImportDatabase + QCoreApplication::applicationDirPath(), + }, + nullptr) + {} + +private: + QQmlJSImporter m_importer; }; void tst_qqmljsscope::initTestCase() @@ -180,5 +191,27 @@ void tst_qqmljsscope::descriptiveNameOfNull() QCOMPARE(unscoped.descriptiveName(), u"bar of (invalid type)::foo with type baz"_qs); } +void tst_qqmljsscope::extensions() +{ + QQmlJSScope::ConstPtr root = run(u"extensions.qml"_qs); + QVERIFY(root); + QVERIFY(root->isFullyResolved()); + + const auto childScopes = root->childScopes(); + QCOMPARE(childScopes.size(), 3); + + QCOMPARE(childScopes[0]->baseTypeName(), u"Extended"_qs); + QCOMPARE(childScopes[1]->baseTypeName(), u"ExtendedIndirect"_qs); + QCOMPARE(childScopes[2]->baseTypeName(), u"ExtendedTwice"_qs); + QVERIFY(childScopes[0]->isFullyResolved()); + QVERIFY(childScopes[1]->isFullyResolved()); + QVERIFY(childScopes[2]->isFullyResolved()); + + QCOMPARE(childScopes[0]->property(u"count"_qs).typeName(), u"int"_qs); + QCOMPARE(childScopes[1]->property(u"count"_qs).typeName(), u"double"_qs); + QCOMPARE(childScopes[2]->property(u"count"_qs).typeName(), u"int"_qs); + QCOMPARE(childScopes[2]->property(u"str"_qs).typeName(), u"QString"_qs); +} + QTEST_MAIN(tst_qqmljsscope) #include "tst_qqmljsscope.moc" diff --git a/tests/auto/qml/qqmllanguage/data/alias.15a.qml b/tests/auto/qml/qqmllanguage/data/alias.15a.qml new file mode 100644 index 0000000000..ba8097c997 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/alias.15a.qml @@ -0,0 +1,12 @@ +import QtQuick 2.15 + +Item { + id: root + + property alias symbol: symbol + symbol.layer.enabled: true + + Item { + id: symbol + } +} diff --git a/tests/auto/qml/qqmllanguage/data/badGroupedProperty.qml b/tests/auto/qml/qqmllanguage/data/badGroupedProperty.qml new file mode 100644 index 0000000000..1b8ba61725 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/badGroupedProperty.qml @@ -0,0 +1,10 @@ +import QtQml + +QtObject { + id: testItem + property rect rect + onComplete { + rect.x: 2 + rect.width: 22 + } +} diff --git a/tests/auto/qml/qqmllanguage/testtypes.h b/tests/auto/qml/qqmllanguage/testtypes.h index 64fe898ff5..3b227291e2 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.h +++ b/tests/auto/qml/qqmllanguage/testtypes.h @@ -1582,6 +1582,33 @@ public: int own() const { return 93; } }; +class ExtendedNamespaceByObject : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_EXTENDED_NAMESPACE(Extension) + + Q_PROPERTY(QString dummy READ dummy CONSTANT) + Q_PROPERTY(int extension READ extension WRITE setExtension NOTIFY extensionChanged) + + int m_ext = 0; + +public: + ExtendedNamespaceByObject(QObject *parent = nullptr) : QObject(parent) {} + QString dummy() const { return QStringLiteral("dummy"); } + int extension() const { return m_ext; } + void setExtension(int e) + { + if (e != m_ext) { + m_ext = e; + Q_EMIT extensionChanged(); + } + } + +Q_SIGNALS: + void extensionChanged(); +}; + class FactorySingleton : public QObject { Q_OBJECT @@ -1972,6 +1999,25 @@ private: QString m_name; }; +class BindableOnly : public QObject +{ + Q_OBJECT + Q_PROPERTY(QByteArray data READ data WRITE setData BINDABLE dataBindable FINAL) + QML_ELEMENT +public: + BindableOnly(QObject *parent = nullptr) + : QObject(parent) + {} + + QBindable<QByteArray> dataBindable() { return QBindable<QByteArray>(&m_data); } + + QByteArray data() const { return m_data.value(); } + void setData(const QByteArray &newData) { m_data.setValue(newData); } + +private: + QProperty<QByteArray> m_data; +}; + void registerTypes(); #endif // TESTTYPES_H diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index b25dec67f1..e5586d27c9 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -355,6 +355,7 @@ private slots: void checkURLtoURLObject(); void registerValueTypes(); void extendedNamespace(); + void extendedNamespaceByObject(); void factorySingleton(); void extendedSingleton(); void qtbug_85932(); @@ -396,6 +397,9 @@ private slots: void componentMix(); void uncreatableAttached(); + void bindableOnly(); + void badGroupedProperty(); + private: QQmlEngine engine; QStringList defaultImportPathList; @@ -2170,6 +2174,22 @@ void tst_qqmllanguage::aliasProperties() QCOMPARE(subItem->property("y").toInt(), 1); } + // Nested property bindings on group properties that are actually aliases (QTBUG-94983) + { + QQmlComponent component(&engine, testFileUrl("alias.15a.qml")); + VERIFY_ERRORS(0); + + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + QPointer<QObject> subItem = qvariant_cast<QObject*>(object->property("symbol")); + QVERIFY(!subItem.isNull()); + + QPointer<QObject> subSubItem = qvariant_cast<QObject*>(subItem->property("layer")); + + QCOMPARE(subSubItem->property("enabled").value<bool>(), true); + } + // Alias to sub-object with binding (QTBUG-57041) { // This is shold *not* crash. @@ -4418,9 +4438,6 @@ void tst_qqmllanguage::deepProperty() void tst_qqmllanguage::groupAssignmentFailure() { auto ep = std::make_unique<QQmlEngine>(); - QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QQmlComponent: Component destroyed while completion pending"); - QTest::ignoreMessage(QtMsgType::QtWarningMsg, "This may have been caused by one of the following errors:"); - QTest::ignoreMessage(QtMsgType::QtWarningMsg, QRegularExpression(".*Cannot set properties on b as it is null.*")); QTest::ignoreMessage(QtMsgType::QtWarningMsg, QRegularExpression(".*Invalid property assignment: url expected - Assigning null to incompatible properties in QML is deprecated. This will become a compile error in future versions of Qt..*")); QQmlComponent component(ep.get(), testFileUrl("groupFailure.qml")); QScopedPointer<QObject> o(component.create()); @@ -6257,6 +6274,22 @@ void tst_qqmllanguage::extendedNamespace() QCOMPARE(obj->property("fromExtension").toInt(), 9); } +void tst_qqmllanguage::extendedNamespaceByObject() +{ + QQmlEngine engine; + QQmlComponent c(&engine); + c.setData("import StaticTest\n" + "import QtQml\n" + "ExtendedNamespaceByObject {\n" + " extension: 10\n" + "}", QUrl()); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> obj(c.create()); + QVERIFY(!obj.isNull()); + + QCOMPARE(obj->property("extension").toInt(), 10); +} + void tst_qqmllanguage::factorySingleton() { QQmlEngine engine; @@ -6873,6 +6906,33 @@ void tst_qqmllanguage::uncreatableAttached() QLatin1String("Could not create attached properties object 'ItemAttached'"))); } +void tst_qqmllanguage::bindableOnly() +{ + qmlRegisterTypesAndRevisions<BindableOnly>("ABC", 1); + QQmlEngine engine; + + QQmlComponent c(&engine); + c.setData("import ABC\nBindableOnly {\n" + " data: \"sc\" + \"ore\"\n" + " objectName: data\n" + "}", QUrl(u"bindableOnly.qml"_qs)); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QCOMPARE(o->property("data").value<QByteArray>(), QByteArray("score")); + QCOMPARE(o->objectName(), QStringLiteral("score")); +} + +void tst_qqmllanguage::badGroupedProperty() +{ + QQmlEngine engine; + const QUrl url = testFileUrl("badGroupedProperty.qml"); + QQmlComponent c(&engine, url); + QVERIFY(c.isError()); + QCOMPARE(c.errorString(), + QStringLiteral("%1:6 Cannot assign to non-existent property \"onComplete\"\n") + .arg(url.toString())); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" diff --git a/tests/auto/qml/qqmlnotifier/tst_qqmlnotifier.cpp b/tests/auto/qml/qqmlnotifier/tst_qqmlnotifier.cpp index 25eaa22b86..6310ef7d54 100644 --- a/tests/auto/qml/qqmlnotifier/tst_qqmlnotifier.cpp +++ b/tests/auto/qml/qqmlnotifier/tst_qqmlnotifier.cpp @@ -361,7 +361,8 @@ void tst_qqmlnotifier::deleteFromHandler() process.setProgram(QCoreApplication::applicationFilePath()); process.setArguments({"deleteFromHandler"}); process.start(); - QTRY_COMPARE(process.exitStatus(), QProcess::CrashExit); + const bool ok = process.waitForFinished(90000); + QVERIFY(ok); const QByteArray output = process.readAllStandardOutput(); QVERIFY(output.contains("QFATAL")); QVERIFY(output.contains("destroyed while one of its QML signal handlers is in progress")); diff --git a/tests/auto/qml/qqmlpropertycache/tst_qqmlpropertycache.cpp b/tests/auto/qml/qqmlpropertycache/tst_qqmlpropertycache.cpp index 7e1dc2f3e5..14af747024 100644 --- a/tests/auto/qml/qqmlpropertycache/tst_qqmlpropertycache.cpp +++ b/tests/auto/qml/qqmlpropertycache/tst_qqmlpropertycache.cpp @@ -572,10 +572,28 @@ class TestClassWithClassInfo : public QObject #define ARRAY_SIZE(arr) \ int(sizeof(arr) / sizeof(arr[0])) +template <typename T, typename = void> +struct SizeofOffsetsAndSizes_helper +{ + static constexpr size_t value = sizeof(T::offsetsAndSize); // old moc +}; + +template <typename T> +struct SizeofOffsetsAndSizes_helper<T, std::void_t<decltype(T::offsetsAndSizes)>> +{ + static constexpr size_t value = sizeof(T::offsetsAndSizes); // new moc +}; + +template <typename T> +constexpr size_t sizeofOffsetsAndSizes(const T &) +{ + return SizeofOffsetsAndSizes_helper<T>::value; +} + #define TEST_CLASS(Class) \ QTest::newRow(#Class) \ << &Class::staticMetaObject << ARRAY_SIZE(qt_meta_data_##Class) \ - << int(sizeof(qt_meta_stringdata_##Class.offsetsAndSize) / (sizeof(uint) * 2)) + << int(sizeofOffsetsAndSizes(qt_meta_stringdata_##Class) / (sizeof(uint) * 2)) Q_DECLARE_METATYPE(const QMetaObject*); diff --git a/tests/auto/qml/qqmlsettings/tst_qqmlsettings.cpp b/tests/auto/qml/qqmlsettings/tst_qqmlsettings.cpp index baaf126be3..72624457c4 100644 --- a/tests/auto/qml/qqmlsettings/tst_qqmlsettings.cpp +++ b/tests/auto/qml/qqmlsettings/tst_qqmlsettings.cpp @@ -46,7 +46,7 @@ public: private slots: void initTestCase() override; - void init(); + void init() override; void cleanup(); void basic(); @@ -161,6 +161,8 @@ void tst_QQmlSettings::initTestCase() void tst_QQmlSettings::init() { + QQmlDataTest::init(); + QSettings settings; settings.clear(); diff --git a/tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp b/tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp index 38e1e585ce..f7dd8f8939 100644 --- a/tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp +++ b/tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp @@ -102,6 +102,7 @@ private slots: void char16Type(); void writeBackOnFunctionCall(); void valueTypeConversions(); + void readReferenceOnGetOwnProperty(); private: QQmlEngine engine; @@ -1984,6 +1985,35 @@ void tst_qqmlvaluetypes::valueTypeConversions() QCOMPARE(resultB.b, a.a); } +class Chose : public QObject +{ + Q_OBJECT + Q_PROPERTY(QRectF f READ ff CONSTANT) +public: + Chose(QObject *parent = nullptr) : QObject(parent) {} + QRectF ff() const { return QRectF(); } + Q_INVOKABLE bool g(QJSValue v) { return v.hasProperty("x"); } +}; + +void tst_qqmlvaluetypes::readReferenceOnGetOwnProperty() +{ + Chose chose; + QQmlEngine engine; + engine.rootContext()->setContextProperty(QStringLiteral("chose"), &chose); + QQmlComponent c(&engine); + c.setData(R"fin( + import QtQml + QtObject { + property bool allo: chose.g(chose.f) + } + )fin", QUrl()); + + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QVERIFY(o->property("allo").toBool()); +} + #undef CHECK_TYPE_IS_NOT_VALUETYPE QTEST_MAIN(tst_qqmlvaluetypes) diff --git a/tests/auto/qml/qv4mm/tst_qv4mm.cpp b/tests/auto/qml/qv4mm/tst_qv4mm.cpp index 9eea229fa6..fe2e267cfd 100644 --- a/tests/auto/qml/qv4mm/tst_qv4mm.cpp +++ b/tests/auto/qml/qv4mm/tst_qv4mm.cpp @@ -50,7 +50,7 @@ private slots: void gcStats(); void multiWrappedQObjects(); void accessParentOnDestruction(); - void clearICParent(); + void cleanInternalClasses(); void createObjectsOnDestruction(); }; @@ -119,16 +119,41 @@ void tst_qv4mm::accessParentOnDestruction() QCOMPARE(obj->property("destructions").toInt(), 100); } -void tst_qv4mm::clearICParent() +void tst_qv4mm::cleanInternalClasses() { QV4::ExecutionEngine engine; QV4::Scope scope(engine.rootContext()); QV4::ScopedObject object(scope, engine.newObject()); + QV4::ScopedObject prototype(scope, engine.newObject()); + + // Set a prototype so that we get a unique IC. + object->setPrototypeOf(prototype); + + QV4::Scoped<QV4::InternalClass> prevIC(scope, object->internalClass()); + QVERIFY(prevIC->d()->transitions.empty()); + + uint prevIcChainLength = 0; + for (QV4::Heap::InternalClass *ic = object->internalClass(); ic; ic = ic->parent) + ++prevIcChainLength; + + const auto checkICCHainLength = [&]() { + uint icChainLength = 0; + for (QV4::Heap::InternalClass *ic = object->internalClass(); ic; ic = ic->parent) + ++icChainLength; + + const uint redundant = object->internalClass()->numRedundantTransitions; + QVERIFY(redundant <= QV4::Heap::InternalClass::MaxRedundantTransitions); + + // A removal makes two transitions redundant. + QVERIFY(icChainLength <= prevIcChainLength + 2 * redundant); + }; + + const uint numTransitions = 16 * 1024; // Keep identifiers in a separate array so that we don't have to allocate them in the loop that // should test the GC on InternalClass allocations. QV4::ScopedArrayObject identifiers(scope, engine.newArrayObject()); - for (uint i = 0; i < 16 * 1024; ++i) { + for (uint i = 0; i < numTransitions; ++i) { QV4::Scope scope(&engine); QV4::ScopedString s(scope); s = engine.newIdentifier(QString::fromLatin1("key%1").arg(i)); @@ -139,22 +164,60 @@ void tst_qv4mm::clearICParent() object->insertMember(s, v); } - // When allocating the InternalClass objects required for deleting properties, the GC should - // eventually run and remove all but the last two. - // If we ever manage to avoid allocating the InternalClasses in the first place we will need - // to change this test. - for (uint i = 0; i < 16 * 1024; ++i) { + // There is a chain of ICs originating from the original class. + QCOMPARE(prevIC->d()->transitions.size(), 1u); + QVERIFY(prevIC->d()->transitions.front().lookup != nullptr); + + // When allocating the InternalClass objects required for deleting properties, eventually + // the IC chain gets truncated, dropping all the removed properties. + for (uint i = 0; i < numTransitions; ++i) { QV4::Scope scope(&engine); QV4::ScopedString s(scope, identifiers->get(i)); QV4::Scoped<QV4::InternalClass> ic(scope, object->internalClass()); QVERIFY(ic->d()->parent != nullptr); - object->deleteProperty(s->toPropertyKey()); + QV4::ScopedValue val(scope, object->get(s->toPropertyKey())); + QCOMPARE(val->toNumber(), double(i)); + QVERIFY(object->deleteProperty(s->toPropertyKey())); + QVERIFY(!object->hasProperty(s->toPropertyKey())); QVERIFY(object->internalClass() != ic->d()); - QCOMPARE(object->internalClass()->parent, ic->d()); - if (ic->d()->parent == nullptr) - return; } - QFAIL("Garbage collector was not triggered by large amount of InternalClasses"); + + // None of the properties we've added are left + for (uint i = 0; i < numTransitions; ++i) { + QV4::ScopedString s(scope, identifiers->get(i)); + QVERIFY(!object->hasProperty(s->toPropertyKey())); + } + + // Also no other properties have appeared + QScopedPointer<QV4::OwnPropertyKeyIterator> iterator(object->ownPropertyKeys(object)); + QVERIFY(!iterator->next(object).isValid()); + + checkICCHainLength(); + + // Add and remove properties until it clears all remaining redundant ones + uint i = 0; + while (object->internalClass()->numRedundantTransitions > 0) { + i = (i + 1) % numTransitions; + QV4::ScopedString s(scope, identifiers->get(i)); + QV4::ScopedValue v(scope); + v->setDouble(i); + object->insertMember(s, v); + QVERIFY(object->deleteProperty(s->toPropertyKey())); + } + + // Make sure that all dangling ICs are actually gone. + scope.engine->memoryManager->runGC(); + + // Now the GC has removed the ICs we originally added by adding properties. + QVERIFY(prevIC->d()->transitions.empty() || prevIC->d()->transitions.front().lookup == nullptr); + + // Same thing with redundant prototypes + for (uint i = 0; i < numTransitions; ++i) { + QV4::ScopedObject prototype(scope, engine.newObject()); + object->setPrototypeOf(prototype); // Makes previous prototype redundant + } + + checkICCHainLength(); } void tst_qv4mm::createObjectsOnDestruction() diff --git a/tests/auto/qmlls/qmlls/tst_qmlls.cpp b/tests/auto/qmlls/qmlls/tst_qmlls.cpp index 2d0d38f352..a7276fcedf 100644 --- a/tests/auto/qmlls/qmlls/tst_qmlls.cpp +++ b/tests/auto/qmlls/qmlls/tst_qmlls.cpp @@ -108,7 +108,8 @@ tst_Qmlls::tst_Qmlls() connect(&m_server, &QProcess::readyReadStandardError, this, [this]() { qWarning() << "LSPerr" << m_server.readAllStandardError(); }); - m_qmllsPath = QLibraryInfo::path(QLibraryInfo::BinariesPath) + QLatin1String("/qmlls"); + m_qmllsPath = + QLibraryInfo::path(QLibraryInfo::LibraryExecutablesPath) + QLatin1String("/qmlls"); #ifdef Q_OS_WIN m_qmllsPath += QLatin1String(".exe"); #endif diff --git a/tests/auto/quick/pointerhandlers/mousearea_interop/data/dragHandlerInMouseAreaGrandparent.qml b/tests/auto/quick/pointerhandlers/mousearea_interop/data/dragHandlerInMouseAreaGrandparent.qml new file mode 100644 index 0000000000..7adb205a1f --- /dev/null +++ b/tests/auto/quick/pointerhandlers/mousearea_interop/data/dragHandlerInMouseAreaGrandparent.qml @@ -0,0 +1,20 @@ +import QtQuick 2.12 + +Rectangle { + width: 200; height: 200 + + DragHandler { } + + Rectangle { + objectName: "button" + width: 100; height: 40; x: 10; y: 10 + border.color: "orange" + color: ma.pressed ? "lightsteelblue" : "beige" + + MouseArea { + id: ma + anchors.fill: parent + onDoubleClicked: console.log("__") + } + } +} diff --git a/tests/auto/quick/pointerhandlers/mousearea_interop/tst_mousearea_interop.cpp b/tests/auto/quick/pointerhandlers/mousearea_interop/tst_mousearea_interop.cpp index ffc0cc333d..ccc1364705 100644 --- a/tests/auto/quick/pointerhandlers/mousearea_interop/tst_mousearea_interop.cpp +++ b/tests/auto/quick/pointerhandlers/mousearea_interop/tst_mousearea_interop.cpp @@ -55,6 +55,7 @@ private slots: void dragHandlerInSiblingStealingGrabFromMouseAreaViaTouch_data(); void dragHandlerInSiblingStealingGrabFromMouseAreaViaTouch(); void hoverHandlerDoesntHoverOnPress(); + void doubleClickInMouseAreaWithDragHandlerInGrandparent(); private: void createView(QScopedPointer<QQuickView> &window, const char *fileName); @@ -209,6 +210,24 @@ void tst_MouseAreaInterop::hoverHandlerDoesntHoverOnPress() // QTBUG-72843 QCOMPARE(hoveredChangedSpy.count(), 0); } +void tst_MouseAreaInterop::doubleClickInMouseAreaWithDragHandlerInGrandparent() +{ + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("dragHandlerInMouseAreaGrandparent.qml"))); + + QQuickDragHandler *handler = window.rootObject()->findChild<QQuickDragHandler*>(); + QVERIFY(handler); + QSignalSpy dragActiveSpy(handler, &QQuickDragHandler::activeChanged); + QQuickMouseArea *ma = window.rootObject()->findChild<QQuickMouseArea*>(); + QVERIFY(ma); + QSignalSpy dClickSpy(ma, &QQuickMouseArea::doubleClicked); + QPoint p = ma->mapToScene(ma->boundingRect().center()).toPoint(); + + QTest::mouseDClick(&window, Qt::LeftButton, Qt::NoModifier, p); + QCOMPARE(dClickSpy.count(), 1); + QCOMPARE(dragActiveSpy.count(), 0); +} + QTEST_MAIN(tst_MouseAreaInterop) #include "tst_mousearea_interop.moc" diff --git a/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp b/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp index 9b11f8170b..c0192a81d9 100644 --- a/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp +++ b/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp @@ -62,6 +62,7 @@ private slots: void hoverHandlerAndUnderlyingHoverHandler(); void mouseAreaAndUnderlyingHoverHandler(); void hoverHandlerAndUnderlyingMouseArea(); + void disabledHoverHandlerAndUnderlyingMouseArea(); void movingItemWithHoverHandler(); void margin(); void window(); @@ -299,6 +300,52 @@ void tst_HoverHandler::hoverHandlerAndUnderlyingMouseArea() #endif } +void tst_HoverHandler::disabledHoverHandlerAndUnderlyingMouseArea() +{ + // Check that if a disabled HoverHandler is installed on an item, it + // will not participate in hover event delivery, and as such, also + // not block propagation to siblings. + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "lesHoverables.qml"); + QQuickView * window = windowPtr.data(); + QQuickItem * bottomSidebar = window->rootObject()->findChild<QQuickItem *>("bottomSidebar"); + QVERIFY(bottomSidebar); + QQuickMouseArea *bottomSidebarMA = bottomSidebar->findChild<QQuickMouseArea *>("bottomSidebarMA"); + QVERIFY(bottomSidebarMA); + QQuickItem * button = bottomSidebar->findChild<QQuickItem *>("buttonWithHH"); + QVERIFY(button); + QQuickHoverHandler *buttonHH = button->findChild<QQuickHoverHandler *>("buttonHH"); + QVERIFY(buttonHH); + + // By disabling the HoverHandler, it should no longer + // block the sibling MouseArea underneath from receiving hover events. + buttonHH->setEnabled(false); + + QPoint buttonCenter(button->mapToScene(QPointF(button->width() / 2, button->height() / 2)).toPoint()); + QPoint rightOfButton(button->mapToScene(QPointF(button->width() + 2, button->height() / 2)).toPoint()); + QPoint outOfSidebar(bottomSidebar->mapToScene(QPointF(bottomSidebar->width() + 2, bottomSidebar->height() / 2)).toPoint()); + QSignalSpy sidebarHoveredSpy(bottomSidebarMA, SIGNAL(hoveredChanged())); + QSignalSpy buttonHoveredSpy(buttonHH, SIGNAL(hoveredChanged())); + + QTest::mouseMove(window, outOfSidebar); + QCOMPARE(bottomSidebarMA->hovered(), false); + QCOMPARE(sidebarHoveredSpy.count(), 0); + QCOMPARE(buttonHH->isHovered(), false); + QCOMPARE(buttonHoveredSpy.count(), 0); + + QTest::mouseMove(window, buttonCenter); + QCOMPARE(bottomSidebarMA->hovered(), true); + QCOMPARE(sidebarHoveredSpy.count(), 1); + QCOMPARE(buttonHH->isHovered(), false); + QCOMPARE(buttonHoveredSpy.count(), 0); + + QTest::mouseMove(window, rightOfButton); + QCOMPARE(bottomSidebarMA->hovered(), true); + QCOMPARE(sidebarHoveredSpy.count(), 1); + QCOMPARE(buttonHH->isHovered(), false); + QCOMPARE(buttonHoveredSpy.count(), 0); +} + void tst_HoverHandler::movingItemWithHoverHandler() { if (isPlatformWayland()) diff --git a/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/pinchproperties.qml b/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/pinchproperties.qml index 3cabde5f59..425494ced6 100644 --- a/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/pinchproperties.qml +++ b/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/pinchproperties.qml @@ -56,7 +56,7 @@ Rectangle { PinchHandler { id: pincharea objectName: "pinchHandler" - minimumScale: 1.0 + minimumScale: 0.5 maximumScale: 4.0 minimumRotation: 0.0 maximumRotation: 90.0 diff --git a/tests/auto/quick/pointerhandlers/qquickpinchhandler/tst_qquickpinchhandler.cpp b/tests/auto/quick/pointerhandlers/qquickpinchhandler/tst_qquickpinchhandler.cpp index da84bb0a63..b064ffcf13 100644 --- a/tests/auto/quick/pointerhandlers/qquickpinchhandler/tst_qquickpinchhandler.cpp +++ b/tests/auto/quick/pointerhandlers/qquickpinchhandler/tst_qquickpinchhandler.cpp @@ -154,19 +154,19 @@ void tst_QQuickPinchHandler::pinchProperties() QSignalSpy scaleMinSpy(pinchHandler, SIGNAL(minimumScaleChanged())); QSignalSpy scaleMaxSpy(pinchHandler, SIGNAL(maximumScaleChanged())); - QCOMPARE(pinchHandler->minimumScale(), 1.0); + QCOMPARE(pinchHandler->minimumScale(), 0.5); QCOMPARE(pinchHandler->maximumScale(), 4.0); - pinchHandler->setMinimumScale(0.5); + pinchHandler->setMinimumScale(0.25); pinchHandler->setMaximumScale(1.5); - QCOMPARE(pinchHandler->minimumScale(), 0.5); + QCOMPARE(pinchHandler->minimumScale(), 0.25); QCOMPARE(pinchHandler->maximumScale(), 1.5); QCOMPARE(scaleMinSpy.count(),1); QCOMPARE(scaleMaxSpy.count(),1); - pinchHandler->setMinimumScale(0.5); + pinchHandler->setMinimumScale(0.25); pinchHandler->setMaximumScale(1.5); QCOMPARE(scaleMinSpy.count(),1); @@ -373,14 +373,18 @@ void tst_QQuickPinchHandler::scaleThreeFingers() void tst_QQuickPinchHandler::scaleNativeGesture_data() { QTest::addColumn<QString>("qmlfile"); + QTest::addColumn<qreal>("scale"); - QTest::newRow("just pinch") << "pinchproperties.qml"; - QTest::newRow("pinch & drag") << "pinchAndDrag.qml"; + QTest::newRow("just pinch") << "pinchproperties.qml" << 1.1; + QTest::newRow("pinch & drag") << "pinchAndDrag.qml" << 1.1; + QTest::newRow("bigger than limit") << "pinchproperties.qml" << 5.0; + QTest::newRow("smaller than limit") << "pinchproperties.qml" << 0.25; } void tst_QQuickPinchHandler::scaleNativeGesture() { QFETCH(QString, qmlfile); + QFETCH(qreal, scale); QQuickView *window = QQuickViewTestUtils::createView(); QScopedPointer<QQuickView> scope(window); @@ -400,8 +404,8 @@ void tst_QQuickPinchHandler::scaleNativeGesture() QPointF targetPos = target->position(); ulong ts = 1; - // first pinch: scale it up - const qreal expectedScale = 1.1; + // first pinch: scale it + const qreal expectedScale = qBound(qreal(0.5), scale, qreal(4)); QPointF pinchPos(75, 75); QPointF pinchLocalPos = target->mapFromScene(pinchPos); // target position is adjusted in QQuickItemPrivate::adjustedPosForTransform() @@ -411,11 +415,11 @@ void tst_QQuickPinchHandler::scaleNativeGesture() QWindowSystemInterface::handleGestureEvent(window, ts++, touchpad, Qt::BeginNativeGesture, pinchPos, pinchPos); QWindowSystemInterface::handleGestureEventWithRealValue(window, ts++, touchpad, - Qt::ZoomNativeGesture, expectedScale - 1, pinchPos, pinchPos); + Qt::ZoomNativeGesture, scale - 1, pinchPos, pinchPos); QTRY_COMPARE(target->scale(), expectedScale); QCOMPARE(pinchHandler->active(), true); - QCOMPARE(pinchHandler->centroid().position(), pinchLocalPos); - QCOMPARE(pinchHandler->centroid().scenePosition(), pinchPos); + QCOMPARE(pinchHandler->centroid().position().toPoint(), pinchLocalPos.toPoint()); + QCOMPARE(pinchHandler->centroid().scenePosition().toPoint(), pinchPos.toPoint()); QVERIFY(qAbs(target->position().x() - expectedPos.x()) < 0.001); QVERIFY(qAbs(target->position().y() - expectedPos.y()) < 0.001); QCOMPARE(pinchHandler->scale(), expectedScale); @@ -431,9 +435,12 @@ void tst_QQuickPinchHandler::scaleNativeGesture() QCOMPARE(pinchHandler->translation(), QVector2D()); QCOMPARE(pinchHandler->rotation(), 0); - // second pinch at a different position: scale it down to original size again + // second pinch at a different position: scale it back to original size again + // but remove the limits first, so that we can scale arbitrarily + pinchHandler->setMaximumScale(qInf()); + pinchHandler->setMinimumScale(-qInf()); const qreal reverseScale = (1 / expectedScale); - pinchPos = QPointF(125, 125); + pinchPos = QPointF(110, 110); pinchLocalPos = target->mapFromScene(pinchPos); QWindowSystemInterface::handleGestureEvent(window, ts++, touchpad, Qt::BeginNativeGesture, pinchPos, pinchPos); @@ -441,8 +448,8 @@ void tst_QQuickPinchHandler::scaleNativeGesture() Qt::ZoomNativeGesture, reverseScale - 1, pinchPos, pinchPos); QTRY_COMPARE(target->scale(), 1); QCOMPARE(pinchHandler->active(), true); - QCOMPARE(pinchHandler->centroid().position(), pinchLocalPos); - QCOMPARE(pinchHandler->centroid().scenePosition(), pinchPos); + QCOMPARE(pinchHandler->centroid().position().toPoint(), pinchLocalPos.toPoint()); + QCOMPARE(pinchHandler->centroid().scenePosition().toPoint(), pinchPos.toPoint()); QCOMPARE(pinchHandler->scale(), 1); QCOMPARE(pinchHandler->activeScale(), reverseScale); QWindowSystemInterface::handleGestureEvent(window, ts++, touchpad, @@ -680,7 +687,7 @@ void tst_QQuickPinchHandler::retouch() // accept some slack QVERIFY(withinBounds(1.4, root->property("scale").toReal(), 1.6)); - QCOMPARE(pinchHandler->centroid().position(), QPointF(40, 40)); // blackrect is at 50,50 + QCOMPARE(pinchHandler->centroid().position().toPoint(), QPoint(40, 40)); // blackrect is at 50,50 QVERIFY(withinBounds(1.4, blackRect->scale(), 1.6)); QCOMPARE(root->property("activeCount").toInt(), 1); @@ -763,7 +770,7 @@ void tst_QQuickPinchHandler::cancel() QQuickTouchUtils::flush(window); QVERIFY(withinBounds(1.4, root->property("scale").toReal(), 1.6)); - QCOMPARE(pinchHandler->centroid().position(), QPointF(40, 40)); // blackrect is at 50,50 + QCOMPARE(pinchHandler->centroid().position().toPoint(), QPoint(40, 40)); // blackrect is at 50,50 QVERIFY(withinBounds(1.4, blackRect->scale(), 1.6)); QSKIP("cancel is not supported atm"); @@ -773,7 +780,7 @@ void tst_QQuickPinchHandler::cancel() QQuickTouchUtils::flush(window); QCOMPARE(root->property("scale").toReal(), 1.0); - QCOMPARE(root->property("center").toPointF(), QPointF(40, 40)); // blackrect is at 50,50 + QCOMPARE(root->property("center").toPoint(), QPoint(40, 40)); // blackrect is at 50,50 QCOMPARE(blackRect->scale(), 1.0); QVERIFY(!root->property("pinchActive").toBool()); } diff --git a/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp b/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp index 6296eb4da8..e68f9b127c 100644 --- a/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp +++ b/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp @@ -341,11 +341,11 @@ void tst_PointerHandlers::touchEventDelivery() QTest::touchEvent(window, touchDevice).move(0, p1, window); QQuickTouchUtils::flush(window); qCDebug(lcPointerTests) << "events after touch move" << eventItem1->eventList; - QCOMPARE(eventItem1->eventList.size(), 2); // no grabs -> no updates + QCOMPARE(eventItem1->eventList.size(), 3); // no grabs -> only the handler gets the update QTest::touchEvent(window, touchDevice).release(0, p1, window); QQuickTouchUtils::flush(window); qCDebug(lcPointerTests) << "events after touch release" << eventItem1->eventList; - QCOMPARE(eventItem1->eventList.size(), 3); + QCOMPARE(eventItem1->eventList.size(), 4); QCOMPARE_EVENT(eventItem1->eventList.size() - 1, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Released, NoGrab); eventItem1->eventList.clear(); @@ -396,12 +396,12 @@ void tst_PointerHandlers::touchEventDelivery() QTest::touchEvent(window, touchDevice).move(0, p1, window); QQuickTouchUtils::flush(window); qCDebug(lcPointerTests) << "events after touch move" << eventItem1->eventList; - QCOMPARE(eventItem1->eventList.size(), 2); + QCOMPARE(eventItem1->eventList.size(), 3); QTest::touchEvent(window, touchDevice).release(0, p1, window); QQuickTouchUtils::flush(window); qCDebug(lcPointerTests) << "events after touch release" << eventItem1->eventList; - QCOMPARE(eventItem1->eventList.size(), 3); - QCOMPARE_EVENT(2, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Released, NoGrab); + QCOMPARE(eventItem1->eventList.size(), 4); + QCOMPARE_EVENT(3, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Released, NoGrab); eventItem1->eventList.clear(); // wait to avoid getting a double click event @@ -423,11 +423,11 @@ void tst_PointerHandlers::touchEventDelivery() QTest::touchEvent(window, touchDevice).move(0, p1, window); QQuickTouchUtils::flush(window); qCDebug(lcPointerTests) << "events after touch move" << eventItem1->eventList; - QCOMPARE(eventItem1->eventList.size(), 2); + QCOMPARE(eventItem1->eventList.size(), 3); QTest::touchEvent(window, touchDevice).release(0, p1, window); QQuickTouchUtils::flush(window); qCDebug(lcPointerTests) << "events after touch release" << eventItem1->eventList; - QCOMPARE(eventItem1->eventList.size(), 3); + QCOMPARE(eventItem1->eventList.size(), 4); eventItem1->eventList.clear(); // wait to avoid getting a double click event @@ -501,14 +501,14 @@ void tst_PointerHandlers::mouseEventDelivery() QPoint p1 = QPoint(20, 20); QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); qCDebug(lcPointerTests) << "events after mouse press" << eventItem1->eventList; - QCOMPARE(eventItem1->eventList.size(), 2); + QCOMPARE(eventItem1->eventList.size(), 3); // handler: hover; handler: press; item: press p1 += QPoint(10, 0); QTest::mouseMove(window, p1); qCDebug(lcPointerTests) << "events after mouse move" << eventItem1->eventList; - QCOMPARE(eventItem1->eventList.size(), 2); + QCOMPARE(eventItem1->eventList.size(), 4); // handler: hover QTest::mouseRelease(window, Qt::LeftButton); qCDebug(lcPointerTests) << "events after mouse release" << eventItem1->eventList; - QCOMPARE(eventItem1->eventList.size(), 2); + QCOMPARE(eventItem1->eventList.size(), 4); eventItem1->eventList.clear(); // wait to avoid getting a double click event @@ -521,9 +521,9 @@ void tst_PointerHandlers::mouseEventDelivery() p1 = QPoint(20, 20); QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); qCDebug(lcPointerTests) << "events after mouse press" << eventItem1->eventList; - QCOMPARE(eventItem1->eventList.size(), 2); - QCOMPARE_EVENT(0, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Pressed, NoGrab); - QCOMPARE_EVENT(1, Event::MouseDestination, QEvent::MouseButtonPress, QEventPoint::State::Pressed, QPointingDevice::GrabExclusive); + QCOMPARE(eventItem1->eventList.size(), 3); + QCOMPARE_EVENT(1, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Pressed, NoGrab); + QCOMPARE_EVENT(2, Event::MouseDestination, QEvent::MouseButtonPress, QEventPoint::State::Pressed, QPointingDevice::GrabExclusive); QCOMPARE(window->mouseGrabberItem(), eventItem1); QPointF localPos = eventItem1->mapFromScene(p1); @@ -536,11 +536,11 @@ void tst_PointerHandlers::mouseEventDelivery() p1 += QPoint(10, 0); QTest::mouseMove(window, p1); qCDebug(lcPointerTests) << "events after mouse move" << eventItem1->eventList; - QCOMPARE(eventItem1->eventList.size(), 4); - QCOMPARE_EVENT(3, Event::MouseDestination, QEvent::MouseMove, QEventPoint::State::Updated, QPointingDevice::GrabExclusive); + QCOMPARE(eventItem1->eventList.size(), 5); + QCOMPARE_EVENT(4, Event::MouseDestination, QEvent::MouseMove, QEventPoint::State::Updated, QPointingDevice::GrabExclusive); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); qCDebug(lcPointerTests) << "events after mouse release" << eventItem1->eventList; - QCOMPARE(eventItem1->eventList.size(), 7); + QCOMPARE(eventItem1->eventList.size(), 8); QCOMPARE_EVENT(eventItem1->eventList.size() - 2, Event::MouseDestination, QEvent::MouseButtonRelease, QEventPoint::State::Released, NoGrab); QCOMPARE_EVENT(eventItem1->eventList.size() - 1, Event::MouseDestination, QEvent::UngrabMouse, QEventPoint::State::Released, QPointingDevice::UngrabExclusive); eventItem1->eventList.clear(); @@ -647,9 +647,10 @@ void tst_PointerHandlers::dynamicCreation() QPoint p1(20, 20); QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); - QTRY_COMPARE(eventItem1->eventList.size(), 2); - QCOMPARE_EVENT(0, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Pressed, NoGrab); - QCOMPARE_EVENT(1, Event::MouseDestination, QEvent::MouseButtonPress, QEventPoint::State::Pressed, NoGrab); + QTRY_COMPARE(eventItem1->eventList.size(), 3); + QCOMPARE_EVENT(0, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Updated, NoGrab); + QCOMPARE_EVENT(1, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Pressed, NoGrab); + QCOMPARE_EVENT(2, Event::MouseDestination, QEvent::MouseButtonPress, QEventPoint::State::Pressed, NoGrab); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); } diff --git a/tests/auto/quick/pointerhandlers/qquickpointhandler/tst_qquickpointhandler.cpp b/tests/auto/quick/pointerhandlers/qquickpointhandler/tst_qquickpointhandler.cpp index d22b3d86aa..f9f3216fbe 100644 --- a/tests/auto/quick/pointerhandlers/qquickpointhandler/tst_qquickpointhandler.cpp +++ b/tests/auto/quick/pointerhandlers/qquickpointhandler/tst_qquickpointhandler.cpp @@ -149,6 +149,8 @@ void tst_PointHandler::tabletStylus() QQuickPointHandler *handler = window->rootObject()->findChild<QQuickPointHandler *>("pointHandler"); QVERIFY(handler); handler->setAcceptedDevices(QInputDevice::DeviceType::Stylus); + // avoid generating a double click + QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10); QSignalSpy activeSpy(handler, SIGNAL(activeChanged())); QSignalSpy pointSpy(handler, SIGNAL(pointChanged())); diff --git a/tests/auto/quick/pointerhandlers/qquicktaphandler/data/tapHandlersOverlapped.qml b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/nested.qml index 8d2e36d921..8d2e36d921 100644 --- a/tests/auto/quick/pointerhandlers/qquicktaphandler/data/tapHandlersOverlapped.qml +++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/nested.qml diff --git a/tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp b/tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp index fb817ca045..c17fb5777a 100644 --- a/tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp +++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp @@ -75,6 +75,8 @@ private slots: void rightLongPressIgnoreWheel(); void negativeZStackingOrder(); void nonTopLevelParentWindow(); + void nestedDoubleTap_data(); + void nestedDoubleTap(); private: void createView(QScopedPointer<QQuickView> &window, const char *fileName, @@ -837,7 +839,7 @@ void tst_TapHandler::rightLongPressIgnoreWheel() void tst_TapHandler::negativeZStackingOrder() // QTBUG-83114 { QScopedPointer<QQuickView> windowPtr; - createView(windowPtr, "tapHandlersOverlapped.qml"); + createView(windowPtr, "nested.qml"); QQuickView *window = windowPtr.data(); QQuickItem *root = window->rootObject(); @@ -891,6 +893,42 @@ void tst_TapHandler::nonTopLevelParentWindow() // QTBUG-91716 QCOMPARE(root->property("tapCount").toInt(), 2); } +void tst_TapHandler::nestedDoubleTap_data() +{ + QTest::addColumn<QQuickTapHandler::GesturePolicy>("childGesturePolicy"); + + QTest::newRow("DragThreshold") << QQuickTapHandler::GesturePolicy::DragThreshold; + QTest::newRow("WithinBounds") << QQuickTapHandler::GesturePolicy::WithinBounds; + QTest::newRow("ReleaseWithinBounds") << QQuickTapHandler::GesturePolicy::ReleaseWithinBounds; + QTest::newRow("DragWithinBounds") << QQuickTapHandler::GesturePolicy::DragWithinBounds; +} + +void tst_TapHandler::nestedDoubleTap() // QTBUG-102625 +{ + QFETCH(QQuickTapHandler::GesturePolicy, childGesturePolicy); + + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("nested.qml"))); + QQuickItem *root = window.rootObject(); + QQuickTapHandler *parentTapHandler = root->findChild<QQuickTapHandler*>("parentTapHandler"); + QVERIFY(parentTapHandler); + QSignalSpy parentSpy(parentTapHandler, &QQuickTapHandler::doubleTapped); + QQuickTapHandler *childTapHandler = root->findChild<QQuickTapHandler*>("childTapHandler"); + QVERIFY(childTapHandler); + QSignalSpy childSpy(childTapHandler, &QQuickTapHandler::doubleTapped); + childTapHandler->setGesturePolicy(childGesturePolicy); + + QTest::mouseDClick(&window, Qt::LeftButton, Qt::NoModifier, QPoint(150, 100)); + + QCOMPARE(childSpy.count(), 1); + // If the child gets by with a passive grab, both handlers see tap and double-tap. + // If the child takes an exclusive grab and stops event propagation, the parent doesn't see them. + QCOMPARE(parentSpy.count(), + childGesturePolicy == QQuickTapHandler::GesturePolicy::DragThreshold ? 1 : 0); + QCOMPARE(root->property("taps").toList().count(), + childGesturePolicy == QQuickTapHandler::GesturePolicy::DragThreshold ? 4 : 2); +} + QTEST_MAIN(tst_TapHandler) #include "tst_qquicktaphandler.moc" diff --git a/tests/auto/quick/qquickaccessible/tst_qquickaccessible.cpp b/tests/auto/quick/qquickaccessible/tst_qquickaccessible.cpp index d48c7ed163..31032ff64b 100644 --- a/tests/auto/quick/qquickaccessible/tst_qquickaccessible.cpp +++ b/tests/auto/quick/qquickaccessible/tst_qquickaccessible.cpp @@ -69,7 +69,7 @@ public: public slots: void initTestCase() override; void cleanupTestCase(); - void init(); + void init() override; void cleanup(); private slots: @@ -111,6 +111,7 @@ void tst_QQuickAccessible::cleanupTestCase() void tst_QQuickAccessible::init() { + QQmlDataTest::init(); QTestAccessibility::clearEvents(); } diff --git a/tests/auto/quick/qquickbehaviors/tst_qquickbehaviors.cpp b/tests/auto/quick/qquickbehaviors/tst_qquickbehaviors.cpp index 159fb1398e..bc1c16de6d 100644 --- a/tests/auto/quick/qquickbehaviors/tst_qquickbehaviors.cpp +++ b/tests/auto/quick/qquickbehaviors/tst_qquickbehaviors.cpp @@ -46,7 +46,7 @@ public: tst_qquickbehaviors() : QQmlDataTest(QT_QMLTEST_DATADIR) {} private slots: - void init() { qApp->processEvents(); } //work around animation timer bug (QTBUG-22865) + void init() override; void simpleBehavior(); void scriptTriggered(); void cppTriggered(); @@ -81,6 +81,13 @@ private slots: void defaultQProperty(); }; +void tst_qquickbehaviors::init() +{ + QQmlDataTest::init(); + //work around animation timer bug (QTBUG-22865) + qApp->processEvents(); +} + void tst_qquickbehaviors::simpleBehavior() { QQmlEngine engine; diff --git a/tests/auto/quick/qquickboundaryrule/tst_qquickboundaryrule.cpp b/tests/auto/quick/qquickboundaryrule/tst_qquickboundaryrule.cpp index ee9321a236..d346e09d40 100644 --- a/tests/auto/quick/qquickboundaryrule/tst_qquickboundaryrule.cpp +++ b/tests/auto/quick/qquickboundaryrule/tst_qquickboundaryrule.cpp @@ -41,10 +41,17 @@ public: tst_qquickboundaryrule() : QQmlDataTest(QT_QMLTEST_DATADIR) {} private slots: - void init() { qApp->processEvents(); } //work around animation timer bug (QTBUG-22865) + void init() override; void dragHandler(); }; +void tst_qquickboundaryrule::init() +{ + QQmlDataTest::init(); + //work around animation timer bug (QTBUG-22865) + qApp->processEvents(); +} + void tst_qquickboundaryrule::dragHandler() { QQuickView window; diff --git a/tests/auto/quick/qquickdroparea/data/ignoreRetriggerEvent.qml b/tests/auto/quick/qquickdroparea/data/ignoreRetriggerEvent.qml new file mode 100644 index 0000000000..af25a04ee7 --- /dev/null +++ b/tests/auto/quick/qquickdroparea/data/ignoreRetriggerEvent.qml @@ -0,0 +1,15 @@ +import QtQuick 2.0 + +DropArea { + property int enterEvents: 0 + property int exitEvents: 0 + width: 100; height: 100 + objectName: "dropArea" + onEntered: function (drag) { ++enterEvents; drag.accepted = false } + onExited: {++exitEvents} + Item { + objectName: "dragItem" + x: 50; y: 50 + width: 10; height: 10 + } +} diff --git a/tests/auto/quick/qquickdroparea/tst_qquickdroparea.cpp b/tests/auto/quick/qquickdroparea/tst_qquickdroparea.cpp index de7e3f1629..dcc05f548b 100644 --- a/tests/auto/quick/qquickdroparea/tst_qquickdroparea.cpp +++ b/tests/auto/quick/qquickdroparea/tst_qquickdroparea.cpp @@ -67,6 +67,9 @@ public: private slots: void containsDrag_internal(); void containsDrag_external(); + + void ignoreRetriggerEvent(); + void keys_internal(); void keys_external(); void source_internal(); @@ -829,6 +832,32 @@ void tst_QQuickDropArea::competingDrags() QCOMPARE(evaluate<QString>(dropArea1, "statuslol"), QStringLiteral("parent")); } +void tst_QQuickDropArea::ignoreRetriggerEvent() +{ + QQuickView window; + QByteArray errorMessage; + QVERIFY2(QQuickTest::initView(window, testFileUrl("ignoreRetriggerEvent.qml"), true, &errorMessage), errorMessage.constData()); + + QQuickItem *dropArea = window.rootObject(); + QVERIFY(dropArea); + QQuickItem *dragItem = dropArea->findChild<QQuickItem *>("dragItem"); + QVERIFY(dragItem); + + evaluate<void>(dragItem, "Drag.active = true"); + // Drag the item within the drop area + dragItem->setPosition(QPointF(25, 25)); + QCoreApplication::processEvents(); + dragItem->setPosition(QPointF(50, 50)); + QCoreApplication::processEvents(); + dragItem->setPosition(QPointF(75, 75)); + QCoreApplication::processEvents(); + + QCOMPARE(evaluate<bool>(dropArea, "containsDrag"), false); + QCOMPARE(evaluate<int>(dropArea, "enterEvents"), 1); + QCOMPARE(evaluate<int>(dropArea, "exitEvents"), 0); +} + + void tst_QQuickDropArea::simultaneousDrags() { QQuickWindow window; diff --git a/tests/auto/quick/qquickflickable/data/contentPosWhileDragging.qml b/tests/auto/quick/qquickflickable/data/contentPosWhileDragging.qml new file mode 100644 index 0000000000..b42fbc1adb --- /dev/null +++ b/tests/auto/quick/qquickflickable/data/contentPosWhileDragging.qml @@ -0,0 +1,24 @@ +import QtQuick +import QtQuick.Shapes +import QtQuick.Controls + +Item { + id: root + width: 500 + height: 500 + Flickable { + anchors.centerIn: parent + width: 100 + height: 100 + clip: true + contentWidth: content.width + contentHeight: content.height + Rectangle { + id: content + width: 320 + height: width + color: "#41cd52" + radius: width/2 + } + } +} diff --git a/tests/auto/quick/qquickflickable/data/fractionalExtent.qml b/tests/auto/quick/qquickflickable/data/fractionalExtent.qml new file mode 100644 index 0000000000..2675e8c85a --- /dev/null +++ b/tests/auto/quick/qquickflickable/data/fractionalExtent.qml @@ -0,0 +1,14 @@ +import QtQuick + +Flickable { + width: 300 + height: 300 + contentWidth: content.width; contentHeight: content.height + Rectangle { + id: content + width: 350 + height: 350 + color: "darkkhaki" + } + boundsBehavior: Flickable.StopAtBounds +} diff --git a/tests/auto/quick/qquickflickable/data/nestedmouseareapce.qml b/tests/auto/quick/qquickflickable/data/nestedmouseareapce.qml new file mode 100644 index 0000000000..b36df04d45 --- /dev/null +++ b/tests/auto/quick/qquickflickable/data/nestedmouseareapce.qml @@ -0,0 +1,26 @@ +import QtQuick 2.0 + +Flickable { + id: outer + objectName: "flickable" + width: 400 + height: 400 + contentX: 50 + contentY: 50 + contentWidth: 500 + contentHeight: 500 + + Rectangle { + objectName: "nested" + x: 100 + y: 100 + width: 300 + height: 300 + + color: "yellow" + MouseArea { + anchors.fill: parent + propagateComposedEvents: true + } + } +} diff --git a/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp b/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp index 9682079e84..ac8e7550bc 100644 --- a/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp +++ b/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp @@ -27,10 +27,11 @@ ****************************************************************************/ #include <qtest.h> #include <QtTest/QSignalSpy> +#include <QtQuick/qquickview.h> +#include <QtQuickTest/QtQuickTest> #include <QtGui/QStyleHints> #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcomponent.h> -#include <QtQuick/qquickview.h> #include <private/qquickflickable_p.h> #include <private/qquickflickable_p_p.h> #include <private/qquickmousearea_p.h> @@ -217,6 +218,7 @@ private slots: void stopAtBounds(); void stopAtBounds_data(); void nestedMouseAreaUsingTouch(); + void nestedMouseAreaPropagateComposedEvents(); void nestedSliderUsingTouch(); void nestedSliderUsingTouch_data(); void pressDelayWithLoader(); @@ -238,6 +240,10 @@ private slots: void receiveTapOutsideContentItem(); void flickWhenRotated_data(); void flickWhenRotated(); + void scrollingWithFractionalExtentSize_data(); + void scrollingWithFractionalExtentSize(); + void setContentPositionWhileDragging_data(); + void setContentPositionWhileDragging(); private: void flickWithTouch(QQuickWindow *window, const QPoint &from, const QPoint &to); @@ -2123,6 +2129,27 @@ void tst_qquickflickable::nestedMouseAreaUsingTouch() QVERIFY(nested->y() < 100.0); } +void tst_qquickflickable::nestedMouseAreaPropagateComposedEvents() +{ + QScopedPointer<QQuickView> window(new QQuickView); + window->setSource(testFileUrl("nestedmouseareapce.qml")); + QTRY_COMPARE(window->status(), QQuickView::Ready); + QQuickViewTestUtils::centerOnScreen(window.data()); + QQuickViewTestUtils::moveMouseAway(window.data()); + window->show(); + QVERIFY(window->rootObject() != nullptr); + QVERIFY(QTest::qWaitForWindowActive(window.data())); + + QQuickFlickable *flickable = qobject_cast<QQuickFlickable*>(window->rootObject()); + QVERIFY(flickable != nullptr); + + QCOMPARE(flickable->contentY(), 50.0f); + flickWithTouch(window.data(), QPoint(100, 300), QPoint(100, 200)); + + // flickable should have moved + QVERIFY(!qFuzzyCompare(flickable->contentY(), 50.0)); +} + void tst_qquickflickable::nestedSliderUsingTouch_data() { QTest::addColumn<bool>("keepMouseGrab"); @@ -2815,7 +2842,7 @@ void tst_qquickflickable::flickWhenRotated_data() for (const auto fr : rotations) { if (pr <= 90) { for (const auto s : scales) - QTest::addRow("parent: %g, flickable: %g, scale: %g", pr, fr) << pr << fr << s; + QTest::addRow("parent: %g, flickable: %g, scale: %g", pr, fr, s) << pr << fr << s; } else { // don't bother trying every scale with every set of rotations, to save time QTest::addRow("parent: %g, flickable: %g, scale: 1", pr, fr) << pr << fr << qreal(1); @@ -2853,6 +2880,194 @@ void tst_qquickflickable::flickWhenRotated() // QTBUG-99639 QVERIFY(!flickable->isAtYBeginning()); } + +void tst_qquickflickable::scrollingWithFractionalExtentSize_data() +{ + QTest::addColumn<QByteArray>("property"); + QTest::addColumn<bool>("isYAxis"); + QTest::addColumn<bool>("atBeginning"); + QTest::addColumn<QQuickFlickable::BoundsBehaviorFlag>("boundsBehaviour"); + + QTest::newRow("minyextent") << QByteArray("topMargin") << true << true << QQuickFlickable::StopAtBounds; + QTest::newRow("maxyextent") << QByteArray("bottomMargin") << true << false << QQuickFlickable::StopAtBounds; + QTest::newRow("minxextent") << QByteArray("leftMargin") << false << true << QQuickFlickable::StopAtBounds; + QTest::newRow("maxxextent") << QByteArray("rightMargin") << false << false << QQuickFlickable::StopAtBounds; + + QTest::newRow("minyextent") << QByteArray("topMargin") << true << true << QQuickFlickable::DragAndOvershootBounds; + QTest::newRow("maxyextent") << QByteArray("bottomMargin") << true << false << QQuickFlickable::DragAndOvershootBounds; + QTest::newRow("minxextent") << QByteArray("leftMargin") << false << true << QQuickFlickable::DragAndOvershootBounds; + QTest::newRow("maxxextent") << QByteArray("rightMargin") << false << false << QQuickFlickable::DragAndOvershootBounds; +} + +void tst_qquickflickable::scrollingWithFractionalExtentSize() // QTBUG-101268 +{ + QFETCH(QByteArray, property); + QFETCH(bool, isYAxis); + QFETCH(bool, atBeginning); + QFETCH(QQuickFlickable::BoundsBehaviorFlag, boundsBehaviour); + + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("fractionalExtent.qml"))); + QQuickItem *rootItem = window.rootObject(); + QVERIFY(rootItem); + QQuickFlickable *flickable = qobject_cast<QQuickFlickable *>(rootItem); + QVERIFY(flickable); + flickable->setBoundsBehavior(boundsBehaviour); + + qreal value = 100.5; + flickable->setProperty(property.constData(), 100.5); + if (isYAxis) + flickable->setContentY(atBeginning ? -value : value + qAbs(flickable->height() - flickable->contentHeight())); + else + flickable->setContentX(atBeginning ? -value : value + qAbs(flickable->width() - flickable->contentWidth())); + + if (isYAxis) { + QVERIFY(atBeginning ? flickable->isAtYBeginning() : flickable->isAtYEnd()); + QVERIFY(!flickable->isMovingVertically()); + } else { + QVERIFY(atBeginning ? flickable->isAtXBeginning() : flickable->isAtXEnd()); + QVERIFY(!flickable->isMovingHorizontally()); + } + + QPointF pos(flickable->x() + flickable->width() / 2, flickable->y() + flickable->height() / 2); + QPoint angleDelta(isYAxis ? 0 : (atBeginning ? -50 : 50), isYAxis ? (atBeginning ? -50 : 50) : 0); + + QWheelEvent wheelEvent1(pos, window.mapToGlobal(pos), QPoint(), angleDelta, + Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false); + + QGuiApplication::sendEvent(&window, &wheelEvent1); + qApp->processEvents(); + if (isYAxis) { + QVERIFY(flickable->isMovingVertically()); + QTRY_VERIFY(!flickable->isMovingVertically()); + QVERIFY(!(atBeginning ? flickable->isAtYBeginning() : flickable->isAtYEnd())); + } else { + QVERIFY(flickable->isMovingHorizontally()); + QTRY_VERIFY(!flickable->isMovingHorizontally()); + QVERIFY(!(atBeginning ? flickable->isAtXBeginning() : flickable->isAtXEnd())); + } + + QWheelEvent wheelEvent2(pos, window.mapToGlobal(pos), QPoint(), -angleDelta, + Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false); + wheelEvent2.setTimestamp(wheelEvent1.timestamp() + 10); + + QGuiApplication::sendEvent(&window, &wheelEvent2); + qApp->processEvents(); + + if (isYAxis) { + QVERIFY(flickable->isMovingVertically()); + QTRY_VERIFY(!flickable->isMovingVertically()); + QVERIFY(atBeginning ? flickable->isAtYBeginning() : flickable->isAtYEnd()); + } else { + QVERIFY(flickable->isMovingHorizontally()); + QTRY_VERIFY(!flickable->isMovingHorizontally()); + QVERIFY(atBeginning ? flickable->isAtXBeginning() : flickable->isAtXEnd()); + } +} + +void tst_qquickflickable::setContentPositionWhileDragging_data() +{ + QTest::addColumn<bool>("isHorizontal"); + QTest::addColumn<int>("newPos"); + QTest::addColumn<int>("newExtent"); + QTest::newRow("horizontal, setContentX") << true << 0 << -1; + QTest::newRow("vertical, setContentY") << false << 0 << -1; + QTest::newRow("horizontal, setContentWidth") << true << -1 << 200; + QTest::newRow("vertical, setContentHeight") << false << -1 << 200; +} + +void tst_qquickflickable::setContentPositionWhileDragging() // QTBUG-104966 +{ + QFETCH(bool, isHorizontal); + QFETCH(int, newPos); + QFETCH(int, newExtent); + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("contentPosWhileDragging.qml"))); + QQuickViewTestUtils::centerOnScreen(&window); + QVERIFY(window.isVisible()); + QQuickItem *rootItem = window.rootObject(); + QVERIFY(rootItem); + QQuickFlickable *flickable = rootItem->findChild<QQuickFlickable *>(); + QVERIFY(flickable); + + const auto contentPos = [flickable]() -> QPoint { + return QPoint(flickable->contentX(), flickable->contentY()); + }; + const qreal threshold = + qApp->styleHints()->startDragDistance() * flickable->parentItem()->scale(); + const QPoint thresholdPnt(qRound(threshold), qRound(threshold)); + const auto flickableCenterPos = flickable->mapToScene({flickable->width() / 2, flickable->height() / 2}).toPoint(); + + // Drag the mouse until we have surpassed the mouse drag threshold and a drag is initiated + // by checking for flickable->isDragging() + QPoint pos = flickableCenterPos; + QQuickViewTestUtils::moveAndPress(&window, pos); + int j = 1; + QVERIFY(!flickable->isDragging()); + while (!flickable->isDragging()) { + pos = flickableCenterPos - QPoint(j, j); + QTest::mouseMove(&window, pos); + j++; + } + + // Now we have entered the drag state + QVERIFY(flickable->isDragging()); + QCOMPARE(flickable->contentX(), 0); + QCOMPARE(flickable->contentY(), 0); + QVERIFY(flickable->width() > 0); + QVERIFY(flickable->height() > 0); + + + const int moveLength = 50; + const QPoint unitDelta(isHorizontal ? 1 : 0, isHorizontal ? 0 : 1); + const QPoint moveDelta = unitDelta * moveLength; + + pos -= 3*moveDelta; + QTest::mouseMove(&window, pos); + // Should be positive because we drag in the opposite direction + QCOMPARE(contentPos(), 3 * moveDelta); + QPoint expectedContentPos; + + // Set the content item position back to zero *while dragging* (!!) + if (newPos >= 0) { + if (isHorizontal) { + flickable->setContentX(newPos); + } else { + flickable->setContentY(newPos); + } + // Continue dragging + pos -= moveDelta; + expectedContentPos = moveDelta; + } else if (newExtent >= 0) { + // ...or reduce the content size be be less than current (contentX, contentY) position + // This forces the content item to move. + // contentY: 150 + // 320 - 150 = 170 pixels down to bottom + // Now reduce contentHeight to 200 + // since we are at the bottom, and the flickable is 100 pixels tall, contentY must land + // at newExtent - 100. + + if (isHorizontal) { + flickable->setContentWidth(newExtent); + } else { + flickable->setContentHeight(newExtent); + } + // Assumption is that the contentItem is aligned to the bottom of the flickable + // We therefore cannot scroll/flick it further down. Drag it up towards the top instead + // (by moving mouse down). + pos += moveDelta; + expectedContentPos = unitDelta * (newExtent - (isHorizontal ? flickable->width() : flickable->height())); + } + + QTest::mouseMove(&window, pos); + + // Make sure that the contentItem was only dragged the delta in mouse movement since the last + // setContentX/Y() call. + QCOMPARE(contentPos(), expectedContentPos); + QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, pos); + QVERIFY(!flickable->isDragging()); +} + QTEST_MAIN(tst_qquickflickable) #include "tst_qquickflickable.moc" diff --git a/tests/auto/quick/qquickfontloader/tst_qquickfontloader.cpp b/tests/auto/quick/qquickfontloader/tst_qquickfontloader.cpp index 9983997dc1..8bfeb7314a 100644 --- a/tests/auto/quick/qquickfontloader/tst_qquickfontloader.cpp +++ b/tests/auto/quick/qquickfontloader/tst_qquickfontloader.cpp @@ -200,9 +200,7 @@ void tst_qquickfontloader::changeFontSourceViaState() { QQuickView window(testFileUrl("qtbug-20268.qml")); window.show(); - window.requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(&window)); - QCOMPARE(&window, qGuiApp->focusWindow()); + QVERIFY(QTest::qWaitForWindowExposed(&window)); QQuickFontLoader *fontObject = qobject_cast<QQuickFontLoader*>(qvariant_cast<QObject *>(window.rootObject()->property("fontloader"))); QVERIFY(fontObject != nullptr); diff --git a/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp b/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp index 9532aa546c..942c7cfd93 100644 --- a/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp +++ b/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp @@ -67,7 +67,7 @@ public: tst_QQuickGridView(); private slots: - void init(); + void init() override; void cleanupTestCase(); void items(); void changed(); @@ -328,6 +328,8 @@ tst_QQuickGridView::tst_QQuickGridView() void tst_QQuickGridView::init() { + QQmlDataTest::init(); + #ifdef SHARE_VIEWS if (m_view && QString(QTest::currentTestFunction()) != testForView) { testForView = QString(); diff --git a/tests/auto/quick/qquickitemlayer/data/itemImageLayer.qml b/tests/auto/quick/qquickitemlayer/data/itemImageLayer.qml new file mode 100644 index 0000000000..1f05aa882f --- /dev/null +++ b/tests/auto/quick/qquickitemlayer/data/itemImageLayer.qml @@ -0,0 +1,14 @@ +import QtQuick + +Item { + width: 300 + height: 300 + + Image { + objectName: "image" + anchors.fill: parent + fillMode: Image.PreserveAspectFit + source: "qt-logo.png" + layer.enabled: true + } +} diff --git a/tests/auto/quick/qquickitemlayer/data/qt-logo.png b/tests/auto/quick/qquickitemlayer/data/qt-logo.png Binary files differnew file mode 100644 index 0000000000..dff7729515 --- /dev/null +++ b/tests/auto/quick/qquickitemlayer/data/qt-logo.png diff --git a/tests/auto/quick/qquickitemlayer/tst_qquickitemlayer.cpp b/tests/auto/quick/qquickitemlayer/tst_qquickitemlayer.cpp index ae6947f01b..90e1ab0a66 100644 --- a/tests/auto/quick/qquickitemlayer/tst_qquickitemlayer.cpp +++ b/tests/auto/quick/qquickitemlayer/tst_qquickitemlayer.cpp @@ -31,10 +31,12 @@ #include <QtQuick/qquickitem.h> #include <QtQuick/qquickview.h> #include <QtQuick/qsgrendererinterface.h> +#include <QtQuick/private/qquickitem_p.h> #include <qopenglcontext.h> #include <qopenglfunctions.h> #include <QtQuickTestUtils/private/qmlutils_p.h> +#include <QtQuickTestUtils/private/viewtestutils_p.h> #include <QtGui/private/qguiapplication_p.h> #include <QtGui/qpa/qplatformintegration.h> @@ -85,6 +87,8 @@ private slots: void textureMirroring_data(); void textureMirroring(); + void effectSourceResizeToItem(); + private: void mirroringCheck(int mirroring, int x, bool shouldMirror, const QImage &fb); }; @@ -468,6 +472,25 @@ void tst_QQuickItemLayer::textureMirroring() mirroringCheck(mirroring, 200, true, fb); } +void tst_QQuickItemLayer::effectSourceResizeToItem() // QTBUG-104442 +{ + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("itemImageLayer.qml"))); + window.setResizeMode(QQuickView::SizeRootObjectToView); + QQuickItem *image = window.rootObject()->findChild<QQuickItem*>("image"); + QVERIFY(image); + QCOMPARE(image->size(), window.rootObject()->size()); + QQuickItemLayer *layer = QQuickItemPrivate::get(image)->layer(); + QVERIFY(layer); + auto *effectSource = layer->effectSource(); + QVERIFY(effectSource); + QCOMPARE(effectSource->size(), image->size()); + + window.resize(200, 200); // shrink it a bit + QTRY_COMPARE(image->size().toSize(), QSize(200, 200)); // wait for the window system + QCOMPARE(effectSource->size(), image->size()); +} + void tst_QQuickItemLayer::mirroringCheck(int mirroring, int x, bool shouldMirror, const QImage &fb) { int offset = 10; diff --git a/tests/auto/quick/qquicklayouts/data/tst_gridlayout.qml b/tests/auto/quick/qquicklayouts/data/tst_gridlayout.qml index 1df04a11e8..52ad3b478d 100644 --- a/tests/auto/quick/qquicklayouts/data/tst_gridlayout.qml +++ b/tests/auto/quick/qquicklayouts/data/tst_gridlayout.qml @@ -586,6 +586,56 @@ Item { tryCompare(layout.children[4], "y", 60); } + Component { + id: layout_alignBaseline_Component + GridLayout { + columns: 2 + columnSpacing: 0 + rowSpacing: 0 + TextInput { + property var itemRect: [x, y, width, height] + text: "red" + baselineOffset: 7 + color: "red" + verticalAlignment: TextInput.AlignVCenter + Layout.preferredWidth: 50 + Layout.preferredHeight: 10 + Layout.fillHeight: true + } + TextInput { + property var itemRect: [x, y, width, height] + text: "green" + baselineOffset: 7 + color: "green" + verticalAlignment: TextInput.AlignVCenter + Layout.preferredWidth: 50 + Layout.preferredHeight: 10 + Layout.fillHeight: true + } + + } + } + + function test_alignBaseline_dont_always_invalidate() + { + var layout = createTemporaryObject(layout_alignBaseline_Component, container); + waitForItemPolished(layout) + layout.height = 20 + // Adjusting height on an item that uses Qt.AlignBaseline might adjust the baseline + // Test if we don't get excessive number of polish() events because of baseline changes + // (In this case, we don't want to align by the baseline) + compare(isPolishScheduled(layout), false) + waitForItemPolished(layout) + var c0 = layout.children[0] + c0.Layout.alignment = Qt.AlignBaseline + var c1 = layout.children[1] + c1.Layout.alignment = Qt.AlignBaseline + + // We want to align by baseline => expect a polish event + compare(isPolishScheduled(layout), true) + waitForItemPolished(layout) + } + Component { id: layout_rightToLeft_Component diff --git a/tests/auto/quick/qquicklayouts/data/tst_stacklayout.qml b/tests/auto/quick/qquicklayouts/data/tst_stacklayout.qml index c567b31db3..552bfb6065 100644 --- a/tests/auto/quick/qquicklayouts/data/tst_stacklayout.qml +++ b/tests/auto/quick/qquicklayouts/data/tst_stacklayout.qml @@ -272,5 +272,55 @@ Item { compare(layout.item2.isCurrentItem, false) compare(layout.item2.layout, layout) } + + Component { + id: layout_setCurrentIndex_Component + + StackLayout { + width: 200 + height: 200 + + property alias firstItem : rect + property alias secondItem: rowLayout + + Rectangle { + id: rect + color: "red" + implicitWidth: 10 + implicitHeight: 10 + } + RowLayout { + id: rowLayout + spacing: 0 + Rectangle { + color: "green" + implicitWidth: 10 + implicitHeight: 10 + Layout.fillWidth: true + Layout.fillHeight: true + } + Rectangle { + color: "blue" + implicitWidth: 10 + implicitHeight: 10 + Layout.fillWidth: true + Layout.fillHeight: true + } + } + } + } + + function test_setCurrentIndex() + { + var layout = layout_setCurrentIndex_Component.createObject(container) + compare(layout.firstItem.width, 200) + + // Invalidate the StackLayout (and its cached size hints) + layout.firstItem.implicitWidth = 42 + + layout.currentIndex = 1 + compare(layout.secondItem.width, 200) // width should not be -1 + layout.destroy() + } } } diff --git a/tests/auto/quick/qquicklistview/BLACKLIST b/tests/auto/quick/qquicklistview/BLACKLIST index 3c5d5ee25b..9b30dd2bc0 100644 --- a/tests/auto/quick/qquicklistview/BLACKLIST +++ b/tests/auto/quick/qquicklistview/BLACKLIST @@ -4,12 +4,6 @@ opensuse-leap #QTBUG-53863 [populateTransitions] opensuse-42.1 -#QTBUG-75960 -#QTBUG-76652 -[currentIndex] -opensuse-leap -ubuntu-18.04 -ubuntu-20.04 # QTBUG-75202 [contentHeightWithDelayRemove] macos ci diff --git a/tests/auto/quick/qquicklistview/data/appendDuringScrollDown.qml b/tests/auto/quick/qquicklistview/data/appendDuringScrollDown.qml index af35c29143..6ba6480297 100644 --- a/tests/auto/quick/qquicklistview/data/appendDuringScrollDown.qml +++ b/tests/auto/quick/qquicklistview/data/appendDuringScrollDown.qml @@ -1,10 +1,11 @@ import QtQuick 2.6 ListView { + id: listView width: 320; height: 240 focus: true delegate: Text { - height: 40; width: parent.width + height: 40; width: listView.width text: model.text verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter diff --git a/tests/auto/quick/qquicklistview/data/displayMargin.qml b/tests/auto/quick/qquicklistview/data/displayMargin.qml index aafbb4235f..e6ea695b49 100644 --- a/tests/auto/quick/qquicklistview/data/displayMargin.qml +++ b/tests/auto/quick/qquicklistview/data/displayMargin.qml @@ -44,7 +44,7 @@ Item { model: 100 delegate: Rectangle { objectName: "delegate" - width: parent.width + width: view.width height: 25 color: index % 2 ? "steelblue" : "lightsteelblue" Text { diff --git a/tests/auto/quick/qquicklistview/data/headerCrash.qml b/tests/auto/quick/qquicklistview/data/headerCrash.qml index 124fa894f2..972ecb2906 100644 --- a/tests/auto/quick/qquicklistview/data/headerCrash.qml +++ b/tests/auto/quick/qquicklistview/data/headerCrash.qml @@ -12,7 +12,7 @@ ListView { } delegate: Rectangle { - width: parent.width; height: 20 + width: myList.width; height: 20 color: index % 2 ? "green" : "red" } diff --git a/tests/auto/quick/qquicklistview/data/listview-itematindex.qml b/tests/auto/quick/qquicklistview/data/listview-itematindex.qml index fba8b11933..2194f1edff 100644 --- a/tests/auto/quick/qquicklistview/data/listview-itematindex.qml +++ b/tests/auto/quick/qquicklistview/data/listview-itematindex.qml @@ -1,13 +1,14 @@ import QtQuick 2.0 ListView { + id: listView width: 400 height: 400 focus: true model: 3 delegate: Text { - width: parent.width + width: listView.width height: 10 property int idx: index text: index diff --git a/tests/auto/quick/qquicklistview/data/outsideViewportChangeNotAffectingView.qml b/tests/auto/quick/qquicklistview/data/outsideViewportChangeNotAffectingView.qml index e0acaf49e4..ef959b10b4 100644 --- a/tests/auto/quick/qquicklistview/data/outsideViewportChangeNotAffectingView.qml +++ b/tests/auto/quick/qquicklistview/data/outsideViewportChangeNotAffectingView.qml @@ -72,7 +72,7 @@ Item { ListElement { size: 300; } } delegate: Rectangle { - width: parent.width + width: list.width color: index % 2 == 0 ? "red" : "blue" height: size Text { anchors.centerIn: parent; text: index } diff --git a/tests/auto/quick/qquicklistview/data/resizeAfterComponentComplete.qml b/tests/auto/quick/qquicklistview/data/resizeAfterComponentComplete.qml index 851d8f9a0c..f5be95b7cc 100644 --- a/tests/auto/quick/qquicklistview/data/resizeAfterComponentComplete.qml +++ b/tests/auto/quick/qquicklistview/data/resizeAfterComponentComplete.qml @@ -56,7 +56,7 @@ ListView { anchors.fill: parent model: 10 delegate: Rectangle { - width: parent.width + width: listView.width height: 40 border.color: "lightsteelblue" Text { diff --git a/tests/auto/quick/qquicklistview/data/sectionSnapping.qml b/tests/auto/quick/qquicklistview/data/sectionSnapping.qml index 2583cc0377..48a893f88c 100644 --- a/tests/auto/quick/qquicklistview/data/sectionSnapping.qml +++ b/tests/auto/quick/qquicklistview/data/sectionSnapping.qml @@ -1,6 +1,7 @@ import QtQuick 2.0 ListView { + id: listView width: 400 height: 400 preferredHighlightBegin: 100 @@ -17,7 +18,7 @@ ListView { } delegate: Rectangle { - width: parent.width + width: listView.width height: 50 color: index % 2 ? "lightsteelblue" : "steelblue" Text { diff --git a/tests/auto/quick/qquicklistview/data/snapOneItemWrongDirection.qml b/tests/auto/quick/qquicklistview/data/snapOneItemWrongDirection.qml index f5b7b35d0c..1dfbfe0feb 100644 --- a/tests/auto/quick/qquicklistview/data/snapOneItemWrongDirection.qml +++ b/tests/auto/quick/qquicklistview/data/snapOneItemWrongDirection.qml @@ -1,13 +1,14 @@ import QtQuick 2.0 ListView { + id: listView width: 400 height: 400 focus: true model: 10 delegate: Rectangle { - width: parent.width + width: listView.width height: 50 color: index % 2 ? "blue" : "green" } diff --git a/tests/auto/quick/qquicklistview/data/strictlyenforcerange-resize.qml b/tests/auto/quick/qquicklistview/data/strictlyenforcerange-resize.qml index 338af38475..16b9c72b16 100644 --- a/tests/auto/quick/qquicklistview/data/strictlyenforcerange-resize.qml +++ b/tests/auto/quick/qquicklistview/data/strictlyenforcerange-resize.qml @@ -1,6 +1,7 @@ import QtQuick 2.0 ListView { + id: listView width: 400 height: 400 focus: true @@ -15,7 +16,7 @@ ListView { model: 10 delegate: Item { - width: parent.width + width: listView.width height: ListView.isCurrentItem ? 100 : 50 Text { diff --git a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp index 6975fa2dbd..23738f13c7 100644 --- a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp +++ b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp @@ -77,7 +77,7 @@ public: private slots: // WARNING: please add new tests to tst_qquicklistview2; this file is too slow to work with. - void init(); + void init() override; void cleanupTestCase(); // Test QAbstractItemModel model types void qAbstractItemModel_package_items(); @@ -410,6 +410,8 @@ tst_QQuickListView::tst_QQuickListView() void tst_QQuickListView::init() { + QQmlDataTest::init(); + #ifdef SHARE_VIEWS if (m_view && QString(QTest::currentTestFunction()) != testForView) { testForView = QString(); @@ -434,6 +436,9 @@ void tst_QQuickListView::cleanupTestCase() template <class T> void tst_QQuickListView::items(const QUrl &source) { + // Make sure we outlive the view, or the context property will become null. + QScopedPointer<TestObject> testObject(new TestObject); + QScopedPointer<QQuickView> window(createView()); T model; @@ -444,7 +449,6 @@ void tst_QQuickListView::items(const QUrl &source) QQmlContext *ctxt = window->rootContext(); ctxt->setContextProperty("testModel", &model); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testObject", testObject.data()); window->setSource(source); @@ -515,6 +519,8 @@ void tst_QQuickListView::items(const QUrl &source) template <class T> void tst_QQuickListView::changed(const QUrl &source) { + QScopedPointer<TestObject> testObject(new TestObject); + QScopedPointer<QQuickView> window(createView()); T model; @@ -525,7 +531,6 @@ void tst_QQuickListView::changed(const QUrl &source) QQmlContext *ctxt = window->rootContext(); ctxt->setContextProperty("testModel", &model); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testObject", testObject.data()); window->setSource(source); @@ -553,6 +558,8 @@ void tst_QQuickListView::changed(const QUrl &source) template <class T> void tst_QQuickListView::inserted(const QUrl &source) { + QScopedPointer<TestObject> testObject(new TestObject); + QScopedPointer<QQuickView> window(createView()); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window.data())); @@ -565,7 +572,6 @@ void tst_QQuickListView::inserted(const QUrl &source) QQmlContext *ctxt = window->rootContext(); ctxt->setContextProperty("testModel", &model); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testObject", testObject.data()); window->setSource(source); @@ -657,11 +663,12 @@ void tst_QQuickListView::inserted_more(QQuickItemView::VerticalLayoutDirection v for (int i = 0; i < 30; i++) model.addItem("Item" + QString::number(i), ""); + QScopedPointer<TestObject> testObject(new TestObject); + QQuickView *window = getView(); QQmlContext *ctxt = window->rootContext(); ctxt->setContextProperty("testModel", &model); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testObject", testObject.data()); window->setSource(testFileUrl("listviewtest.qml")); @@ -958,6 +965,8 @@ void tst_QQuickListView::insertBeforeVisible_data() template <class T> void tst_QQuickListView::removed(const QUrl &source, bool /* animated */) { + QScopedPointer<TestObject> testObject(new TestObject); + QScopedPointer<QQuickView> window(createView()); T model; @@ -967,7 +976,6 @@ void tst_QQuickListView::removed(const QUrl &source, bool /* animated */) QQmlContext *ctxt = window->rootContext(); ctxt->setContextProperty("testModel", &model); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testObject", testObject.data()); window->setSource(source); @@ -1321,6 +1329,8 @@ void tst_QQuickListView::removed_more_data() template <class T> void tst_QQuickListView::clear(const QUrl &source, QQuickItemView::VerticalLayoutDirection verticalLayoutDirection) { + QScopedPointer<TestObject> testObject(new TestObject); + QScopedPointer<QQuickView> window(createView()); T model; @@ -1330,7 +1340,6 @@ void tst_QQuickListView::clear(const QUrl &source, QQuickItemView::VerticalLayou QQmlContext *ctxt = window->rootContext(); ctxt->setContextProperty("testModel", &model); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testObject", testObject.data()); window->setSource(source); @@ -1853,6 +1862,8 @@ void tst_QQuickListView::multipleChanges_data() void tst_QQuickListView::swapWithFirstItem() { + QScopedPointer<TestObject> testObject(new TestObject); + QScopedPointer<QQuickView> window(createView()); QaimModel model; @@ -1862,7 +1873,6 @@ void tst_QQuickListView::swapWithFirstItem() QQmlContext *ctxt = window->rootContext(); ctxt->setContextProperty("testModel", &model); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testObject", testObject.data()); window->setSource(testFileUrl("listviewtest.qml")); @@ -1881,6 +1891,8 @@ void tst_QQuickListView::swapWithFirstItem() void tst_QQuickListView::checkCountForMultiColumnModels() { + QScopedPointer<TestObject> testObject(new TestObject); + // Check that a list view will only load items for the first // column, even if the model reports that it got several columns. // We test this since QQmlDelegateModel has been changed to @@ -1898,7 +1910,6 @@ void tst_QQuickListView::checkCountForMultiColumnModels() QQmlContext *ctxt = window->rootContext(); ctxt->setContextProperty("testModel", &model); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testObject", testObject.data()); window->setSource(testFileUrl("listviewtest.qml")); @@ -2035,6 +2046,8 @@ void tst_QQuickListView::enforceRange_withoutHighlight() void tst_QQuickListView::spacing() { + QScopedPointer<TestObject> testObject(new TestObject); + QScopedPointer<QQuickView> window(createView()); QaimModel model; @@ -2044,7 +2057,6 @@ void tst_QQuickListView::spacing() QQmlContext *ctxt = window->rootContext(); ctxt->setContextProperty("testModel", &model); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testObject", testObject.data()); window->setSource(testFileUrl("listviewtest.qml")); @@ -2965,8 +2977,8 @@ void tst_QQuickListView::keyNavigation() for (int i = 0; i < 30; i++) model.addItem("Item" + QString::number(i), ""); - QQuickView *window = getView(); QScopedPointer<TestObject> testObject(new TestObject); + QQuickView *window = getView(); window->rootContext()->setContextProperty("testModel", &model); window->rootContext()->setContextProperty("testObject", testObject.data()); window->setSource(testFileUrl("listviewtest.qml")); @@ -3170,6 +3182,8 @@ void tst_QQuickListView::itemListFlicker() void tst_QQuickListView::cacheBuffer() { + QScopedPointer<TestObject> testObject(new TestObject); + QScopedPointer<QQuickView> window(createView()); QaimModel model; @@ -3179,7 +3193,6 @@ void tst_QQuickListView::cacheBuffer() QQmlContext *ctxt = window->rootContext(); ctxt->setContextProperty("testModel", &model); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testObject", testObject.data()); window->setSource(testFileUrl("listviewtest.qml")); @@ -3272,6 +3285,8 @@ void tst_QQuickListView::cacheBuffer() void tst_QQuickListView::positionViewAtBeginningEnd() { + QScopedPointer<TestObject> testObject(new TestObject); + QScopedPointer<QQuickView> window(createView()); QaimModel model; @@ -3281,7 +3296,6 @@ void tst_QQuickListView::positionViewAtBeginningEnd() QQmlContext *ctxt = window->rootContext(); ctxt->setContextProperty("testModel", &model); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testObject", testObject.data()); window->show(); window->setSource(testFileUrl("listviewtest.qml")); @@ -3330,6 +3344,8 @@ void tst_QQuickListView::positionViewAtIndex() QFETCH(QQuickListView::PositionMode, mode); QFETCH(qreal, contentY); + QScopedPointer<TestObject> testObject(new TestObject); + QQuickView *window = getView(); QaimModel model; @@ -3339,7 +3355,6 @@ void tst_QQuickListView::positionViewAtIndex() QQmlContext *ctxt = window->rootContext(); ctxt->setContextProperty("testModel", &model); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testObject", testObject.data()); window->show(); window->setSource(testFileUrl("listviewtest.qml")); @@ -3679,6 +3694,8 @@ void tst_QQuickListView::manualHighlight() void tst_QQuickListView::QTBUG_11105() { + QScopedPointer<TestObject> testObject(new TestObject); + QScopedPointer<QQuickView> window(createView()); QaimModel model; for (int i = 0; i < 30; i++) @@ -3687,7 +3704,6 @@ void tst_QQuickListView::QTBUG_11105() QQmlContext *ctxt = window->rootContext(); ctxt->setContextProperty("testModel", &model); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testObject", testObject.data()); window->setSource(testFileUrl("listviewtest.qml")); @@ -4349,6 +4365,8 @@ void tst_QQuickListView::resetModel_headerFooter() void tst_QQuickListView::resizeView() { + QScopedPointer<TestObject> testObject(new TestObject); + QScopedPointer<QQuickView> window(createView()); QaimModel model; for (int i = 0; i < 40; i++) @@ -4357,7 +4375,6 @@ void tst_QQuickListView::resizeView() QQmlContext *ctxt = window->rootContext(); ctxt->setContextProperty("testModel", &model); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testObject", testObject.data()); window->setSource(testFileUrl("listviewtest.qml")); @@ -4458,6 +4475,8 @@ void tst_QQuickListView::resizeViewAndRepaint() void tst_QQuickListView::sizeLessThan1() { + QScopedPointer<TestObject> testObject(new TestObject); + QScopedPointer<QQuickView> window(createView()); QaimModel model; @@ -4467,7 +4486,6 @@ void tst_QQuickListView::sizeLessThan1() QQmlContext *ctxt = window->rootContext(); ctxt->setContextProperty("testModel", &model); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testObject", testObject.data()); window->setSource(testFileUrl("sizelessthan1.qml")); @@ -4588,6 +4606,8 @@ void tst_QQuickListView::resizeFirstDelegate() { // QTBUG-20712: Content Y jumps constantly if first delegate height == 0 // and other delegates have height > 0 + QScopedPointer<TestObject> testObject(new TestObject); + QScopedPointer<QQuickView> window(createView()); // bug only occurs when all items in the model are visible @@ -4598,7 +4618,6 @@ void tst_QQuickListView::resizeFirstDelegate() QQmlContext *ctxt = window->rootContext(); ctxt->setContextProperty("testModel", &model); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testObject", testObject.data()); window->setSource(testFileUrl("listviewtest.qml")); @@ -4796,6 +4815,8 @@ void tst_QQuickListView::indexAt_itemAt() QFETCH(qreal, y); QFETCH(int, index); + QScopedPointer<TestObject> testObject(new TestObject); + QQuickView *window = getView(); QaimModel model; @@ -4805,7 +4826,6 @@ void tst_QQuickListView::indexAt_itemAt() QQmlContext *ctxt = window->rootContext(); ctxt->setContextProperty("testModel", &model); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testObject", testObject.data()); window->setSource(testFileUrl("listviewtest.qml")); @@ -6673,8 +6693,8 @@ void tst_QQuickListView::populateTransitions() model.addItem("item" + QString::number(i), ""); } + QScopedPointer<TestObject> testObject(new TestObject()); QQuickView *window = getView(); - QScopedPointer<TestObject> testObject(new TestObject(window->rootContext())); window->rootContext()->setContextProperty("testModel", &model); window->rootContext()->setContextProperty("testObject", testObject.data()); window->rootContext()->setContextProperty("usePopulateTransition", usePopulateTransition); @@ -6797,12 +6817,12 @@ void tst_QQuickListView::populateTransitions_data() void tst_QQuickListView::sizeTransitions() { QFETCH(bool, topToBottom); + QScopedPointer<TestObject> testObject(new TestObject); QQuickView *window = getView(); QQmlContext *ctxt = window->rootContext(); QaimModel model; ctxt->setContextProperty("testModel", &model); ctxt->setContextProperty("topToBottom", topToBottom); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testObject", &model); window->setSource(testFileUrl("sizeTransitions.qml")); window->show(); @@ -6857,9 +6877,9 @@ void tst_QQuickListView::addTransitions() QaimModel model_targetItems_transitionFrom; QaimModel model_displacedItems_transitionVia; + QScopedPointer<TestObject> testObject(new TestObject); QQuickView *window = getView(); QQmlContext *ctxt = window->rootContext(); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testModel", &model); ctxt->setContextProperty("model_targetItems_transitionFrom", &model_targetItems_transitionFrom); ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia); @@ -7052,9 +7072,9 @@ void tst_QQuickListView::moveTransitions() QaimModel model_targetItems_transitionVia; QaimModel model_displacedItems_transitionVia; + QScopedPointer<TestObject> testObject(new TestObject); QQuickView *window = getView(); QQmlContext *ctxt = window->rootContext(); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testModel", &model); ctxt->setContextProperty("model_targetItems_transitionVia", &model_targetItems_transitionVia); ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia); @@ -7254,9 +7274,9 @@ void tst_QQuickListView::removeTransitions() QaimModel model_targetItems_transitionTo; QaimModel model_displacedItems_transitionVia; + QScopedPointer<TestObject> testObject(new TestObject); QQuickView *window = getView(); QQmlContext *ctxt = window->rootContext(); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testModel", &model); ctxt->setContextProperty("model_targetItems_transitionTo", &model_targetItems_transitionTo); ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia); @@ -7452,9 +7472,9 @@ void tst_QQuickListView::displacedTransitions() QPointF moveDisplaced_transitionVia(50, -100); QPointF removeDisplaced_transitionVia(150, 100); + QScopedPointer<TestObject> testObject(new TestObject()); QQuickView *window = getView(); QQmlContext *ctxt = window->rootContext(); - QScopedPointer<TestObject> testObject(new TestObject(window)); ctxt->setContextProperty("testModel", &model); ctxt->setContextProperty("testObject", testObject.data()); ctxt->setContextProperty("model_displaced_transitionVia", &model_displaced_transitionVia); @@ -7678,9 +7698,9 @@ void tst_QQuickListView::multipleTransitions() for (int i = 0; i < initialCount; i++) model.addItem("Original item" + QString::number(i), ""); + QScopedPointer<TestObject> testObject(new TestObject); QQuickView *window = getView(); QQmlContext *ctxt = window->rootContext(); - QScopedPointer<TestObject> testObject(new TestObject); ctxt->setContextProperty("testModel", &model); ctxt->setContextProperty("testObject", testObject.data()); ctxt->setContextProperty("addTargets_transitionFrom", addTargets_transitionFrom); @@ -7845,9 +7865,9 @@ void tst_QQuickListView::multipleDisplaced() for (int i = 0; i < 30; i++) model.addItem("Original item" + QString::number(i), ""); + QScopedPointer<TestObject> testObject(new TestObject()); QQuickView *window = getView(); QQmlContext *ctxt = window->rootContext(); - QScopedPointer<TestObject> testObject(new TestObject(window)); ctxt->setContextProperty("testModel", &model); ctxt->setContextProperty("testObject", testObject.data()); window->setSource(testFileUrl("multipleDisplaced.qml")); @@ -9466,7 +9486,7 @@ void tst_QQuickListView::QTBUG_66163_setModelViewPortSizeChange() delegate: Rectangle { color: index % 2 ? "green" : "orange" - width: parent.width + width: view.width height: 50 } @@ -9518,8 +9538,8 @@ void tst_QQuickListView::itemFiltered() QScopedPointer<QQuickView> window(createView()); window->engine()->rootContext()->setContextProperty("_model", &proxy2); QQmlComponent component(window->engine()); - component.setData("import QtQuick 2.4; ListView { " - "anchors.fill: parent; model: _model; delegate: Text { width: parent.width;" + component.setData("import QtQuick 2.4; ListView { id: listView; " + "anchors.fill: parent; model: _model; delegate: Text { width: listView.width;" "text: model.display; } }", QUrl()); window->setContent(QUrl(), &component, component.create()); @@ -9802,12 +9822,24 @@ void tst_QQuickListView::delegateWithRequiredProperties() void tst_QQuickListView::reuse_reuseIsOffByDefault() { + QScopedPointer<TestObject> testObject(new TestObject); + // Check that delegate recycling is off by default. The reason is that // ListView needs to be backwards compatible with legacy applications. And // when using delegate recycling, there are certain differences, like that // a delegates Component.onCompleted will just be called the first time the // item is created, and not when it's reused. QScopedPointer<QQuickView> window(createView()); + + QaimModel model; + for (int i = 0; i < 40; i++) + model.addItem("Item" + QString::number(i), ""); + + QQmlContext *ctxt = window->rootContext(); + ctxt->setContextProperty("testModel", &model); + + ctxt->setContextProperty("testObject", testObject.data()); + window->setSource(testFileUrl("listviewtest.qml")); window->resize(640, 480); window->show(); diff --git a/tests/auto/quick/qquicklistview2/BLACKLIST b/tests/auto/quick/qquicklistview2/BLACKLIST new file mode 100644 index 0000000000..0162bbc852 --- /dev/null +++ b/tests/auto/quick/qquicklistview2/BLACKLIST @@ -0,0 +1,5 @@ +[tapDelegateDuringFlicking] +android # QTBUG-104471 +[flickDuringFlicking] +android # QTBUG-104471 +macos ci # QTBUG-105190 diff --git a/tests/auto/quick/qquicklistview2/data/buttonDelegate.qml b/tests/auto/quick/qquicklistview2/data/buttonDelegate.qml new file mode 100644 index 0000000000..a40ba1cd7e --- /dev/null +++ b/tests/auto/quick/qquicklistview2/data/buttonDelegate.qml @@ -0,0 +1,27 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +ListView { + id: root + width: 320 + height: 480 + model: 100 + + property var pressedDelegates: [] + property var releasedDelegates: [] + property var tappedDelegates: [] + property var canceledDelegates: [] + + delegate: Button { + required property int index + objectName: text + text: "button " + index + height: 100 + width: 320 + + onPressed: root.pressedDelegates.push(index) + onReleased: root.releasedDelegates.push(index) + onClicked: root.tappedDelegates.push(index) + onCanceled: root.canceledDelegates.push(index) + } +} diff --git a/tests/auto/quick/qquicklistview2/data/mouseAreaDelegate.qml b/tests/auto/quick/qquicklistview2/data/mouseAreaDelegate.qml new file mode 100644 index 0000000000..ad556913a5 --- /dev/null +++ b/tests/auto/quick/qquicklistview2/data/mouseAreaDelegate.qml @@ -0,0 +1,30 @@ +import QtQuick 2.15 + +ListView { + id: root + width: 320 + height: 480 + model: 100 + + property var pressedDelegates: [] + property var releasedDelegates: [] + property var tappedDelegates: [] + property var canceledDelegates: [] + + delegate: MouseArea { + height: 100 + width: 320 + + onPressed: root.pressedDelegates.push(index) + onReleased: root.releasedDelegates.push(index) + onClicked: root.tappedDelegates.push(index) + onCanceled: root.canceledDelegates.push(index) + + Rectangle { + id: buttonArea + anchors.fill: parent + border.color: "#41cd52" + color: parent.pressed ? "lightsteelblue" : "beige" + } + } +} diff --git a/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp b/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp index 27bf389891..1905c0f660 100644 --- a/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp +++ b/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp @@ -38,6 +38,8 @@ #include <QtQuickTestUtils/private/visualtestutils_p.h> #include <QtQuickTestUtils/private/qmlutils_p.h> +Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests") + using namespace QQuickViewTestUtils; using namespace QQuickVisualTestUtils; @@ -60,6 +62,14 @@ private slots: void sectionsNoOverlap(); void metaSequenceAsModel(); void noCrashOnIndexChange(); + void tapDelegateDuringFlicking_data(); + void tapDelegateDuringFlicking(); + void flickDuringFlicking_data(); + void flickDuringFlicking(); + +private: + void flickWithTouch(QQuickWindow *window, const QPoint &from, const QPoint &to); + QScopedPointer<QPointingDevice> touchDevice = QScopedPointer<QPointingDevice>(QTest::createTouchDevice()); }; tst_QQuickListView2::tst_QQuickListView2() @@ -307,6 +317,149 @@ void tst_QQuickListView2::noCrashOnIndexChange() QCOMPARE(items->property("count").toInt(), 4); } +void tst_QQuickListView2::tapDelegateDuringFlicking_data() +{ + QTest::addColumn<QByteArray>("qmlFile"); + QTest::addColumn<QQuickFlickable::BoundsBehavior>("boundsBehavior"); + + QTest::newRow("Button StopAtBounds") << QByteArray("buttonDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::StopAtBounds); + QTest::newRow("MouseArea StopAtBounds") << QByteArray("mouseAreaDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::StopAtBounds); + QTest::newRow("Button DragOverBounds") << QByteArray("buttonDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragOverBounds); + QTest::newRow("MouseArea DragOverBounds") << QByteArray("mouseAreaDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragOverBounds); + QTest::newRow("Button OvershootBounds") << QByteArray("buttonDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::OvershootBounds); + QTest::newRow("MouseArea OvershootBounds") << QByteArray("mouseAreaDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::OvershootBounds); + QTest::newRow("Button DragAndOvershootBounds") << QByteArray("buttonDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragAndOvershootBounds); + QTest::newRow("MouseArea DragAndOvershootBounds") << QByteArray("mouseAreaDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragAndOvershootBounds); +} + +void tst_QQuickListView2::tapDelegateDuringFlicking() // QTBUG-103832 +{ + QFETCH(QByteArray, qmlFile); + QFETCH(QQuickFlickable::BoundsBehavior, boundsBehavior); + + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl(qmlFile.constData()))); + QQuickListView *listView = qobject_cast<QQuickListView*>(window.rootObject()); + QVERIFY(listView); + listView->setBoundsBehavior(boundsBehavior); + + flickWithTouch(&window, {100, 400}, {100, 100}); + QTRY_VERIFY(listView->contentY() > 501); // let it flick some distance + QVERIFY(listView->isFlicking()); // we want to test the case when it's still moving while we tap + // @y = 400 we pressed the 4th delegate; started flicking, and the press was canceled + QCOMPARE(listView->property("pressedDelegates").toList().first(), 4); + QCOMPARE(listView->property("canceledDelegates").toList().first(), 4); + + // press a delegate during flicking (at y > 501 + 100, so likely delegate 6) + QTest::touchEvent(&window, touchDevice.data()).press(0, {100, 100}); + QQuickTouchUtils::flush(&window); + QTest::touchEvent(&window, touchDevice.data()).release(0, {100, 100}); + QQuickTouchUtils::flush(&window); + + const QVariantList pressedDelegates = listView->property("pressedDelegates").toList(); + const QVariantList releasedDelegates = listView->property("releasedDelegates").toList(); + const QVariantList tappedDelegates = listView->property("tappedDelegates").toList(); + const QVariantList canceledDelegates = listView->property("canceledDelegates").toList(); + + qCDebug(lcTests) << "pressed" << pressedDelegates; // usually [4, 6] + qCDebug(lcTests) << "released" << releasedDelegates; + qCDebug(lcTests) << "tapped" << tappedDelegates; + qCDebug(lcTests) << "canceled" << canceledDelegates; + + // which delegate received the second press, during flicking? + const int lastPressed = pressedDelegates.last().toInt(); + QVERIFY(lastPressed > 5); + QCOMPARE(releasedDelegates.last(), lastPressed); + QCOMPARE(tappedDelegates.last(), lastPressed); + QCOMPARE(canceledDelegates.count(), 1); // only the first press was canceled, not the second +} + +void tst_QQuickListView2::flickDuringFlicking_data() +{ + QTest::addColumn<QByteArray>("qmlFile"); + QTest::addColumn<QQuickFlickable::BoundsBehavior>("boundsBehavior"); + + QTest::newRow("Button StopAtBounds") << QByteArray("buttonDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::StopAtBounds); + QTest::newRow("MouseArea StopAtBounds") << QByteArray("mouseAreaDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::StopAtBounds); + QTest::newRow("Button DragOverBounds") << QByteArray("buttonDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragOverBounds); + QTest::newRow("MouseArea DragOverBounds") << QByteArray("mouseAreaDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragOverBounds); + QTest::newRow("Button OvershootBounds") << QByteArray("buttonDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::OvershootBounds); + QTest::newRow("MouseArea OvershootBounds") << QByteArray("mouseAreaDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::OvershootBounds); + QTest::newRow("Button DragAndOvershootBounds") << QByteArray("buttonDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragAndOvershootBounds); + QTest::newRow("MouseArea DragAndOvershootBounds") << QByteArray("mouseAreaDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragAndOvershootBounds); +} + +void tst_QQuickListView2::flickDuringFlicking() // QTBUG-103832 +{ + QFETCH(QByteArray, qmlFile); + QFETCH(QQuickFlickable::BoundsBehavior, boundsBehavior); + + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl(qmlFile.constData()))); + QQuickListView *listView = qobject_cast<QQuickListView*>(window.rootObject()); + QVERIFY(listView); + listView->setBoundsBehavior(boundsBehavior); + + flickWithTouch(&window, {100, 400}, {100, 100}); + // let it flick some distance + QTRY_VERIFY2(listView->contentY() > 1000, qPrintable(QString::fromLatin1( + "Expected ListView's contentY to be greater than 1000, but it's %1").arg(listView->contentY()))); + QVERIFY(listView->isFlicking()); // we want to test the case when it's moving and then we flick again + const qreal posBeforeSecondFlick = listView->contentY(); + + // flick again during flicking, and make sure that it doesn't jump back to the first delegate, + // but flicks incrementally further from the position at that time + QTest::touchEvent(&window, touchDevice.data()).press(0, {100, 400}); + QQuickTouchUtils::flush(&window); + qCDebug(lcTests) << "second press: contentY" << posBeforeSecondFlick << "->" << listView->contentY(); + qCDebug(lcTests) << "pressed delegates" << listView->property("pressedDelegates").toList(); + QVERIFY(listView->contentY() >= posBeforeSecondFlick); + + QTest::qWait(20); + QTest::touchEvent(&window, touchDevice.data()).move(0, {100, 300}); + QQuickTouchUtils::flush(&window); + qCDebug(lcTests) << "first move after second press: contentY" << posBeforeSecondFlick << "->" << listView->contentY(); + QVERIFY(listView->contentY() >= posBeforeSecondFlick); + + QTest::qWait(20); + QTest::touchEvent(&window, touchDevice.data()).move(0, {100, 200}); + QQuickTouchUtils::flush(&window); + qCDebug(lcTests) << "second move after second press: contentY" << posBeforeSecondFlick << "->" << listView->contentY(); + QVERIFY(listView->contentY() >= posBeforeSecondFlick + 100); + + QTest::touchEvent(&window, touchDevice.data()).release(0, {100, 100}); +} + +void tst_QQuickListView2::flickWithTouch(QQuickWindow *window, const QPoint &from, const QPoint &to) +{ + QTest::touchEvent(window, touchDevice.data()).press(0, from, window); + QQuickTouchUtils::flush(window); + + QPoint diff = to - from; + for (int i = 1; i <= 8; ++i) { + QTest::touchEvent(window, touchDevice.data()).move(0, from + i * diff / 8, window); + QQuickTouchUtils::flush(window); + } + QTest::touchEvent(window, touchDevice.data()).release(0, to, window); + QQuickTouchUtils::flush(window); +} + class SingletonModel : public QStringListModel { Q_OBJECT diff --git a/tests/auto/quick/qquickloader/tst_qquickloader.cpp b/tests/auto/quick/qquickloader/tst_qquickloader.cpp index e990e94bc3..4bb98db003 100644 --- a/tests/auto/quick/qquickloader/tst_qquickloader.cpp +++ b/tests/auto/quick/qquickloader/tst_qquickloader.cpp @@ -711,7 +711,7 @@ void tst_QQuickLoader::initialPropertyValues_data() << (QVariantList() << 12); QTest::newRow("initial property errors get reported") << testFileUrl("initialPropertyTriggerException.qml") - << (QStringList() << "^.*: Error: Cannot assign JavaScript function to int") + << (QStringList() << "^.*:10: Error: Cannot assign JavaScript function to int") << QStringList() << QVariantList(); } diff --git a/tests/auto/quick/qquickmousearea/data/simple.qml b/tests/auto/quick/qquickmousearea/data/simple.qml index 56d561e5af..2b8a2af118 100644 --- a/tests/auto/quick/qquickmousearea/data/simple.qml +++ b/tests/auto/quick/qquickmousearea/data/simple.qml @@ -4,8 +4,9 @@ Rectangle { id: whiteRect width: 200 height: 200 - color: "white" + color: ma.pressed ? "lightsteelblue" : "white" MouseArea { + id: ma objectName: "mousearea" anchors.fill: parent } diff --git a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp index f6080d94cd..ecdb57955f 100644 --- a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp +++ b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp @@ -42,6 +42,7 @@ #include <QtGui/QCursor> #include <QtGui/QScreen> #include <qpa/qwindowsysteminterface.h> +#include <qpa/qwindowsysteminterface_p.h> Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests") @@ -159,6 +160,9 @@ private slots: void containsMouseAndVisibility(); void doubleClickToHide(); void releaseFirstTouchAfterSecond(); +#if QT_CONFIG(tabletevent) + void tabletStylusTap(); +#endif private: int startDragDistance() const { @@ -2420,6 +2424,37 @@ void tst_QQuickMouseArea::releaseFirstTouchAfterSecond() // QTBUG-103766 QTRY_COMPARE(releaseSpy.count(), 1); } +#if QT_CONFIG(tabletevent) +void tst_QQuickMouseArea::tabletStylusTap() +{ + QVERIFY(qApp->testAttribute(Qt::AA_SynthesizeMouseForUnhandledTabletEvents)); // MouseArea depends on it + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("simple.qml"))); + QQuickMouseArea *mouseArea = window.rootObject()->findChild<QQuickMouseArea *>(); + QVERIFY(mouseArea); + QSignalSpy pressSpy(mouseArea, SIGNAL(pressed(QQuickMouseEvent*))); + QSignalSpy releaseSpy(mouseArea, &QQuickMouseArea::released); + QSignalSpy clickSpy(mouseArea, &QQuickMouseArea::clicked); + const qint64 stylusId = 1234567890; + + const QPoint point(100,100); + QWindowSystemInterface::handleTabletEvent(&window, point, window.mapToGlobal(point), + int(QInputDevice::DeviceType::Stylus), int(QPointingDevice::PointerType::Pen), + Qt::LeftButton, 0.5, 0, 0, 0, 0, 0, stylusId, Qt::NoModifier); + if (QWindowSystemInterfacePrivate::TabletEvent::platformSynthesizesMouse) + QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, point); // simulate what the platform does + QTRY_COMPARE(pressSpy.count(), 1); + QWindowSystemInterface::handleTabletEvent(&window, point, window.mapToGlobal(point), + int(QInputDevice::DeviceType::Stylus), int(QPointingDevice::PointerType::Pen), + Qt::NoButton, 0.5, 0, 0, 0, 0, 0, stylusId, Qt::NoModifier); + if (QWindowSystemInterfacePrivate::TabletEvent::platformSynthesizesMouse) + QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, point); + QTRY_COMPARE(releaseSpy.count(), 1); + QCOMPARE(clickSpy.count(), 1); + QCOMPARE(pressSpy.count(), 1); +} +#endif + QTEST_MAIN(tst_QQuickMouseArea) #include "tst_qquickmousearea.moc" diff --git a/tests/auto/quick/qquickmultipointtoucharea/BLACKLIST b/tests/auto/quick/qquickmultipointtoucharea/BLACKLIST index abe240f048..6d53fa2274 100644 --- a/tests/auto/quick/qquickmultipointtoucharea/BLACKLIST +++ b/tests/auto/quick/qquickmultipointtoucharea/BLACKLIST @@ -1,7 +1,9 @@ [nonOverlapping] ubuntu-20.04 +ubuntu-22.04 [nested] ubuntu-20.04 +ubuntu-22.04 # QTBUG-103200 [inFlickable2] diff --git a/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp b/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp index 8f45368f1c..2b7aca72d0 100644 --- a/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp +++ b/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp @@ -153,6 +153,10 @@ private slots: void objectModelMove(); void requiredPropertiesInDelegate(); void requiredPropertiesInDelegatePreventUnrelated(); + void touchMove(); + +private: + QScopedPointer<QPointingDevice> touchDevice = QScopedPointer<QPointingDevice>(QTest::createTouchDevice()); }; class TestObject : public QObject @@ -1390,8 +1394,7 @@ void tst_QQuickPathView::package() QVERIFY(window); window->setSource(testFileUrl("pathview_package.qml")); window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window.data())); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickPathView *pathView = window->rootObject()->findChild<QQuickPathView*>("photoPathView"); QVERIFY(pathView); @@ -1519,9 +1522,7 @@ void tst_QQuickPathView::mouseDrag() QQuickVisualTestUtils::moveMouseAway(window.data()); window->setSource(testFileUrl("dragpath.qml")); window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window.data())); - QCOMPARE(window.data(), qGuiApp->focusWindow()); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickPathView *pathview = qobject_cast<QQuickPathView*>(window->rootObject()); QVERIFY(pathview != nullptr); @@ -1589,10 +1590,7 @@ void tst_QQuickPathView::nestedMouseAreaDrag() QQuickVisualTestUtils::moveMouseAway(window.data()); window->setSource(testFileUrl("nestedmousearea.qml")); window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window.data())); - QCOMPARE(window.data(), qGuiApp->focusWindow()); - + QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickPathView *pathview = qobject_cast<QQuickPathView*>(window->rootObject()); QVERIFY(pathview != nullptr); @@ -1612,9 +1610,7 @@ void tst_QQuickPathView::flickNClick() // QTBUG-77173 QQuickVisualTestUtils::moveMouseAway(window.data()); window->setSource(testFileUrl("nestedmousearea2.qml")); window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window.data())); - QCOMPARE(window.data(), qGuiApp->focusWindow()); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickPathView *pathview = qobject_cast<QQuickPathView*>(window->rootObject()); QVERIFY(pathview != nullptr); @@ -1703,9 +1699,7 @@ void tst_QQuickPathView::changePreferredHighlight() window->setGeometry(0,0,400,200); window->setSource(testFileUrl("dragpath.qml")); window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window.data())); - QCOMPARE(window.data(), qGuiApp->focusWindow()); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickPathView *pathview = qobject_cast<QQuickPathView*>(window->rootObject()); QVERIFY(pathview != nullptr); @@ -1906,9 +1900,7 @@ void tst_QQuickPathView::cancelDrag() window->setSource(testFileUrl("dragpath.qml")); QQuickVisualTestUtils::moveMouseAway(window.data()); window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window.data())); - QCOMPARE(window.data(), qGuiApp->focusWindow()); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickPathView *pathview = qobject_cast<QQuickPathView*>(window->rootObject()); QVERIFY(pathview != nullptr); @@ -1953,9 +1945,7 @@ void tst_QQuickPathView::maximumFlickVelocity() window->setSource(testFileUrl("dragpath.qml")); QQuickVisualTestUtils::moveMouseAway(window.data()); window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window.data())); - QCOMPARE(window.data(), qGuiApp->focusWindow()); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickPathView *pathview = qobject_cast<QQuickPathView*>(window->rootObject()); QVERIFY(pathview != nullptr); @@ -2000,9 +1990,7 @@ void tst_QQuickPathView::snapToItem() QQuickVisualTestUtils::moveMouseAway(window.data()); window->setSource(testFileUrl("panels.qml")); window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window.data())); - QCOMPARE(window.data(), qGuiApp->focusWindow()); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickPathView *pathview = window->rootObject()->findChild<QQuickPathView*>("view"); QVERIFY(pathview != nullptr); @@ -2044,9 +2032,7 @@ void tst_QQuickPathView::snapOneItem() QQuickVisualTestUtils::moveMouseAway(window.data()); window->setSource(testFileUrl("panels.qml")); window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window.data())); - QCOMPARE(window.data(), qGuiApp->focusWindow()); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickPathView *pathview = window->rootObject()->findChild<QQuickPathView*>("view"); QVERIFY(pathview != nullptr); @@ -2097,9 +2083,7 @@ void tst_QQuickPathView::positionViewAtIndex() QScopedPointer<QQuickView> window(createView()); window->setSource(testFileUrl("pathview3.qml")); window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window.data())); - QCOMPARE(window.data(), qGuiApp->focusWindow()); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickPathView *pathview = qobject_cast<QQuickPathView*>(window->rootObject()); QVERIFY(pathview != nullptr); @@ -2160,9 +2144,7 @@ void tst_QQuickPathView::indexAt_itemAt() QScopedPointer<QQuickView> window(createView()); window->setSource(testFileUrl("pathview3.qml")); window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window.data())); - QCOMPARE(window.data(), qGuiApp->focusWindow()); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickPathView *pathview = qobject_cast<QQuickPathView*>(window->rootObject()); QVERIFY(pathview != nullptr); @@ -2286,8 +2268,7 @@ void tst_QQuickPathView::changePathDuringRefill() window->setSource(testFileUrl("changePathDuringRefill.qml")); window->show(); - QVERIFY(QTest::qWaitForWindowActive(window.data())); - QCOMPARE(window.data(), qGuiApp->focusWindow()); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickPathView *pathView = qobject_cast<QQuickPathView*>(window->rootObject()); QVERIFY(pathView != nullptr); @@ -2313,9 +2294,7 @@ void tst_QQuickPathView::nestedinFlickable() QQuickVisualTestUtils::moveMouseAway(window.data()); window->setSource(testFileUrl("nestedInFlickable.qml")); window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window.data())); - QCOMPARE(window.data(), qGuiApp->focusWindow()); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickPathView *pathview = findItem<QQuickPathView>(window->rootObject(), "pathView"); QVERIFY(pathview != nullptr); @@ -2421,9 +2400,7 @@ void tst_QQuickPathView::ungrabNestedinFlickable() QQuickVisualTestUtils::moveMouseAway(window.data()); window->setSource(testFileUrl("ungrabNestedinFlickable.qml")); window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window.data())); - QCOMPARE(window.data(), qGuiApp->focusWindow()); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickPathView *pathview = findItem<QQuickPathView>(window->rootObject(), "pathView"); QVERIFY(pathview != nullptr); @@ -2455,9 +2432,7 @@ void tst_QQuickPathView::flickableDelegate() QQuickVisualTestUtils::moveMouseAway(window.data()); window->setSource(testFileUrl("flickableDelegate.qml")); window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window.data())); - QCOMPARE(window.data(), qGuiApp->focusWindow()); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickPathView *pathview = qobject_cast<QQuickPathView*>(window->rootObject()); QVERIFY(pathview != nullptr); @@ -2537,8 +2512,7 @@ void tst_QQuickPathView::qtbug37815() window->setSource(testFileUrl("qtbug37815.qml")); window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window.data())); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); // cache items will be created async. Let's wait... QTest::qWait(1000); @@ -2568,8 +2542,7 @@ void tst_QQuickPathView::qtbug42716() window->setSource(testFileUrl("qtbug42716.qml")); window->show(); - QVERIFY(QTest::qWaitForWindowActive(window.data())); - QCOMPARE(window.data(), qGuiApp->focusWindow()); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickPathView *pathView = findItem<QQuickPathView>(window->rootObject(), "pathView"); QVERIFY(pathView != nullptr); @@ -2610,8 +2583,7 @@ void tst_QQuickPathView::qtbug53464() window->setSource(testFileUrl("qtbug53464.qml")); window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window.data())); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickPathView *pathView = findItem<QQuickPathView>(window->rootObject(), "pathView"); QVERIFY(pathView != nullptr); @@ -2688,9 +2660,7 @@ void tst_QQuickPathView::movementDirection() QQuickVisualTestUtils::moveMouseAway(window.data()); window->setSource(testFileUrl("movementDirection.qml")); window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window.data())); - QCOMPARE(window.data(), qGuiApp->focusWindow()); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickPathView *pathview = window->rootObject()->findChild<QQuickPathView*>("view"); QVERIFY(pathview != nullptr); @@ -2797,6 +2767,112 @@ void tst_QQuickPathView::requiredPropertiesInDelegatePreventUnrelated() window->show(); } +void tst_QQuickPathView::touchMove() +{ + QScopedPointer<QQuickView> window(createView()); + QQuickVisualTestUtils::moveMouseAway(window.data()); + window->setSource(testFileUrl("dragpath.qml")); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + QQuickPathView *pathview = qobject_cast<QQuickPathView*>(window->rootObject()); + QVERIFY(pathview != nullptr); + + QSignalSpy movingSpy(pathview, SIGNAL(movingChanged())); + QSignalSpy moveStartedSpy(pathview, SIGNAL(movementStarted())); + QSignalSpy moveEndedSpy(pathview, SIGNAL(movementEnded())); + QSignalSpy draggingSpy(pathview, SIGNAL(draggingChanged())); + QSignalSpy dragStartedSpy(pathview, SIGNAL(dragStarted())); + QSignalSpy dragEndedSpy(pathview, SIGNAL(dragEnded())); + QSignalSpy flickStartedSpy(pathview, SIGNAL(flickStarted())); + QSignalSpy flickEndedSpy(pathview, SIGNAL(flickEnded())); + + int current = pathview->currentIndex(); + + // touch move from left to right + QPoint from(250, 100); + QPoint to(10, 100); + + QTest::touchEvent(window.data(), touchDevice.data()).press(0, from, window.data()); + QQuickTouchUtils::flush(window.data()); + + QVERIFY(!pathview->isMoving()); + QVERIFY(!pathview->isDragging()); + QCOMPARE(movingSpy.count(), 0); + QCOMPARE(moveStartedSpy.count(), 0); + QCOMPARE(moveEndedSpy.count(), 0); + QCOMPARE(draggingSpy.count(), 0); + QCOMPARE(dragStartedSpy.count(), 0); + QCOMPARE(dragEndedSpy.count(), 0); + QCOMPARE(flickStartedSpy.count(), 0); + QCOMPARE(flickEndedSpy.count(), 0); + + from -= QPoint(QGuiApplication::styleHints()->startDragDistance() + 1, 0); + QTest::touchEvent(window.data(), touchDevice.data()).move(0, from, window.data()); + QQuickTouchUtils::flush(window.data()); + + // first move does not trigger move/drag + QVERIFY(!pathview->isMoving()); + QVERIFY(!pathview->isDragging()); + QCOMPARE(movingSpy.count(), 0); + QCOMPARE(moveStartedSpy.count(), 0); + QCOMPARE(moveEndedSpy.count(), 0); + QCOMPARE(draggingSpy.count(), 0); + QCOMPARE(dragStartedSpy.count(), 0); + QCOMPARE(dragEndedSpy.count(), 0); + QCOMPARE(flickStartedSpy.count(), 0); + QCOMPARE(flickEndedSpy.count(), 0); + + QPoint diff = from - to; + int moveCount = 4; + for (int i = 1; i <= moveCount; ++i) { + QTest::touchEvent(window.data(), touchDevice.data()).move(0, from - i * diff / moveCount, window.data()); + QQuickTouchUtils::flush(window.data()); + + QVERIFY(pathview->isMoving()); + QVERIFY(pathview->isDragging()); + QCOMPARE(movingSpy.count(), 1); + QCOMPARE(moveStartedSpy.count(), 1); + QCOMPARE(moveEndedSpy.count(), 0); + QCOMPARE(draggingSpy.count(), 1); + QCOMPARE(dragStartedSpy.count(), 1); + QCOMPARE(dragEndedSpy.count(), 0); + QCOMPARE(flickStartedSpy.count(), 0); + QCOMPARE(flickEndedSpy.count(), 0); + } + QVERIFY(pathview->currentIndex() != current); + + QTest::touchEvent(window.data(), touchDevice.data()).release(0, to, window.data()); + QQuickTouchUtils::flush(window.data()); + + QVERIFY(pathview->isMoving()); + QVERIFY(!pathview->isDragging()); + QCOMPARE(movingSpy.count(), 1); + QCOMPARE(moveStartedSpy.count(), 1); + QCOMPARE(moveEndedSpy.count(), 0); + QCOMPARE(draggingSpy.count(), 2); + QCOMPARE(dragStartedSpy.count(), 1); + QCOMPARE(dragEndedSpy.count(), 1); + QCOMPARE(flickStartedSpy.count(), 1); + QCOMPARE(flickEndedSpy.count(), 0); + + // Wait for the flick to finish + QVERIFY(QTest::qWaitFor([&]() + { return !pathview->isFlicking(); } + )); + QVERIFY(!pathview->isMoving()); + QVERIFY(!pathview->isDragging()); + QCOMPARE(movingSpy.count(), 2); + QCOMPARE(moveStartedSpy.count(), 1); + QCOMPARE(moveEndedSpy.count(), 1); + QCOMPARE(draggingSpy.count(), 2); + QCOMPARE(dragStartedSpy.count(), 1); + QCOMPARE(dragEndedSpy.count(), 1); + QCOMPARE(flickStartedSpy.count(), 1); + QCOMPARE(flickEndedSpy.count(), 1); + +} + QTEST_MAIN(tst_QQuickPathView) #include "tst_qquickpathview.moc" diff --git a/tests/auto/quick/qquickpincharea/data/pinchAreaInPathView.qml b/tests/auto/quick/qquickpincharea/data/pinchAreaInPathView.qml new file mode 100644 index 0000000000..dc909c2c7c --- /dev/null +++ b/tests/auto/quick/qquickpincharea/data/pinchAreaInPathView.qml @@ -0,0 +1,48 @@ +import QtQuick + +PathView { + width: 600 + height: 200 + + model: 3 + delegate: Rectangle { + width: 200 + height: 200 + color: "salmon" + opacity: PathView.isCurrentItem ? 1 : 0.5 + + property alias pinchArea: pinchArea + + Text { + text: "Test" + font.pixelSize: 100 + anchors.fill: parent + fontSizeMode: Text.Fit + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + PinchArea { + id: pinchArea + anchors.fill: parent + pinch.target: parent + pinch.dragAxis: Pinch.XAndYAxis + pinch.minimumScale: 1.0 + pinch.maximumScale: 5.0 + + onPinchFinished: (pinch) => { + parent.scale = 1 + parent.x = 0 + parent.y = 0 + } + } + } + path: Path { + startX: 100 + startY: 100 + PathLine { + x: 700 + y: 100 + } + } +} diff --git a/tests/auto/quick/qquickpincharea/data/pinchproperties.qml b/tests/auto/quick/qquickpincharea/data/pinchproperties.qml index 899c6380da..42096d0b77 100644 --- a/tests/auto/quick/qquickpincharea/data/pinchproperties.qml +++ b/tests/auto/quick/qquickpincharea/data/pinchproperties.qml @@ -31,18 +31,18 @@ Rectangle { pinch.maximumScale: 2.0 pinch.minimumRotation: 0.0 pinch.maximumRotation: 90.0 - onPinchStarted: { + onPinchStarted: (pinch) => { whiteRect.center = pinch.center whiteRect.scale = pinch.scale whiteRect.pointCount = pinch.pointCount; whiteRect.pinchActive = true; } - onPinchUpdated: { + onPinchUpdated: (pinch) => { whiteRect.center = pinch.center whiteRect.scale = pinch.scale whiteRect.pointCount = pinch.pointCount; } - onPinchFinished: { + onPinchFinished: (pinch) => { whiteRect.center = pinch.center whiteRect.scale = pinch.scale whiteRect.pointCount = pinch.pointCount; diff --git a/tests/auto/quick/qquickpincharea/tst_qquickpincharea.cpp b/tests/auto/quick/qquickpincharea/tst_qquickpincharea.cpp index 85c186b2d8..17e225f1e2 100644 --- a/tests/auto/quick/qquickpincharea/tst_qquickpincharea.cpp +++ b/tests/auto/quick/qquickpincharea/tst_qquickpincharea.cpp @@ -26,6 +26,7 @@ ** ****************************************************************************/ +#include <QtCore/private/qvariantanimation_p.h> #include <QtTest/QtTest> #include <QtTest/QSignalSpy> #include <QtGui/QStyleHints> @@ -33,6 +34,7 @@ #include <QtGui/private/qeventpoint_p.h> #include <qpa/qwindowsysteminterface.h> #include <private/qquickpincharea_p.h> +#include <QtQuick/private/qquickpathview_p.h> #include <QtQuick/private/qquickrectangle_p.h> #include <QtQuick/qquickview.h> #include <QtQml/qqmlcontext.h> @@ -55,6 +57,8 @@ private slots: void transformedPinchArea(); void dragTransformedPinchArea_data(); void dragTransformedPinchArea(); + void pinchAreaKeepsDragInView(); + void pinchInPathView(); private: QQuickView *createView(); @@ -643,6 +647,147 @@ void tst_QQuickPinchArea::dragTransformedPinchArea() // QTBUG-63673 QCOMPARE(pinchArea->pinch()->active(), false); } +template<typename F> +void forEachLerpStep(int steps, F &&func) +{ + for (int i = 0; i < steps; ++i) { + const qreal t = qreal(i) / steps; + func(t); + } +} + +QPoint lerpPoints(const QPoint &point1, const QPoint &point2, qreal t) +{ + return QPoint(_q_interpolate(point1.x(), point2.x(), t), _q_interpolate(point1.y(), point2.y(), t)); +}; + +// QTBUG-105058 +void tst_QQuickPinchArea::pinchAreaKeepsDragInView() +{ + QQuickView view; + view.setSource(testFileUrl("pinchAreaInPathView.qml")); + QVERIFY(view.rootObject()); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + QQuickPathView *pathView = qobject_cast<QQuickPathView*>(view.rootObject()); + QVERIFY(pathView); + QCOMPARE(pathView->count(), 3); + + const QQuickItem *pinchDelegateItem = pathView->itemAtIndex(0); + QQuickPinchArea *pinchArea = pinchDelegateItem->property("pinchArea").value<QQuickPinchArea*>(); + QVERIFY(pinchArea); + + // Press. + QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(&view, device); + QPoint point1Start = { 80, 120 }; + QPoint point2Start = { 120, 80 }; + const int dragThreshold = qApp->styleHints()->startDragDistance(); + pinchSequence.press(1, pinchArea->mapToScene(point1Start).toPoint(), &view) + .press(2, pinchArea->mapToScene(point2Start).toPoint(), &view).commit(); + QQuickTouchUtils::flush(&view); + + // Move past the drag threshold to begin the pinch. + const int steps = 30; + QPoint point1End = point1Start + QPoint(-dragThreshold, dragThreshold); + QPoint point2End = point2Start + QPoint(dragThreshold, -dragThreshold); + forEachLerpStep(steps, [&](qreal t) { + pinchSequence.move(1, lerpPoints(point1Start, point1End, t), &view) + .move(2, lerpPoints(point2Start, point2End, t), &view).commit(); + QQuickTouchUtils::flush(&view); + QTest::qWait(5); + }); + QCOMPARE(pinchArea->pinch()->active(), true); + QVERIFY2(pinchDelegateItem->scale() > 1.0, qPrintable(QString::number(pinchDelegateItem->scale()))); + // The PathView contents shouldn't have moved. + QCOMPARE(pathView->offset(), 0); + + // Release a touch point. + pinchSequence.stationary(1).release(2, point2End, &view).commit(); + QQuickTouchUtils::flush(&view); + + // Press it again. + pinchSequence.stationary(1).press(2, point2End, &view).commit(); + QQuickTouchUtils::flush(&view); + QCOMPARE(pinchArea->pinch()->active(), true); + + // Drag to the right; the PathView still shouldn't move. + point1Start = point1End; + point2Start = point2End; + point1End = point1Start + QPoint(100, 0); + point2End = point2Start + QPoint(100, 0); + forEachLerpStep(steps, [&](qreal t) { + pinchSequence.move(1, lerpPoints(point1Start, point1End, t), &view) + .move(2, lerpPoints(point2Start, point2End, t), &view).commit(); + QQuickTouchUtils::flush(&view); + QTest::qWait(5); + }); + QCOMPARE(pinchArea->pinch()->active(), true); + QVERIFY2(pinchDelegateItem->scale() > 1.0, qPrintable(QString::number(pinchDelegateItem->scale()))); + QCOMPARE(pathView->offset(), 0); + + // Release pinch. + pinchSequence.release(1, point1End, &view).release(2, point2End, &view).commit(); + QQuickTouchUtils::flush(&view); + QCOMPARE(pinchArea->pinch()->active(), false); + QCOMPARE(pathView->offset(), 0); +} + +void tst_QQuickPinchArea::pinchInPathView() +{ + QQuickView view; + view.setSource(testFileUrl("pinchAreaInPathView.qml")); + QVERIFY(view.rootObject()); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + QQuickPathView *pathView = qobject_cast<QQuickPathView*>(view.rootObject()); + QVERIFY(pathView); + QCOMPARE(pathView->count(), 3); + + const QQuickItem *pinchDelegateItem = pathView->itemAtIndex(0); + QQuickPinchArea *pinchArea = pinchDelegateItem->property("pinchArea").value<QQuickPinchArea*>(); + QVERIFY(pinchArea); + + // press + QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(&view, device); + QPoint point1Start = { 10, 10 }; + QPoint point2Start = { 100, 100 }; + const int dragThreshold = qApp->styleHints()->startDragDistance(); + pinchSequence.press(0, point1Start, &view) + .press(1, point2Start, &view) + .commit(); + QQuickTouchUtils::flush(&view); + QTest::qWait(20); + + // move + QPoint moveDistance = QPoint(dragThreshold * 3, dragThreshold * 3); + QPoint point2End = point2Start + moveDistance; + pinchSequence.stationary(0) + .move(1, point2End, &view) + .commit(); + QQuickTouchUtils::flush(&view); + QTest::qWait(20); + + point2End += moveDistance; + pinchSequence.stationary(0) + .move(1, point2End, &view) + .commit(); + QQuickTouchUtils::flush(&view); + QTest::qWait(20); + + QCOMPARE(pinchArea->pinch()->active(), true); + QVERIFY2(pinchDelegateItem->scale() > 1.0, qPrintable(QString::number(pinchDelegateItem->scale()))); + // PathView shouldn't have moved. + QCOMPARE(pathView->offset(), 0); + + // release pinch. + pinchSequence.release(0, point1Start, &view).release(1, point2End, &view).commit(); + QQuickTouchUtils::flush(&view); + QCOMPARE(pinchArea->pinch()->active(), false); + QCOMPARE(pathView->offset(), 0); +} + QQuickView *tst_QQuickPinchArea::createView() { QQuickView *window = new QQuickView(nullptr); diff --git a/tests/auto/quick/qquickshadereffect/tst_qquickshadereffect.cpp b/tests/auto/quick/qquickshadereffect/tst_qquickshadereffect.cpp index c7619ab73c..788323ecf4 100644 --- a/tests/auto/quick/qquickshadereffect/tst_qquickshadereffect.cpp +++ b/tests/auto/quick/qquickshadereffect/tst_qquickshadereffect.cpp @@ -32,9 +32,11 @@ #include <QByteArray> #include <private/qquickshadereffect_p.h> #include <QMatrix4x4> -#include <QtQuick/QQuickView> #include <QtQml/QQmlEngine> +#include <QtQuick/QQuickView> +#include <QtQuick/private/qquickshadereffectsource_p.h> #include <QtQuickTestUtils/private/qmlutils_p.h> +#include <QtQuickTestUtils/private/viewtestutils_p.h> class TestShaderEffect : public QQuickShaderEffect { @@ -120,17 +122,17 @@ void tst_qquickshadereffect::cleanupTestCase() void tst_qquickshadereffect::testConnection() { // verify that the property notify signal is connected - QQuickView *view = new QQuickView(nullptr); - view->setSource(QUrl(QStringLiteral("qrc:/data/connections.qml"))); + QQuickView view; + QVERIFY(QQuickTest::initView(view, QStringLiteral("qrc:/data/connections.qml"))); - auto *shaderEffectItem = qobject_cast<TestShaderEffect*>(view->rootObject()); + auto *shaderEffectItem = qobject_cast<TestShaderEffect*>(view.rootObject()); QVERIFY(shaderEffectItem); QCOMPARE(shaderEffectItem->signalsConnected, 0); - view->show(); - QVERIFY(QTest::qWaitForWindowExposed(view)); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); - QSGRendererInterface *rif = view->rendererInterface(); + QSGRendererInterface *rif = view.rendererInterface(); if (rif && rif->graphicsApi() != QSGRendererInterface::Software) QCOMPARE(shaderEffectItem->signalsConnected, 1); } @@ -138,76 +140,62 @@ void tst_qquickshadereffect::testConnection() void tst_qquickshadereffect::deleteSourceItem() { // purely to ensure that deleting the sourceItem of a shader doesn't cause a crash - QQuickView *view = new QQuickView(nullptr); - view->setSource(QUrl(QStringLiteral("qrc:/data/deleteSourceItem.qml"))); - view->show(); - QVERIFY(QTest::qWaitForWindowExposed(view)); - QVERIFY(view); - QObject *obj = view->rootObject(); + QQuickView view; + QVERIFY(QQuickTest::showView(view, QStringLiteral("qrc:/data/deleteSourceItem.qml"))); + QObject *obj = view.rootObject(); QVERIFY(obj); QMetaObject::invokeMethod(obj, "setDeletedSourceItem"); - QTest::qWait(50); - delete view; + const auto shaderEffectSource = obj->findChild<QQuickShaderEffectSource*>(); + QVERIFY(shaderEffectSource); + QTRY_VERIFY(!shaderEffectSource->sourceItem()); } void tst_qquickshadereffect::deleteShaderEffectSource() { - // purely to ensure that deleting the sourceItem of a shader doesn't cause a crash - QQuickView *view = new QQuickView(nullptr); - view->setSource(QUrl(QStringLiteral("qrc:/data/deleteShaderEffectSource.qml"))); - view->show(); - QVERIFY(QTest::qWaitForWindowExposed(view)); - QVERIFY(view); - QObject *obj = view->rootObject(); + // purely to ensure that deleting the ShaderEffectSource doesn't cause a crash + QQuickView view; + QVERIFY(QQuickTest::showView(view, QStringLiteral("qrc:/data/deleteShaderEffectSource.qml"))); + QObject *obj = view.rootObject(); QVERIFY(obj); + const QPointer<QQuickShaderEffectSource> shaderEffectSource = obj->findChild<QQuickShaderEffectSource*>(); + QVERIFY(shaderEffectSource); QMetaObject::invokeMethod(obj, "setDeletedShaderEffectSource"); - QTest::qWait(50); - delete view; + QTRY_VERIFY(shaderEffectSource); } void tst_qquickshadereffect::twoImagesOneShaderEffect() { // Don't crash when having a ShaderEffect and an Image sharing the texture via supportsAtlasTextures - QQuickView *view = new QQuickView(nullptr); - view->setSource(QUrl(QStringLiteral("qrc:/data/twoImagesOneShaderEffect.qml"))); - view->show(); - QVERIFY(QTest::qWaitForWindowExposed(view)); - QVERIFY(view); - QObject *obj = view->rootObject(); + QQuickView view; + QVERIFY(QQuickTest::showView(view, QStringLiteral("qrc:/data/twoImagesOneShaderEffect.qml"))); + QObject *obj = view.rootObject(); QVERIFY(obj); - delete view; } void tst_qquickshadereffect::withoutQmlEngine() { // using a shader without QML engine used to crash - auto window = new QQuickWindow; - auto shaderEffect = new TestShaderEffect(window->contentItem()); + const QQuickWindow window; + auto shaderEffect = new TestShaderEffect(window.contentItem()); shaderEffect->setVertexShader(QUrl()); QVERIFY(shaderEffect->isComponentComplete()); - delete window; } // QTBUG-86402: hiding the parent of an item that uses an effect should not cause a crash. void tst_qquickshadereffect::hideParent() { - QScopedPointer<QQuickView> view(new QQuickView); - view->setSource(QUrl(QStringLiteral("qrc:/data/hideParent.qml"))); - QCOMPARE(view->status(), QQuickView::Ready); - view->show(); - QVERIFY(QTest::qWaitForWindowExposed(view.data())); + QQuickView view; + view.setSource(QUrl(QStringLiteral("qrc:/data/hideParent.qml"))); + QVERIFY(QQuickTest::showView(view, QStringLiteral("qrc:/data/hideParent.qml"))); // Should finish without crashing. - QTRY_VERIFY(view->rootObject()->property("finished").toBool()); + QTRY_VERIFY(view.rootObject()->property("finished").toBool()); } void tst_qquickshadereffect::testPropertyMappings() { - QScopedPointer<QQuickView> view(new QQuickView); - view->setSource(QUrl(QStringLiteral("qrc:/data/testProperties.qml"))); - QCOMPARE(view->status(), QQuickView::Ready); - view->show(); - QVERIFY(QTest::qWaitForWindowExposed(view.data())); - QTRY_VERIFY(view->rootObject()->property("finished").toBool()); + QQuickView view; + view.setSource(QUrl(QStringLiteral("qrc:/data/testProperties.qml"))); + QTRY_VERIFY(view.rootObject()->property("finished").toBool()); } QTEST_MAIN(tst_qquickshadereffect) diff --git a/tests/auto/quick/qquickstates/data/removeBindingWithTransition.qml b/tests/auto/quick/qquickstates/data/removeBindingWithTransition.qml new file mode 100644 index 0000000000..ed40e18374 --- /dev/null +++ b/tests/auto/quick/qquickstates/data/removeBindingWithTransition.qml @@ -0,0 +1,23 @@ +import QtQuick + +Item { + id: root + property bool toggle: true + property int state1Width: 500 + + states: [ + State { + when: root.toggle + PropertyChanges { root.width: root.state1Width } + }, + State { + when: !root.toggle + PropertyChanges { root.width: 300 } + } + ] + + transitions: Transition { + id: transition + SmoothedAnimation { target: root; property: "width"; velocity: 200 } + } +} diff --git a/tests/auto/quick/qquickstates/tst_qquickstates.cpp b/tests/auto/quick/qquickstates/tst_qquickstates.cpp index d6814bd057..35d6deee58 100644 --- a/tests/auto/quick/qquickstates/tst_qquickstates.cpp +++ b/tests/auto/quick/qquickstates/tst_qquickstates.cpp @@ -207,6 +207,7 @@ private slots: void parentChangeInvolvingBindings(); void deferredProperties(); void rewindAnchorChange(); + void bindingProperlyRemovedWithTransition(); }; void tst_qquickstates::initTestCase() @@ -1978,6 +1979,26 @@ void tst_qquickstates::rewindAnchorChange() QTRY_COMPARE(innerRect->height(), 200); } +void tst_qquickstates::bindingProperlyRemovedWithTransition() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("removeBindingWithTransition.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> root(c.create()); + QVERIFY(root); + QQuickItem *item = qobject_cast<QQuickItem *>(root.get()); + QVERIFY(item); + + item->setProperty("toggle", false); + QTRY_COMPARE(item->width(), 300); + + item->setProperty("state1Width", 100); + QCOMPARE(item->width(), 300); + + item->setProperty("toggle", true); + QTRY_COMPARE(item->width(), 100); +} + QTEST_MAIN(tst_qquickstates) #include "tst_qquickstates.moc" diff --git a/tests/auto/quick/qquicktext/BLACKLIST b/tests/auto/quick/qquicktext/BLACKLIST index e7ad2cdada..a4e9c44eab 100644 --- a/tests/auto/quick/qquicktext/BLACKLIST +++ b/tests/auto/quick/qquicktext/BLACKLIST @@ -21,7 +21,5 @@ android # QTBUG-103096 [largeTextObservesViewport] android -[imgTagsAlign] -android [baselineOffset] android diff --git a/tests/auto/quick/qquicktext/tst_qquicktext.cpp b/tests/auto/quick/qquicktext/tst_qquicktext.cpp index 1f7d000bda..8fcdcca5a2 100644 --- a/tests/auto/quick/qquicktext/tst_qquicktext.cpp +++ b/tests/auto/quick/qquicktext/tst_qquicktext.cpp @@ -3278,12 +3278,12 @@ void tst_qquicktext::imgTagsAlign_data() QTest::addColumn<QString>("src"); QTest::addColumn<int>("imgHeight"); QTest::addColumn<QString>("align"); - QTest::newRow("heart-bottom") << "data/images/heart200.png" << 181 << "bottom"; - QTest::newRow("heart-middle") << "data/images/heart200.png" << 181 << "middle"; - QTest::newRow("heart-top") << "data/images/heart200.png" << 181 << "top"; - QTest::newRow("starfish-bottom") << "data/images/starfish_2.png" << 217 << "bottom"; - QTest::newRow("starfish-middle") << "data/images/starfish_2.png" << 217 << "middle"; - QTest::newRow("starfish-top") << "data/images/starfish_2.png" << 217 << "top"; + QTest::newRow("heart-bottom") << "images/heart200.png" << 181 << "bottom"; + QTest::newRow("heart-middle") << "images/heart200.png" << 181 << "middle"; + QTest::newRow("heart-top") << "images/heart200.png" << 181 << "top"; + QTest::newRow("starfish-bottom") << "images/starfish_2.png" << 217 << "bottom"; + QTest::newRow("starfish-middle") << "images/starfish_2.png" << 217 << "middle"; + QTest::newRow("starfish-top") << "images/starfish_2.png" << 217 << "top"; } void tst_qquicktext::imgTagsAlign() @@ -3293,7 +3293,7 @@ void tst_qquicktext::imgTagsAlign() QFETCH(QString, align); QString componentStr = "import QtQuick 2.0\nText { text: \"This is a test <img src=\\\"" + src + "\\\" align=\\\"" + align + "\\\"> of image.\" }"; QQmlComponent textComponent(&engine); - textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(".")); + textComponent.setData(componentStr.toLatin1(), testFileUrl(".")); QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create()); QVERIFY(textObject != nullptr); @@ -3315,10 +3315,10 @@ void tst_qquicktext::imgTagsAlign() void tst_qquicktext::imgTagsMultipleImages() { - QString componentStr = "import QtQuick 2.0\nText { text: \"This is a starfish<img src=\\\"data/images/starfish_2.png\\\" width=\\\"60\\\" height=\\\"60\\\" > and another one<img src=\\\"data/images/heart200.png\\\" width=\\\"85\\\" height=\\\"85\\\">.\" }"; + QString componentStr = "import QtQuick 2.0\nText { text: \"This is a starfish<img src=\\\"images/starfish_2.png\\\" width=\\\"60\\\" height=\\\"60\\\" > and another one<img src=\\\"images/heart200.png\\\" width=\\\"85\\\" height=\\\"85\\\">.\" }"; QQmlComponent textComponent(&engine); - textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(".")); + textComponent.setData(componentStr.toLatin1(), testFileUrl(".")); QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create()); QVERIFY(textObject != nullptr); @@ -3374,11 +3374,15 @@ void tst_qquicktext::imgTagsUpdates() void tst_qquicktext::imgTagsError() { - QString componentStr = "import QtQuick 2.0\nText { text: \"This is a starfish<img src=\\\"data/images/starfish_2.pn\\\" width=\\\"60\\\" height=\\\"60\\\">.\" }"; + QString componentStr = "import QtQuick 2.0\nText { text: \"This is a starfish<img src=\\\"images/starfish_2.pn\\\" width=\\\"60\\\" height=\\\"60\\\">.\" }"; QQmlComponent textComponent(&engine); - QTest::ignoreMessage(QtWarningMsg, "<Unknown File>:2:1: QML Text: Cannot open: file:data/images/starfish_2.pn"); - textComponent.setData(componentStr.toLatin1(), QUrl("file:")); + const QString expectedMessage( + testFileUrl(".").toString() + + ":2:1: QML Text: Cannot open: " + + testFileUrl("images/starfish_2.pn").toString()); + QTest::ignoreMessage(QtWarningMsg, expectedMessage.toLatin1()); + textComponent.setData(componentStr.toLatin1(), testFileUrl(".")); QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create()); QVERIFY(textObject != nullptr); @@ -3626,6 +3630,46 @@ void tst_qquicktext::fontSizeMode() myText->setElideMode(QQuickText::ElideNone); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); + + // Growing height needs to update the baselineOffset when AlignBottom is used + // and text is NOT wrapped + myText->setVAlign(QQuickText::AlignBottom); + myText->setFontSizeMode(QQuickText::Fit); + QVERIFY(QQuickTest::qWaitForItemPolished(myText)); + + int baselineOffset = myText->baselineOffset(); + myText->setHeight(myText->height() * 2); + QVERIFY(QQuickTest::qWaitForItemPolished(myText)); + QVERIFY(myText->baselineOffset() > baselineOffset); + + // Growing height needs to update the baselineOffset when AlignBottom is used + // and the text is wrapped + myText->setVAlign(QQuickText::AlignBottom); + myText->setFontSizeMode(QQuickText::Fit); + myText->setWrapMode(QQuickText::NoWrap); + myText->resetMaximumLineCount(); + QVERIFY(QQuickTest::qWaitForItemPolished(myText)); + + baselineOffset = myText->baselineOffset(); + myText->setHeight(myText->height() * 2); + QVERIFY(QQuickTest::qWaitForItemPolished(myText)); + QVERIFY(myText->baselineOffset() > baselineOffset); + + // Check baselineOffset for the HorizontalFit case + myText->setVAlign(QQuickText::AlignBottom); + myText->setFontSizeMode(QQuickText::HorizontalFit); + QVERIFY(QQuickTest::qWaitForItemPolished(myText)); + QSignalSpy baselineOffsetSpy(myText, SIGNAL(baselineOffsetChanged(qreal))); + QVERIFY(QQuickTest::qWaitForItemPolished(myText)); + const qreal oldBaselineOffset = myText->baselineOffset(); + myText->setHeight(myText->height() + 42); + QVERIFY(QQuickTest::qWaitForItemPolished(myText)); + QCOMPARE(baselineOffsetSpy.count(), 1); + QCOMPARE(myText->baselineOffset(), oldBaselineOffset + 42); + myText->setHeight(myText->height() - 42); + QVERIFY(QQuickTest::qWaitForItemPolished(myText)); + QCOMPARE(baselineOffsetSpy.count(), 2); + QCOMPARE(myText->baselineOffset(), oldBaselineOffset); } void tst_qquicktext::fontSizeModeMultiline_data() diff --git a/tests/auto/quick/qquicktextedit/data/threeLines.qml b/tests/auto/quick/qquicktextedit/data/threeLines.qml new file mode 100644 index 0000000000..cee03bfa15 --- /dev/null +++ b/tests/auto/quick/qquicktextedit/data/threeLines.qml @@ -0,0 +1,7 @@ +import QtQuick +import Qt.test 1.0 + +NodeCheckerTextEdit { + width: 200; height: 100 + text: "Line 1\nLine 2\nLine 3\n" +} diff --git a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp index 7cdaf6819e..66d1d29ae6 100644 --- a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp +++ b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp @@ -101,6 +101,8 @@ public: tst_qquicktextedit(); private slots: + void initTestCase() override; + void cleanup(); void text(); void width(); @@ -182,6 +184,7 @@ private slots: void implicitSizeBinding(); void largeTextObservesViewport_data(); void largeTextObservesViewport(); + void renderingAroundSelection(); void signal_editingfinished(); @@ -369,6 +372,56 @@ tst_qquicktextedit::tst_qquicktextedit() // } +class NodeCheckerTextEdit : public QQuickTextEdit +{ +public: + NodeCheckerTextEdit(QQuickItem *parent = nullptr) : QQuickTextEdit(parent) {} + + void populateLinePositions(QSGNode *node) + { + linePositions.clear(); + lastLinePosition = 0; + QSGNode *ch = node->firstChild(); + while (ch != node->lastChild()) { + QCOMPARE(ch->type(), QSGNode::TransformNodeType); + QSGTransformNode *tn = static_cast<QSGTransformNode *>(ch); + int y = 0; + if (!tn->matrix().isIdentity()) + y = tn->matrix().column(3).y(); + if (tn->childCount() == 0) { + // A TransformNode with no children is a waste of memory. + // So far, QQuickTextEdit still creates a couple of extras. + qCDebug(lcTests) << "ignoring leaf TransformNode" << tn << "@ y" << y; + } else { + qCDebug(lcTests) << "child" << tn << "@ y" << y << "has children" << tn->childCount(); + if (!linePositions.contains(y)) { + linePositions.append(y); + lastLinePosition = qMax(lastLinePosition, y); + } + } + ch = ch->nextSibling(); + } + std::sort(linePositions.begin(), linePositions.end()); + } + + QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *data) override + { + QSGNode *ret = QQuickTextEdit::updatePaintNode(node, data); + qCDebug(lcTests) << "updated root node" << ret; + populateLinePositions(ret); + return ret; + } + + QList<int> linePositions; + int lastLinePosition; +}; + +void tst_qquicktextedit::initTestCase() +{ + QQmlDataTest::initTestCase(); + qmlRegisterType<NodeCheckerTextEdit>("Qt.test", 1, 0, "NodeCheckerTextEdit"); +} + void tst_qquicktextedit::cleanup() { // ensure not even skipped tests with custom input context leave it dangling @@ -935,7 +988,7 @@ void tst_qquicktextedit::hAlignVisual() // Try to check whether alignment works by checking the number of black // pixels in the thirds of the grabbed image. - const int windowWidth = 200; + const int windowWidth = view.width(); const int textWidth = qCeil(text->implicitWidth()); QVERIFY2(textWidth < windowWidth, "System font too large."); const int sectionWidth = textWidth / 3; @@ -983,7 +1036,7 @@ void tst_qquicktextedit::hAlignVisual() { // Left Align QImage image = view.grabWindow(); - int x = qCeil(text->implicitWidth()); + int x = qCeil(text->implicitWidth() * view.devicePixelRatio()); int left = numberOfNonWhitePixels(0, x, image); int right = numberOfNonWhitePixels(x, image.width() - x, image); QVERIFY2(left > 0, msgNotGreaterThan(left, 0).constData()); @@ -993,7 +1046,7 @@ void tst_qquicktextedit::hAlignVisual() // HCenter Align text->setHAlign(QQuickText::AlignHCenter); QImage image = view.grabWindow(); - int x1 = qFloor(image.width() - text->implicitWidth()) / 2; + int x1 = qFloor(image.width() - text->implicitWidth() * view.devicePixelRatio()) / 2; int x2 = image.width() - x1; int left = numberOfNonWhitePixels(0, x1, image); int mid = numberOfNonWhitePixels(x1, x2 - x1, image); @@ -1006,7 +1059,7 @@ void tst_qquicktextedit::hAlignVisual() // Right Align text->setHAlign(QQuickText::AlignRight); QImage image = view.grabWindow(); - int x = image.width() - qCeil(text->implicitWidth()); + int x = image.width() - qCeil(text->implicitWidth() * view.devicePixelRatio()); int left = numberOfNonWhitePixels(0, x, image); int right = numberOfNonWhitePixels(x, image.width() - x, image); QCOMPARE(left, 0); @@ -2612,8 +2665,6 @@ void tst_qquicktextedit::linkHover() QCOMPARE(window.cursor().shape(), Qt::IBeamCursor); QCOMPARE(hover.last()[0].toString(), QString()); - texteditObject->setCursor(Qt::OpenHandCursor); - QCursor::setPos(linkPos); QTRY_COMPARE(hover.count(), 3); QCOMPARE(window.cursor().shape(), Qt::PointingHandCursor); @@ -2621,7 +2672,7 @@ void tst_qquicktextedit::linkHover() QCursor::setPos(textPos); QTRY_COMPARE(hover.count(), 4); - QCOMPARE(window.cursor().shape(), Qt::OpenHandCursor); + QCOMPARE(window.cursor().shape(), Qt::IBeamCursor); QCOMPARE(hover.last()[0].toString(), QString()); } #endif @@ -3826,6 +3877,40 @@ void tst_qquicktextedit::largeTextObservesViewport() QCOMPARE(textPriv->cursorItem->isVisible(), textPriv->renderedRegion.intersects(textItem->cursorRectangle())); } +void tst_qquicktextedit::renderingAroundSelection() +{ + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("threeLines.qml"))); + NodeCheckerTextEdit *textItem = qmlobject_cast<NodeCheckerTextEdit*>(window.rootObject()); + QVERIFY(textItem); + QTRY_VERIFY(textItem->linePositions.count() > 0); + const auto linePositions = textItem->linePositions; + const int lastLinePosition = textItem->lastLinePosition; + QQuickTextEditPrivate *textPriv = QQuickTextEditPrivate::get(textItem); + QSignalSpy renderSpy(&window, &QQuickWindow::afterRendering); + + if (lcTests().isDebugEnabled()) + QTest::qWait(500); // for visual check; not needed in CI + + const int renderCount = renderSpy.count(); + QPoint p1 = textItem->mapToScene(textItem->positionToRectangle(8).center()).toPoint(); + QPoint p2 = textItem->mapToScene(textItem->positionToRectangle(10).center()).toPoint(); + qCDebug(lcTests) << "drag from" << p1 << "to" << p2; + QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, p1); + QTest::mouseMove(&window, p2); + QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, p2); + // ensure that QQuickTextEdit::updatePaintNode() has a chance to run + QTRY_VERIFY(renderSpy.count() > renderCount); + + if (lcTests().isDebugEnabled()) + QTest::qWait(500); // for visual check; not needed in CI + + qCDebug(lcTests) << "TextEdit's nodes" << textPriv->textNodeMap; + qCDebug(lcTests) << "font" << textItem->font() << "line positions" << textItem->linePositions << "should be" << linePositions; + QCOMPARE(textItem->lastLinePosition, lastLinePosition); + QTRY_COMPARE(textItem->linePositions, linePositions); +} + void tst_qquicktextedit::signal_editingfinished() { QQuickView *window = new QQuickView(nullptr); diff --git a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp index fdaaaaca5e..438e6ede02 100644 --- a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp +++ b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp @@ -1247,6 +1247,7 @@ void tst_qquickwindow::mouseFromTouch_basic() QCOMPARE(item->lastVelocityFromMouseMove, velocity); // QVERIFY(item->lastMouseCapabilityFlags.testFlag(QInputDevice::Capability::Velocity)); // TODO + QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10); // avoid generating a double-click // Now the same with a transformation. item->setRotation(90); // clockwise QMutableEventPoint::setState(points[0], QEventPoint::State::Pressed); diff --git a/tests/auto/quickcontrols2/controls/data/tst_container.qml b/tests/auto/quickcontrols2/controls/data/tst_container.qml index be2b9a12ba..5fa3137ade 100644 --- a/tests/auto/quickcontrols2/controls/data/tst_container.qml +++ b/tests/auto/quickcontrols2/controls/data/tst_container.qml @@ -169,7 +169,7 @@ TestCase { // don't crash (QTBUG-61310) function test_repeater(data) { - var control = createTemporaryObject(data.component) + var control = createTemporaryObject(data.component, testCase) verify(control) compare(control.itemAt(0).objectName, "0") @@ -213,4 +213,86 @@ TestCase { wait(1) verify(item3) } + + Component { + id: contentItemDeletionOrder1 + + Item { + objectName: "parentItem" + + Item { + id: item + objectName: "contentItem" + } + Container { + objectName: "control" + contentItem: item + } + } + } + + Component { + id: contentItemDeletionOrder2 + + Item { + objectName: "parentItem" + + Container { + objectName: "control" + contentItem: item + } + Item { + id: item + objectName: "contentItem" + } + } + } + + function test_contentItemDeletionOrder() { + var control1 = createTemporaryObject(contentItemDeletionOrder1, testCase) + verify(control1) + var control2 = createTemporaryObject(contentItemDeletionOrder2, testCase) + verify(control2) + } + + Component { + id: backgroundDeletionOrder1 + + Item { + objectName: "parentItem" + + Item { + id: item + objectName: "backgroundItem" + } + Container { + objectName: "control" + background: item + } + } + } + + Component { + id: backgroundDeletionOrder2 + + Item { + objectName: "parentItem" + + Container { + objectName: "control" + background: item + } + Item { + id: item + objectName: "backgroundItem" + } + } + } + + function test_backgroundDeletionOrder() { + var control1 = createTemporaryObject(backgroundDeletionOrder1, testCase) + verify(control1) + var control2 = createTemporaryObject(backgroundDeletionOrder2, testCase) + verify(control2) + } } diff --git a/tests/auto/quickcontrols2/controls/data/tst_control.qml b/tests/auto/quickcontrols2/controls/data/tst_control.qml index 4afa719ef6..e12cbbf4ef 100644 --- a/tests/auto/quickcontrols2/controls/data/tst_control.qml +++ b/tests/auto/quickcontrols2/controls/data/tst_control.qml @@ -1414,4 +1414,86 @@ TestCase { compare(control.background.width, 100) compare(control.background.height, 100) } + + Component { + id: contentItemDeletionOrder1 + + Item { + objectName: "parentItem" + + Item { + id: item + objectName: "contentItem" + } + Control { + objectName: "control" + contentItem: item + } + } + } + + Component { + id: contentItemDeletionOrder2 + + Item { + objectName: "parentItem" + + Control { + objectName: "control" + contentItem: item + } + Item { + id: item + objectName: "contentItem" + } + } + } + + function test_contentItemDeletionOrder() { + var control1 = createTemporaryObject(contentItemDeletionOrder1, testCase) + verify(control1) + var control2 = createTemporaryObject(contentItemDeletionOrder2, testCase) + verify(control2) + } + + Component { + id: backgroundDeletionOrder1 + + Item { + objectName: "parentItem" + + Item { + id: item + objectName: "backgroundItem" + } + Control { + objectName: "control" + background: item + } + } + } + + Component { + id: backgroundDeletionOrder2 + + Item { + objectName: "parentItem" + + Control { + objectName: "control" + background: item + } + Item { + id: item + objectName: "backgroundItem" + } + } + } + + function test_backgroundDeletionOrder() { + var control1 = createTemporaryObject(backgroundDeletionOrder1, testCase) + verify(control1) + var control2 = createTemporaryObject(backgroundDeletionOrder2, testCase) + verify(control2) + } } diff --git a/tests/auto/quickcontrols2/controls/data/tst_dialogbuttonbox.qml b/tests/auto/quickcontrols2/controls/data/tst_dialogbuttonbox.qml index 7d4c5d9e4d..57216ed11c 100644 --- a/tests/auto/quickcontrols2/controls/data/tst_dialogbuttonbox.qml +++ b/tests/auto/quickcontrols2/controls/data/tst_dialogbuttonbox.qml @@ -528,4 +528,86 @@ TestCase { control.destroy() } } + + Component { + id: contentItemDeletionOrder1 + + Item { + objectName: "parentItem" + + Item { + id: item + objectName: "contentItem" + } + DialogButtonBox { + objectName: "control" + contentItem: item + } + } + } + + Component { + id: contentItemDeletionOrder2 + + Item { + objectName: "parentItem" + + DialogButtonBox { + objectName: "control" + contentItem: item + } + Item { + id: item + objectName: "contentItem" + } + } + } + + function test_contentItemDeletionOrder() { + var control1 = createTemporaryObject(contentItemDeletionOrder1, testCase) + verify(control1) + var control2 = createTemporaryObject(contentItemDeletionOrder2, testCase) + verify(control2) + } + + Component { + id: backgroundDeletionOrder1 + + Item { + objectName: "parentItem" + + Item { + id: item + objectName: "backgroundItem" + } + DialogButtonBox { + objectName: "control" + background: item + } + } + } + + Component { + id: backgroundDeletionOrder2 + + Item { + objectName: "parentItem" + + DialogButtonBox { + objectName: "control" + background: item + } + Item { + id: item + objectName: "backgroundItem" + } + } + } + + function test_backgroundDeletionOrder() { + var control1 = createTemporaryObject(backgroundDeletionOrder1, testCase) + verify(control1) + var control2 = createTemporaryObject(backgroundDeletionOrder2, testCase) + verify(control2) + } } diff --git a/tests/auto/quickcontrols2/controls/data/tst_popup.qml b/tests/auto/quickcontrols2/controls/data/tst_popup.qml index 0d2ec72ee2..ddf3150877 100644 --- a/tests/auto/quickcontrols2/controls/data/tst_popup.qml +++ b/tests/auto/quickcontrols2/controls/data/tst_popup.qml @@ -1004,45 +1004,45 @@ TestCase { control.contentItem.implicitWidth = 10 compare(control.implicitWidth, 10 + control.leftPadding + control.rightPadding) compare(control.width, control.implicitWidth) - compare(control.contentItem.width, control.width - control.leftPadding - control.rightPadding) + compare(control.contentItem.width, control.availableWidth) control.contentItem.implicitHeight = 20 compare(control.implicitHeight, 20 + control.topPadding + control.bottomPadding) compare(control.height, control.implicitHeight) - compare(control.contentItem.height, control.height - control.topPadding - control.bottomPadding) + compare(control.contentItem.height, control.availableHeight) // implicit size of the popup control.implicitWidth = 30 compare(control.implicitWidth, 30) compare(control.width, 30) - compare(control.contentItem.width, control.width - control.leftPadding - control.rightPadding) + compare(control.contentItem.width, control.availableWidth) control.implicitHeight = 40 compare(control.implicitHeight, 40) compare(control.height, 40) - compare(control.contentItem.height, control.height - control.topPadding - control.bottomPadding) + compare(control.contentItem.height, control.availableHeight) // set explicit size control.width = 50 compare(control.implicitWidth, 30) compare(control.width, 50) - compare(control.contentItem.width, control.width - control.leftPadding - control.rightPadding) + compare(control.contentItem.width, control.availableWidth) control.height = 60 compare(control.implicitHeight, 40) compare(control.height, 60) - compare(control.contentItem.height, control.height - control.topPadding - control.bottomPadding) + compare(control.contentItem.height, control.availableHeight) // reset explicit size control.width = undefined compare(control.implicitWidth, 30) compare(control.width, 30) - compare(control.contentItem.width, control.width - control.leftPadding - control.rightPadding) + compare(control.contentItem.width, control.availableWidth) control.height = undefined compare(control.implicitHeight, 40) compare(control.height, 40) - compare(control.contentItem.height, control.height - control.topPadding - control.bottomPadding) + compare(control.contentItem.height, control.availableHeight) } function test_visible() { diff --git a/tests/auto/quickcontrols2/controls/data/tst_scrollview.qml b/tests/auto/quickcontrols2/controls/data/tst_scrollview.qml index 6e09c65e70..dc9eff0456 100644 --- a/tests/auto/quickcontrols2/controls/data/tst_scrollview.qml +++ b/tests/auto/quickcontrols2/controls/data/tst_scrollview.qml @@ -61,7 +61,7 @@ TestCase { name: "ScrollView" Component { - id: signalSpy + id: signalSpyComponent SignalSpy { } } @@ -581,4 +581,95 @@ TestCase { verify(newHorizontalScrollBar.visible) verify(!oldHorizontalScrollBar.visible) } + + Component { + id: mouseAreaWheelComponent + + MouseArea { + anchors.fill: parent + + property alias scrollView: scrollView + property alias flickable: flickable + + ScrollView { + id: scrollView + anchors.fill: parent + wheelEnabled: false + + Flickable { + id: flickable + contentHeight: 1000 + + Text { + text: "Test" + width: 500 + height: 1000 + } + } + } + } + } + + // If a ScrollView containing a Flickable sets wheelEnabled to false, + // neither item should consume wheel events. + function test_wheelEnabled() { + let mouseArea = createTemporaryObject(mouseAreaWheelComponent, testCase) + verify(mouseArea) + + let mouseWheelSpy = signalSpyComponent.createObject(mouseArea, + { target: mouseArea, signalName: "wheel" }) + verify(mouseWheelSpy.valid) + + let scrollView = mouseArea.scrollView + mouseWheel(scrollView, scrollView.width / 2, scrollView.height / 2, 0, 120) + compare(mouseWheelSpy.count, 1) + compare(mouseArea.flickable.contentY, 0) + } + + Component { + id: bindingToContentItemAndStandaloneFlickable + + Item { + width: 200 + height: 200 + + property alias scrollView: scrollView + + ScrollView { + id: scrollView + anchors.fill: parent + contentItem: listView + + property Item someBinding: contentItem + } + ListView { + id: listView + model: 10 + delegate: ItemDelegate { + text: modelData + width: listView.width + } + } + } + } + + // Tests that scroll bars show up for a ScrollView where + // - its contentItem is declared as a standalone, separate item + // - there is a binding to contentItem (which causes a default Flickable to be created) + function test_bindingToContentItemAndStandaloneFlickable() { + let root = createTemporaryObject(bindingToContentItemAndStandaloneFlickable, testCase) + verify(root) + + let control = root.scrollView + let verticalScrollBar = control.ScrollBar.vertical + let horizontalScrollBar = control.ScrollBar.horizontal + compare(verticalScrollBar.parent, control) + compare(horizontalScrollBar.parent, control) + verify(verticalScrollBar.visible) + verify(horizontalScrollBar.visible) + + mouseDrag(verticalScrollBar, verticalScrollBar.width / 2, verticalScrollBar.height / 2, 0, 50) + verify(verticalScrollBar.active) + verify(horizontalScrollBar.active) + } } diff --git a/tests/auto/quickcontrols2/controls/data/tst_selectionrectangle.qml b/tests/auto/quickcontrols2/controls/data/tst_selectionrectangle.qml index 437e8ec7f2..210c6594cd 100644 --- a/tests/auto/quickcontrols2/controls/data/tst_selectionrectangle.qml +++ b/tests/auto/quickcontrols2/controls/data/tst_selectionrectangle.qml @@ -337,7 +337,7 @@ TestCase { } // Drag on the bottom right handle, so that the selection shrinks to cell 1, 1 - mouseDrag(tableView, cellWidth * 2, cellHeight * 2, -cellWidth / 2, -cellHeight / 2, Qt.LeftButton) + mouseDrag(tableView, (cellWidth * 3) - 1, (cellHeight * 3) - 1, -cellWidth, -cellHeight, Qt.LeftButton) compare(tableView.selectionModel.selectedIndexes.length, 1) verify(tableView.selectionModel.isSelected(tableView.model.index(1, 1))) } diff --git a/tests/auto/quickcontrols2/controls/data/tst_splitview.qml b/tests/auto/quickcontrols2/controls/data/tst_splitview.qml index b9ace27ee3..6004f9eb44 100644 --- a/tests/auto/quickcontrols2/controls/data/tst_splitview.qml +++ b/tests/auto/quickcontrols2/controls/data/tst_splitview.qml @@ -1883,6 +1883,436 @@ TestCase { verify(firstItem.height > firstItemOriginalHeight) } + Component { + id: splitViewHandleContainmentMaskComponent + + MouseArea { + property alias mouseArea1: mouseArea1 + property alias mouseArea2: mouseArea2 + property alias splitView: splitView + + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton + + Rectangle { + anchors.fill: parent + color: 'green' + opacity: 0.3 + } + + SplitView { + id: splitView + + anchors { + fill: parent + margins: 100 + } + + handle: Rectangle { + id: handleRoot + + readonly property bool containsMouse: SplitHandle.hovered + readonly property int defaultSize: 2 + + implicitWidth: splitView.orientation === Qt.Horizontal ? handleRoot.defaultSize : splitView.width + implicitHeight: splitView.orientation === Qt.Vertical ? handleRoot.defaultSize : splitView.height + + color: 'red' + objectName: "handle" + + Text { + objectName: "handleText_" + text + text: parent.x + "," + parent.y + " " + parent.width + "x" + parent.height + color: "black" + anchors.centerIn: parent + rotation: 90 + } + + containmentMask: Item { + readonly property real extraOverflow: 20 + + x: splitView.orientation === Qt.Horizontal ? -extraOverflow : 0 + y: splitView.orientation === Qt.Horizontal ? 0 : -extraOverflow + width: splitView.orientation === Qt.Horizontal ? handleRoot.defaultSize + (extraOverflow * 2): handleRoot.width + height: splitView.orientation === Qt.Horizontal ? handleRoot.height : handleRoot.defaultSize + (extraOverflow * 2) + } + } + + MouseArea { + id: mouseArea1 + + SplitView.fillHeight: splitView.orientation === Qt.Horizontal + SplitView.fillWidth: splitView.orientation === Qt.Vertical + SplitView.preferredWidth: splitView.orientation === Qt.Horizontal ? parent.width / 2 : undefined + SplitView.preferredHeight: splitView.orientation === Qt.Vertical ? parent.height / 2 : undefined + hoverEnabled: true + + Rectangle { + anchors.fill: parent + color: 'cyan' + opacity: 0.3 + } + } + + MouseArea { + id: mouseArea2 + + SplitView.fillHeight: splitView.orientation === Qt.Horizontal + SplitView.fillWidth: splitView.orientation === Qt.Vertical + SplitView.preferredWidth: splitView.orientation === Qt.Horizontal ? parent.width / 2 : undefined + SplitView.preferredHeight: splitView.orientation === Qt.Vertical ? parent.height / 2 : undefined + hoverEnabled: true + + Rectangle { + anchors.fill: parent + color: 'cyan' + opacity: 0.3 + } + } + } + } + } + + function test_handleContainmentMask_data() { + const data = [ + { + tag: "handleContainmentMaskHorizontalLeftEdgeDragRight", + orientation: Qt.Horizontal, + press: { + x: (handle) => handle.containmentMask.x, + y: (handle) => handle.height / 2 + }, + dx: 25, + dy: 0, + }, + { + tag: "handleContainmentMaskHorizontalRightEdgeDragLeft", + orientation: Qt.Horizontal, + press: { + x: (handle) => handle.containmentMask.x, + y: (handle) => handle.height / 2 + }, + dx: -50, + dy: 0 + }, + { + tag: "handleContainmentMaskHorizontalTopEdgeDragRight", + orientation: Qt.Horizontal, + press: { + x: (handle) => handle.containmentMask.x + handle.containmentMask.width - 1, + y: (handle) => handle.height / 2 + }, + dx: 25, + dy: 0, + }, + { + tag: "handleContainmentMaskHorizontalBottomEdgeDragLeft", + orientation: Qt.Horizontal, + press: { + x: (handle) => handle.containmentMask.x + handle.containmentMask.width - 1, + y: (handle) => handle.containmentMask.y + }, + dx: -50, + dy: 0 + }, + { + tag: "handleContainmentMaskVerticalTopEdgeDragUp", + orientation: Qt.Vertical, + press: { + x: (handle) => handle.width / 2, + y: (handle) => handle.containmentMask.y + }, + dx: 0, + dy: -40, + }, + { + tag: "handleContainmentMaskVerticalTopEdgeDragDown", + orientation: Qt.Vertical, + press: { + x: (handle) => handle.width / 2, + y: (handle) => handle.containmentMask.y + }, + dx: 0, + dy: 70 + }, + { + tag: "handleContainmentMaskVerticalBottomEdgeDragUp", + orientation: Qt.Vertical, + press: { + x: (handle) => handle.width / 2, + y: (handle) => handle.containmentMask.y + handle.containmentMask.height - 1 + }, + dx: 0, + dy: -40, + }, + { + tag: "handleContainmentMaskVerticalBottomEdgeDragDown", + orientation: Qt.Vertical, + press: { + x: (handle) => handle.width / 2, + y: (handle) => handle.containmentMask.y + handle.containmentMask.height - 1 + }, + dx: 0, + dy: 70 + } + ] + + return data + } + + function test_handleContainmentMask(data) { + const control = createTemporaryObject(splitViewHandleContainmentMaskComponent, testCase) + verify(control) + const splitView = control.splitView + splitView.orientation = data.orientation + + const handle = findHandles(splitView)[0] + if (splitView.orientation === Qt.Vertical) + handle.height = handle.defaultHeight + + verify(isPolishScheduled(splitView)) + verify(waitForItemPolished(splitView)) + + const firstItem = control.mouseArea1 + const secondItem = control.mouseArea2 + + const backgroundMouseAreaPress = signalSpyComponent.createObject(control, + { target: control, signalName: "onPressed" }) + + const mouseArea1Press = signalSpyComponent.createObject(firstItem, + { target: firstItem, signalName: "onPressed" }) + + const mouseArea2Press = signalSpyComponent.createObject(secondItem, + { target: secondItem, signalName: "onPressed" }) + + verify(backgroundMouseAreaPress.valid) + verify(mouseArea1Press.valid) + verify(mouseArea2Press.valid) + + const firstItemWidthBeforeDrag = firstItem.width + const secondItemWidthBeforeDrag = secondItem.width + const firstItemHeightBeforeDrag = firstItem.height + const secondItemHeightBeforeDrag = secondItem.height + + const dx = data.dx + const dy = data.dy + const pressX = data.press.x(handle) + const pressY = data.press.y(handle) + + mousePress(handle, pressX, pressY, Qt.LeftButton) + mouseMove(handle, pressX + dx, pressY + dy, -1, Qt.LeftButton) + mouseRelease(handle, pressX + dx, pressY + dy, Qt.LeftButton) + + compare(firstItem.width, firstItemWidthBeforeDrag + dx) + compare(secondItem.width, secondItemWidthBeforeDrag - dx) + compare(firstItem.height, firstItemHeightBeforeDrag + dy) + compare(secondItem.height, secondItemHeightBeforeDrag - dy) + + compare(backgroundMouseAreaPress.count, 0) + compare(mouseArea1Press.count, 0) + compare(mouseArea2Press.count, 0) + } + + function test_handleContainmentMaskHovered_data() { + const data = [ + { + tag: "firstItemHorizontalHover", + orientation: Qt.Horizontal, + press: { + "x": (item) => item.width / 2, + "y": (item) => item.height / 2 + }, + hoverItem: "firstItem", + expectedHover: { + "firstItem": true, + "handle": false, + "secondItem": false + } + }, + { + tag: "handleHorizontalHoverOnTheLeft", + orientation: Qt.Horizontal, + press: { + "x": (item) => item.containmentMask.x, + "y": (item) => item.height / 2 + }, + hoverItem: "handle", + expectedHover: { + "firstItem": true, + "handle": true, + "secondItem": false + } + }, + { + tag: "handleHorizontalHoverOnTheCenter", + orientation: Qt.Horizontal, + press: { + "x": (item) => item.width / 2, + "y": (item) => item.height / 2 + }, + hoverItem: "handle", + expectedHover: { + "firstItem": false, + "handle": true, + "secondItem": false + } + }, + { + tag: "handleHorizontalHoverOnTheRight", + orientation: Qt.Horizontal, + press: { + "x": (item) => item.containmentMask.x + item.containmentMask.width - 1, + "y": (item) => item.height / 2 + }, + hoverItem: "handle", + expectedHover: { + "firstItem": false, + "handle": true, + "secondItem": true + } + }, + { + tag: "secondItemHorizontalHover", + orientation: Qt.Horizontal, + press: { + "x": (item) => item.width / 2, + "y": (item) => item.height / 2 + }, + hoverItem: "secondItem", + expectedHover: { + "firstItem": false, + "handle": false, + "secondItem": true + } + }, + { + tag: "firstItemVerticalHover", + orientation: Qt.Vertical, + press: { + "x": (item) => item.width / 2, + "y": (item) => item.height / 2 + }, + hoverItem: "firstItem", + expectedHover: { + "firstItem": true, + "handle": false, + "secondItem": false + } + }, + { + tag: "handleVerticalHoverOnTheTop", + orientation: Qt.Vertical, + press: { + "x": (item) => item.width / 2, + "y": (item) => item.containmentMask.y + }, + hoverItem: "handle", + expectedHover: { + "firstItem": true, + "handle": true, + "secondItem": false + } + }, + { + tag: "handleVerticalHoverOnTheCenter", + orientation: Qt.Vertical, + press: { + "x": (item) => item.width / 2, + "y": (item) => item.height / 2 + }, + hoverItem: "handle", + expectedHover: { + "firstItem": false, + "handle": true, + "secondItem": false + } + }, + { + tag: "handleVerticalHoverOnTheBottom", + orientation: Qt.Vertical, + press: { + "x": (item) => item.width / 2, + "y": (item) => item.containmentMask.y + item.containmentMask.height - 1 + }, + hoverItem: "handle", + expectedHover: { + "firstItem": false, + "handle": true, + "secondItem": true + } + }, + { + tag: "secondItemVerticalHover", + orientation: Qt.Vertical, + press: { + "x": (item) => item.width / 2, + "y": (item) => item.height / 2 + }, + hoverItem: "secondItem", + expectedHover: { + "firstItem": false, + "handle": false, + "secondItem": true + } + } + ] + + return data + } + + function test_handleContainmentMaskHovered(data) { + if ((Qt.platform.pluginName === "offscreen") || (Qt.platform.pluginName === "minimal")) + skip("Mouse hovering not functional on offscreen/minimal platforms") + + const control = createTemporaryObject(splitViewHandleContainmentMaskComponent, testCase) + verify(control) + const splitView = control.splitView + splitView.orientation = data.orientation + + const handle = findHandles(splitView)[0] + if (splitView.orientation === Qt.Vertical) + handle.height = handle.defaultHeight + + verify(isPolishScheduled(splitView)) + verify(waitForItemPolished(splitView)) + + const firstItem = control.mouseArea1 + const secondItem = control.mouseArea2 + + verify(!firstItem.containsMouse) + verify(!secondItem.containsMouse) + verify(!handle.containsMouse) + + const actualItem = { + "firstItem": firstItem, + "secondItem": secondItem, + "handle": handle + }[data.hoverItem] + + const pressX = data.press.x(actualItem) + const pressY = data.press.y(actualItem) + + // Test fails if we don't do two moves for some reason... + mouseMove(actualItem, pressX, pressY) + mouseMove(actualItem, pressX, pressY) + + compare(firstItem.containsMouse, data.expectedHover.firstItem) + compare(secondItem.containsMouse, data.expectedHover.secondItem) + compare(handle.containsMouse, data.expectedHover.handle) + + // Hide SplitView, then all children shouldn't be hovered + control.visible = false + + verify(isPolishScheduled(splitView)) + verify(waitForItemPolished(splitView)) + + verify(!control.containsMouse) + verify(!firstItem.containsMouse) + verify(!secondItem.containsMouse) + verify(!handle.containsMouse) + } + function test_hoveredPressed() { if ((Qt.platform.pluginName === "offscreen") || (Qt.platform.pluginName === "minimal")) skip("Mouse hovering not functional on offscreen/minimal platforms") diff --git a/tests/auto/quickcontrols2/controls/data/tst_stackview.qml b/tests/auto/quickcontrols2/controls/data/tst_stackview.qml index 72450ebb49..e1b8fcb79c 100644 --- a/tests/auto/quickcontrols2/controls/data/tst_stackview.qml +++ b/tests/auto/quickcontrols2/controls/data/tst_stackview.qml @@ -1558,4 +1558,37 @@ TestCase { verify(control.currentItem.i === 42) control.pop(StackView.Immediate) } + + // QTBUG-104491 + // Tests that correctly set a busy state when the transition is stolen(canceled) + function test_continuousTransition() { + let redRect = createTemporaryObject(rectangleComponent, testCase, { color: "red" }) + verify(redRect) + let blueRect = createTemporaryObject(rectangleComponent, testCase, { color: "blue" }) + verify(blueRect) + let greenRect = createTemporaryObject(rectangleComponent, testCase, { color: "green" }) + verify(greenRect) + let yellowRect = createTemporaryObject(rectangleComponent, testCase, { color: "yellow" }) + verify(yellowRect) + let control = createTemporaryObject(qtbug96966_stackViewComponent, testCase, + { "anchors.fill": testCase, initialItem: redRect }) + verify(control) + + control.push(blueRect) + control.pop() + tryCompare(control, "busy", true) + tryCompare(control, "busy", false) + + control.push(blueRect) + control.push(greenRect) + control.push(yellowRect) + tryCompare(control, "busy", true) + tryCompare(control, "busy", false) + + control.pop() + control.pop() + control.pop() + tryCompare(control, "busy", true) + tryCompare(control, "busy", false) + } } diff --git a/tests/auto/quickcontrols2/controls/data/tst_tooltip.qml b/tests/auto/quickcontrols2/controls/data/tst_tooltip.qml index 3b3e0b43c1..89e179575f 100644 --- a/tests/auto/quickcontrols2/controls/data/tst_tooltip.qml +++ b/tests/auto/quickcontrols2/controls/data/tst_tooltip.qml @@ -296,7 +296,13 @@ TestCase { function test_activateShortcutWhileToolTipVisible() { if ((Qt.platform.pluginName === "offscreen") || (Qt.platform.pluginName === "minimal")) - skip("Mouse hoovering not functional on offscreen/minimal platforms") + skip("Mouse hovering not functional on offscreen/minimal platforms") + + // Window shortcuts (the default context for Shortcut) require the window to have focus. + var window = testCase.Window.window + verify(window) + window.requestActivate() + tryCompare(window, "active", true) var root = createTemporaryObject(buttonAndShortcutComponent, testCase) verify(root) diff --git a/tests/auto/quickcontrols2/cursor/tst_cursor.cpp b/tests/auto/quickcontrols2/cursor/tst_cursor.cpp index b3a44fa061..afc76a9a68 100644 --- a/tests/auto/quickcontrols2/cursor/tst_cursor.cpp +++ b/tests/auto/quickcontrols2/cursor/tst_cursor.cpp @@ -52,12 +52,13 @@ public: tst_cursor(); private slots: - void init(); + void init() override; void controls_data(); void controls(); void editable(); void pageIndicator(); void scrollBar(); + void textArea(); }; tst_cursor::tst_cursor() @@ -68,6 +69,8 @@ tst_cursor::tst_cursor() void tst_cursor::init() { + QQmlDataTest::init(); + #if QT_CONFIG(cursor) // Ensure mouse cursor was not left by previous tests where widgets // will appear, as it could cause events and interfere with the tests. @@ -225,6 +228,19 @@ void tst_cursor::scrollBar() QCOMPARE(window->cursor().shape(), textArea->cursor().shape()); } +// QTBUG-104089 +void tst_cursor::textArea() +{ + QQuickTextArea textArea; + QCOMPARE(textArea.cursor().shape(), Qt::IBeamCursor); + + textArea.setReadOnly(true); + QCOMPARE(textArea.cursor().shape(), Qt::ArrowCursor); + + textArea.setSelectByMouse(true); + QCOMPARE(textArea.cursor().shape(), Qt::IBeamCursor); +} + QTEST_MAIN(tst_cursor) #include "tst_cursor.moc" diff --git a/tests/auto/quickcontrols2/customization/tst_customization.cpp b/tests/auto/quickcontrols2/customization/tst_customization.cpp index b1fa3ea6a3..aa1c97b3e6 100644 --- a/tests/auto/quickcontrols2/customization/tst_customization.cpp +++ b/tests/auto/quickcontrols2/customization/tst_customization.cpp @@ -112,7 +112,7 @@ private slots: void initTestCase() override; void cleanupTestCase(); - void init(); + void init() override; void cleanup(); void creation_data(); @@ -208,8 +208,11 @@ extern "C" Q_DECL_EXPORT void qt_removeQObject(QObject *object) } } +// We don't want to fail on warnings until QTBUG-98964 is fixed, +// as we deliberately prevent deferred execution in some of the tests here, +// which causes warnings. tst_customization::tst_customization() - : QQmlDataTest(QT_QMLTEST_DATADIR) + : QQmlDataTest(QT_QMLTEST_DATADIR, FailOnWarningsPolicy::DoNotFailOnWarnings) { } @@ -228,6 +231,8 @@ void tst_customization::cleanupTestCase() void tst_customization::init() { + QQmlDataTest::init(); + engine = new QQmlEngine(this); engine->addImportPath(testFile("styles")); diff --git a/tests/auto/quickcontrols2/platform/CMakeLists.txt b/tests/auto/quickcontrols2/platform/CMakeLists.txt index 6633de5c02..8e6df7d2b4 100644 --- a/tests/auto/quickcontrols2/platform/CMakeLists.txt +++ b/tests/auto/quickcontrols2/platform/CMakeLists.txt @@ -17,6 +17,7 @@ qt_internal_add_test(tst_platform tst_platform.cpp PUBLIC_LIBRARIES Qt::Gui + Qt::Qml TESTDATA ${test_data} ) diff --git a/tests/auto/quickcontrols2/platform/data/tst_menu.qml b/tests/auto/quickcontrols2/platform/data/tst_menu.qml index 7e38922255..aedf6529d7 100644 --- a/tests/auto/quickcontrols2/platform/data/tst_menu.qml +++ b/tests/auto/quickcontrols2/platform/data/tst_menu.qml @@ -51,6 +51,8 @@ import QtQuick import QtTest import Qt.labs.platform +import QtQuick.Controls as Controls +import org.qtproject.Test TestCase { id: testCase @@ -75,6 +77,11 @@ TestCase { signalName: "itemsChanged" } + Component { + id: signalSpyComponent + SignalSpy {} + } + function init() { verify(!itemsSpy.target) compare(itemsSpy.count, 0) @@ -261,4 +268,68 @@ TestCase { compare(subMenu.title, "Title") compare(subMenuItem.text, "Title") } + + Component { + id: disabledMenuItemAndActionComponent + + Item { + property alias action: action + property alias menu: menu + + Controls.Action { + id: action + shortcut: StandardKey.Copy + } + + Menu { + id: menu + + MenuItem { + enabled: !action.enabled + shortcut: StandardKey.Copy + text: "test" + } + } + } + } + + function test_shortcuts() { + if (!TestHelper.shortcutsSupported) + skip("This test requires shortcut support") + + let root = createTemporaryObject(disabledMenuItemAndActionComponent, testCase) + verify(root) + let menu = root.menu + let menuItem = menu.items[0] + verify(menuItem) + let action = root.action + + let actionTriggeredSpy = signalSpyComponent.createObject(root, + { target: action, signalName: "triggered" }) + verify(actionTriggeredSpy.valid) + let menuItemTriggeredSpy = signalSpyComponent.createObject(root, + { target: menuItem, signalName: "triggered" }) + verify(menuItemTriggeredSpy.valid) + + // Perform the shortcut; the Action should be triggered since the MenuItem is disabled. + keySequence(StandardKey.Copy) + compare(actionTriggeredSpy.count, 1) + compare(menuItemTriggeredSpy.count, 0) + + // Disable the Action, enabling the MenuItem in the process. + action.enabled = false + verify(menuItem.enabled) + // Perform the shortcut; the MenuItem should be triggered since the Action is disabled. + keySequence(StandardKey.Copy) + compare(actionTriggeredSpy.count, 1) + compare(menuItemTriggeredSpy.count, 1) + + // Re-enable the Action, disabling the MenuItem in the process. + action.enabled = true + verify(!menuItem.enabled) + // Perform the shortcut; the Action should be triggered since the MenuItem is disabled. + keySequence(StandardKey.Copy) + compare(actionTriggeredSpy.count, 2) + compare(menuItemTriggeredSpy.count, 1) + } } diff --git a/tests/auto/quickcontrols2/platform/tst_platform.cpp b/tests/auto/quickcontrols2/platform/tst_platform.cpp index b67042e2f9..bd4db00cd6 100644 --- a/tests/auto/quickcontrols2/platform/tst_platform.cpp +++ b/tests/auto/quickcontrols2/platform/tst_platform.cpp @@ -26,5 +26,31 @@ ** ****************************************************************************/ +#include <QtQml/qqmlengine.h> #include <QtQuickTest/quicktest.h> -QUICK_TEST_MAIN(tst_platform) + +class Setup : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool shortcutsSupported READ areShortcutsSupported CONSTANT FINAL) + +public: + bool areShortcutsSupported() const + { +#if QT_CONFIG(shortcut) + return true; +#else + return false; +#endif + } + +public slots: + void qmlEngineAvailable(QQmlEngine *) + { + qmlRegisterSingletonInstance("org.qtproject.Test", 1, 0, "TestHelper", this); + } +}; + +QUICK_TEST_MAIN_WITH_SETUP(tst_platform, Setup) + +#include "tst_platform.moc" diff --git a/tests/auto/quickcontrols2/qquickdrawer/tst_qquickdrawer.cpp b/tests/auto/quickcontrols2/qquickdrawer/tst_qquickdrawer.cpp index b3a5b9da33..d2593ef2e4 100644 --- a/tests/auto/quickcontrols2/qquickdrawer/tst_qquickdrawer.cpp +++ b/tests/auto/quickcontrols2/qquickdrawer/tst_qquickdrawer.cpp @@ -34,6 +34,8 @@ #include <QtGui/qstylehints.h> #include <QtGui/qguiapplication.h> #include <QtGui/qpa/qwindowsysteminterface.h> +#include <QtGui/qpa/qplatformintegration.h> +#include <QtGui/private/qguiapplication_p.h> #include <QtQuick/private/qquickwindow_p.h> #include <QtQuick/private/qquickflickable_p.h> #include <QtQuickTestUtils/private/qmlutils_p.h> @@ -356,8 +358,7 @@ void tst_QQuickDrawer::position() QQuickApplicationWindow *window = helper.appWindow; window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickDrawer *drawer = helper.appWindow->property("drawer").value<QQuickDrawer*>(); QVERIFY(drawer); @@ -404,8 +405,7 @@ void tst_QQuickDrawer::dragMargin() QQuickApplicationWindow *window = helper.appWindow; window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickDrawer *drawer = helper.appWindow->property("drawer").value<QQuickDrawer*>(); QVERIFY(drawer); @@ -552,7 +552,7 @@ void tst_QQuickDrawer::dragHandlerInteraction() auto window = helper.appWindow;; QVERIFY(window); window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QTest::mousePress(window, Qt::LeftButton, Qt::KeyboardModifiers(), QPoint(250, 250)); QTest::mouseMove(window, QPoint(100, 100)); QTest::mouseRelease(window, Qt::LeftButton, Qt::KeyboardModifiers(), QPoint(100, 100)); @@ -579,8 +579,7 @@ void tst_QQuickDrawer::hover() QVERIFY2(helper.ready, helper.failureMessage()); QQuickWindow *window = helper.window; window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickDrawer *drawer = window->property("drawer").value<QQuickDrawer*>(); QVERIFY(drawer); @@ -906,7 +905,7 @@ void tst_QQuickDrawer::multiTouch() QVERIFY2(helper.ready, helper.failureMessage()); QQuickWindow *window = helper.window; window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickOverlay *overlay = QQuickOverlay::overlay(window); QVERIFY(overlay); @@ -1060,6 +1059,9 @@ void tst_QQuickDrawer::interactive_data() void tst_QQuickDrawer::interactive() { + if (!(QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))) + QSKIP("Window activation is not supported"); + QFETCH(QString, source); QQuickControlsApplicationHelper helper(this, source); QVERIFY2(helper.ready, helper.failureMessage()); @@ -1185,7 +1187,7 @@ void tst_QQuickDrawer::dragOverModalShadow() QVERIFY2(helper.ready, helper.failureMessage()); QQuickWindow *window = helper.window; window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickDrawer *drawer = window->property("drawer").value<QQuickDrawer *>(); QVERIFY(drawer); @@ -1242,7 +1244,7 @@ void tst_QQuickDrawer::nonModal() QVERIFY2(helper.ready, helper.failureMessage()); QQuickWindow *window = helper.window; window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickDrawer *drawer = window->property("drawer").value<QQuickDrawer *>(); QVERIFY(drawer); @@ -1326,7 +1328,7 @@ void tst_QQuickDrawer::slider() QVERIFY2(helper.ready, helper.failureMessage()); QQuickWindow *window = helper.window; window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickDrawer *drawer = window->property("drawer").value<QQuickDrawer *>(); QVERIFY(drawer); @@ -1374,7 +1376,7 @@ void tst_QQuickDrawer::topEdgeScreenEdge() QVERIFY2(helper.ready, helper.failureMessage()); QQuickWindow *window = helper.window; window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickDrawer *drawer = window->property("drawer").value<QQuickDrawer *>(); QVERIFY(drawer); diff --git a/tests/auto/quickcontrols2/qquickheaderview/tst_qquickheaderview.cpp b/tests/auto/quickcontrols2/qquickheaderview/tst_qquickheaderview.cpp index 62593f60cc..82187752f6 100644 --- a/tests/auto/quickcontrols2/qquickheaderview/tst_qquickheaderview.cpp +++ b/tests/auto/quickcontrols2/qquickheaderview/tst_qquickheaderview.cpp @@ -211,7 +211,7 @@ public: private slots: void initTestCase() override; void cleanupTestCase(); - void init(); + void init() override; void cleanup(); void defaults(); @@ -254,6 +254,8 @@ void tst_QQuickHeaderView::cleanupTestCase() void tst_QQuickHeaderView::init() { + QQmlDataTest::init(); + engine = new QQmlEngine(this); } diff --git a/tests/auto/quickcontrols2/qquickmenu/BLACKLIST b/tests/auto/quickcontrols2/qquickmenu/BLACKLIST index 8f69450b4f..9e2954b3f5 100644 --- a/tests/auto/quickcontrols2/qquickmenu/BLACKLIST +++ b/tests/auto/quickcontrols2/qquickmenu/BLACKLIST @@ -1,10 +1,3 @@ -[popup] -macos # Can't control cursor (QTBUG-76312) - -# QTBUG-87018 -[subMenuDisabledMouse] -macos - # QTBUG-98491 [disableWhenTriggered] macos diff --git a/tests/auto/quickcontrols2/qquickmenu/data/customMenuCullItems.qml b/tests/auto/quickcontrols2/qquickmenu/data/customMenuCullItems.qml new file mode 100644 index 0000000000..0f56ecdd87 --- /dev/null +++ b/tests/auto/quickcontrols2/qquickmenu/data/customMenuCullItems.qml @@ -0,0 +1,55 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ApplicationWindow { + width: 200 + height: 200 + property alias menu: menu + + Menu { + id: menu + + contentItem: FocusScope { + implicitHeight: view.implicitHeight + Button { + anchors { + top: parent.top + topMargin: 5 + horizontalCenter: parent.horizontalCenter + } + z: 1 + text: "Button Up" + visible: view.interactive + } + ListView { + id: view + width: parent.width + implicitHeight: Math.min(contentHeight, 300) + model: menu.contentModel + + clip: true + currentIndex: menu.currentIndex + ScrollIndicator.vertical: ScrollIndicator {} + } + Button { + anchors { + bottom: parent.bottom + bottomMargin: 5 + horizontalCenter: parent.horizontalCenter + } + z: 1 + text: "Button Down" + visible: view.interactive + } + } + + Repeater { + model: 20 + MenuItem { + objectName: "Item: " + modelData + text: objectName + } + } + } +} diff --git a/tests/auto/quickcontrols2/qquickmenu/data/customMenuUseRepeaterAsTheContentItem.qml b/tests/auto/quickcontrols2/qquickmenu/data/customMenuUseRepeaterAsTheContentItem.qml new file mode 100644 index 0000000000..bfa8f66be5 --- /dev/null +++ b/tests/auto/quickcontrols2/qquickmenu/data/customMenuUseRepeaterAsTheContentItem.qml @@ -0,0 +1,65 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ApplicationWindow { + width: 200 + height: 200 + property alias menu: menu + + Menu { + id: menu + visible: true + + contentItem: FocusScope { + implicitHeight: flickable.height + + Button { + anchors { + top: parent.top + topMargin: 5 + horizontalCenter: parent.horizontalCenter + } + z: 1 + text: "Button Up" + } + + Flickable { + id: flickable + width: parent.width + height: Math.min(contentHeight, 300) + contentHeight: repeaterLayout.implicitHeight + clip: true + + ScrollIndicator.vertical: ScrollIndicator {} + + ColumnLayout { + id: repeaterLayout + width: parent.width + + Repeater { + model: menu.contentModel + } + } + } + + Button { + anchors { + bottom: parent.bottom + bottomMargin: 5 + horizontalCenter: parent.horizontalCenter + } + z: 1 + text: "Button Down" + } + } + + Repeater { + model: 20 + MenuItem { + objectName: "Item: " + modelData + text: objectName + } + } + } +} diff --git a/tests/auto/quickcontrols2/qquickmenu/data/subMenus.qml b/tests/auto/quickcontrols2/qquickmenu/data/subMenus.qml index fad59ee6d1..8227a29fb2 100644 --- a/tests/auto/quickcontrols2/qquickmenu/data/subMenus.qml +++ b/tests/auto/quickcontrols2/qquickmenu/data/subMenus.qml @@ -69,6 +69,7 @@ ApplicationWindow { } Menu { + overlap: 0 id: subMenu1 objectName: "subMenu1" title: "Sub Menu 1" @@ -78,6 +79,7 @@ ApplicationWindow { objectName: "subMenuItem1" text: "Sub 1" } + MenuItem { id: subMenuItem2 objectName: "subMenuItem2" @@ -85,6 +87,7 @@ ApplicationWindow { } Menu { + overlap: 0 id: subSubMenu1 objectName: "subSubMenu1" title: "Sub Sub Menu 1" diff --git a/tests/auto/quickcontrols2/qquickmenu/tst_qquickmenu.cpp b/tests/auto/quickcontrols2/qquickmenu/tst_qquickmenu.cpp index 9806eff7c3..12707998d7 100644 --- a/tests/auto/quickcontrols2/qquickmenu/tst_qquickmenu.cpp +++ b/tests/auto/quickcontrols2/qquickmenu/tst_qquickmenu.cpp @@ -33,6 +33,8 @@ #include <QtGui/qkeysequence.h> #endif #include <QtGui/qstylehints.h> +#include <QtGui/qpa/qplatformintegration.h> +#include <QtGui/private/qguiapplication_p.h> #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcomponent.h> #include <QtQml/qqmlcontext.h> @@ -74,7 +76,9 @@ private slots: void menuSeparator(); void repeater(); void order(); +#if QT_CONFIG(cursor) void popup(); +#endif void actions(); #if QT_CONFIG(shortcut) void actionShortcuts(); @@ -103,6 +107,11 @@ private slots: void menuItemWidthAfterImplicitWidthChanged(); void menuItemWidthAfterRetranslate(); void giveMenuItemFocusOnButtonPress(); + void customMenuCullItems(); + void customMenuUseRepeaterAsTheContentItem(); + +private: + static bool hasWindowActivation(); }; tst_QQuickMenu::tst_QQuickMenu() @@ -110,6 +119,11 @@ tst_QQuickMenu::tst_QQuickMenu() { } +bool tst_QQuickMenu::hasWindowActivation() +{ + return (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)); +} + void tst_QQuickMenu::defaults() { QQuickControlsApplicationHelper helper(this, QLatin1String("applicationwindow.qml")); @@ -153,6 +167,9 @@ void tst_QQuickMenu::count() void tst_QQuickMenu::mouse() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + if ((QGuiApplication::platformName() == QLatin1String("offscreen")) || (QGuiApplication::platformName() == QLatin1String("minimal"))) QSKIP("Mouse hovering not functional on offscreen/minimal platforms"); @@ -183,14 +200,14 @@ void tst_QQuickMenu::mouse() // Ensure that presses cause the current index to change, // so that the highlight acts as a way of illustrating press state. QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, - QPoint(menu->leftPadding() + firstItem->width() / 2, menu->topPadding() + firstItem->height() / 2)); + QPoint(menu->x() + menu->leftPadding() + firstItem->width() / 2, menu->y() + menu->topPadding() + firstItem->height() / 2)); QVERIFY(firstItem->hasActiveFocus()); QCOMPARE(menu->currentIndex(), 0); QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(0)); QVERIFY(menu->isVisible()); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, - QPoint(menu->leftPadding() + firstItem->width() / 2, menu->topPadding() + firstItem->height() / 2)); + QPoint(menu->x() + menu->leftPadding() + firstItem->width() / 2, menu->y() + menu->topPadding() + firstItem->height() / 2)); QCOMPARE(clickedSpy.count(), 1); QCOMPARE(triggeredSpy.count(), 1); QTRY_COMPARE(visibleSpy.count(), 1); @@ -232,8 +249,8 @@ void tst_QQuickMenu::mouse() if (!hoverItem || !hoverItem->isVisible() || hoverItem == prevHoverItem) continue; QTest::mouseMove(window, QPoint( - menu->leftPadding() + hoverItem->x() + hoverItem->width() / 2, - menu->topPadding() + hoverItem->y() + hoverItem->height() / 2)); + menu->x() + menu->leftPadding() + hoverItem->x() + hoverItem->width() / 2, + menu->y() + menu->topPadding() + hoverItem->y() + hoverItem->height() / 2)); QTRY_VERIFY(hoverItem->property("highlighted").toBool()); if (prevHoverItem) QVERIFY(!prevHoverItem->property("highlighted").toBool()); @@ -265,7 +282,7 @@ void tst_QQuickMenu::pressAndHold() QQuickWindow *window = helper.window; window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickMenu *menu = window->property("menu").value<QQuickMenu *>(); QVERIFY(menu); @@ -282,6 +299,9 @@ void tst_QQuickMenu::pressAndHold() void tst_QQuickMenu::contextMenuKeyboard() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls) QSKIP("This platform only allows tab focus for text controls"); @@ -468,6 +488,9 @@ void tst_QQuickMenu::contextMenuKeyboard() // QTBUG-70181 void tst_QQuickMenu::disabledMenuItemKeyNavigation() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls) QSKIP("This platform only allows tab focus for text controls"); @@ -532,6 +555,9 @@ void tst_QQuickMenu::disabledMenuItemKeyNavigation() void tst_QQuickMenu::mnemonics() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + #ifdef Q_OS_MACOS QSKIP("Mnemonics are not used on macOS"); #endif @@ -587,6 +613,9 @@ void tst_QQuickMenu::mnemonics() void tst_QQuickMenu::menuButton() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls) QSKIP("This platform only allows tab focus for text controls"); @@ -621,7 +650,7 @@ void tst_QQuickMenu::addItem() QVERIFY2(helper.ready, helper.failureMessage()); QQuickApplicationWindow *window = helper.appWindow; window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickMenu *menu = window->property("menu").value<QQuickMenu*>(); QVERIFY(menu); @@ -639,6 +668,9 @@ void tst_QQuickMenu::addItem() void tst_QQuickMenu::menuSeparator() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + QQuickControlsApplicationHelper helper(this, QLatin1String("menuSeparator.qml")); QVERIFY2(helper.ready, helper.failureMessage()); QQuickWindow *window = helper.window; @@ -663,6 +695,7 @@ void tst_QQuickMenu::menuSeparator() QVERIFY(saveMenuItem); QCOMPARE(saveMenuItem->text(), QStringLiteral("Save")); QTRY_VERIFY(!QQuickItemPrivate::get(saveMenuItem)->culled); // QTBUG-53262 + QTRY_VERIFY(menu->isOpened()); // Clicking on items should still close the menu. QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, @@ -670,7 +703,7 @@ void tst_QQuickMenu::menuSeparator() QTRY_VERIFY(!menu->isVisible()); menu->open(); - QVERIFY(menu->isVisible()); + QTRY_VERIFY(menu->isOpened()); // Clicking on a separator shouldn't close the menu. QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, @@ -721,7 +754,7 @@ void tst_QQuickMenu::repeater() QVERIFY2(helper.ready, helper.failureMessage()); QQuickWindow *window = helper.window; window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickMenu *menu = window->property("menu").value<QQuickMenu*>(); QVERIFY(menu); @@ -766,7 +799,7 @@ void tst_QQuickMenu::order() QVERIFY2(helper.ready, helper.failureMessage()); QQuickWindow *window = helper.window; window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickMenu *menu = window->property("menu").value<QQuickMenu*>(); QVERIFY(menu); @@ -782,15 +815,29 @@ void tst_QQuickMenu::order() } } +#if QT_CONFIG(cursor) void tst_QQuickMenu::popup() { +#if defined(Q_OS_ANDROID) + QSKIP("Setting cursor position is not supported on Android"); +#endif + if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) + QSKIP("Setting cursor position is not supported on Wayland"); + + // Try moving the cursor from the current position + // Skip if it fails since the test relies on moving the cursor + const QPoint point = QCursor::pos() + QPoint(1, 1); + QCursor::setPos(point); + if (!QTest::qWaitFor([point]{ return QCursor::pos() == point; })) + QSKIP("Setting cursor position is not supported on this platform"); + QQuickControlsApplicationHelper helper(this, QLatin1String("popup.qml")); QVERIFY2(helper.ready, helper.failureMessage()); QQuickApplicationWindow *window = helper.appWindow; centerOnScreen(window); moveMouseAway(window); window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickMenu *menu = window->property("menu").value<QQuickMenu *>(); QVERIFY(menu); @@ -807,8 +854,6 @@ void tst_QQuickMenu::popup() QQuickItem *button = window->property("button").value<QQuickItem *>(); QVERIFY(button); - // Android does not support settings cursor position -#if QT_CONFIG(cursor) && !defined(Q_OS_ANDROID) QPoint oldCursorPos = QCursor::pos(); QPoint cursorPos = window->mapToGlobal(QPoint(11, 22)); QCursor::setPos(cursorPos); @@ -832,16 +877,16 @@ void tst_QQuickMenu::popup() QCOMPARE(menu->parentItem(), window->contentItem()); QCOMPARE(menu->currentIndex(), -1); QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), -1); - QTRY_VERIFY(qFuzzyCompare(menu->x(), 33)); - QTRY_VERIFY(qFuzzyCompare(menu->y(), 44)); + QTRY_VERIFY(qFuzzyCompare(menu->x(), qMax(qreal(33), menu->leftMargin()))); + QTRY_VERIFY(qFuzzyCompare(menu->y(), qMax(qreal(44), menu->topMargin()))); menu->close(); QVERIFY(QMetaObject::invokeMethod(window, "popupAtCoord", Q_ARG(QVariant, 55), Q_ARG(QVariant, 66))); QCOMPARE(menu->parentItem(), window->contentItem()); QCOMPARE(menu->currentIndex(), -1); QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), -1); - QTRY_VERIFY(qFuzzyCompare(menu->x(), 55)); - QTRY_VERIFY(qFuzzyCompare(menu->y(), 66)); + QTRY_VERIFY(qFuzzyCompare(menu->x(), qMax(qreal(55), menu->leftMargin()))); + QTRY_VERIFY(qFuzzyCompare(menu->y(), qMax(qreal(66), menu->topMargin()))); menu->close(); menu->setParentItem(nullptr); @@ -937,8 +982,8 @@ void tst_QQuickMenu::popup() QCursor::setPos(oldCursorPos); QTRY_COMPARE(QCursor::pos(), oldCursorPos); -#endif } +#endif // QT_CONFIG(cursor) void tst_QQuickMenu::actions() { @@ -946,7 +991,7 @@ void tst_QQuickMenu::actions() QVERIFY2(helper.ready, helper.failureMessage()); QQuickWindow *window = helper.window; window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickMenu *menu = window->property("menu").value<QQuickMenu *>(); QVERIFY(menu); @@ -1012,6 +1057,9 @@ void tst_QQuickMenu::actions() #if QT_CONFIG(shortcut) void tst_QQuickMenu::actionShortcuts() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + QQuickControlsApplicationHelper helper(this, QLatin1String("actionShortcuts.qml")); QVERIFY2(helper.ready, helper.failureMessage()); QQuickWindow *window = helper.window; @@ -1065,7 +1113,7 @@ void tst_QQuickMenu::removeTakeItem() QVERIFY2(helper.ready, helper.failureMessage()); QQuickWindow *window = helper.window; window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickMenu *menu = window->property("menu").value<QQuickMenu *>(); QVERIFY(menu); @@ -1125,7 +1173,7 @@ void tst_QQuickMenu::subMenuMouse() centerOnScreen(window); moveMouseAway(window); window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickMenu *mainMenu = window->property("mainMenu").value<QQuickMenu *>(); QVERIFY(mainMenu); @@ -1244,7 +1292,7 @@ void tst_QQuickMenu::subMenuDisabledMouse() centerOnScreen(window); moveMouseAway(window); window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickMenu *mainMenu = window->property("mainMenu").value<QQuickMenu *>(); QVERIFY(mainMenu); @@ -1258,16 +1306,22 @@ void tst_QQuickMenu::subMenuDisabledMouse() QVERIFY(subMenu); mainMenu->open(); - QVERIFY(mainMenu->isVisible()); + QTRY_VERIFY(mainMenu->isOpened()); QVERIFY(!menuItem1->isHighlighted()); QVERIFY(!subMenu->isVisible()); + // Hover-highlighting does not work on Android +#ifndef Q_OS_ANDROID + // Generate a hover event to set the current index + QTest::mouseMove(window, menuItem1->mapToScene(QPoint(2, 2)).toPoint()); + QTRY_VERIFY(menuItem1->isHighlighted()); +#endif // Open the sub-menu with a mouse click. QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, menuItem1->mapToScene(QPoint(1, 1)).toPoint()); - // Need to use the TRY variant here when cascade is false, - // as e.g. Material style menus have transitions and don't close immediately. + // Need to use the TRY variant here, + // as e.g. Material, iOS style menus have transitions and don't open/close immediately. QTRY_COMPARE(mainMenu->isVisible(), cascade); - QVERIFY(subMenu->isVisible()); + QTRY_VERIFY(subMenu->isOpened()); QTRY_VERIFY(menuItem1->isHighlighted()); // Now the sub-menu is open. The current behavior is that the first menu item // in the new menu is highlighted; make sure that we choose the next item if @@ -1298,6 +1352,9 @@ void tst_QQuickMenu::subMenuKeyboard_data() void tst_QQuickMenu::subMenuKeyboard() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + QFETCH(bool, cascade); QFETCH(bool, mirrored); @@ -1424,6 +1481,9 @@ void tst_QQuickMenu::subMenuDisabledKeyboard_data() // QTBUG-69540 void tst_QQuickMenu::subMenuDisabledKeyboard() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + QFETCH(bool, cascade); QFETCH(bool, mirrored); @@ -1539,7 +1599,7 @@ void tst_QQuickMenu::subMenuPosition() // unpredictable results window->showNormal(); #endif - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); if (mirrored) { QQmlExpression mirroringExpression(qmlContext(window), window, @@ -1653,7 +1713,7 @@ void tst_QQuickMenu::addRemoveSubMenus() QVERIFY2(helper.ready, helper.failureMessage()); QQuickWindow *window = helper.window; window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickMenu *mainMenu = window->property("mainMenu").value<QQuickMenu *>(); QVERIFY(mainMenu); @@ -1729,7 +1789,7 @@ void tst_QQuickMenu::scrollable() #else window->showNormal(); #endif - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickMenu *menu = window->property("menu").value<QQuickMenu*>(); menu->open(); @@ -1769,7 +1829,7 @@ void tst_QQuickMenu::disableWhenTriggered() QVERIFY2(helper.ready, helper.failureMessage()); QQuickWindow *window = helper.window; window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickMenu *menu = window->findChild<QQuickMenu*>("Menu"); QVERIFY(menu); @@ -1834,7 +1894,7 @@ void tst_QQuickMenu::menuItemWidth() QVERIFY2(helper.ready, helper.failureMessage()); QQuickApplicationWindow *window = helper.appWindow; window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); if (mirrored) { QQmlExpression mirroringExpression(qmlContext(window), window, @@ -1866,7 +1926,7 @@ void tst_QQuickMenu::menuItemWidthAfterMenuWidthChanged() QVERIFY2(helper.ready, helper.failureMessage()); QQuickApplicationWindow *window = helper.appWindow; window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); if (mirrored) { QQmlExpression mirroringExpression(qmlContext(window), window, @@ -1914,7 +1974,7 @@ void tst_QQuickMenu::menuItemWidthAfterImplicitWidthChanged() QVERIFY2(helper.ready, helper.failureMessage()); QQuickApplicationWindow *window = helper.appWindow; window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); if (mirrored) { QQmlExpression mirroringExpression(qmlContext(window), window, @@ -1948,7 +2008,7 @@ void tst_QQuickMenu::menuItemWidthAfterRetranslate() QVERIFY2(helper.ready, helper.failureMessage()); QQuickApplicationWindow *window = helper.appWindow; window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickMenu *menu = window->property("menu").value<QQuickMenu *>(); QVERIFY(menu); @@ -1976,6 +2036,9 @@ void tst_QQuickMenu::menuItemWidthAfterRetranslate() void tst_QQuickMenu::giveMenuItemFocusOnButtonPress() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + QQuickControlsApplicationHelper helper(this, QLatin1String("giveMenuItemFocusOnButtonPress.qml")); QVERIFY2(helper.ready, helper.failureMessage()); QQuickApplicationWindow *window = helper.appWindow; @@ -2000,6 +2063,46 @@ void tst_QQuickMenu::giveMenuItemFocusOnButtonPress() QTRY_VERIFY(menu->isOpened()); } +void tst_QQuickMenu::customMenuCullItems() +{ + QQuickControlsApplicationHelper helper(this, QLatin1String("customMenuCullItems.qml")); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickApplicationWindow *window = helper.appWindow; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + QQuickMenu *menu = window->property("menu").value<QQuickMenu*>(); + QVERIFY(menu); + menu->open(); + QTRY_VERIFY(menu->isOpened()); + + QQuickItem *menuItemFirst = menu->itemAt(0); + QQuickItem *menuItemLast = menu->itemAt(menu->count() - 1); + QVERIFY(menuItemFirst); + QVERIFY(menuItemLast); + QTRY_VERIFY(!QQuickItemPrivate::get(menuItemFirst)->culled); + QTRY_VERIFY(QQuickItemPrivate::get(menuItemLast)->culled); +} + +void tst_QQuickMenu::customMenuUseRepeaterAsTheContentItem() +{ + QQuickControlsApplicationHelper helper(this, QLatin1String("customMenuUseRepeaterAsTheContentItem.qml")); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickApplicationWindow *window = helper.appWindow; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + QQuickMenu *menu = window->property("menu").value<QQuickMenu*>(); + QVERIFY(menu); + menu->open(); + QTRY_VERIFY(menu->isVisible()); + + QQuickItem *menuItemFirst = menu->itemAt(0); + QQuickItem *menuItemLast = menu->itemAt(menu->count() - 1); + QTRY_VERIFY(!QQuickItemPrivate::get(menuItemFirst)->culled); + QTRY_VERIFY(!QQuickItemPrivate::get(menuItemLast)->culled); +} + QTEST_QUICKCONTROLS_MAIN(tst_QQuickMenu) #include "tst_qquickmenu.moc" diff --git a/tests/auto/quickcontrols2/qquickmenubar/tst_qquickmenubar.cpp b/tests/auto/quickcontrols2/qquickmenubar/tst_qquickmenubar.cpp index 1e718ff193..54c758ee23 100644 --- a/tests/auto/quickcontrols2/qquickmenubar/tst_qquickmenubar.cpp +++ b/tests/auto/quickcontrols2/qquickmenubar/tst_qquickmenubar.cpp @@ -26,6 +26,8 @@ ** ****************************************************************************/ +#include <QtGui/qpa/qplatformintegration.h> +#include <QtGui/private/qguiapplication_p.h> #include <QtTest> #include <QtQml> #include <QtQuickTestUtils/private/qmlutils_p.h> @@ -54,6 +56,9 @@ private slots: void mnemonics(); void addRemove(); void checkHighlightWhenMenuDismissed(); + +private: + static bool hasWindowActivation(); }; tst_qquickmenubar::tst_qquickmenubar() @@ -61,6 +66,11 @@ tst_qquickmenubar::tst_qquickmenubar() { } +bool tst_qquickmenubar::hasWindowActivation() +{ + return (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)); +} + void tst_qquickmenubar::delegate() { QQmlApplicationEngine engine(testFileUrl("empty.qml")); @@ -76,6 +86,9 @@ void tst_qquickmenubar::delegate() void tst_qquickmenubar::mouse() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + if ((QGuiApplication::platformName() == QLatin1String("offscreen")) || (QGuiApplication::platformName() == QLatin1String("minimal"))) QSKIP("Mouse highlight not functional on offscreen/minimal platforms"); @@ -192,9 +205,10 @@ void tst_qquickmenubar::mouse() QQuickMenu *alignmentSubMenu = alignmentSubMenuItem->subMenu(); QVERIFY(alignmentSubMenu); QTest::mouseClick(window.data(), Qt::LeftButton, Qt::NoModifier, alignmentSubMenuItem->mapToScene(QPointF(alignmentSubMenuItem->width() / 2, alignmentSubMenuItem->height() / 2)).toPoint()); -#ifndef Q_OS_ANDROID +#if !defined(Q_OS_ANDROID) and !defined(Q_OS_WEBOS) // The screen on Android is too small to fit the whole hierarchy, so the // Alignment sub-menu is shown on top of View menu. + // WebOS also shows alignment sub-menu on top of View menu. QVERIFY(viewMenuBarMenu->isVisible()); #endif QVERIFY(alignmentSubMenu->isVisible()); @@ -206,9 +220,10 @@ void tst_qquickmenubar::mouse() QQuickMenu *verticalSubMenu = verticalSubMenuItem->subMenu(); QVERIFY(verticalSubMenu); QTest::mouseClick(window.data(), Qt::LeftButton, Qt::NoModifier, verticalSubMenuItem->mapToScene(QPointF(verticalSubMenuItem->width() / 2, verticalSubMenuItem->height() / 2)).toPoint()); -#ifndef Q_OS_ANDROID +#if !defined(Q_OS_ANDROID) and !defined(Q_OS_WEBOS) // The screen on Android is too small to fit the whole hierarchy, so the // Vertical sub-menu is shown on top of View menu and Alignment sub-menu. + // WebOS also shows vertical sub-menu on top of View menu and Alignment sub-menu. QVERIFY(viewMenuBarMenu->isVisible()); QVERIFY(alignmentSubMenu->isVisible()); #endif @@ -250,6 +265,9 @@ void tst_qquickmenubar::mouse() void tst_qquickmenubar::keys() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + QQmlApplicationEngine engine(testFileUrl("menubar.qml")); QScopedPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0))); @@ -296,6 +314,11 @@ void tst_qquickmenubar::keys() QVERIFY(editMenuBarItem->isHighlighted()); QVERIFY(editMenuBarItem->hasActiveFocus()); QTRY_VERIFY(!editMenuBarMenu->isVisible()); + +// There seem to be problems in focus handling in webOS QPA, see https://bugreports.qt.io/browse/WEBOSCI-45 +#ifdef Q_OS_WEBOS + QEXPECT_FAIL("", "WEBOSCI-45", Abort); +#endif QVERIFY(!cutMenuItem->isHighlighted()); QVERIFY(!cutMenuItem->hasActiveFocus()); @@ -434,8 +457,11 @@ void tst_qquickmenubar::keys() void tst_qquickmenubar::mnemonics() { -#ifdef Q_OS_MACOS - QSKIP("Mnemonics are not used on macOS"); + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + +#if defined(Q_OS_MACOS) or defined(Q_OS_WEBOS) + QSKIP("Mnemonics are not used on this platform"); #endif QQmlApplicationEngine engine(testFileUrl("menubar.qml")); @@ -654,7 +680,7 @@ void tst_qquickmenubar::checkHighlightWhenMenuDismissed() centerOnScreen(window.data()); moveMouseAway(window.data()); - QVERIFY(QTest::qWaitForWindowActive(window.data())); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickMenuBar *menuBar = window->findChild<QQuickMenuBar *>("menuBar"); QVERIFY(menuBar); diff --git a/tests/auto/quickcontrols2/qquickpopup/data/applicationwindow-hover.qml b/tests/auto/quickcontrols2/qquickpopup/data/applicationwindow-hover.qml index 80b22e6d5d..fc93d16d91 100644 --- a/tests/auto/quickcontrols2/qquickpopup/data/applicationwindow-hover.qml +++ b/tests/auto/quickcontrols2/qquickpopup/data/applicationwindow-hover.qml @@ -68,9 +68,14 @@ ApplicationWindow { id: popup x: 1 y: 1 - padding: 1 + leftPadding: 1 + rightPadding: 1 + topPadding: 1 + bottomPadding: 1 + Button { + anchors.centerIn: parent id: childButton text: "Child" } diff --git a/tests/auto/quickcontrols2/qquickpopup/data/applicationwindow.qml b/tests/auto/quickcontrols2/qquickpopup/data/applicationwindow.qml index 7a298d9620..399f1c67c1 100644 --- a/tests/auto/quickcontrols2/qquickpopup/data/applicationwindow.qml +++ b/tests/auto/quickcontrols2/qquickpopup/data/applicationwindow.qml @@ -57,7 +57,9 @@ ApplicationWindow { property alias popup: popup property alias popup2: popup2 + property alias popup3: popup3 property alias button: button + property alias slider: slider Button { id: button @@ -79,6 +81,15 @@ ApplicationWindow { } } } + + Popup { + id: popup3 + y: parent.height + + Slider { + id: slider + } + } } Popup { diff --git a/tests/auto/quickcontrols2/qquickpopup/data/window-hover.qml b/tests/auto/quickcontrols2/qquickpopup/data/window-hover.qml index e0eef30206..5064fda7bc 100644 --- a/tests/auto/quickcontrols2/qquickpopup/data/window-hover.qml +++ b/tests/auto/quickcontrols2/qquickpopup/data/window-hover.qml @@ -69,9 +69,13 @@ Window { id: popup x: 1 y: 1 - padding: 1 + topPadding: 1 + bottomPadding: 1 + leftPadding: 1 + rightPadding: 1 Button { + anchors.centerIn: parent id: childButton text: "Child" } diff --git a/tests/auto/quickcontrols2/qquickpopup/data/window.qml b/tests/auto/quickcontrols2/qquickpopup/data/window.qml index f6b76b7e14..d46d8ccbf1 100644 --- a/tests/auto/quickcontrols2/qquickpopup/data/window.qml +++ b/tests/auto/quickcontrols2/qquickpopup/data/window.qml @@ -58,7 +58,9 @@ Window { property alias popup: popup property alias popup2: popup2 + property alias popup3: popup3 property alias button: button + property alias slider: slider Button { id: button @@ -80,6 +82,15 @@ Window { } } } + + Popup { + id: popup3 + y: parent.height + + Slider { + id: slider + } + } } Popup { diff --git a/tests/auto/quickcontrols2/qquickpopup/tst_qquickpopup.cpp b/tests/auto/quickcontrols2/qquickpopup/tst_qquickpopup.cpp index 87667989a2..44299d674f 100644 --- a/tests/auto/quickcontrols2/qquickpopup/tst_qquickpopup.cpp +++ b/tests/auto/quickcontrols2/qquickpopup/tst_qquickpopup.cpp @@ -31,6 +31,8 @@ #include <QtCore/qoperatingsystemversion.h> #include <QtGui/qpa/qwindowsysteminterface.h> +#include <QtGui/qpa/qplatformintegration.h> +#include <QtGui/private/qguiapplication_p.h> #include <QtQuick/qquickview.h> #include <QtQuick/private/qquickpalette_p.h> #include <QtQuickTestUtils/private/qmlutils_p.h> @@ -71,6 +73,8 @@ private slots: void windowChange(); void closePolicy_data(); void closePolicy(); + void closePolicy_grabberInside_data(); + void closePolicy_grabberInside(); void activeFocusOnClose1(); void activeFocusOnClose2(); void activeFocusOnClose3(); @@ -106,6 +110,9 @@ private slots: void dimmerContainmentMask(); void shrinkPopupThatWasLargerThanWindow_data(); void shrinkPopupThatWasLargerThanWindow(); + +private: + static bool hasWindowActivation(); }; tst_QQuickPopup::tst_QQuickPopup() @@ -126,6 +133,11 @@ void tst_QQuickPopup::visible_data() QTest::newRow("ApplicationWindow") << "applicationwindow.qml"; } +bool tst_QQuickPopup::hasWindowActivation() +{ + return (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)); +} + void tst_QQuickPopup::visible() { QFETCH(QString, source); @@ -134,8 +146,7 @@ void tst_QQuickPopup::visible() QQuickWindow *window = helper.window; window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickPopup *popup = window->property("popup").value<QQuickPopup*>(); QVERIFY(popup); @@ -231,8 +242,7 @@ void tst_QQuickPopup::overlay() QQuickWindow *window = helper.window; window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickOverlay *overlay = QQuickOverlay::overlay(window); QVERIFY(overlay); @@ -383,8 +393,7 @@ void tst_QQuickPopup::zOrder() QQuickWindow *window = helper.window; window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickPopup *popup = window->property("popup").value<QQuickPopup*>(); QVERIFY(popup); @@ -485,6 +494,9 @@ void tst_QQuickPopup::closePolicy_data() void tst_QQuickPopup::closePolicy() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + QFETCH(QString, source); QFETCH(QQuickPopup::ClosePolicy, closePolicy); @@ -510,12 +522,15 @@ void tst_QQuickPopup::closePolicy() QVERIFY(popup->isVisible()); QTRY_VERIFY(popup->isOpened()); + // wait for dimmer + QTest::qWait(50); + // press outside popup and its parent - QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, QPoint(1, 1), 50); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, QPoint(1, 1)); if (closePolicy.testFlag(QQuickPopup::CloseOnPressOutside) || closePolicy.testFlag(QQuickPopup::CloseOnPressOutsideParent)) QTRY_VERIFY(!popup->isVisible()); else - QVERIFY(popup->isVisible()); + QVERIFY(popup->isOpened()); popup->open(); QVERIFY(popup->isVisible()); @@ -523,10 +538,10 @@ void tst_QQuickPopup::closePolicy() // release outside popup and its parent QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, QPoint(1, 1)); - if (closePolicy.testFlag(QQuickPopup::CloseOnReleaseOutside)) + if (closePolicy.testFlag(QQuickPopup::CloseOnReleaseOutside) || closePolicy.testFlag(QQuickPopup::CloseOnReleaseOutsideParent)) QTRY_VERIFY(!popup->isVisible()); else - QVERIFY(popup->isVisible()); + QVERIFY(popup->isOpened()); popup->open(); QVERIFY(popup->isVisible()); @@ -537,7 +552,7 @@ void tst_QQuickPopup::closePolicy() if (closePolicy.testFlag(QQuickPopup::CloseOnPressOutside) && !closePolicy.testFlag(QQuickPopup::CloseOnPressOutsideParent)) QTRY_VERIFY(!popup->isVisible()); else - QVERIFY(popup->isVisible()); + QVERIFY(popup->isOpened()); popup->open(); QVERIFY(popup->isVisible()); @@ -548,7 +563,7 @@ void tst_QQuickPopup::closePolicy() if (closePolicy.testFlag(QQuickPopup::CloseOnReleaseOutside) && !closePolicy.testFlag(QQuickPopup::CloseOnReleaseOutsideParent)) QTRY_VERIFY(!popup->isVisible()); else - QVERIFY(popup->isVisible()); + QVERIFY(popup->isOpened()); popup->open(); QVERIFY(popup->isVisible()); @@ -557,9 +572,9 @@ void tst_QQuickPopup::closePolicy() // press inside and release outside QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, QPoint(button->x() + popup->x() + 1, button->y() + popup->y() + 1)); - QVERIFY(popup->isVisible()); + QVERIFY(popup->isOpened()); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, QPoint(1, 1)); - QVERIFY(popup->isVisible()); + QVERIFY(popup->isOpened()); // escape QTest::keyClick(window, Qt::Key_Escape); @@ -569,8 +584,59 @@ void tst_QQuickPopup::closePolicy() QVERIFY(popup->isVisible()); } +void tst_QQuickPopup::closePolicy_grabberInside_data() +{ + qRegisterMetaType<QQuickPopup::ClosePolicy>(); + + QTest::addColumn<QString>("source"); + QTest::addColumn<QQuickPopup::ClosePolicy>("closePolicy"); + + QTest::newRow("Window:CloseOnReleaseOutside") << "window.qml"<< static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnReleaseOutside); + QTest::newRow("Window:CloseOnReleaseOutside|Parent") << "window.qml"<< static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnReleaseOutside | QQuickPopup::CloseOnReleaseOutsideParent); + + QTest::newRow("ApplicationWindow:CloseOnReleaseOutside") << "applicationwindow.qml"<< static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnReleaseOutside); + QTest::newRow("ApplicationWindow:CloseOnReleaseOutside|Parent") << "applicationwindow.qml"<< static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnReleaseOutside | QQuickPopup::CloseOnReleaseOutsideParent); +} + +void tst_QQuickPopup::closePolicy_grabberInside() +{ + QFETCH(QString, source); + QFETCH(QQuickPopup::ClosePolicy, closePolicy); + + QQuickControlsApplicationHelper helper(this, source); + QVERIFY2(helper.ready, helper.failureMessage()); + + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + QQuickPopup *popup = window->property("popup3").value<QQuickPopup*>(); + QVERIFY(popup); + + QQuickSlider *slider = window->property("slider").value<QQuickSlider*>(); + QVERIFY(slider); + + popup->setModal(true); + popup->setClosePolicy(closePolicy); + + popup->open(); + QVERIFY(popup->isVisible()); + QTRY_VERIFY(popup->isOpened()); + + // press on a mouse grabber inside and release outside + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, + slider->handle()->mapToItem(window->contentItem(),slider->handle()->boundingRect().center()).toPoint()); + + QVERIFY(popup->isOpened()); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, QPoint(1, 1)); + QVERIFY(popup->isOpened()); +} + void tst_QQuickPopup::activeFocusOnClose1() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + // Test that a popup that never sets focus: true (e.g. ToolTip) doesn't affect // the active focus item when it closes. QQuickControlsApplicationHelper helper(this, QStringLiteral("activeFocusOnClose1.qml")); @@ -614,6 +680,9 @@ void tst_QQuickPopup::activeFocusOnClose1() void tst_QQuickPopup::activeFocusOnClose2() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + // Test that a popup that sets focus: true but relinquishes focus (e.g. by // calling forceActiveFocus() on another item) before it closes doesn't // affect the active focus item when it closes. @@ -653,6 +722,9 @@ void tst_QQuickPopup::activeFocusOnClose2() void tst_QQuickPopup::activeFocusOnClose3() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + // Test that a closing popup that had focus doesn't steal focus from // another popup that the focus was transferred to. QQuickControlsApplicationHelper helper(this, QStringLiteral("activeFocusOnClose3.qml")); @@ -686,6 +758,9 @@ void tst_QQuickPopup::activeFocusOnClose3() void tst_QQuickPopup::activeFocusOnClosingSeveralPopups() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + // Test that active focus isn't lost when multiple popup closing simultaneously QQuickControlsApplicationHelper helper(this, QStringLiteral("activeFocusOnClosingSeveralPopups.qml")); QVERIFY2(helper.ready, helper.failureMessage()); @@ -735,6 +810,9 @@ void tst_QQuickPopup::activeFocusOnClosingSeveralPopups() void tst_QQuickPopup::activeFocusAfterExit() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + // Test that after closing a popup the highest one in z-order receives it instead. QQuickControlsApplicationHelper helper(this, QStringLiteral("activeFocusAfterExit.qml")); QVERIFY2(helper.ready, helper.failureMessage()); @@ -784,6 +862,9 @@ void tst_QQuickPopup::activeFocusAfterExit() void tst_QQuickPopup::activeFocusOnDelayedEnter() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + // Test that after opening two popups, first of which has an animation, does not cause // the first one to receive focus after the animation stops. QQuickControlsApplicationHelper helper(this, QStringLiteral("activeFocusOnDelayedEnter.qml")); @@ -826,8 +907,7 @@ void tst_QQuickPopup::hover() QVERIFY2(helper.ready, helper.failureMessage()); QQuickWindow *window = helper.window; window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickPopup *popup = window->property("popup").value<QQuickPopup*>(); QVERIFY(popup); @@ -1062,7 +1142,7 @@ void tst_QQuickPopup::grabber() QCOMPARE(combo->isVisible(), false); // click a menu item to open the popup - QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, QPoint(menu->width() / 2, menu->height() / 2)); + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, QPoint(menu->x() + menu->width() / 2, menu->y() + menu->height() / 2)); QTRY_COMPARE(menu->isVisible(), false); QTRY_COMPARE(popup->isOpened(), true); QCOMPARE(combo->isVisible(), false); @@ -1152,13 +1232,16 @@ void tst_QQuickPopup::componentComplete() void tst_QQuickPopup::closeOnEscapeWithNestedPopups() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + // Tests the scenario in the Gallery example, where there are nested popups that should // close in the correct order when the Escape key is pressed. QQuickControlsApplicationHelper helper(this, QStringLiteral("closeOnEscapeWithNestedPopups.qml")); QVERIFY2(helper.ready, helper.failureMessage()); QQuickApplicationWindow *window = helper.appWindow; window->show(); - QVERIFY(QTest::qWaitForWindowExposed(window)); + QVERIFY(QTest::qWaitForWindowActive(window)); // The stack view should have two items, and it should pop the second when escape is pressed // and it has focus. @@ -1188,6 +1271,7 @@ void tst_QQuickPopup::closeOnEscapeWithNestedPopups() QQuickPopup *settingsDialog = window->contentItem()->findChild<QQuickPopup*>("settingsDialog"); QVERIFY(settingsDialog); + QTRY_VERIFY(!optionsMenu->isVisible()); QTRY_VERIFY(settingsDialog->isOpened()); QQuickComboBox *comboBox = window->contentItem()->findChild<QQuickComboBox*>("comboBox"); @@ -1218,6 +1302,9 @@ void tst_QQuickPopup::closeOnEscapeWithNestedPopups() void tst_QQuickPopup::closeOnEscapeWithVisiblePopup() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + QQuickControlsApplicationHelper helper(this, QStringLiteral("closeOnEscapeWithVisiblePopup.qml")); QVERIFY2(helper.ready, helper.failureMessage()); QQuickWindow *window = helper.window; @@ -1309,7 +1396,7 @@ void tst_QQuickPopup::orientation() QQuickWindow *window = helper.window; window->reportContentOrientationChange(orientation); window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickPopup *popup = window->property("popup").value<QQuickPopup*>(); QVERIFY(popup); @@ -1343,6 +1430,9 @@ void tst_QQuickPopup::qquickview() // QTBUG-73447 void tst_QQuickPopup::disabledPalette() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + QQuickControlsApplicationHelper helper(this, "disabledPalette.qml"); QVERIFY2(helper.ready, helper.failureMessage()); @@ -1380,6 +1470,9 @@ void tst_QQuickPopup::disabledPalette() void tst_QQuickPopup::disabledParentPalette() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + QQuickControlsApplicationHelper helper(this, "disabledPalette.qml"); QVERIFY2(helper.ready, helper.failureMessage()); @@ -1431,7 +1524,7 @@ void tst_QQuickPopup::countChanged() QQuickWindow *window = helper.window; window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickComboBox *comboBox = window->property("comboBox").value<QQuickComboBox*>(); QVERIFY(comboBox); @@ -1452,7 +1545,7 @@ void tst_QQuickPopup::toolTipCrashOnClose() QQuickWindow *window = helper.window; window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QTest::mouseMove(window, QPoint(window->width() / 2, window->height() / 2)); QTRY_VERIFY(window->property("toolTipOpened").toBool()); @@ -1473,7 +1566,7 @@ void tst_QQuickPopup::setOverlayParentToNull() centerOnScreen(window); moveMouseAway(window); window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QVERIFY(QMetaObject::invokeMethod(window, "nullifyOverlayParent")); @@ -1486,6 +1579,9 @@ void tst_QQuickPopup::setOverlayParentToNull() void tst_QQuickPopup::tabFence() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls) QSKIP("This platform only allows tab focus for text controls"); @@ -1551,7 +1647,7 @@ void tst_QQuickPopup::invisibleToolTipOpen() centerOnScreen(window); moveMouseAway(window); window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickItem *mouseArea = qvariant_cast<QQuickItem *>(window->property("mouseArea")); QVERIFY(mouseArea); @@ -1596,6 +1692,9 @@ void tst_QQuickPopup::centerInOverlayWithinStackViewItem() void tst_QQuickPopup::destroyDuringExitTransition() { + if (!hasWindowActivation()) + QSKIP("Window activation is not supported"); + QQuickControlsApplicationHelper helper(this, "destroyDuringExitTransition.qml"); QVERIFY2(helper.ready, helper.failureMessage()); @@ -1633,7 +1732,7 @@ void tst_QQuickPopup::releaseAfterExitTransition() QQuickWindow *window = helper.window; window->show(); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickOverlay *overlay = QQuickOverlay::overlay(window); QQuickPopup *modalPopup = window->property("modalPopup").value<QQuickPopup *>(); @@ -1683,7 +1782,7 @@ void tst_QQuickPopup::dimmerContainmentMask() QQuickWindow *window = helper.window; window->show(); QCOMPARE(window->property("clickCount").toInt(), expectedClickCount); - QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickOverlay *overlay = QQuickOverlay::overlay(window); QQuickPopup *modalPopup = window->property("modalPopup").value<QQuickPopup *>(); diff --git a/tests/auto/quickcontrols2/qquickstyle/tst_qquickstyle.cpp b/tests/auto/quickcontrols2/qquickstyle/tst_qquickstyle.cpp index 1408bafb43..c1e7505819 100644 --- a/tests/auto/quickcontrols2/qquickstyle/tst_qquickstyle.cpp +++ b/tests/auto/quickcontrols2/qquickstyle/tst_qquickstyle.cpp @@ -144,7 +144,9 @@ void tst_QQuickStyle::configurationFile() // Make it small so that there's less possibility for the default/system // pixel size to match it and give us false positives. QCOMPARE(label->font().pixelSize(), 3); +#ifdef QT_BUILD_INTERNAL QCOMPARE(QQuickLabelPrivate::get(label)->palette()->windowText(), Qt::red); +#endif } void tst_QQuickStyle::commandLineArgument() diff --git a/tests/auto/quickcontrols2/snippets/CMakeLists.txt b/tests/auto/quickcontrols2/snippets/CMakeLists.txt index ce76c4fbc5..aaae0ae783 100644 --- a/tests/auto/quickcontrols2/snippets/CMakeLists.txt +++ b/tests/auto/quickcontrols2/snippets/CMakeLists.txt @@ -10,11 +10,27 @@ file(GLOB_RECURSE test_data_glob ${CMAKE_CURRENT_SOURCE_DIR}/data/*) list(APPEND test_data ${test_data_glob}) +set(SNIPPETS_PATH \\\"${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/quickcontrols2/doc/snippets\\\") + +if(WEBOS) + # Collect snippets for webOS + file(GLOB_RECURSE test_snippets_glob + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/quickcontrols2/doc/snippets/*) + list(APPEND test_snippets ${test_snippets_glob}) + + # Copy snippets to a location which is included in the webOS emulator image + file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/snippets) + file(COPY ${test_snippets} DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/data/snippets) + + set(SNIPPETS_PATH \\\"./data/snippets\\\") +endif() + qt_internal_add_test(tst_snippets SOURCES tst_snippets.cpp DEFINES - QQC2_SNIPPETS_PATH=\\\"${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/quickcontrols2/doc/snippets\\\" + QQC2_SNIPPETS_PATH=${SNIPPETS_PATH} PUBLIC_LIBRARIES Qt::Gui Qt::Quick diff --git a/tests/auto/quickcontrols2/styleimportscompiletimeqmlonly/tst_styleimportscompiletimeqmlonly.cpp b/tests/auto/quickcontrols2/styleimportscompiletimeqmlonly/tst_styleimportscompiletimeqmlonly.cpp index 010924e378..5697ae50e2 100644 --- a/tests/auto/quickcontrols2/styleimportscompiletimeqmlonly/tst_styleimportscompiletimeqmlonly.cpp +++ b/tests/auto/quickcontrols2/styleimportscompiletimeqmlonly/tst_styleimportscompiletimeqmlonly.cpp @@ -60,7 +60,7 @@ tst_StyleImportsCompileTimeQmlOnly::tst_StyleImportsCompileTimeQmlOnly() void tst_StyleImportsCompileTimeQmlOnly::importQmlOnlyStyleWithoutControls() { QQuickControlsApplicationHelper helper(this, - QLatin1String("importQmlOnlyStyleWithoutControls.qml"), QStringList() << dataDirectory()); + QLatin1String("importQmlOnlyStyleWithoutControls.qml"), {}, QStringList() << dataDirectory()); QVERIFY2(helper.ready, helper.failureMessage()); auto button = helper.window->property("button").value<QQuickButton*>(); diff --git a/tests/auto/quickdialogs/qquickfiledialogimpl/BLACKLIST b/tests/auto/quickdialogs/qquickfiledialogimpl/BLACKLIST index bcf39c598d..822b92dd19 100644 --- a/tests/auto/quickdialogs/qquickfiledialogimpl/BLACKLIST +++ b/tests/auto/quickdialogs/qquickfiledialogimpl/BLACKLIST @@ -5,6 +5,3 @@ # QTBUG-92585 [fileMode:OpenFiles] * -#QTBUG-101329 -[goUp] -qnx diff --git a/tests/auto/quickdialogs/qquickfiledialogimpl/data/setSelectedFile.qml b/tests/auto/quickdialogs/qquickfiledialogimpl/data/setSelectedFile.qml new file mode 100644 index 0000000000..b7ff588b68 --- /dev/null +++ b/tests/auto/quickdialogs/qquickfiledialogimpl/data/setSelectedFile.qml @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +ApplicationWindow { + id: root + width: 640 + height: 480 + + required property url tempFile1Url + required property int fileMode + + property alias dialog: dialog + + FileDialog { + id: dialog + objectName: "FileDialog" + fileMode: root.fileMode + selectedFile: root.tempFile1Url + } +} diff --git a/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp b/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp index e70c7e7171..2718bf52a0 100644 --- a/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp +++ b/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp @@ -26,6 +26,7 @@ ** ****************************************************************************/ +#include <QtCore/qloggingcategory.h> #include <QtTest/qtest.h> #include <QtTest/qsignalspy.h> #include <QtQml/qqmlfile.h> @@ -36,6 +37,7 @@ #include <QtQuickDialogs2/private/qquickfiledialog_p.h> #include <QtQuickDialogs2QuickImpl/private/qquickplatformfiledialog_p.h> #include <QtQuickDialogs2QuickImpl/private/qquickfiledialogdelegate_p.h> +#include <QtQuickDialogs2QuickImpl/private/qquickfiledialogimpl_p_p.h> #include <QtQuickDialogs2QuickImpl/private/qquickfolderbreadcrumbbar_p.h> #include <QtQuickDialogs2QuickImpl/private/qquickfolderbreadcrumbbar_p_p.h> #include <QtQuickDialogs2Utils/private/qquickfilenamefilter_p.h> @@ -51,6 +53,8 @@ using namespace QQuickVisualTestUtils; using namespace QQuickDialogTestUtils; using namespace QQuickControlsTestUtils; +Q_LOGGING_CATEGORY(lcTest, "qt.quick.dialogs.tests.quickfiledialogimpl") + class tst_QQuickFileDialogImpl : public QQmlDataTest { Q_OBJECT @@ -69,6 +73,7 @@ public: private slots: void initTestCase() override; + void init() override; void cleanupTestCase(); void defaults(); @@ -79,14 +84,17 @@ private slots: void bindCurrentFolder_data(); void bindCurrentFolder(); void changeFolderViaStandardButtons(); + void changeFolderViaDoubleClick_data(); void changeFolderViaDoubleClick(); void chooseFolderViaTextEdit(); void chooseFolderViaEnter(); void chooseFileAndThenFolderViaTextEdit(); void cancelDialogWhileTextEditHasFocus(); + void closingDialogCancels(); void goUp(); void goUpWhileTextEditHasFocus(); void goIntoLargeFolder(); + void goUpIntoLargeFolder(); void keyAndShortcutHandling(); void bindNameFilters(); void changeNameFilters(); @@ -101,6 +109,8 @@ private slots: void defaultSuffix(); void done_data(); void done(); + void setSelectedFile_data(); + void setSelectedFile(); private: QTemporaryDir tempDir; @@ -110,11 +120,23 @@ private: QDir tempSubSubDir; QScopedPointer<QFile> tempSubFile1; QScopedPointer<QFile> tempSubFile2; + + QTemporaryDir largeTempDir; + QStringList largeTempDirPaths; + QDir largeTempDirLargeSubDir; + const int largeTempDirLargeSubDirIndex = 80; + QDir oldCurrentDir; + + const QKeySequence goUpKeySequence = QKeySequence(Qt::ALT | Qt::Key_Up); + const QKeySequence editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L); }; +// We don't want to fail on warnings until QTBUG-98964 is fixed, +// as we deliberately prevent deferred execution in some of the tests here, +// which causes warnings. tst_QQuickFileDialogImpl::tst_QQuickFileDialogImpl() - : QQmlDataTest(QT_QMLTEST_DATADIR) + : QQmlDataTest(QT_QMLTEST_DATADIR, FailOnWarningsPolicy::DoNotFailOnWarnings) { } @@ -122,7 +144,7 @@ void tst_QQuickFileDialogImpl::initTestCase() { QQmlDataTest::initTestCase(); - qputenv("QT_QUICK_DIALOGS_SHOW_DIRS_FIRST", "1"); + qputenv("QT_QUICK_DIALOGS_PRESELECT_FIRST_FILE", "1"); QVERIFY(tempDir.isValid()); // QTEST_QUICKCONTROLS_MAIN constructs the test case object once, @@ -167,58 +189,143 @@ void tst_QQuickFileDialogImpl::initTestCase() tempFile2.reset(new QFile(tempDir.path() + "/file2.txt")); QVERIFY(tempFile2->open(QIODevice::ReadWrite)); + /* + Create another temporary directory that contains a large amount of folders. + */ + QVERIFY(largeTempDir.isValid()); + const static int largeFileCount = 100; + const QDir largeTempDirectory(largeTempDir.path()); + for (int i = 0; i < largeFileCount; ++i) { + // Pad with zeroes so that the directories are ordered as we expect. + const QString dirName = QString::fromLatin1("dir%1").arg(i, 3, 10, QLatin1Char('0')); + QVERIFY(largeTempDirectory.mkdir(dirName)); + largeTempDirPaths.append(largeTempDirectory.filePath(dirName)); + } + + // ... and within one of those folders, more folders. + largeTempDirLargeSubDir = QDir(largeTempDir.path() + "/dir" + + QString::fromLatin1("%1").arg(largeTempDirLargeSubDirIndex, 3, 10, QLatin1Char('0'))); + QVERIFY(largeTempDirLargeSubDir.exists()); + const QDir largeTempSubDirectory = QDir(largeTempDirLargeSubDir.path()); + for (int i = 0; i < largeFileCount; ++i) + QVERIFY(largeTempSubDirectory.mkdir(QString::fromLatin1("sub-dir%1").arg(i, 3, 10, QLatin1Char('0')))); + // Ensure that each test starts off in the temporary directory. oldCurrentDir = QDir::current(); QDir::setCurrent(tempDir.path()); } +void tst_QQuickFileDialogImpl::init() +{ + // Do this before each test function in case the test sets it. + qputenv("QT_QUICK_DIALOGS_SHOW_DIRS_FIRST", "1"); +} + void tst_QQuickFileDialogImpl::cleanupTestCase() { // Just in case... QDir::setCurrent(oldCurrentDir.path()); } -void tst_QQuickFileDialogImpl::defaults() +#define VERIFY_FILE_SELECTED(expectedCurrentFolderUrl, expectedCurrentFileUrl) \ +{ \ + COMPARE_URL(dialogHelper.dialog->selectedFile(), expectedCurrentFileUrl); \ + COMPARE_URLS(dialogHelper.dialog->selectedFiles(), { expectedCurrentFileUrl }); \ + /* We also test the deprecated currentFile* API until it's removed. */ \ + COMPARE_URL(dialogHelper.dialog->currentFile(), expectedCurrentFileUrl); \ + COMPARE_URLS(dialogHelper.dialog->currentFiles(), { expectedCurrentFileUrl }); \ + COMPARE_URL(dialogHelper.quickDialog->selectedFile(), expectedCurrentFileUrl); \ + COMPARE_URL(dialogHelper.dialog->currentFolder(), expectedCurrentFolderUrl); \ + COMPARE_URL(dialogHelper.quickDialog->currentFolder(), expectedCurrentFolderUrl); \ +} + +#define VERIFY_DELEGATE_CURRENT(expectedCurrentFileUrl, expectedCurrentIndex) \ +{ \ + QTRY_COMPARE(dialogHelper.fileDialogListView->currentIndex(), expectedCurrentIndex); \ + QQuickFileDialogDelegate *fileDelegate = nullptr; \ + QTRY_VERIFY(findViewDelegateItem(dialogHelper.fileDialogListView, expectedCurrentIndex, fileDelegate)); \ + COMPARE_URL(fileDelegate->file(), expectedCurrentFileUrl); \ +} + +#define VERIFY_DELEGATE_FOCUSED(expectedCurrentIndex) \ +{ \ + QTRY_COMPARE(dialogHelper.fileDialogListView->currentIndex(), expectedCurrentIndex); \ + QQuickFileDialogDelegate *fileDelegate = nullptr; \ + QTRY_VERIFY(findViewDelegateItem(dialogHelper.fileDialogListView, expectedCurrentIndex, fileDelegate)); \ + QVERIFY2(fileDelegate->hasActiveFocus(), qPrintable(QString::fromLatin1( \ + "Expected delegate at index %1 to have focus, but %2 has it") \ + .arg(QString::number(expectedCurrentIndex)) \ + .arg(QDebug::toString(dialogHelper.window()->activeFocusItem())))); \ + QVERIFY(fileDelegate->isHighlighted()); \ +} + +/* + Checks that currentFolder, selectedFile, and currentIndex are what we expect. + + It also checks that the relevant delegate in the view is current, has focus, and is highlighted. + As the FolderListModel (the view's model) is asynchronous, we need to wait for the currentIndex to change. + + Can only be called when the dialog is open. +*/ +#define VERIFY_FILE_SELECTED_AND_FOCUSED(expectedCurrentFolderUrl, expectedCurrentFileUrl, expectedCurrentIndex) \ +VERIFY_FILE_SELECTED(expectedCurrentFolderUrl, expectedCurrentFileUrl) \ +VERIFY_DELEGATE_CURRENT(expectedCurrentFileUrl, expectedCurrentIndex) \ +VERIFY_DELEGATE_FOCUSED(expectedCurrentIndex) + +class FileDialogTestHelper : public DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> { - QQuickApplicationHelper helper(this, "fileDialog.qml"); - QVERIFY2(helper.ready, helper.failureMessage()); +public: + FileDialogTestHelper(QQmlDataTest *testCase, const QString &testFilePath, + const QStringList &qmlImportPaths = {}, const QVariantMap &initialProperties = {}); - QQuickWindow *window = helper.window; - window->show(); - window->requestActivate(); - QVERIFY(QTest::qWaitForWindowExposed(window)); + bool openDialog() override; - QQuickFileDialog *dialog = window->property("dialog").value<QQuickFileDialog*>(); - QVERIFY(dialog); - COMPARE_URL(dialog->selectedFile(), QUrl()); + QPointer<QQuickListView> fileDialogListView; +}; + +FileDialogTestHelper::FileDialogTestHelper(QQmlDataTest *testCase, const QString &testFilePath, + const QStringList &qmlImportPaths, const QVariantMap &initialProperties) + : DialogTestHelper(testCase, testFilePath, qmlImportPaths, initialProperties) +{ +} + +bool FileDialogTestHelper::openDialog() +{ + if (!DialogTestHelper::openDialog()) + return false; + + fileDialogListView = quickDialog->findChild<QQuickListView*>("fileDialogListView"); + return fileDialogListView != nullptr; +} + +void tst_QQuickFileDialogImpl::defaults() +{ + FileDialogTestHelper dialogHelper(this, "fileDialog.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + + COMPARE_URL(dialogHelper.dialog->selectedFile(), QUrl()); // It should default to the current directory. - COMPARE_URL(dialog->currentFolder(), QUrl::fromLocalFile(QDir().absolutePath())); + COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(QDir().absolutePath())); // The first file in the directory should be selected, but not until the dialog is actually open, // as QQuickFileDialogImpl hasn't been created yet. - COMPARE_URL(dialog->currentFile(), QUrl()); - COMPARE_URLS(dialog->currentFiles(), {}); - QCOMPARE(dialog->title(), QString()); + COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl()); + COMPARE_URLS(dialogHelper.dialog->currentFiles(), {}); + QCOMPARE(dialogHelper.dialog->title(), QString()); - dialog->open(); - QQuickFileDialogImpl *quickDialog = window->findChild<QQuickFileDialogImpl*>(); - QTRY_VERIFY(quickDialog->isOpened()); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + QQuickFileDialogImpl *quickDialog = dialogHelper.window()->findChild<QQuickFileDialogImpl*>(); QVERIFY(quickDialog); - COMPARE_URL(quickDialog->currentFolder(), QUrl::fromLocalFile(QDir().absolutePath())); - COMPARE_URL(dialog->selectedFile(), QUrl::fromLocalFile(tempSubDir.path())); - COMPARE_URLS(dialog->selectedFiles(), { QUrl::fromLocalFile(tempSubDir.path()) }); - COMPARE_URL(dialog->currentFile(), dialog->selectedFile()); - COMPARE_URLS(dialog->currentFiles(), dialog->selectedFiles()); + VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(QDir().absolutePath()), QUrl::fromLocalFile(tempSubDir.path()), 0); QCOMPARE(quickDialog->title(), QString()); } void tst_QQuickFileDialogImpl::chooseFileViaStandardButtons() { // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + FileDialogTestHelper dialogHelper(this, "fileDialog.qml"); + OPEN_QUICK_DIALOG(); // Select the delegate by clicking once. QSignalSpy dialogSelectedFileChangedSpy(dialogHelper.dialog, SIGNAL(selectedFileChanged())); @@ -226,16 +333,11 @@ void tst_QQuickFileDialogImpl::chooseFileViaStandardButtons() QSignalSpy dialogCurrentFileChangedSpy(dialogHelper.dialog, SIGNAL(currentFileChanged())); QVERIFY(dialogCurrentFileChangedSpy.isValid()); - auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); - QVERIFY(fileDialogListView); QQuickFileDialogDelegate *delegate = nullptr; - QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 2, delegate)); + QTRY_VERIFY(findViewDelegateItem(dialogHelper.fileDialogListView, 2, delegate)); COMPARE_URL(delegate->file(), QUrl::fromLocalFile(tempFile2->fileName())); QVERIFY(clickButton(delegate)); - COMPARE_URL(dialogHelper.dialog->selectedFile(), QUrl::fromLocalFile(tempFile2->fileName())); - COMPARE_URL(dialogHelper.quickDialog->selectedFile(), QUrl::fromLocalFile(tempFile2->fileName())); - COMPARE_URL(dialogHelper.dialog->currentFile(), dialogHelper.dialog->selectedFile()); - COMPARE_URLS(dialogHelper.dialog->currentFiles(), { dialogHelper.dialog->selectedFile() }); + VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempDir.path()), QUrl::fromLocalFile(tempFile2->fileName()), 2); QCOMPARE(dialogSelectedFileChangedSpy.count(), 1); QCOMPARE(dialogCurrentFileChangedSpy.count(), 1); @@ -258,17 +360,12 @@ void tst_QQuickFileDialogImpl::chooseFileViaStandardButtons() void tst_QQuickFileDialogImpl::chooseFileViaDoubleClick() { // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + FileDialogTestHelper dialogHelper(this, "fileDialog.qml"); + OPEN_QUICK_DIALOG(); // Select the delegate by double-clicking. - auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); - QVERIFY(fileDialogListView); QQuickFileDialogDelegate *delegate = nullptr; - QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 2, delegate)); + QTRY_VERIFY(findViewDelegateItem(dialogHelper.fileDialogListView, 2, delegate)); COMPARE_URL(delegate->file(), QUrl::fromLocalFile(tempFile2->fileName())) QVERIFY(doubleClickButton(delegate)); COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempFile2->fileName())) @@ -282,14 +379,15 @@ void tst_QQuickFileDialogImpl::chooseFileViaDoubleClick() void tst_QQuickFileDialogImpl::chooseFileViaTextEdit() { // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + FileDialogTestHelper dialogHelper(this, "fileDialog.qml"); + OPEN_QUICK_DIALOG(); + // Ensure that fileDialogListView has loaded its items, as we force active focus + // on the current item when we set it in setFileDialogListViewCurrentIndex(), + // which can make the TextField's visibility check + // below fail due to it being hidden when it loses activeFocus. + VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempDir.path()), QUrl::fromLocalFile(tempSubDir.path()), 0); // Get the text edit visible with Ctrl+L. - const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L); QTest::keySequence(dialogHelper.window(), editPathKeySequence); auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>(); QVERIFY(breadcrumbBar); @@ -313,20 +411,11 @@ void tst_QQuickFileDialogImpl::chooseFileViaTextEdit() void tst_QQuickFileDialogImpl::chooseFileViaEnter() { // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + FileDialogTestHelper dialogHelper(this, "fileDialog.qml"); + OPEN_QUICK_DIALOG(); // Before moving down, the first delegate in the view should be selected and have focus. - COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempSubDir.path())); - auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); - QVERIFY(fileDialogListView); - QQuickFileDialogDelegate *subDirDelegate = nullptr; - QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, subDirDelegate)); - COMPARE_URL(subDirDelegate->file(), QUrl::fromLocalFile(tempSubDir.path())); - QVERIFY(subDirDelegate->hasActiveFocus()); + VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempDir.path()), QUrl::fromLocalFile(tempSubDir.path()), 0); // Select the first file in the view by navigating with the down key. QTest::keyClick(dialogHelper.window(), Qt::Key_Down); @@ -367,21 +456,16 @@ void tst_QQuickFileDialogImpl::bindCurrentFolder() QFETCH(QStringList, expectedVisibleFiles); // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "bindCurrentFolder.qml", {}, + FileDialogTestHelper dialogHelper(this, "bindCurrentFolder.qml", {}, {{ "initialFolder", initialFolder }}); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + OPEN_QUICK_DIALOG(); COMPARE_URL(dialogHelper.dialog->currentFolder(), expectedFolder); - auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); - QVERIFY(fileDialogListView); QString failureMessage; // Even waiting for ListView polish and that the FolderListModel's status is ready aren't enough // on Windows, apparently, as sometimes there just aren't any delegates by the time we do the check. // So, we use QTRY_VERIFY2 each time we call this function just to be safe. - QTRY_VERIFY2(verifyFileDialogDelegates(fileDialogListView, expectedVisibleFiles, failureMessage), qPrintable(failureMessage)); + QTRY_VERIFY2(verifyFileDialogDelegates(dialogHelper.fileDialogListView, expectedVisibleFiles, failureMessage), qPrintable(failureMessage)); // Check that the breadcrumb bar is correct by constructing the expected files from the expectedFolder. auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>(); @@ -392,25 +476,16 @@ void tst_QQuickFileDialogImpl::bindCurrentFolder() void tst_QQuickFileDialogImpl::changeFolderViaStandardButtons() { // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + FileDialogTestHelper dialogHelper(this, "fileDialog.qml"); + OPEN_QUICK_DIALOG(); // Select the delegate by clicking once. - auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); - QVERIFY(fileDialogListView); QQuickFileDialogDelegate *delegate = nullptr; - QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, delegate)); + QTRY_VERIFY(findViewDelegateItem(dialogHelper.fileDialogListView, 0, delegate)); COMPARE_URL(delegate->file(), QUrl::fromLocalFile(tempSubDir.path())); QVERIFY(clickButton(delegate)); // The selectedFile should change, but not currentFolder. - COMPARE_URL(dialogHelper.dialog->selectedFile(), QUrl::fromLocalFile(tempSubDir.path())); - COMPARE_URLS(dialogHelper.dialog->selectedFiles(), { QUrl::fromLocalFile(tempSubDir.path()) }); - COMPARE_URL(dialogHelper.dialog->currentFile(), dialogHelper.dialog->selectedFile()); - COMPARE_URLS(dialogHelper.dialog->currentFiles(), dialogHelper.dialog->selectedFiles()); - COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempDir.path())); + VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempDir.path()), QUrl::fromLocalFile(tempSubDir.path()), 0); // Click the "Open" button. The dialog should navigate to that directory, but still be open. QVERIFY(dialogHelper.quickDialog->footer()); @@ -428,33 +503,38 @@ void tst_QQuickFileDialogImpl::changeFolderViaStandardButtons() QTRY_VERIFY(!dialogHelper.quickDialog->isVisible()); } +void tst_QQuickFileDialogImpl::changeFolderViaDoubleClick_data() +{ + QTest::addColumn<bool>("showDirsFirst"); + + QTest::newRow("showDirsFirst=true") << true; + QTest::newRow("showDirsFirst=false") << false; +} + void tst_QQuickFileDialogImpl::changeFolderViaDoubleClick() { + QFETCH(bool, showDirsFirst); + + qputenv("QT_QUICK_DIALOGS_SHOW_DIRS_FIRST", showDirsFirst ? "true" : "false"); + // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + FileDialogTestHelper dialogHelper(this, "fileDialog.qml"); + OPEN_QUICK_DIALOG(); // Select the "sub-dir" delegate by double-clicking. - auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); - QVERIFY(fileDialogListView); QQuickFileDialogDelegate *subDirDelegate = nullptr; - QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, subDirDelegate)); + const int subDirIndex = showDirsFirst ? 0 : 2; + QTRY_VERIFY(findViewDelegateItem(dialogHelper.fileDialogListView, subDirIndex, subDirDelegate)); COMPARE_URL(subDirDelegate->file(), QUrl::fromLocalFile(tempSubDir.path())); QVERIFY(doubleClickButton(subDirDelegate)); - // The first file in the directory should be selected, which is "sub-sub-dir". - COMPARE_URL(dialogHelper.dialog->selectedFile(), QUrl::fromLocalFile(tempSubSubDir.path())); - COMPARE_URLS(dialogHelper.dialog->selectedFiles(), { QUrl::fromLocalFile(tempSubSubDir.path()) }); - COMPARE_URL(dialogHelper.dialog->currentFile(), dialogHelper.dialog->selectedFile()); - COMPARE_URLS(dialogHelper.dialog->currentFiles(), { dialogHelper.dialog->selectedFiles() }); - QQuickFileDialogDelegate *subSubDirDelegate = nullptr; - QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, subSubDirDelegate)); - QCOMPARE(subSubDirDelegate->isHighlighted(), true); - COMPARE_URL(dialogHelper.dialog->selectedFile(), QUrl::fromLocalFile(tempSubSubDir.path())); - COMPARE_URLS(dialogHelper.dialog->selectedFiles(), { QUrl::fromLocalFile(tempSubSubDir.path()) }); - COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempSubDir.path())); + const QStringList expectedVisibleFiles = showDirsFirst + ? QStringList { tempSubSubDir.path(), tempSubFile1->fileName(), tempSubFile2->fileName() } + : QStringList { tempSubFile1->fileName(), tempSubFile2->fileName(), tempSubSubDir.path() }; + QString failureMessage; + QTRY_VERIFY2(verifyFileDialogDelegates(dialogHelper.fileDialogListView, expectedVisibleFiles, failureMessage), qPrintable(failureMessage)); + // The first file in the directory should now be selected. + const QUrl firstFileUrl = showDirsFirst ? QUrl::fromLocalFile(tempSubSubDir.path()) : QUrl::fromLocalFile(tempSubFile1->fileName()); + VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempSubDir.path()), firstFileUrl, 0); // Since we only chose a folder, the dialog should still be open. QVERIFY(dialogHelper.dialog->isVisible()); @@ -466,11 +546,10 @@ void tst_QQuickFileDialogImpl::changeFolderViaDoubleClick() void tst_QQuickFileDialogImpl::chooseFolderViaTextEdit() { // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + FileDialogTestHelper dialogHelper(this, "fileDialog.qml"); + OPEN_QUICK_DIALOG(); + // See comment in chooseFileViaTextEdit for why we check for this. + VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempDir.path()), QUrl::fromLocalFile(tempSubDir.path()), 0); // Get the text edit visible with Ctrl+L. const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L); @@ -488,16 +567,10 @@ void tst_QQuickFileDialogImpl::chooseFolderViaTextEdit() // Hit enter to accept. QTest::keyClick(dialogHelper.window(), Qt::Key_Return); // The first file in the directory should be selected, which is "sub-sub-dir". - COMPARE_URL(dialogHelper.dialog->selectedFile(), QUrl::fromLocalFile(tempSubSubDir.path())); - auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); - QVERIFY(fileDialogListView); - QQuickFileDialogDelegate *subSubDirDelegate = nullptr; - QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, subSubDirDelegate)); - QCOMPARE(subSubDirDelegate->isHighlighted(), true); - COMPARE_URL(dialogHelper.dialog->selectedFile(), QUrl::fromLocalFile(tempSubSubDir.path())); - COMPARE_URLS(dialogHelper.dialog->selectedFiles(), { QUrl::fromLocalFile(tempSubSubDir.path()) }); - COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempSubDir.path())); - QTRY_VERIFY(dialogHelper.dialog->isVisible()); + // Note that the TextEdit will still have focus, so we can't use VERIFY_FILE_SELECTED_AND_FOCUSED. + VERIFY_FILE_SELECTED(QUrl::fromLocalFile(tempSubDir.path()), QUrl::fromLocalFile(tempSubSubDir.path())); + VERIFY_DELEGATE_CURRENT(QUrl::fromLocalFile(tempSubSubDir.path()), 0) + QVERIFY(dialogHelper.dialog->isVisible()); dialogHelper.dialog->close(); QVERIFY(!dialogHelper.dialog->isVisible()); @@ -507,28 +580,19 @@ void tst_QQuickFileDialogImpl::chooseFolderViaTextEdit() void tst_QQuickFileDialogImpl::chooseFolderViaEnter() { // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + FileDialogTestHelper dialogHelper(this, "fileDialog.qml"); + OPEN_QUICK_DIALOG(); // The first delegate in the view should be selected and have focus. - COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempDir.path())); - COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempSubDir.path())); - COMPARE_URLS(dialogHelper.dialog->currentFiles(), { QUrl::fromLocalFile(tempSubDir.path()) }); - auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); - QVERIFY(fileDialogListView); - QQuickFileDialogDelegate *subDirDelegate = nullptr; - QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, subDirDelegate)); - COMPARE_URL(subDirDelegate->file(), QUrl::fromLocalFile(tempSubDir.path())); - QVERIFY(subDirDelegate->hasActiveFocus()); + VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempDir.path()), QUrl::fromLocalFile(tempSubDir.path()), 0); // Select the delegate by pressing enter. QTest::keyClick(dialogHelper.window(), Qt::Key_Return); - COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempSubDir.path())); + const QStringList expectedVisibleFiles = { tempSubSubDir.path(), tempSubFile1->fileName(), tempSubFile2->fileName() }; + QString failureMessage; + QTRY_VERIFY2(verifyFileDialogDelegates(dialogHelper.fileDialogListView, expectedVisibleFiles, failureMessage), qPrintable(failureMessage)); // The first file in the new directory should be selected, which is "sub-sub-dir". - COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempSubSubDir.path())); + VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempSubDir.path()), QUrl::fromLocalFile(tempSubSubDir.path()), 0); // Since we only chose a folder, the dialog should still be open. QVERIFY(dialogHelper.dialog->isVisible()); @@ -540,14 +604,12 @@ void tst_QQuickFileDialogImpl::chooseFolderViaEnter() void tst_QQuickFileDialogImpl::chooseFileAndThenFolderViaTextEdit() { // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + FileDialogTestHelper dialogHelper(this, "fileDialog.qml"); + OPEN_QUICK_DIALOG(); + // See comment in chooseFileViaTextEdit for why we check for this. + VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempDir.path()), QUrl::fromLocalFile(tempSubDir.path()), 0); // Get the text edit visible with Ctrl+L. - const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L); QTest::keySequence(dialogHelper.window(), editPathKeySequence); auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>(); QVERIFY(breadcrumbBar); @@ -589,17 +651,9 @@ void tst_QQuickFileDialogImpl::chooseFileAndThenFolderViaTextEdit() // Hit enter to accept. QTest::keyClick(dialogHelper.window(), Qt::Key_Return); // The first file in the directory should be selected, which is "sub-sub-dir". - COMPARE_URL(dialogHelper.dialog->selectedFile(), QUrl::fromLocalFile(tempSubSubDir.path())); - auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); - QVERIFY(fileDialogListView); - QQuickFileDialogDelegate *subSubDirDelegate = nullptr; - QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, subSubDirDelegate)); - QCOMPARE(subSubDirDelegate->isHighlighted(), true); - // We just changed directories, so the actual selected file shouldn't change. - COMPARE_URL(dialogHelper.dialog->selectedFile(), QUrl::fromLocalFile(tempSubSubDir.path())); - COMPARE_URLS(dialogHelper.dialog->selectedFiles(), { QUrl::fromLocalFile(tempSubSubDir.path()) }); - COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempSubDir.path())); - QTRY_VERIFY(dialogHelper.dialog->isVisible()); + // Note that the TextEdit will still have focus, so we can't use VERIFY_FILE_SELECTED_AND_FOCUSED. + VERIFY_FILE_SELECTED(QUrl::fromLocalFile(tempSubDir.path()), QUrl::fromLocalFile(tempSubSubDir.path())); + VERIFY_DELEGATE_CURRENT(QUrl::fromLocalFile(tempSubSubDir.path()), 0) // Close the dialog. dialogHelper.dialog->close(); @@ -610,14 +664,12 @@ void tst_QQuickFileDialogImpl::chooseFileAndThenFolderViaTextEdit() void tst_QQuickFileDialogImpl::cancelDialogWhileTextEditHasFocus() { // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + FileDialogTestHelper dialogHelper(this, "fileDialog.qml"); + OPEN_QUICK_DIALOG(); + // See comment in chooseFileViaTextEdit for why we check for this. + VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempDir.path()), QUrl::fromLocalFile(tempSubDir.path()), 0); // Get the text edit visible with Ctrl+L. - const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L); QTest::keySequence(dialogHelper.window(), editPathKeySequence); auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>(); QVERIFY(breadcrumbBar); @@ -641,17 +693,42 @@ void tst_QQuickFileDialogImpl::cancelDialogWhileTextEditHasFocus() QVERIFY(!breadcrumbBar->textField()->isVisible()); } +void tst_QQuickFileDialogImpl::closingDialogCancels() +{ + // Open the dialog. + DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); + OPEN_QUICK_DIALOG(); + + QSignalSpy accepted(dialogHelper.dialog, &QQuickAbstractDialog::accepted); + QSignalSpy rejected(dialogHelper.dialog, &QQuickAbstractDialog::rejected); + + // Accept the dialog. + QVERIFY(QMetaObject::invokeMethod(dialogHelper.window(), "doneAccepted")); + QVERIFY(!dialogHelper.dialog->isVisible()); + QTRY_VERIFY(!dialogHelper.quickDialog->isVisible()); + QCOMPARE(accepted.size(), 1); + QCOMPARE(rejected.size(), 0); + + // Re-open the dialog. + accepted.clear(); + OPEN_QUICK_DIALOG(); + + // Close the dialog. + CLOSE_QUICK_DIALOG(); + QCOMPARE(accepted.size(), 0); + QCOMPARE(rejected.size(), 1); +} + void tst_QQuickFileDialogImpl::goUp() { // Open the dialog. Start off in "sub-dir". - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "bindCurrentFolder.qml", {}, + FileDialogTestHelper dialogHelper(this, "bindCurrentFolder.qml", {}, {{ "initialFolder", QUrl::fromLocalFile(tempSubDir.path()) }}); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + OPEN_QUICK_DIALOG(); // Go up a directory via the button next to the breadcrumb bar. + qCDebug(lcTest) << "going up to" << tempDir.path() << "- files in that dir:\n" + << QQuickFileDialogImplPrivate::get(dialogHelper.quickDialog)->fileList(QDir(tempDir.path())); auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>(); QVERIFY(breadcrumbBar); auto barListView = qobject_cast<QQuickListView*>(breadcrumbBar->contentItem()); @@ -659,38 +736,33 @@ void tst_QQuickFileDialogImpl::goUp() if (QQuickTest::qIsPolishScheduled(barListView)) QVERIFY(QQuickTest::qWaitForItemPolished(barListView)); QVERIFY(clickButton(breadcrumbBar->upButton())); - COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempDir.path())); // The previous directory that we were in should now be selected (matches e.g. Windows and Ubuntu). - COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempSubDir.path())); - COMPARE_URLS(dialogHelper.dialog->currentFiles(), { QUrl::fromLocalFile(tempSubDir.path()) }); - auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); - QVERIFY(fileDialogListView); - QQuickFileDialogDelegate *subDirDelegate = nullptr; - QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, subDirDelegate)); - QCOMPARE(subDirDelegate->isHighlighted(), true); + VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempDir.path()), QUrl::fromLocalFile(tempSubDir.path()), 0); - // Go up a directory via the keyboard shortcut next to the breadcrumb bar. - const auto goUpKeySequence = QKeySequence(Qt::ALT | Qt::Key_Up); - QTest::keySequence(dialogHelper.window(), goUpKeySequence); + // Go up a directory via the keyboard shortcut. QDir tempParentDir(tempDir.path()); QVERIFY(tempParentDir.cdUp()); - COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempParentDir.path())); - COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempDir.path())); - COMPARE_URLS(dialogHelper.dialog->currentFiles(), { QUrl::fromLocalFile(tempDir.path()) }); + const auto filesInTempParentDir = QQuickFileDialogImplPrivate::get(dialogHelper.quickDialog)->fileList(tempParentDir); + qCDebug(lcTest) << "going up to" << tempParentDir.path() << "- files in that dir:\n" << filesInTempParentDir; + QTest::keySequence(dialogHelper.window(), goUpKeySequence); + // Ubuntu on QEMU arm shows no files in /tmp even if there are. + if (!filesInTempParentDir.isEmpty()) { + const int expectedCurrentIndex = filesInTempParentDir.indexOf(QFileInfo(tempDir.path())); + QVERIFY(expectedCurrentIndex != -1); + VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempParentDir.path()), QUrl::fromLocalFile(tempDir.path()), expectedCurrentIndex); + } } void tst_QQuickFileDialogImpl::goUpWhileTextEditHasFocus() { // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "bindCurrentFolder.qml", {}, + FileDialogTestHelper dialogHelper(this, "bindCurrentFolder.qml", {}, {{ "initialFolder", QUrl::fromLocalFile(tempSubDir.path()) }}); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + OPEN_QUICK_DIALOG(); + // See comment in chooseFileViaTextEdit for why we check for this. + VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempSubDir.path()), QUrl::fromLocalFile(tempSubSubDir.path()), 0); // Get the text edit visible with Ctrl+L. - const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L); QTest::keySequence(dialogHelper.window(), editPathKeySequence); auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>(); QVERIFY(breadcrumbBar); @@ -709,82 +781,72 @@ void tst_QQuickFileDialogImpl::goUpWhileTextEditHasFocus() QVERIFY(!breadcrumbBar->textField()->isVisible()); // The focus should be given to the first delegate. QVERIFY(dialogHelper.window()->activeFocusItem()); - auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); - QVERIFY(fileDialogListView); QQuickFileDialogDelegate *firstDelegate = nullptr; - QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, firstDelegate)); + QTRY_VERIFY(findViewDelegateItem(dialogHelper.fileDialogListView, 0, firstDelegate)); QCOMPARE(dialogHelper.window()->activeFocusItem(), firstDelegate); } void tst_QQuickFileDialogImpl::goIntoLargeFolder() { - // Create a throwaway directory with a lot of folders within it... - QTemporaryDir anotherTempDir; - QVERIFY(anotherTempDir.isValid()); - for (int i = 0; i < 30; ++i) { - QDir newDir(anotherTempDir.path()); - QVERIFY(newDir.exists()); - // Pad with zeroes so that the directories are ordered as we expect. - QVERIFY(newDir.mkdir(QString::fromLatin1("dir%1").arg(i, 2, 10, QLatin1Char('0')))); - } - - // ... and within one of those folders, more folders. - QDir dir20(anotherTempDir.path() + "/dir20"); - QVERIFY(dir20.exists()); - for (int i = 0; i < 30; ++i) { - QDir newDir(dir20.path()); - QVERIFY(newDir.exists()); - QVERIFY(newDir.mkdir(QString::fromLatin1("subdir%1").arg(i, 2, 10, QLatin1Char('0')))); - } - - // Open the dialog. Start off in the throwaway directory. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "bindCurrentFolder.qml", {}, - {{ "initialFolder", QUrl::fromLocalFile(anotherTempDir.path()) }}); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + // Open the dialog. Start off in the large directory. + FileDialogTestHelper dialogHelper(this, "bindCurrentFolder.qml", {}, + {{ "initialFolder", QUrl::fromLocalFile(largeTempDir.path()) }}); + OPEN_QUICK_DIALOG(); // If the screen is so tall that the contentItem is not vertically larger than the view, // then the test makes no sense. - auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); - QVERIFY(fileDialogListView); - if (QQuickTest::qIsPolishScheduled(fileDialogListView)) - QVERIFY(QQuickTest::qWaitForItemPolished(fileDialogListView)); + if (QQuickTest::qIsPolishScheduled(dialogHelper.fileDialogListView)) + QVERIFY(QQuickTest::qWaitForItemPolished(dialogHelper.fileDialogListView)); // Just to be safe, make sure it's at least twice as big. - if (fileDialogListView->contentItem()->height() < fileDialogListView->height() * 2) { - QSKIP(qPrintable(QString::fromLatin1("Expected height of fileDialogListView's contentItem (%1)" \ + if (dialogHelper.fileDialogListView->contentItem()->height() < dialogHelper.fileDialogListView->height() * 2) { + QSKIP(qPrintable(QString::fromLatin1("Expected height of dialogHelper.fileDialogListView's contentItem (%1)" \ " to be at least twice as big as the ListView's height (%2)") - .arg(fileDialogListView->contentItem()->height()).arg(fileDialogListView->height()))); + .arg(dialogHelper.fileDialogListView->contentItem()->height()).arg(dialogHelper.fileDialogListView->height()))); } - // Scroll down to dir20. The view should be somewhere past the middle. - QVERIFY(QMetaObject::invokeMethod(fileDialogListView, "positionViewAtIndex", Qt::DirectConnection, - Q_ARG(int, 20), Q_ARG(int, QQuickItemView::PositionMode::Center))); - QVERIFY(fileDialogListView->contentY() > 0); + // Scroll down to largeTempDirLargeSubDirIndex. The view should be somewhere towards the end. + QVERIFY(QMetaObject::invokeMethod(dialogHelper.fileDialogListView, "positionViewAtIndex", Qt::DirectConnection, + Q_ARG(int, largeTempDirLargeSubDirIndex), Q_ARG(int, QQuickItemView::PositionMode::Center))); + QVERIFY(dialogHelper.fileDialogListView->contentY() > 0); // Go into it. The view should start at the top of the directory, not at the same contentY // that it had in the previous directory. - QQuickFileDialogDelegate *dir20Delegate = nullptr; - QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 20, dir20Delegate)); - COMPARE_URL(dir20Delegate->file(), QUrl::fromLocalFile(dir20.path())); - QVERIFY(doubleClickButton(dir20Delegate)); - COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(dir20.path())); - COMPARE_URL(dialogHelper.quickDialog->currentFolder(), QUrl::fromLocalFile(dir20.path())); - QCOMPARE(fileDialogListView->contentY(), 0); + QQuickFileDialogDelegate *subDirDelegate = nullptr; + QTRY_VERIFY(findViewDelegateItem(dialogHelper.fileDialogListView, largeTempDirLargeSubDirIndex, subDirDelegate)); + COMPARE_URL(subDirDelegate->file(), QUrl::fromLocalFile(largeTempDirLargeSubDir.path())); + QVERIFY(doubleClickButton(subDirDelegate)); + COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(largeTempDirLargeSubDir.path())); + COMPARE_URL(dialogHelper.quickDialog->currentFolder(), QUrl::fromLocalFile(largeTempDirLargeSubDir.path())); + QCOMPARE(dialogHelper.fileDialogListView->contentY(), 0); +} + +void tst_QQuickFileDialogImpl::goUpIntoLargeFolder() +{ + // Open the dialog. Start off in the large sub-directory. + FileDialogTestHelper dialogHelper(this, "bindCurrentFolder.qml", {}, + {{ "initialFolder", QUrl::fromLocalFile(largeTempDirLargeSubDir.path()) }}); + OPEN_QUICK_DIALOG(); + // We didn't set an initial selectedFile, so the first file in the directory will be selected. + VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(largeTempDirLargeSubDir.path()), + QUrl::fromLocalFile(largeTempDirLargeSubDir.path() + "/sub-dir000"), 0); + + // Go up a directory via the keyboard shortcut. + QTest::keySequence(dialogHelper.window(), goUpKeySequence); + QString failureMessage; + QTRY_VERIFY2(verifyFileDialogDelegates(dialogHelper.fileDialogListView, + largeTempDirPaths, failureMessage), qPrintable(failureMessage)); + VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(largeTempDir.path()), + QUrl::fromLocalFile(largeTempDirLargeSubDir.path()), largeTempDirLargeSubDirIndex); } void tst_QQuickFileDialogImpl::keyAndShortcutHandling() { // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + FileDialogTestHelper dialogHelper(this, "fileDialog.qml"); + OPEN_QUICK_DIALOG(); + VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempDir.path()), QUrl::fromLocalFile(tempSubDir.path()), 0); // Get the text edit visible with Ctrl+L. - const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L); QTest::keySequence(dialogHelper.window(), editPathKeySequence); auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>(); QVERIFY(breadcrumbBar); @@ -819,23 +881,18 @@ void tst_QQuickFileDialogImpl::keyAndShortcutHandling() void tst_QQuickFileDialogImpl::bindNameFilters() { // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "bindTxtHtmlNameFilters.qml"); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + FileDialogTestHelper dialogHelper(this, "bindTxtHtmlNameFilters.qml"); + OPEN_QUICK_DIALOG(); // Only "sub-dir", "text1.txt" and "text2.txt" should be visible, since *.txt is the first filter. - auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); - QVERIFY(fileDialogListView); QString failureMessage; - QTRY_VERIFY2(verifyFileDialogDelegates(fileDialogListView, + QTRY_VERIFY2(verifyFileDialogDelegates(dialogHelper.fileDialogListView, { tempSubDir.path(), tempFile1->fileName(), tempFile2->fileName() }, failureMessage), qPrintable(failureMessage)); } void tst_QQuickFileDialogImpl::changeNameFilters() { - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); + FileDialogTestHelper dialogHelper(this, "fileDialog.qml"); QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); QVERIFY(dialogHelper.waitForWindowActive()); @@ -879,10 +936,8 @@ void tst_QQuickFileDialogImpl::changeNameFilters() QCOMPARE(dialogHelper.quickDialog->selectedNameFilter()->globs(), { "*.txt" }); // Only "sub-dir", "text1.txt" and "text2.txt" should be visible, since *.txt is the first filter. - auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); - QVERIFY(fileDialogListView); QString failureMessage; - QTRY_VERIFY2(verifyFileDialogDelegates(fileDialogListView, + QTRY_VERIFY2(verifyFileDialogDelegates(dialogHelper.fileDialogListView, { tempSubDir.path(), tempFile1->fileName(), tempFile2->fileName() }, failureMessage), qPrintable(failureMessage)); // Open the ComboBox's popup. @@ -903,7 +958,7 @@ void tst_QQuickFileDialogImpl::changeNameFilters() QTRY_VERIFY(!comboBox->popup()->isVisible()); // Use QTRY_VERIFY2 here to fix a failure on QEMU armv7 (QT_QPA_PLATFORM=offscreen). // Not sure why it's necessary. - QTRY_VERIFY2(verifyFileDialogDelegates(fileDialogListView, { tempSubDir.path() }, failureMessage), qPrintable(failureMessage)); + QTRY_VERIFY2(verifyFileDialogDelegates(dialogHelper.fileDialogListView, { tempSubDir.path() }, failureMessage), qPrintable(failureMessage)); // Open the popup again. QTest::mouseClick(dialogHelper.window(), Qt::LeftButton, Qt::NoModifier, comboBoxCenterPos); @@ -917,27 +972,22 @@ void tst_QQuickFileDialogImpl::changeNameFilters() QVERIFY(clickButton(txtDelegate)); } QTRY_VERIFY(!comboBox->popup()->isVisible()); - QTRY_VERIFY2(verifyFileDialogDelegates(fileDialogListView, + QTRY_VERIFY2(verifyFileDialogDelegates(dialogHelper.fileDialogListView, { tempSubDir.path(), tempFile1->fileName(), tempFile2->fileName() }, failureMessage), qPrintable(failureMessage)); } void tst_QQuickFileDialogImpl::changeNameFiltersAfterChangingFolder() { // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "bindAllTxtHtmlNameFilters.qml"); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + FileDialogTestHelper dialogHelper(this, "bindAllTxtHtmlNameFilters.qml"); + OPEN_QUICK_DIALOG(); // Go into the "sub-dir" folder. - auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); - QVERIFY(fileDialogListView); QString failureMessage; - QTRY_VERIFY2(verifyFileDialogDelegates(fileDialogListView, + QTRY_VERIFY2(verifyFileDialogDelegates(dialogHelper.fileDialogListView, { tempSubDir.path(), tempFile1->fileName(), tempFile2->fileName() }, failureMessage), qPrintable(failureMessage)); QQuickFileDialogDelegate *subDirDelegate = nullptr; - QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, subDirDelegate)); + QTRY_VERIFY(findViewDelegateItem(dialogHelper.fileDialogListView, 0, subDirDelegate)); QVERIFY(doubleClickButton(subDirDelegate)); COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempSubDir.path())); COMPARE_URL(dialogHelper.quickDialog->currentFolder(), QUrl::fromLocalFile(tempSubDir.path())); @@ -959,25 +1009,20 @@ void tst_QQuickFileDialogImpl::changeNameFiltersAfterChangingFolder() } QTRY_VERIFY(!comboBox->popup()->isVisible()); // There are no HTML files in "sub-dir", so there should only be the one "sub-sub-dir" delegate. - QTRY_VERIFY2(verifyFileDialogDelegates(fileDialogListView, { tempSubSubDir.path() }, failureMessage), qPrintable(failureMessage)); + QTRY_VERIFY2(verifyFileDialogDelegates(dialogHelper.fileDialogListView, { tempSubSubDir.path() }, failureMessage), qPrintable(failureMessage)); } void tst_QQuickFileDialogImpl::tabFocusNavigation() { // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "bindTxtHtmlNameFilters.qml"); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + FileDialogTestHelper dialogHelper(this, "bindTxtHtmlNameFilters.qml"); + OPEN_QUICK_DIALOG(); QList<QQuickItem*> expectedFocusItems; // The initial focus should be on the first delegate. - auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); - QVERIFY(fileDialogListView); QQuickFileDialogDelegate *firstDelegate = nullptr; - QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, firstDelegate)); + QTRY_VERIFY(findViewDelegateItem(dialogHelper.fileDialogListView, 0, firstDelegate)); expectedFocusItems << firstDelegate; // Tab should move to the name filters combobox. @@ -1004,9 +1049,9 @@ void tst_QQuickFileDialogImpl::tabFocusNavigation() expectedFocusItems << breadcrumbBar->upButton(); // Finally, add each bread crumb delegate. - for (int i = 0; i < fileDialogListView->count(); ++i) { + for (int i = 0; i < dialogHelper.fileDialogListView->count(); ++i) { QQuickFileDialogDelegate *delegate = nullptr; - QTRY_VERIFY(findViewDelegateItem(fileDialogListView, i, delegate)); + QTRY_VERIFY(findViewDelegateItem(dialogHelper.fileDialogListView, i, delegate)); expectedFocusItems << delegate; } @@ -1036,11 +1081,8 @@ void tst_QQuickFileDialogImpl::tabFocusNavigation() void tst_QQuickFileDialogImpl::acceptRejectLabel() { // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "acceptRejectLabel.qml"); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + FileDialogTestHelper dialogHelper(this, "acceptRejectLabel.qml"); + OPEN_QUICK_DIALOG(); // Check that the accept and reject buttons' labels have changed. auto dialogButtonBox = dialogHelper.quickDialog->footer()->findChild<QQuickDialogButtonBox*>(); @@ -1074,11 +1116,8 @@ void tst_QQuickFileDialogImpl::acceptRejectLabel() void tst_QQuickFileDialogImpl::bindTitle() { // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "bindTitle.qml"); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + FileDialogTestHelper dialogHelper(this, "bindTitle.qml"); + OPEN_QUICK_DIALOG(); // Open the dialog and check that the correct title is displayed. QQuickFileDialog *dialog = dialogHelper.window()->property("dialog").value<QQuickFileDialog*>(); @@ -1102,12 +1141,9 @@ void tst_QQuickFileDialogImpl::itemsDisabledWhenNecessary() QVERIFY(subDir.cd("emptyDir")); // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "bindCurrentFolder.qml", {}, + FileDialogTestHelper dialogHelper(this, "bindCurrentFolder.qml", {}, {{ "initialFolder", QUrl::fromLocalFile(subDir.path()) }}); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + OPEN_QUICK_DIALOG(); COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(subDir.path())); COMPARE_URL(dialogHelper.quickDialog->currentFolder(), QUrl::fromLocalFile(subDir.path())); @@ -1128,7 +1164,6 @@ void tst_QQuickFileDialogImpl::itemsDisabledWhenNecessary() COMPARE_URL(dialogHelper.quickDialog->currentFolder(), QUrl::fromLocalFile(anotherTempDir.path())); // Get the text edit visible with Ctrl+L. The Open button should now be disabled. - const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L); QTest::keySequence(dialogHelper.window(), editPathKeySequence); QVERIFY(breadcrumbBar->textField()->isVisible()); QCOMPARE(openButton->isEnabled(), false); @@ -1153,18 +1188,13 @@ void tst_QQuickFileDialogImpl::fileMode() QFETCH(QQuickFileDialog::FileMode, fileMode); // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); + FileDialogTestHelper dialogHelper(this, "fileDialog.qml"); dialogHelper.dialog->setFileMode(fileMode); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + OPEN_QUICK_DIALOG(); // Select the first file (not a directory). - auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); - QVERIFY(fileDialogListView); QQuickFileDialogDelegate *tempFile1Delegate = nullptr; - QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 1, tempFile1Delegate)); + QTRY_VERIFY(findViewDelegateItem(dialogHelper.fileDialogListView, 1, tempFile1Delegate)); COMPARE_URL(tempFile1Delegate->file(), QUrl::fromLocalFile(tempFile1->fileName())); QVERIFY(clickButton(tempFile1Delegate)); COMPARE_URL(dialogHelper.dialog->currentFile(), QUrl::fromLocalFile(tempFile1->fileName())); @@ -1180,7 +1210,7 @@ void tst_QQuickFileDialogImpl::fileMode() // Only the OpenFiles mode should allow multiple files to be selected, however. QQuickFileDialogDelegate *tempFile2Delegate = nullptr; - QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 2, tempFile2Delegate)); + QTRY_VERIFY(findViewDelegateItem(dialogHelper.fileDialogListView, 2, tempFile2Delegate)); COMPARE_URL(tempFile2Delegate->file(), QUrl::fromLocalFile(tempFile2->fileName())); QTest::keyPress(dialogHelper.window(), Qt::Key_Shift); QVERIFY(clickButton(tempFile2Delegate)); @@ -1199,7 +1229,6 @@ void tst_QQuickFileDialogImpl::fileMode() } // Get the text edit visible with Ctrl+L. - const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L); QTest::keySequence(dialogHelper.window(), editPathKeySequence); auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>(); QVERIFY(breadcrumbBar); @@ -1252,25 +1281,20 @@ void tst_QQuickFileDialogImpl::defaultSuffix() QVERIFY(tempFile1.open(QIODevice::ReadWrite)); // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "bindCurrentFolder.qml", {}, + FileDialogTestHelper dialogHelper(this, "bindCurrentFolder.qml", {}, {{ "initialFolder", QUrl::fromLocalFile(tempSubSubDir.path()) }}); dialogHelper.dialog->setDefaultSuffix(defaultSuffix); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + OPEN_QUICK_DIALOG(); COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempSubSubDir.path())); // There should be one extension-less file: "file1". - auto fileDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("fileDialogListView"); - QVERIFY(fileDialogListView); QString failureMessage; const QStringList expectedVisibleFiles = { tempFile1.fileName() }; - QTRY_VERIFY2(verifyFileDialogDelegates(fileDialogListView, expectedVisibleFiles, failureMessage), qPrintable(failureMessage)); + QTRY_VERIFY2(verifyFileDialogDelegates(dialogHelper.fileDialogListView, expectedVisibleFiles, failureMessage), qPrintable(failureMessage)); // Choose the delegate. The suffix should be added to the delegates. QQuickFileDialogDelegate *file1Delegate = nullptr; - QTRY_VERIFY(findViewDelegateItem(fileDialogListView, 0, file1Delegate)); + QTRY_VERIFY(findViewDelegateItem(dialogHelper.fileDialogListView, 0, file1Delegate)); COMPARE_URL(file1Delegate->file(), QUrl::fromLocalFile(tempFile1.fileName())); QVERIFY(doubleClickButton(file1Delegate)); QVERIFY(!dialogHelper.dialog->isVisible()); @@ -1293,11 +1317,8 @@ void tst_QQuickFileDialogImpl::done() QFETCH(QQuickFileDialog::StandardCode, result); // Open the dialog. - DialogTestHelper<QQuickFileDialog, QQuickFileDialogImpl> dialogHelper(this, "fileDialog.qml"); - QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); - QVERIFY(dialogHelper.waitForWindowActive()); - QVERIFY(dialogHelper.openDialog()); - QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + FileDialogTestHelper dialogHelper(this, "fileDialog.qml"); + OPEN_QUICK_DIALOG(); switch (result) { case QQuickFileDialog::Accepted: @@ -1313,6 +1334,70 @@ void tst_QQuickFileDialogImpl::done() QCOMPARE(dialogHelper.dialog->result(), result); } +void tst_QQuickFileDialogImpl::setSelectedFile_data() +{ + fileMode_data(); +} + +void tst_QQuickFileDialogImpl::setSelectedFile() +{ + QFETCH(QQuickFileDialog::FileMode, fileMode); + + // Open the dialog. + const auto tempFile1Url = QUrl::fromLocalFile(tempFile1->fileName()); + const QVariantMap initialProperties = { + { "tempFile1Url", QVariant::fromValue(tempFile1Url) }, + { "fileMode", QVariant::fromValue(fileMode) } + }; + FileDialogTestHelper dialogHelper( + this, "setSelectedFile.qml", {}, initialProperties); + OPEN_QUICK_DIALOG(); + + // The selected file should be what we set. + VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempDir.path()), tempFile1Url, 1); + + // Select the next file in the view by navigating with the down key. + // We know it already has focus, as VERIFY_FILE_SELECTED_AND_FOCUSED checks that. + QTest::keyClick(dialogHelper.window(), Qt::Key_Down); + const auto tempFile2Url = QUrl::fromLocalFile(tempFile2->fileName()); + COMPARE_URL(dialogHelper.quickDialog->selectedFile(), tempFile2Url); + COMPARE_URL(dialogHelper.dialog->selectedFile(), tempFile2Url); + + // Select the delegate by pressing enter. + QTest::keyClick(dialogHelper.window(), Qt::Key_Return); + COMPARE_URL(dialogHelper.dialog->selectedFile(), tempFile2Url); + COMPARE_URLS(dialogHelper.dialog->selectedFiles(), { tempFile2Url }); + QVERIFY(!dialogHelper.dialog->isVisible()); + QTRY_VERIFY(!dialogHelper.quickDialog->isVisible()); + QCOMPARE(dialogHelper.dialog->result(), QQuickFileDialog::Accepted); + + // Set a different initial selectedFile and re-open. + dialogHelper.dialog->setSelectedFile(QUrl::fromLocalFile(tempFile1->fileName())); + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempDir.path()), tempFile1Url, 1); + + // Close it. + dialogHelper.dialog->close(); + QVERIFY(!dialogHelper.dialog->isVisible()); + QTRY_VERIFY(!dialogHelper.quickDialog->isVisible()); + + // Try to set an invalid selectedFile; it should be a no-op for modes other than SaveFile, + // and the previous selectedFile should still be selected. + const QString invalidPath = tempDir.path() + "/does-not-exist.txt"; + if (fileMode != QQuickFileDialog::SaveFile) { + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QLatin1String(".*QML FileDialog: Cannot set ") + + invalidPath + QLatin1String(" as a selected file because it doesn't exist"))); + } + dialogHelper.dialog->setSelectedFile(QUrl::fromLocalFile(invalidPath)); + QVERIFY(dialogHelper.openDialog()); + if (fileMode != QQuickFileDialog::SaveFile) { + VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempDir.path()), tempFile1Url, 1); + } else { + QTRY_COMPARE(dialogHelper.fileDialogListView->currentIndex(), -1); + } +} + QTEST_MAIN(tst_QQuickFileDialogImpl) #include "tst_qquickfiledialogimpl.moc" diff --git a/tests/auto/quickdialogs/qquickfontdialogimpl/tst_qquickfontdialogimpl.cpp b/tests/auto/quickdialogs/qquickfontdialogimpl/tst_qquickfontdialogimpl.cpp index 1bfb8b6199..a4c4f6aab5 100644 --- a/tests/auto/quickdialogs/qquickfontdialogimpl/tst_qquickfontdialogimpl.cpp +++ b/tests/auto/quickdialogs/qquickfontdialogimpl/tst_qquickfontdialogimpl.cpp @@ -133,8 +133,11 @@ private: QVERIFY2(closePopup(&dialogHelper, BUTTON, errorMessage), qPrintable(errorMessage)); \ QTRY_VERIFY(!dialogHelper.quickDialog->isVisible()); +// We don't want to fail on warnings until QTBUG-98964 is fixed, +// as we deliberately prevent deferred execution in some of the tests here, +// which causes warnings. tst_QQuickFontDialogImpl::tst_QQuickFontDialogImpl() - : QQmlDataTest(QT_QMLTEST_DATADIR) + : QQmlDataTest(QT_QMLTEST_DATADIR, FailOnWarningsPolicy::DoNotFailOnWarnings) { } diff --git a/tests/auto/quickdialogs/qquickmessagedialogimpl/tst_qquickmessagedialogimpl.cpp b/tests/auto/quickdialogs/qquickmessagedialogimpl/tst_qquickmessagedialogimpl.cpp index 4123772317..f7edd2dd70 100644 --- a/tests/auto/quickdialogs/qquickmessagedialogimpl/tst_qquickmessagedialogimpl.cpp +++ b/tests/auto/quickdialogs/qquickmessagedialogimpl/tst_qquickmessagedialogimpl.cpp @@ -77,7 +77,13 @@ private slots: void detailedText(); }; -tst_QQuickMessageDialogImpl::tst_QQuickMessageDialogImpl() : QQmlDataTest(QT_QMLTEST_DATADIR) { } +// We don't want to fail on warnings until QTBUG-98964 is fixed, +// as we deliberately prevent deferred execution in some of the tests here, +// which causes warnings. +tst_QQuickMessageDialogImpl::tst_QQuickMessageDialogImpl() + : QQmlDataTest(QT_QMLTEST_DATADIR, FailOnWarningsPolicy::DoNotFailOnWarnings) +{ +} void tst_QQuickMessageDialogImpl::changeText_data() { diff --git a/tests/auto/quicktest/signalspy/tst_signalspy.cpp b/tests/auto/quicktest/signalspy/tst_signalspy.cpp index 6ae099845e..120e4394e1 100644 --- a/tests/auto/quicktest/signalspy/tst_signalspy.cpp +++ b/tests/auto/quicktest/signalspy/tst_signalspy.cpp @@ -79,7 +79,7 @@ void tst_SignalSpy::testCount() window.resize(200, 200); window.setSource(url); window.show(); - QVERIFY(QTest::qWaitForWindowActive(&window)); + QVERIFY(QTest::qWaitForWindowExposed(&window)); QVERIFY(window.rootObject() != nullptr); QObject *mouseSpy = window.rootObject()->findChild<QObject *>("mouseSpy"); diff --git a/tests/auto/quickwidgets/qquickwidget/BLACKLIST b/tests/auto/quickwidgets/qquickwidget/BLACKLIST index 44ab3e9397..095e9ee484 100644 --- a/tests/auto/quickwidgets/qquickwidget/BLACKLIST +++ b/tests/auto/quickwidgets/qquickwidget/BLACKLIST @@ -3,5 +3,3 @@ opensuse-42.3 opensuse-leap [enterLeave] macos -[synthMouseFromTouch] # QTBUG-86729 -* diff --git a/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp b/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp index 35187feaf8..1ef70e6515 100644 --- a/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp +++ b/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp @@ -86,17 +86,18 @@ public: class MouseRecordingItem : public QQuickItem { public: - MouseRecordingItem(bool acceptTouch, QQuickItem *parent = nullptr) + MouseRecordingItem(bool acceptTouch, bool acceptTouchPress, QQuickItem *parent = nullptr) : QQuickItem(parent) - , m_acceptTouch(acceptTouch) + , m_acceptTouchPress(acceptTouchPress) { setSize(QSizeF(300, 300)); setAcceptedMouseButtons(Qt::LeftButton); + setAcceptTouchEvents(acceptTouch); } protected: void touchEvent(QTouchEvent* event) override { - event->setAccepted(m_acceptTouch); + event->setAccepted(m_acceptTouchPress); m_touchEvents << event->type(); qCDebug(lcTests) << "accepted?" << event->isAccepted() << event; } @@ -118,7 +119,7 @@ public: QList<QEvent::Type> m_touchEvents; private: - bool m_acceptTouch; + bool m_acceptTouchPress; }; class tst_qquickwidget : public QQmlDataTest @@ -625,7 +626,7 @@ void tst_qquickwidget::synthMouseFromTouch() QWidget window; window.setAttribute(Qt::WA_AcceptTouchEvents); QScopedPointer<MouseRecordingQQWidget> childView(new MouseRecordingQQWidget(&window)); - MouseRecordingItem *item = new MouseRecordingItem(acceptTouch, nullptr); + MouseRecordingItem *item = new MouseRecordingItem(!synthMouse, acceptTouch, nullptr); childView->setContent(QUrl(), nullptr, item); window.resize(300, 300); childView->resize(300, 300); @@ -640,8 +641,8 @@ void tst_qquickwidget::synthMouseFromTouch() QTest::touchEvent(&window, device).move(0, p2, &window); QTest::touchEvent(&window, device).release(0, p2, &window); - QCOMPARE(item->m_touchEvents.count(), !synthMouse && !acceptTouch ? 1 : 3); - QCOMPARE(item->m_mouseEvents.count(), (acceptTouch || !synthMouse) ? 0 : 3); + QCOMPARE(item->m_touchEvents.count(), synthMouse ? 0 : (acceptTouch ? 3 : 1)); + QCOMPARE(item->m_mouseEvents.count(), synthMouse ? 3 : 0); QCOMPARE(childView->m_mouseEvents.count(), 0); for (const auto &ev : item->m_mouseEvents) QCOMPARE(ev, Qt::MouseEventSynthesizedByQt); |