diff options
Diffstat (limited to 'tests/auto')
124 files changed, 3225 insertions, 303 deletions
diff --git a/tests/auto/qml/ecmascripttests/TestExpectations b/tests/auto/qml/ecmascripttests/TestExpectations index 75fdd1cb0c..7c671040a3 100644 --- a/tests/auto/qml/ecmascripttests/TestExpectations +++ b/tests/auto/qml/ecmascripttests/TestExpectations @@ -249,8 +249,6 @@ built-ins/String/prototype/toLocaleLowerCase/special_casing_conditional.js fails built-ins/String/prototype/toLowerCase/Final_Sigma_U180E.js fails built-ins/String/prototype/toLowerCase/special_casing_conditional.js fails built-ins/TypedArray/prototype/constructor.js fails -built-ins/TypedArray/prototype/fill/fill-values-conversion-operations-consistent-nan.js fails -built-ins/TypedArray/prototype/slice/bit-precision.js fails built-ins/TypedArray/prototype/sort/arraylength-internal.js fails built-ins/TypedArray/prototype/sort/comparefn-call-throws.js fails built-ins/TypedArray/prototype/sort/comparefn-calls.js fails diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index 2c5dc7af43..f9bd5c28aa 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -314,6 +314,11 @@ private slots: void deleteDefineCycle(); void deleteFromSparseArray(); + void generatorFunctionInTailCallPosition(); + void generatorMethodInTailCallPosition(); + + void consoleLogSequence(); + public: Q_INVOKABLE QJSValue throwingCppMethod1(); Q_INVOKABLE void throwingCppMethod2(); @@ -6345,6 +6350,71 @@ void tst_QJSEngine::deleteFromSparseArray() QVERIFY(result.property(20000).isUndefined()); } +void tst_QJSEngine::generatorFunctionInTailCallPosition() { + QJSEngine engine; + QJSValue result = engine.evaluate(R"( + "use strict"; + function* gen() { + yield 0; + } + function caller() { return gen(); } + caller(); + )"); + + QVERIFY(!result.isError()); + QVERIFY(!result.isUndefined()); +} + +void tst_QJSEngine::generatorMethodInTailCallPosition() { + QJSEngine engine; + QJSValue result = engine.evaluate(R"( + "use strict"; + class Class { + *gen() { + yield 0; + } + + caller() { return this.gen(); } + } + var c = new Class(); + c.caller(); + )"); + + QVERIFY(!result.isError()); + QVERIFY(!result.isUndefined()); +} + +static unsigned stringListFetchCount = 0; +class StringListProvider : public QObject +{ + Q_OBJECT + Q_PROPERTY(QStringList strings READ strings CONSTANT) + +public: + QStringList strings() const + { + ++stringListFetchCount; + QStringList ret; + for (int i = 0; i < 10; ++i) + ret.append(QString::number(i)); + return ret; + } +}; + +void tst_QJSEngine::consoleLogSequence() +{ + QJSEngine engine; + engine.installExtensions(QJSEngine::ConsoleExtension); + + engine.globalObject().setProperty( + QStringLiteral("object"), engine.newQObject(new StringListProvider)); + + QTest::ignoreMessage(QtDebugMsg, "[0,1,2,3,4,5,6,7,8,9]"); + + engine.evaluate(QStringLiteral("console.log(object.strings)")); + QCOMPARE(stringListFetchCount, 1); +} + QTEST_MAIN(tst_QJSEngine) #include "tst_qjsengine.moc" diff --git a/tests/auto/qml/qmlformat/data/enumWithValues.formatted.qml b/tests/auto/qml/qmlformat/data/enumWithValues.formatted.qml new file mode 100644 index 0000000000..bbf978936f --- /dev/null +++ b/tests/auto/qml/qmlformat/data/enumWithValues.formatted.qml @@ -0,0 +1,12 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Rectangle { + enum AxisAlignment { + Bottom = 0, + Left = 1, + Right = 2 + } +} diff --git a/tests/auto/qml/qmlformat/data/enumWithValues.qml b/tests/auto/qml/qmlformat/data/enumWithValues.qml new file mode 100644 index 0000000000..2dbe7fbac5 --- /dev/null +++ b/tests/auto/qml/qmlformat/data/enumWithValues.qml @@ -0,0 +1,12 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Rectangle{ +enum AxisAlignment{ +Bottom = 0, +Left = 1, +Right = 2 +} +} diff --git a/tests/auto/qml/qmlformat/tst_qmlformat.cpp b/tests/auto/qml/qmlformat/tst_qmlformat.cpp index 0c3a2a276c..877cd92f95 100644 --- a/tests/auto/qml/qmlformat/tst_qmlformat.cpp +++ b/tests/auto/qml/qmlformat/tst_qmlformat.cpp @@ -333,6 +333,9 @@ void TestQmlformat::testFormat_data() QTest::newRow("javascriptBlock") << "javascriptBlock.qml" << "javascriptBlock.formatted.qml" << QStringList{} << RunOption::OnCopy; + QTest::newRow("enumWithValues") + << "enumWithValues.qml" + << "enumWithValues.formatted.qml" << QStringList{} << RunOption::OnCopy; } void TestQmlformat::testFormat() diff --git a/tests/auto/qml/qmllint/CMakeLists.txt b/tests/auto/qml/qmllint/CMakeLists.txt index 422e7a08b3..dafac26475 100644 --- a/tests/auto/qml/qmllint/CMakeLists.txt +++ b/tests/auto/qml/qmllint/CMakeLists.txt @@ -48,3 +48,10 @@ if (TARGET qmljsrootgen) QT_QMLJSROOTGEN_PRESENT ) endif() + +if(QT_FEATURE_process) + add_subdirectory(importRelScript) + add_dependencies(tst_qmllint tst_qmllint_import_rel_script) + target_compile_definitions(tst_qmllint PUBLIC + TST_QMLLINT_IMPORT_REL_SCRIPT_ARGS="@${CMAKE_CURRENT_BINARY_DIR}/importRelScript/.rcc/qmllint/tst_qmllint_import_rel_script.rsp") +endif() diff --git a/tests/auto/qml/qmllint/data/something.qml b/tests/auto/qml/qmllint/data/something.qml new file mode 100644 index 0000000000..38998f606d --- /dev/null +++ b/tests/auto/qml/qmllint/data/something.qml @@ -0,0 +1,2 @@ +import ModuleInImportPath +A {} diff --git a/tests/auto/qml/qmllint/importRelScript/CMakeLists.txt b/tests/auto/qml/qmllint/importRelScript/CMakeLists.txt new file mode 100644 index 0000000000..4b8f26b5bf --- /dev/null +++ b/tests/auto/qml/qmllint/importRelScript/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_policy(SET QTP0001 NEW) + +set(CMAKE_AUTOMOC ON) + +qt_add_qml_module(tst_qmllint_import_rel_script + URI ImportRelScript + QML_FILES + Main.qml + script.js + PLUGIN_TARGET + tst_qmllint_import_rel_script +) + +qt_autogen_tools_initial_setup(tst_qmllint_import_rel_script) +add_dependencies(tst_qmllint_import_rel_script Qt::qmllint) diff --git a/tests/auto/qml/qmllint/importRelScript/Main.qml b/tests/auto/qml/qmllint/importRelScript/Main.qml new file mode 100644 index 0000000000..341f1d730c --- /dev/null +++ b/tests/auto/qml/qmllint/importRelScript/Main.qml @@ -0,0 +1,6 @@ +import QtQml +import "script.js" as JS + +QtObject { + Component.onCompleted: JS.f() +} diff --git a/tests/auto/qml/qmllint/importRelScript/script.js b/tests/auto/qml/qmllint/importRelScript/script.js new file mode 100644 index 0000000000..27b14ed35c --- /dev/null +++ b/tests/auto/qml/qmllint/importRelScript/script.js @@ -0,0 +1,3 @@ +function f() { + return 1 +} diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp index c18c9ff326..56e31dba8f 100644 --- a/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -97,10 +97,17 @@ private Q_SLOTS: void testLineEndings(); void ignoreSettingsNotCommandLineOptions(); + void backslashedQmldirPath(); + #if QT_CONFIG(library) void testPlugin(); void quickPlugin(); #endif + +#if QT_CONFIG(process) + void importRelScript(); +#endif + private: enum DefaultImportOption { NoDefaultImports, UseDefaultImports }; enum ContainOption { StringNotContained, StringContained }; @@ -2076,5 +2083,25 @@ void TestQmllint::ignoreSettingsNotCommandLineOptions() QCOMPARE(output, QString()); } +void TestQmllint::backslashedQmldirPath() +{ + const QString qmldirPath + = testFile(u"ImportPath/ModuleInImportPath/qmldir"_s).replace('/', QDir::separator()); + const QString output = runQmllint( + testFile(u"something.qml"_s), true, QStringList{ u"-i"_s, qmldirPath }); + QVERIFY(output.isEmpty()); +} + +#if QT_CONFIG(process) +void TestQmllint::importRelScript() +{ + QProcess proc; + proc.start(m_qmllintPath, { QStringLiteral(TST_QMLLINT_IMPORT_REL_SCRIPT_ARGS) }); + QVERIFY(proc.waitForFinished()); + QVERIFY(proc.readAllStandardOutput().isEmpty()); + QVERIFY(proc.readAllStandardError().isEmpty()); +} +#endif + QTEST_MAIN(TestQmllint) #include "tst_qmllint.moc" diff --git a/tests/auto/qml/qqmlcomponent/data/bindingInRequired.qml b/tests/auto/qml/qqmlcomponent/data/bindingInRequired.qml new file mode 100644 index 0000000000..03d6d0f805 --- /dev/null +++ b/tests/auto/qml/qqmlcomponent/data/bindingInRequired.qml @@ -0,0 +1,16 @@ +import QtQml + +QtObject { + + property Component object: QtObject { + property QtObject obj + } + + property QtObject outer + property QtObject inner + + Component.onCompleted: { + inner = object.createObject(this, { obj: null }) + outer = object.createObject(this, { obj: Qt.binding(() => inner) }) + } +} diff --git a/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp b/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp index 288b4d1a01..1ccf7a6f23 100644 --- a/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp +++ b/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp @@ -148,6 +148,7 @@ private slots: void loadFromQrc(); void removeBinding(); void complexObjectArgument(); + void bindingInRequired(); private: QQmlEngine engine; @@ -1506,6 +1507,24 @@ void tst_qqmlcomponent::complexObjectArgument() QCOMPARE(o->objectName(), QStringLiteral("26 - 25")); } +void tst_qqmlcomponent::bindingInRequired() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("bindingInRequired.qml")); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + QObject *outer = object->property("outer").value<QObject *>(); + QVERIFY(outer); + + QObject *inner = object->property("inner").value<QObject *>(); + QVERIFY(inner); + + QCOMPARE(inner, outer->property("obj").value<QObject *>()); + QVERIFY(!inner->property("obj").value<QObject *>()); +} + QTEST_MAIN(tst_qqmlcomponent) #include "tst_qqmlcomponent.moc" diff --git a/tests/auto/qml/qqmldelegatemodel/CMakeLists.txt b/tests/auto/qml/qqmldelegatemodel/CMakeLists.txt index 705ee6f357..edf0a1165b 100644 --- a/tests/auto/qml/qqmldelegatemodel/CMakeLists.txt +++ b/tests/auto/qml/qqmldelegatemodel/CMakeLists.txt @@ -23,6 +23,7 @@ qt_internal_add_test(tst_qqmldelegatemodel Qt::QmlModelsPrivate Qt::QmlPrivate Qt::Quick + Qt::QuickPrivate Qt::QuickTestUtilsPrivate TESTDATA ${test_data} ) diff --git a/tests/auto/qml/qqmldelegatemodel/data/reset.qml b/tests/auto/qml/qqmldelegatemodel/data/reset.qml new file mode 100644 index 0000000000..0fcd5e8afa --- /dev/null +++ b/tests/auto/qml/qqmldelegatemodel/data/reset.qml @@ -0,0 +1,28 @@ +import QtQuick +import Test + +Window { + id: root + width: 200 + height: 200 + + property alias listView: listView + + ResettableModel { + id: resetModel + } + + ListView { + id: listView + anchors.fill: parent + model: resetModel + + delegate: Rectangle { + implicitWidth: 100 + implicitHeight: 50 + color: "olivedrab" + + required property string display + } + } +} diff --git a/tests/auto/qml/qqmldelegatemodel/data/resetInQAIMConstructor.qml b/tests/auto/qml/qqmldelegatemodel/data/resetInQAIMConstructor.qml new file mode 100644 index 0000000000..cb1f226737 --- /dev/null +++ b/tests/auto/qml/qqmldelegatemodel/data/resetInQAIMConstructor.qml @@ -0,0 +1,28 @@ +import QtQuick +import Test + +Window { + id: root + width: 200 + height: 200 + + property alias listView: listView + + ResetInConstructorModel { + id: resetInConstructorModel + } + + ListView { + id: listView + anchors.fill: parent + model: resetInConstructorModel + + delegate: Rectangle { + implicitWidth: 100 + implicitHeight: 50 + color: "olivedrab" + + required property string display + } + } +} diff --git a/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp b/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp index 1f85f68891..e9c22ca1e6 100644 --- a/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp +++ b/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp @@ -3,6 +3,7 @@ #include <QtTest/qtest.h> #include <QtCore/QConcatenateTablesProxyModel> +#include <QtCore/qtimer.h> #include <QtGui/QStandardItemModel> #include <QtQml/qqmlcomponent.h> #include <QtQml/qqmlapplicationengine.h> @@ -10,9 +11,16 @@ #include <QtQmlModels/private/qqmllistmodel_p.h> #include <QtQuick/qquickview.h> #include <QtQuick/qquickitem.h> +#include <QtQuick/private/qquickitemview_p_p.h> +#include <QtQuick/private/qquicklistview_p.h> +#include <QtQuickTest/quicktest.h> #include <QtQuickTestUtils/private/qmlutils_p.h> +#include <QtQuickTestUtils/private/visualtestutils_p.h> #include <QtTest/QSignalSpy> + +using namespace QQuickVisualTestUtils; + class tst_QQmlDelegateModel : public QQmlDataTest { Q_OBJECT @@ -22,6 +30,8 @@ public: private slots: void resettingRolesRespected(); + void resetInQAIMConstructor(); + void reset(); void valueWithoutCallingObjectFirst_data(); void valueWithoutCallingObjectFirst(); void qtbug_86017(); @@ -36,16 +46,9 @@ private slots: void clearCacheDuringInsertion(); }; -class AbstractItemModel : public QAbstractItemModel +class BaseAbstractItemModel : public QAbstractItemModel { - Q_OBJECT public: - AbstractItemModel() - { - for (int i = 0; i < 3; ++i) - mValues.append(QString::fromLatin1("Item %1").arg(i)); - } - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override { if (parent.isValid()) @@ -83,10 +86,21 @@ public: return mValues.at(index.row()); } -private: +protected: QVector<QString> mValues; }; +class AbstractItemModel : public BaseAbstractItemModel +{ + Q_OBJECT +public: + AbstractItemModel() + { + for (int i = 0; i < 3; ++i) + mValues.append(QString::fromLatin1("Item %1").arg(i)); + } +}; + tst_QQmlDelegateModel::tst_QQmlDelegateModel() : QQmlDataTest(QT_QMLTEST_DATADIR) { @@ -145,7 +159,109 @@ void tst_QQmlDelegateModel::resettingRolesRespected() QObject *root = engine.rootObjects().constFirst(); QVERIFY(!root->property("success").toBool()); model->change(); - QTRY_VERIFY(root->property("success").toBool()); + QTRY_VERIFY_WITH_TIMEOUT(root->property("success").toBool(), 100); +} + +class ResetInConstructorModel : public BaseAbstractItemModel +{ + Q_OBJECT + QML_ELEMENT + +public: + ResetInConstructorModel() + { + beginResetModel(); + QTimer::singleShot(0, this, &ResetInConstructorModel::finishReset); + } + +private: + void finishReset() + { + mValues.append("First"); + endResetModel(); + } +}; + +void tst_QQmlDelegateModel::resetInQAIMConstructor() +{ + qmlRegisterTypesAndRevisions<ResetInConstructorModel>("Test", 1); + + QQuickApplicationHelper helper(this, "resetInQAIMConstructor.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + auto *listView = window->property("listView").value<QQuickListView *>(); + QVERIFY(listView); + QTRY_VERIFY_WITH_TIMEOUT(listView->itemAtIndex(0), 100); + QQuickItem *firstDelegateItem = listView->itemAtIndex(0); + QVERIFY(firstDelegateItem); + QCOMPARE(firstDelegateItem->property("display").toString(), "First"); +} + +class ResettableModel : public BaseAbstractItemModel +{ + Q_OBJECT + QML_ELEMENT + +public: + ResettableModel() + { + mValues.append("First"); + } + + void callBeginResetModel() + { + beginResetModel(); + mValues.clear(); + } + + void appendData() + { + mValues.append(QString::fromLatin1("Item %1").arg(mValues.size())); + } + + void callEndResetModel() + { + endResetModel(); + } +}; + +// Tests that everything works as expected when calling beginResetModel/endResetModel +// after the QAIM subclass constructor has run. +void tst_QQmlDelegateModel::reset() +{ + qmlRegisterTypesAndRevisions<ResettableModel>("Test", 1); + + QQuickApplicationHelper helper(this, "reset.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + auto *listView = window->property("listView").value<QQuickListView *>(); + QVERIFY(listView); + QQuickItem *firstDelegateItem = listView->itemAtIndex(0); + QVERIFY(firstDelegateItem); + QCOMPARE(firstDelegateItem->property("display").toString(), "First"); + + const auto delegateModel = QQuickItemViewPrivate::get(listView)->model; + QSignalSpy rootIndexChangedSpy(delegateModel, SIGNAL(rootIndexChanged())); + QVERIFY(rootIndexChangedSpy.isValid()); + + auto *model = listView->model().value<ResettableModel *>(); + model->callBeginResetModel(); + model->appendData(); + model->callEndResetModel(); + // This is verifies that handleModelReset isn't called + // more than once during this process, since it unconditionally emits rootIndexChanged. + QCOMPARE(rootIndexChangedSpy.count(), 1); + + QTRY_VERIFY_WITH_TIMEOUT(listView->itemAtIndex(0), 100); + firstDelegateItem = listView->itemAtIndex(0); + QVERIFY(firstDelegateItem); + QCOMPARE(firstDelegateItem->property("display").toString(), "Item 0"); } void tst_QQmlDelegateModel::valueWithoutCallingObjectFirst_data() diff --git a/tests/auto/qml/qqmlecmascript/data/lookupsDoNotBypassProxy.qml b/tests/auto/qml/qqmlecmascript/data/lookupsDoNotBypassProxy.qml new file mode 100644 index 0000000000..321bd21ad8 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/lookupsDoNotBypassProxy.qml @@ -0,0 +1,29 @@ +import QtQml + +QtObject { + function test_proxy() { + let base = { + id: 'baseid', + name: 'basename', + length: 42 + }; + + let handler = { + get: function (ao, prop) { + return Reflect.get(ao, prop); + } + }; + + let r = new Proxy(base, handler); + let validCount = 0; + if (r.id === base.id) + ++validCount; + if (r.length === base.length) + ++validCount; + if (r.name === base.name) + ++validCount; + return validCount; + } + + property int result: test_proxy() +} diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index 4023824f75..63655fa60d 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -400,6 +400,7 @@ private slots: void sequenceConversionMethod(); void proxyIteration(); void proxyHandlerTraps(); + void lookupsDoNotBypassProxy(); void gcCrashRegressionTest(); void cmpInThrows(); void frozenQObject(); @@ -921,7 +922,9 @@ void tst_qqmlecmascript::bindingLoop() { QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("bindingLoop.qml")); - QString warning = component.url().toString() + ":9:9: QML MyQmlObject: Binding loop detected for property \"stringProperty\""; + const auto urlString = component.url().toString(); + const QString warning = urlString + ":9:9: QML MyQmlObject: Binding loop detected for property \"stringProperty\":\n" + + urlString + ":11:13"; QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData()); QScopedPointer<QObject> object(component.create()); QVERIFY2(object, qPrintable(component.errorString())); @@ -10056,6 +10059,17 @@ void tst_qqmlecmascript::proxyHandlerTraps() QVERIFY(value.isString() && value.toString() == QStringLiteral("SUCCESS")); } +void tst_qqmlecmascript::lookupsDoNotBypassProxy() +{ + QQmlEngine engine; + // we need a component to have a proper compilation to byte code; + // otherwise, we don't actually end up with lookups + QQmlComponent comp(&engine, testFileUrl("lookupsDoNotBypassProxy.qml")); + QVERIFY(comp.isReady()); + std::unique_ptr<QObject> obj { comp.create() }; + QCOMPARE(obj->property("result").toInt(), 3); +} + void tst_qqmlecmascript::cmpInThrows() { QJSEngine engine; diff --git a/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp index 00d28cfe60..b82a1f4174 100644 --- a/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp +++ b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp @@ -1337,6 +1337,8 @@ void tst_qqmlengine::createComponentOnSingletonDestruction() void tst_qqmlengine::uiLanguage() { + const QRegularExpression bindingLoopWarningRegex(".*QML QtObject: Binding loop detected for property \"textToTranslate\".*"); + { QQmlEngine engine; @@ -1348,19 +1350,19 @@ void tst_qqmlengine::uiLanguage() QQmlComponent component(&engine, testFileUrl("uiLanguage.qml")); - QTest::ignoreMessage(QtMsgType::QtWarningMsg, (component.url().toString() + ":2:1: QML QtObject: Binding loop detected for property \"textToTranslate\"").toLatin1()); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, bindingLoopWarningRegex); QScopedPointer<QObject> object(component.create()); QVERIFY(!object.isNull()); QVERIFY(engine.uiLanguage().isEmpty()); QCOMPARE(object->property("numberOfTranslationBindingEvaluations").toInt(), 1); - QTest::ignoreMessage(QtMsgType::QtWarningMsg, (component.url().toString() + ":2:1: QML QtObject: Binding loop detected for property \"textToTranslate\"").toLatin1()); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, bindingLoopWarningRegex); engine.setUiLanguage("TestLanguage"); QCOMPARE(object->property("numberOfTranslationBindingEvaluations").toInt(), 2); QCOMPARE(object->property("chosenLanguage").toString(), "TestLanguage"); - QTest::ignoreMessage(QtMsgType::QtWarningMsg, (component.url().toString() + ":2:1: QML QtObject: Binding loop detected for property \"textToTranslate\"").toLatin1()); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, bindingLoopWarningRegex); engine.evaluate("Qt.uiLanguage = \"anotherLanguage\""); QCOMPARE(engine.uiLanguage(), QString("anotherLanguage")); QCOMPARE(object->property("numberOfTranslationBindingEvaluations").toInt(), 3); @@ -1371,7 +1373,7 @@ void tst_qqmlengine::uiLanguage() QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("uiLanguage.qml")); - QTest::ignoreMessage(QtMsgType::QtWarningMsg, (component.url().toString() + ":2:1: QML QtObject: Binding loop detected for property \"textToTranslate\"").toLatin1()); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, bindingLoopWarningRegex); QScopedPointer<QObject> object(component.create()); QVERIFY(!object.isNull()); diff --git a/tests/auto/qml/qqmlinfo/tst_qqmlinfo.cpp b/tests/auto/qml/qqmlinfo/tst_qqmlinfo.cpp index 4908ca210b..46154640d0 100644 --- a/tests/auto/qml/qqmlinfo/tst_qqmlinfo.cpp +++ b/tests/auto/qml/qqmlinfo/tst_qqmlinfo.cpp @@ -220,7 +220,8 @@ void tst_qqmlinfo::attachedObject() const QString qmlBindingLoopMessageFull = component.url().toString() + ":8:9: " + qmlBindingLoopMessage; QTest::ignoreMessage(QtWarningMsg, qPrintable(qmlBindingLoopMessageFull)); - const QString cppBindingLoopMessage = "QML AttachedObject (parent or ancestor of Attached): Binding loop detected for property \"a\""; + const QString cppBindingLoopMessage = "QML AttachedObject (parent or ancestor of Attached): Binding loop detected for property \"a\":\n" + + component.url().toString() + ":5:5"; const QString cppBindingLoopMessageFull = component.url().toString() + ":4:1: " + cppBindingLoopMessage; QTest::ignoreMessage(QtWarningMsg, qPrintable(cppBindingLoopMessageFull)); diff --git a/tests/auto/qml/qqmllanguage/CMakeLists.txt b/tests/auto/qml/qqmllanguage/CMakeLists.txt index e07f741bf6..fc9bbf17df 100644 --- a/tests/auto/qml/qqmllanguage/CMakeLists.txt +++ b/tests/auto/qml/qqmllanguage/CMakeLists.txt @@ -27,6 +27,8 @@ qt_internal_add_test(tst_qqmllanguage TESTDATA ${test_data} ) +add_subdirectory(testhelper) + #### Keys ignored in scope 1:.:.:qqmllanguage.pro:<TRUE>: # OTHER_FILES = "data/readonlyObjectProperty.qml" # QML_IMPORT_NAME = "StaticTest" diff --git a/tests/auto/qml/qqmllanguage/data/BindingOverrider.qml b/tests/auto/qml/qqmllanguage/data/BindingOverrider.qml new file mode 100644 index 0000000000..de7e1e96a3 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/BindingOverrider.qml @@ -0,0 +1,5 @@ +import QtQml 2.15 + +SimpleWidget { + width: 20 +} diff --git a/tests/auto/qml/qqmllanguage/data/SimpleWidget.qml b/tests/auto/qml/qqmllanguage/data/SimpleWidget.qml new file mode 100644 index 0000000000..9150ebaa4e --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/SimpleWidget.qml @@ -0,0 +1,25 @@ +import QtQuick 2.15 + +Item { + id: outer + + property real innerWidth: 0 + + Item { + id: inner + width: style.width + onWidthChanged: outer.innerWidth = width + } + + width: inner.width + + onWidthChanged: { + if (width !== inner.width) + inner.width = width // overwrite binding + } + + QtObject { + id: style + property int width: 50 + } +} diff --git a/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/SingletonWithRequired1.qml b/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/SingletonWithRequired1.qml new file mode 100644 index 0000000000..f549e851a3 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/SingletonWithRequired1.qml @@ -0,0 +1,6 @@ +pragma Singleton +import QtQml + +QtObject { + required property int i +} diff --git a/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/SingletonWithRequired2.qml b/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/SingletonWithRequired2.qml new file mode 100644 index 0000000000..1f9e7e3a42 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/SingletonWithRequired2.qml @@ -0,0 +1,8 @@ +pragma Singleton +import QtQml + +QtObject { + property QtObject o: QtObject { + required property int i + } +} diff --git a/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/qmldir b/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/qmldir new file mode 100644 index 0000000000..46e397ca76 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/qmldir @@ -0,0 +1,4 @@ +module SingletonWithRequiredProperties + +singleton SingletonWithRequired1 1.0 SingletonWithRequired1.qml +singleton SingletonWithRequired2 1.0 SingletonWithRequired2.qml diff --git a/tests/auto/qml/qqmllanguage/data/nestedVectors.qml b/tests/auto/qml/qqmllanguage/data/nestedVectors.qml new file mode 100644 index 0000000000..0bcea52133 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/nestedVectors.qml @@ -0,0 +1,27 @@ +import Test +import QtQml + +NestedVectors { + id: self + + property var list1 + + Component.onCompleted: { + list1 = self.getList() + + let list2 = [] + let data1 = [] + data1.push(2) + data1.push(3) + data1.push(4) + + let data2 = [] + data2.push(5) + data2.push(6) + + list2.push(data1) + list2.push(data2) + + self.setList(list2) + } +} diff --git a/tests/auto/qml/qqmllanguage/testhelper/CMakeLists.txt b/tests/auto/qml/qqmllanguage/testhelper/CMakeLists.txt new file mode 100644 index 0000000000..6a58889335 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/testhelper/CMakeLists.txt @@ -0,0 +1,13 @@ +qt_policy(SET QTP0001 NEW) +qt_add_library(tst_qqmllanguage_qmlmodule STATIC) +qt_autogen_tools_initial_setup(tst_qqmllanguage_qmlmodule) +qt_add_qml_module(tst_qqmllanguage_qmlmodule + URI testhelper + VERSION 1.0 + SOURCES + "declarativelyregistered.h" + "declarativelyregistered.cpp" +) + +qt_autogen_tools_initial_setup(tst_qqmllanguage_qmlmoduleplugin) +target_link_libraries(tst_qqmllanguage PRIVATE tst_qqmllanguage_qmlmoduleplugin) diff --git a/tests/auto/qml/qqmllanguage/testhelper/declarativelyregistered.cpp b/tests/auto/qml/qqmllanguage/testhelper/declarativelyregistered.cpp new file mode 100644 index 0000000000..24fcd83d42 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/testhelper/declarativelyregistered.cpp @@ -0,0 +1,7 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "declarativelyregistered.h" + +PurelyDeclarativeSingleton::PurelyDeclarativeSingleton() = default; + +#include "moc_declarativelyregistered.cpp" diff --git a/tests/auto/qml/qqmllanguage/testhelper/declarativelyregistered.h b/tests/auto/qml/qqmllanguage/testhelper/declarativelyregistered.h new file mode 100644 index 0000000000..4845cc68b9 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/testhelper/declarativelyregistered.h @@ -0,0 +1,20 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DECLARATIVELYREGISTERED_LANGUAGE_H +#define DECLARATIVELYREGISTERED_LANGUAGE_H + +#include <QtCore/qobject.h> +#include <QtQml/qqmlregistration.h> + +class PurelyDeclarativeSingleton : public QObject +{ + Q_OBJECT + QML_SINGLETON + QML_ELEMENT +public: + PurelyDeclarativeSingleton(); +}; + + +#endif diff --git a/tests/auto/qml/qqmllanguage/testtypes.cpp b/tests/auto/qml/qqmllanguage/testtypes.cpp index e796fb6cd5..12fe042c20 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.cpp +++ b/tests/auto/qml/qqmllanguage/testtypes.cpp @@ -160,6 +160,7 @@ void registerTypes() qmlRegisterTypesAndRevisions<ByteArrayReceiver>("Test", 1); qmlRegisterTypesAndRevisions<Counter>("Test", 1); + qmlRegisterTypesAndRevisions<NestedVectors>("Test", 1); } QVariant myCustomVariantTypeConverter(const QString &data) diff --git a/tests/auto/qml/qqmllanguage/testtypes.h b/tests/auto/qml/qqmllanguage/testtypes.h index fe1a825e87..bfee2d07c9 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.h +++ b/tests/auto/qml/qqmllanguage/testtypes.h @@ -2608,4 +2608,36 @@ public: } }; +class NestedVectors : public QObject +{ + Q_OBJECT + QML_ELEMENT +public: + NestedVectors(QObject *parent = nullptr) : QObject(parent) + { + std::vector<int> data; + data.push_back(1); + data.push_back(2); + data.push_back(3); + m_list.push_back(data); + data.clear(); + data.push_back(4); + data.push_back(5); + m_list.push_back(data); + } + + Q_INVOKABLE std::vector<std::vector<int>> getList() + { + return m_list; + } + + Q_INVOKABLE void setList(std::vector<std::vector<int>> list) + { + m_list = list; + } + +private: + std::vector<std::vector<int>> m_list; +}; + #endif // TESTTYPES_H diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 7dd68df62e..7992896506 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -248,6 +248,8 @@ private slots: void compositeSingletonSelectors(); void compositeSingletonRegistered(); void compositeSingletonCircular(); + void compositeSingletonRequiredProperties(); + void compositeSingletonRequiredProperties_data(); void singletonsHaveContextAndEngine(); @@ -429,6 +431,10 @@ private slots: void typedObjectList(); + void nestedVectors(); + + void overrideInnerBinding(); + private: QQmlEngine engine; QStringList defaultImportPathList; @@ -1920,9 +1926,9 @@ void tst_qqmllanguage::valueTypes() QQmlComponent component(&engine, testFileUrl("valueTypes.qml")); VERIFY_ERRORS(0); - QString message = component.url().toString() + ":2:1: QML MyTypeObject: Binding loop detected for property \"rectProperty.width\""; - QTest::ignoreMessage(QtWarningMsg, qPrintable(message)); - QTest::ignoreMessage(QtWarningMsg, qPrintable(message)); + const auto bindingLoopRegex = QRegularExpression(".*QML MyTypeObject: Binding loop detected for property \"rectProperty.width\".*"); + QTest::ignoreMessage(QtWarningMsg, bindingLoopRegex); + QTest::ignoreMessage(QtWarningMsg, bindingLoopRegex); QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject*>(component.create())); QVERIFY(object != nullptr); @@ -4867,6 +4873,36 @@ void tst_qqmllanguage::compositeSingletonCircular() QCOMPARE(o->property("value").toInt(), 2); } +void tst_qqmllanguage::compositeSingletonRequiredProperties() +{ + QFETCH(QString, warning); + QFETCH(QString, singletonName); + QQmlEngine engine; + engine.addImportPath(dataDirectory()); + { + QTest::ignoreMessage(QtMsgType::QtWarningMsg, qPrintable(warning)); + std::unique_ptr<QObject> singleton {engine.singletonInstance<QObject *>( + "SingletonWithRequiredProperties", + singletonName + )}; + QVERIFY(!singleton); + } +} + +void tst_qqmllanguage::compositeSingletonRequiredProperties_data() +{ + QTest::addColumn<QString>("warning"); + QTest::addColumn<QString>("singletonName"); + + QString warning1 = testFileUrl("SingletonWithRequiredProperties/SingletonWithRequired1.qml").toString() + + ":5:5: Required property i was not initialized"; + QString warning2 = testFileUrl("SingletonWithRequiredProperties/SingletonWithRequired2.qml").toString() + + ":6:9: Required property i was not initialized"; + + QTest::addRow("toplevelRequired") << warning1 << "SingletonWithRequired1"; + QTest::addRow("subObjectRequired") << warning2 << "SingletonWithRequired2"; +} + void tst_qqmllanguage::singletonsHaveContextAndEngine() { QObject *qmlSingleton = nullptr; @@ -5677,6 +5713,9 @@ void tst_qqmllanguage::retrieveQmlTypeId() QVERIFY(qmlTypeId("Test", 1, 0, "MyExtendedUncreateableBaseClass") >= 0); QVERIFY(qmlTypeId("Test", 1, 0, "MyUncreateableBaseClass") >= 0); QVERIFY(qmlTypeId("Test", 1, 0, "MyTypeObjectSingleton") >= 0); + + // Must also work for declaratively registered types whose module wasn't imported so far + QVERIFY(qmlTypeId("testhelper", 1, 0, "PurelyDeclarativeSingleton") >= 0); } void tst_qqmllanguage::polymorphicFunctionLookup() @@ -8194,6 +8233,37 @@ void tst_qqmllanguage::typedObjectList() QVERIFY(list.at(&list, 0) != nullptr); } +void tst_qqmllanguage::nestedVectors() +{ + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("nestedVectors.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + NestedVectors *n = qobject_cast<NestedVectors *>(o.data()); + QVERIFY(n); + + const std::vector<std::vector<int>> expected1 { { 1, 2, 3 }, { 4, 5 } }; + const QVariant list1 = n->property("list1"); + QCOMPARE(e.fromVariant<std::vector<std::vector<int>>>(list1), expected1); + + const std::vector<std::vector<int>> expected2 { { 2, 3, 4 }, { 5, 6 } }; + QCOMPARE(n->getList(), expected2); +} + +void tst_qqmllanguage::overrideInnerBinding() +{ + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("BindingOverrider.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("width").toReal(), 20.0); + QCOMPARE(o->property("innerWidth").toReal(), 20.0); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" diff --git a/tests/auto/qml/qqmllistmodel/data/deadModelData.qml b/tests/auto/qml/qqmllistmodel/data/deadModelData.qml new file mode 100644 index 0000000000..fc3dd6fa11 --- /dev/null +++ b/tests/auto/qml/qqmllistmodel/data/deadModelData.qml @@ -0,0 +1,45 @@ +import QtQml + +QtObject { + function swapCorpses() { + const lhsData = getModelData(lhsButtonListModel); + const rhsData = getModelData(rhsButtonListModel); + + lhsButtonListModel.clear(); + rhsButtonListModel.clear(); + + addToModel(lhsButtonListModel, rhsData); + addToModel(rhsButtonListModel, lhsData); + } + + property ListModel l1: ListModel { + id: lhsButtonListModel + } + + property ListModel l2: ListModel { + id: rhsButtonListModel + } + + Component.onCompleted: { + lhsButtonListModel.append({ "ident": 1, "buttonText": "B 1"}); + lhsButtonListModel.append({ "ident": 2, "buttonText": "B 2"}); + lhsButtonListModel.append({ "ident": 3, "buttonText": "B 3"}); + + rhsButtonListModel.append({ "ident": 4, "buttonText": "B 4"}); + rhsButtonListModel.append({ "ident": 5, "buttonText": "B 5"}); + rhsButtonListModel.append({ "ident": 6, "buttonText": "B 6"}); + } + + function getModelData(model) { + var dataList = [] + for (var i = 0; i < model.count; ++i) + dataList.push(model.get(i)); + + return dataList; + } + + function addToModel(model, buttonData) { + for (var i = 0; i < buttonData.length; ++i) + model.append(buttonData[i]); + } +} diff --git a/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp b/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp index 6fd173fb0c..8f0e657e54 100644 --- a/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp +++ b/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp @@ -119,6 +119,7 @@ private slots: void objectOwnershipFlip(); void enumsInListElement(); void protectQObjectFromGC(); + void deadModelData(); }; bool tst_qqmllistmodel::compareVariantList(const QVariantList &testList, QVariant object) @@ -1931,6 +1932,67 @@ void tst_qqmllistmodel::protectQObjectFromGC() } } +void tst_qqmllistmodel::deadModelData() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("deadModelData.qml")); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> o(component.create()); + QVERIFY(!o.isNull()); + + QQmlListModel *l1 = o->property("l1").value<QQmlListModel *>(); + QVERIFY(l1); + QQmlListModel *l2 = o->property("l2").value<QQmlListModel *>(); + QVERIFY(l2); + + QCOMPARE(l1->count(), 3); + QCOMPARE(l2->count(), 3); + + for (int i = 0; i < 3; ++i) { + QObject *i1 = qjsvalue_cast<QObject *>(l1->get(i)); + QVERIFY(i1); + QCOMPARE(i1->property("ident").value<double>(), i + 1); + QCOMPARE(i1->property("buttonText").value<QString>(), + QLatin1String("B %1").arg(QLatin1Char('0' + i + 1))); + + QObject *i2 = qjsvalue_cast<QObject *>(l2->get(i)); + QVERIFY(i2); + QCOMPARE(i2->property("ident").value<double>(), i + 4); + QCOMPARE(i2->property("buttonText").value<QString>(), + QLatin1String("B %1").arg(QLatin1Char('0' + i + 4))); + } + + for (int i = 0; i < 6; ++i) { + QTest::ignoreMessage( + QtWarningMsg, + QRegularExpression(".*: ident is undefined. Adding an object with a undefined " + "member does not create a role for it.")); + QTest::ignoreMessage( + QtWarningMsg, + QRegularExpression(".*: buttonText is undefined. Adding an object with a undefined " + "member does not create a role for it.")); + } + + QMetaObject::invokeMethod(o.data(), "swapCorpses"); + + // We get default-created values for all the roles now. + + QCOMPARE(l1->count(), 3); + QCOMPARE(l2->count(), 3); + + for (int i = 0; i < 3; ++i) { + QObject *i1 = qjsvalue_cast<QObject *>(l1->get(i)); + QVERIFY(i1); + QCOMPARE(i1->property("ident").value<double>(), double()); + QCOMPARE(i1->property("buttonText").value<QString>(), QString()); + + QObject *i2 = qjsvalue_cast<QObject *>(l2->get(i)); + QVERIFY(i2); + QCOMPARE(i2->property("ident").value<double>(), double()); + QCOMPARE(i2->property("buttonText").value<QString>(), QString()); + } +} + QTEST_MAIN(tst_qqmllistmodel) #include "tst_qqmllistmodel.moc" diff --git a/tests/auto/qml/qqmllistreference/data/compositeListProp.qml b/tests/auto/qml/qqmllistreference/data/compositeListProp.qml index 99965f289b..7aa3d0845a 100644 --- a/tests/auto/qml/qqmllistreference/data/compositeListProp.qml +++ b/tests/auto/qml/qqmllistreference/data/compositeListProp.qml @@ -1,5 +1,14 @@ import QtQml QtObject { - property list<AListItem> items + id: self + property list<AListItem> items: [ self ] + + Component.onCompleted: { + items.push(self); + items.push(null); + items[2] = self; + items.splice(1, 0, self); + items.unshift(self); + } } diff --git a/tests/auto/qml/qqmllistreference/tst_qqmllistreference.cpp b/tests/auto/qml/qqmllistreference/tst_qqmllistreference.cpp index 66c17aa0d5..4d633bb3ab 100644 --- a/tests/auto/qml/qqmllistreference/tst_qqmllistreference.cpp +++ b/tests/auto/qml/qqmllistreference/tst_qqmllistreference.cpp @@ -862,9 +862,31 @@ void tst_qqmllistreference::compositeListProperty() { QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("compositeListProp.qml")); + + QTest::ignoreMessage( + QtWarningMsg, QRegularExpression("Cannot append QObject_QML_[0-9]+\\(0x[0-9a-f]+\\) " + "to a QML list of AListItem_QMLTYPE_[0-9]+\\*")); + QTest::ignoreMessage( + QtWarningMsg, QRegularExpression("Cannot append QObject_QML_[0-9]+\\(0x[0-9a-f]+\\) " + "to a QML list of AListItem_QMLTYPE_[0-9]+\\*")); + QTest::ignoreMessage( + QtWarningMsg, QRegularExpression("Cannot insert QObject_QML_[0-9]+\\(0x[0-9a-f]+\\) " + "into a QML list of AListItem_QMLTYPE_[0-9]+\\*")); + QTest::ignoreMessage( + QtWarningMsg, QRegularExpression("Cannot splice QObject_QML_[0-9]+\\(0x[0-9a-f]+\\) " + "into a QML list of AListItem_QMLTYPE_[0-9]+\\*")); + QTest::ignoreMessage( + QtWarningMsg, QRegularExpression("Cannot unshift QObject_QML_[0-9]+\\(0x[0-9a-f]+\\) " + "into a QML list of AListItem_QMLTYPE_[0-9]+\\*")); + QScopedPointer<QObject> object(component.create()); QVERIFY(!object.isNull()); + QQmlListReference list1(object.data(), "items"); + QCOMPARE(list1.size(), 5); + for (qsizetype i = 0; i < 5; ++i) + QCOMPARE(list1.at(i), nullptr); + QQmlComponent item(&engine, testFileUrl("AListItem.qml")); QScopedPointer<QObject> i1(item.create()); QScopedPointer<QObject> i2(item.create()); @@ -872,7 +894,6 @@ void tst_qqmllistreference::compositeListProperty() QVERIFY(!i2.isNull()); // We know the element type now. - QQmlListReference list1(object.data(), "items"); QVERIFY(list1.listElementType() != nullptr); QVERIFY(list1.append(i1.data())); QVERIFY(list1.replace(0, i2.data())); diff --git a/tests/auto/qml/qqmltablemodel/CMakeLists.txt b/tests/auto/qml/qqmltablemodel/CMakeLists.txt index ebd3e94835..2800fd642c 100644 --- a/tests/auto/qml/qqmltablemodel/CMakeLists.txt +++ b/tests/auto/qml/qqmltablemodel/CMakeLists.txt @@ -23,6 +23,7 @@ qt_internal_add_test(tst_qqmltablemodel Qt::Quick Qt::QuickPrivate Qt::QuickTestUtilsPrivate + Qt::LabsQmlModelsPrivate TESTDATA ${test_data} ) diff --git a/tests/auto/qml/qqmltablemodel/tst_qqmltablemodel.cpp b/tests/auto/qml/qqmltablemodel/tst_qqmltablemodel.cpp index cd6eb3e9c5..860c782fd3 100644 --- a/tests/auto/qml/qqmltablemodel/tst_qqmltablemodel.cpp +++ b/tests/auto/qml/qqmltablemodel/tst_qqmltablemodel.cpp @@ -13,6 +13,7 @@ #include <QtQuickTestUtils/private/qmlutils_p.h> #include <QtQuickTestUtils/private/viewtestutils_p.h> +#include <QtLabsQmlModels/private/qqmltablemodel_p.h> class tst_QQmlTableModel : public QQmlDataTest { @@ -179,6 +180,18 @@ void tst_QQmlTableModel::appendRemoveRow() QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")), QVariant()); QCOMPARE(columnCountSpy.size(), 0); QCOMPARE(rowCountSpy.size(), ++rowCountSignalEmissions); + + QVariantMap variantMap; + variantMap["name"] = QLatin1String("VariantMap"); + variantMap["age"] = int(QMetaType::QVariantMap); + auto *tableModel = view.rootObject()->property("testModel") .value<QQmlTableModel *>(); + tableModel->appendRow(QVariant::fromValue(variantMap)); + QCOMPARE(model->rowCount(), 1); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("VariantMap")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), int(QMetaType::QVariantMap)); + QCOMPARE(columnCountSpy.size(), 0); + QCOMPARE(rowCountSpy.size(), ++rowCountSignalEmissions); } void tst_QQmlTableModel::appendRowToEmptyModel() @@ -432,6 +445,31 @@ void tst_QQmlTableModel::insertRow() QCOMPARE(rowCountSpy.size(), ++rowCountSignalEmissions); QTRY_COMPARE(tableView->rows(), 5); QCOMPARE(tableView->columns(), 2); + + // Pass variant map object to qml table model + QVariantMap variantMap; + variantMap["name"] = QLatin1String("VariantMap"); + variantMap["age"] = int(QMetaType::QVariantMap); + auto *tableModel = view.rootObject()->property("testModel") .value<QQmlTableModel *>(); + tableModel->insertRow(5, QVariant::fromValue(variantMap)); + QCOMPARE(model->rowCount(), 6); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 99); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Daisy")); + QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 30); + QCOMPARE(model->data(model->index(3, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(3, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(model->data(model->index(4, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max")); + QCOMPARE(model->data(model->index(4, 1, QModelIndex()), roleNames.key("display")).toInt(), 40); + QCOMPARE(model->data(model->index(5, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("VariantMap")); + QCOMPARE(model->data(model->index(5, 1, QModelIndex()), roleNames.key("display")).toInt(), int(QMetaType::QVariantMap)); + QCOMPARE(columnCountSpy.size(), 0); + QCOMPARE(rowCountSpy.size(), ++rowCountSignalEmissions); + QTRY_COMPARE(tableView->rows(), 6); + QCOMPARE(tableView->columns(), 2); } void tst_QQmlTableModel::moveRow() @@ -716,6 +754,24 @@ void tst_QQmlTableModel::setRow() QCOMPARE(rowCountSpy.size(), ++rowCountSignalEmissions); QTRY_COMPARE(tableView->rows(), 3); QCOMPARE(tableView->columns(), 2); + + QVariantMap variantMap; + variantMap["name"] = QLatin1String("VariantMap"); + variantMap["age"] = int(QMetaType::QVariantMap); + auto *tableModel = view.rootObject()->property("testModel") .value<QQmlTableModel *>(); + tableModel->setRow(0, QVariant::fromValue(variantMap)); + QCOMPARE(model->rowCount(), 3); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("VariantMap")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), int(QMetaType::QVariantMap)); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Daisy")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 30); + QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Wot")); + QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 99); + QCOMPARE(columnCountSpy.size(), 0); + QCOMPARE(rowCountSpy.size(), rowCountSignalEmissions); + QTRY_COMPARE(tableView->rows(), 3); + QCOMPARE(tableView->columns(), 2); } void tst_QQmlTableModel::setDataThroughDelegate() diff --git a/tests/auto/qml/qv4estable/tst_qv4estable.cpp b/tests/auto/qml/qv4estable/tst_qv4estable.cpp index 45df62b23e..7d137ae7d2 100644 --- a/tests/auto/qml/qv4estable/tst_qv4estable.cpp +++ b/tests/auto/qml/qv4estable/tst_qv4estable.cpp @@ -18,7 +18,7 @@ void tst_qv4estable::checkRemoveAvoidsHeapBufferOverflow() QV4::ESTable estable; // Fill the ESTable with values so it is at max capacity. - QCOMPARE_EQ(estable.m_capacity, 8); + QCOMPARE_EQ(estable.m_capacity, 8U); for (uint i = 0; i < estable.m_capacity; ++i) { estable.set(QV4::Value::fromUInt32(i), QV4::Value::fromUInt32(i)); } @@ -27,8 +27,8 @@ void tst_qv4estable::checkRemoveAvoidsHeapBufferOverflow() for (uint i = 0; i < estable.m_capacity; ++i) { QVERIFY(estable.m_keys[i].sameValueZero(QV4::Value::fromUInt32(i))); } - QCOMPARE_EQ(estable.m_capacity, 8); - QCOMPARE_EQ(estable.m_size, 8); + QCOMPARE_EQ(estable.m_capacity, 8U); + QCOMPARE_EQ(estable.m_size, 8U); // Remove the first item from the set to verify that asan does not trip. // Relies on the CI platform propagating asan flag to all tests. diff --git a/tests/auto/qmltest/listview/tst_listview.qml b/tests/auto/qmltest/listview/tst_listview.qml index e9c91e10f2..039114114b 100644 --- a/tests/auto/qmltest/listview/tst_listview.qml +++ b/tests/auto/qmltest/listview/tst_listview.qml @@ -38,12 +38,16 @@ Item { ListView { id: modelchange + width: 100 + height: 100 model: firstmodel delegate: Text { text: model.name } } ListView { id: modelalter + width: 100 + height: 100 model: altermodel delegate: Text { text: model.name } } diff --git a/tests/auto/qmltest/selftests/tst_findChild.qml b/tests/auto/qmltest/selftests/tst_findChild.qml index c8af04810a..3232191150 100644 --- a/tests/auto/qmltest/selftests/tst_findChild.qml +++ b/tests/auto/qmltest/selftests/tst_findChild.qml @@ -71,6 +71,8 @@ TestCase { } ListView { + width: 100 + height: 100 model: 5 delegate: Item { objectName: "listViewItem" + index diff --git a/tests/auto/quick/doc/how-tos/how-to-qml/tst_how-to-qml.cpp b/tests/auto/quick/doc/how-tos/how-to-qml/tst_how-to-qml.cpp index fdc8981932..4c6e331efa 100644 --- a/tests/auto/quick/doc/how-tos/how-to-qml/tst_how-to-qml.cpp +++ b/tests/auto/quick/doc/how-tos/how-to-qml/tst_how-to-qml.cpp @@ -60,6 +60,7 @@ void tst_HowToQml::activeFocusDebugging() auto *window = qobject_cast<QQuickWindow*>(engine.rootObjects().at(0)); window->show(); + window->requestActivate(); QTest::ignoreMessage(QtDebugMsg, QRegularExpression("activeFocusItem: .*\"ActiveFocusDebuggingMain\"")); QVERIFY(QTest::qWaitForWindowActive(window)); diff --git a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp index 0a126a13e8..ce5473c8a5 100644 --- a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp +++ b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp @@ -101,6 +101,7 @@ private slots: void restartAnimationGroupWhenDirty(); void restartNestedAnimationGroupWhenDirty(); void targetsDeletedNotRemoved(); + void alwaysRunToEndSetFalseRestartBug(); }; #define QTIMED_COMPARE(lhs, rhs) do { \ @@ -2295,6 +2296,41 @@ void tst_qquickanimations::targetsDeletedNotRemoved() } } +//QTBUG-125224 +void tst_qquickanimations::alwaysRunToEndSetFalseRestartBug() +{ + QQuickRectangle rect; + QQuickSequentialAnimation sequential; + QQuickPropertyAnimation beginAnim; + QQuickPropertyAnimation endAnim; + + beginAnim.setTargetObject(&rect); + beginAnim.setProperty("x"); + beginAnim.setTo(200); + beginAnim.setDuration(1000); + + endAnim.setTargetObject(&rect); + endAnim.setProperty("x"); + endAnim.setFrom(200); + endAnim.setDuration(1000); + + beginAnim.setGroup(&sequential); + endAnim.setGroup(&sequential); + + sequential.setLoops(-1); + sequential.setAlwaysRunToEnd(true); + + QCOMPARE(sequential.loops(), -1); + QVERIFY(sequential.alwaysRunToEnd()); + sequential.start(); + sequential.stop(); + sequential.setAlwaysRunToEnd(false); + sequential.start(); + QCOMPARE(sequential.isRunning(), true); + sequential.stop(); + QCOMPARE(sequential.isRunning(), false); +} + QTEST_MAIN(tst_qquickanimations) #include "tst_qquickanimations.moc" diff --git a/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp b/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp index a440c0c2f8..b3633a9dcc 100644 --- a/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp +++ b/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp @@ -246,7 +246,7 @@ void tst_qquickapplication::styleHints() { // technically not in QQuickApplication, but testing anyway here QQmlComponent component(&engine); - component.setData("import QtQuick 2.0; Item { property variant styleHints: Qt.styleHints }", QUrl::fromLocalFile("")); + component.setData("import QtQuick 2.0; Item { property variant styleHints: Application.styleHints }", QUrl::fromLocalFile("")); QQuickItem *item = qobject_cast<QQuickItem *>(component.create()); QVERIFY(item); QQuickView view; diff --git a/tests/auto/quick/qquickdragattached/tst_qquickdragattached.cpp b/tests/auto/quick/qquickdragattached/tst_qquickdragattached.cpp index 5ce72a747f..d82ebe77c2 100644 --- a/tests/auto/quick/qquickdragattached/tst_qquickdragattached.cpp +++ b/tests/auto/quick/qquickdragattached/tst_qquickdragattached.cpp @@ -44,6 +44,9 @@ void tst_QQuickDragAttached::setMimeData_data() QTest::addRow("text/uri-list, string") << makeMap("text/uri-list", QString("https://qt-project.org")) << QStringList{"text/uri-list"}; + QTest::addRow("text/uri-list, RFC2483 string") + << makeMap("text/uri-list", QString("https://qt-project.org\r\nhttps://www.test.com")) + << QStringList{"text/uri-list"}; QTest::addRow("text/uri-list, strings") << makeMap("text/uri-list", QStringList{"file://foo", "https://www.test.com"}) << QStringList{"text/uri-list"}; diff --git a/tests/auto/quick/qquickimage/tst_qquickimage.cpp b/tests/auto/quick/qquickimage/tst_qquickimage.cpp index 343bde5f5f..ca2314c336 100644 --- a/tests/auto/quick/qquickimage/tst_qquickimage.cpp +++ b/tests/auto/quick/qquickimage/tst_qquickimage.cpp @@ -180,6 +180,11 @@ void tst_qquickimage::imageSource() QFETCH(bool, cache); QFETCH(QString, error); +#if !QT_CONFIG(qml_network) + if (remote) + QSKIP("Skipping due to lack of QML network feature"); +#endif + TestHTTPServer server; if (remote) { QVERIFY2(server.listen(), qPrintable(server.errorString())); @@ -550,6 +555,10 @@ void tst_qquickimage::tiling_QTBUG_6716_data() void tst_qquickimage::noLoading() { +#if !QT_CONFIG(qml_network) + QSKIP("Skipping due to lack of QML network feature"); +#endif + qRegisterMetaType<QQuickImageBase::Status>(); TestHTTPServer server; @@ -692,6 +701,10 @@ void tst_qquickimage::sourceSize_QTBUG_16389() // QTBUG-15690 void tst_qquickimage::nullPixmapPaint() { +#if !QT_CONFIG(qml_network) + QSKIP("Skipping due to lack of QML network feature"); +#endif + QScopedPointer<QQuickView> window(new QQuickView(nullptr)); window->setSource(testFileUrl("nullpixmap.qml")); window->show(); @@ -714,6 +727,10 @@ void tst_qquickimage::nullPixmapPaint() void tst_qquickimage::imageCrash_QTBUG_22125() { +#if !QT_CONFIG(qml_network) + QSKIP("Skipping due to lack of QML network feature"); +#endif + TestHTTPServer server; QVERIFY2(server.listen(), qPrintable(server.errorString())); server.serveDirectory(dataDirectory(), TestHTTPServer::Delay); @@ -826,6 +843,10 @@ void tst_qquickimage::sourceSizeChanges() QTRY_COMPARE(sourceSizeSpy.size(), 3); // Remote +#if !QT_CONFIG(qml_network) + QSKIP("Skipping due to lack of QML network feature"); +#endif + ctxt->setContextProperty("srcImage", server.url("/heart.png")); QTRY_COMPARE(img->status(), QQuickImage::Ready); QTRY_COMPARE(sourceSizeSpy.size(), 4); @@ -959,6 +980,10 @@ void tst_qquickimage::progressAndStatusChanges() QTRY_COMPARE(statusSpy.size(), 1); // Loading remote file +#if !QT_CONFIG(qml_network) + QSKIP("Skipping due to lack of QML network feature"); +#endif + ctxt->setContextProperty("srcImage", server.url("/heart.png")); QTRY_COMPARE(obj->status(), QQuickImage::Loading); QTRY_COMPARE(obj->progress(), 0.0); diff --git a/tests/auto/quick/qquickitem/tst_qquickitem.cpp b/tests/auto/quick/qquickitem/tst_qquickitem.cpp index 061dea3ad7..1db150e675 100644 --- a/tests/auto/quick/qquickitem/tst_qquickitem.cpp +++ b/tests/auto/quick/qquickitem/tst_qquickitem.cpp @@ -1527,7 +1527,7 @@ void tst_qquickitem::polishLoopDetection_data() QTest::newRow("test1.100") << PolishItemSpans({ {1, 100} }) << 0; QTest::newRow("test1.1002") << PolishItemSpans({ {1, 1002} }) << 3; - QTest::newRow("test1.2020") << PolishItemSpans({ {1, 2020} }) << 10; + QTest::newRow("test1.2020") << PolishItemSpans({ {1, 2020} }) << 5; QTest::newRow("test5.1") << PolishItemSpans({ {5, 1} }) << 0; QTest::newRow("test5.10") << PolishItemSpans({ {5, 10} }) << 0; diff --git a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp index ebd1749e68..f6de1b482c 100644 --- a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp +++ b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp @@ -2015,7 +2015,7 @@ void tst_QQuickItem::layoutMirroringIllegalParent() { QQmlComponent component(&engine); component.setData("import QtQuick 2.0; QtObject { LayoutMirroring.enabled: true; LayoutMirroring.childrenInherit: true }", QUrl::fromLocalFile("")); - QTest::ignoreMessage(QtWarningMsg, "<Unknown File>:1:21: QML QtObject: LayoutDirection attached property only works with Items and Windows"); + QTest::ignoreMessage(QtWarningMsg, "<Unknown File>:1:21: QML QtObject: LayoutMirroring attached property only works with Items and Windows"); QObject *object = component.create(); QVERIFY(object != nullptr); } diff --git a/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml b/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml index 8524366f14..5454cf672b 100644 --- a/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml +++ b/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml @@ -936,7 +936,19 @@ Item { }, layoutWidth: 0, expectedWidths: [0] - } + },{ + tag: "preferred_infinity", // Do not crash/assert when the preferred size is infinity + layout: { + type: "RowLayout", + items: [ + {minimumWidth: 10, preferredWidth: Number.POSITIVE_INFINITY, fillWidth: true}, + {minimumWidth: 20, preferredWidth: Number.POSITIVE_INFINITY, fillWidth: true}, + ] + }, + layoutWidth: 31, // Important that this is between minimum and preferred width of the layout. + expectedWidths: [10, 21] // The result here does not have to be exact. (This + // test is mostly concerned about not crashing). + } ]; } @@ -1178,6 +1190,78 @@ Item { } Component { + id: sizeHintBindingLoopComp + Item { + id: root + anchors.fill: parent + property var customWidth: 100 + RowLayout { + id: col + Item { + id: item + implicitHeight: 80 + implicitWidth: Math.max(col2.implicitWidth, root.customWidth + 20) + ColumnLayout { + id: col2 + width: parent.width + Item { + id: rect + implicitWidth: root.customWidth + implicitHeight: 80 + } + } + } + } + } + } + + function test_sizeHintBindingLoopIssue() { + var item = createTemporaryObject(sizeHintBindingLoopComp, container) + waitForRendering(item) + item.customWidth += 10 + waitForRendering(item) + verify(!LayoutSetup.bindingLoopDetected, "Detected binding loop") + LayoutSetup.resetBindingLoopDetectedFlag() + } + + Component { + id: polishLayoutItemComp + Item { + anchors.fill: parent + implicitHeight: contentLayout.implicitHeight + implicitWidth: contentLayout.implicitWidth + property alias textLayout: contentLayout + RowLayout { + width: parent.width + height: parent.height + ColumnLayout { + id: contentLayout + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + Layout.maximumWidth: 200 + Repeater { + model: 2 + Text { + Layout.fillWidth: true + text: "This is a long text causing line breaks to show the bug." + wrapMode: Text.Wrap + } + } + Item { + Layout.fillHeight: true + } + } + } + } + } + + function test_polishLayoutItemIssue() { + var rootItem = createTemporaryObject(polishLayoutItemComp, container) + waitForRendering(rootItem) + var textItem = rootItem.textLayout.children[1] + verify(textItem.y >= rootItem.textLayout.children[0].height) + } + + Component { id: rearrangeNestedLayouts_Component RowLayout { id: layout @@ -1520,8 +1604,8 @@ Item { compare(rootItem.maxWidth, 66) // Should not trigger a binding loop - verify(!BindingLoopDetector.bindingLoopDetected, "Detected binding loop") - BindingLoopDetector.reset() + verify(!LayoutSetup.bindingLoopDetected, "Detected binding loop") + LayoutSetup.resetBindingLoopDetectedFlag() } diff --git a/tests/auto/quick/qquicklayouts/tst_qquicklayouts.cpp b/tests/auto/quick/qquicklayouts/tst_qquicklayouts.cpp index 26b01b806d..1576a00c81 100644 --- a/tests/auto/quick/qquicklayouts/tst_qquicklayouts.cpp +++ b/tests/auto/quick/qquicklayouts/tst_qquicklayouts.cpp @@ -15,13 +15,12 @@ public: bool wasBindingLoopDetected() const { return mBindingLoopDetected; } public slots: - void reset() { mBindingLoopDetected = false; } + void resetBindingLoopDetectedFlag() { mBindingLoopDetected = false; } void qmlEngineAvailable(QQmlEngine *engine) { connect(engine, &QQmlEngine::warnings, this, &Setup::qmlWarnings); - - qmlRegisterSingletonInstance("org.qtproject.Test", 1, 0, "BindingLoopDetector", this); + qmlRegisterSingletonInstance("org.qtproject.Test", 1, 0, "LayoutSetup", this); } void qmlWarnings(const QList<QQmlError> &warnings) diff --git a/tests/auto/quick/qquicklistview/data/emptymodel.qml b/tests/auto/quick/qquicklistview/data/emptymodel.qml index 3feec691cf..c7f1df31d2 100644 --- a/tests/auto/quick/qquicklistview/data/emptymodel.qml +++ b/tests/auto/quick/qquicklistview/data/emptymodel.qml @@ -6,6 +6,8 @@ Rectangle { } ListView { id: list + width: 100 + height: 100 model: model delegate: Item { } diff --git a/tests/auto/quick/qquicklistview/data/snapToItemWithSectionAtStart.qml b/tests/auto/quick/qquicklistview/data/snapToItemWithSectionAtStart.qml new file mode 100644 index 0000000000..0d4c233345 --- /dev/null +++ b/tests/auto/quick/qquicklistview/data/snapToItemWithSectionAtStart.qml @@ -0,0 +1,52 @@ +import QtQuick + +ListView { + id: listView + width: 240 + height: 300 + + model: ListModel { + ListElement { section: "section 1" } + ListElement { section: "section 1" } + ListElement { section: "section 1" } + ListElement { section: "section 2" } + ListElement { section: "section 2" } + ListElement { section: "section 2" } + ListElement { section: "section 3" } + ListElement { section: "section 3" } + ListElement { section: "section 3" } + ListElement { section: "section 4" } + ListElement { section: "section 4" } + ListElement { section: "section 4" } + ListElement { section: "section 5" } + ListElement { section: "section 5" } + ListElement { section: "section 5" } + } + + section.property: "section" + section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart + section.delegate: Rectangle { + width: listView.width + height: 30 + Text { + anchors.fill: parent + text: section + } + color: "lightblue" + } + + snapMode: ListView.SnapToItem + + delegate: Rectangle { + width: listView.width + height: 30 + Text { + anchors.fill: parent + text: index + } + border { + width: 1 + color: "black" + } + } +} diff --git a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp index 46e1254453..747478fc9a 100644 --- a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp +++ b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp @@ -171,6 +171,7 @@ private slots: void headerSnapToItem_data(); void headerSnapToItem(); void snapToItemWithSpacing_QTBUG_59852(); + void snapToItemWithSectionAtStart(); void snapOneItemResize_QTBUG_43555(); void snapOneItem_data(); void snapOneItem(); @@ -5395,6 +5396,27 @@ void tst_QQuickListView::snapToItemWithSpacing_QTBUG_59852() releaseView(window); } +void tst_QQuickListView::snapToItemWithSectionAtStart() // QTBUG-30768 +{ + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("snapToItemWithSectionAtStart.qml"))); + QQuickListView *listView = qobject_cast<QQuickListView *>(window.rootObject()); + QTRY_VERIFY(listView); + + // Both sections and elements are 30px high. The list height is 300px, so + // it fits exactly 10 elements. We can do some random flicks, but the + // content position always MUST be divisible by 30. + for (int i = 0; i < 10; ++i) { + const bool even = (i % 2 == 0); + const QPoint start = even ? QPoint(20, 100 + i * 5) : QPoint(20, 20 + i * 3); + const QPoint end = even ? start - QPoint(0, 50 + i * 10) : start + QPoint(0, 50 + i * 5); + + flick(&window, start, end, 180); + QTRY_COMPARE(listView->isMoving(), false); // wait until it stops + QCOMPARE(int(listView->contentY()) % 30, 0); + } +} + static void drag_helper(QWindow *window, QPoint *startPos, const QPoint &delta) { QPoint pos = *startPos; diff --git a/tests/auto/quick/qquicklistview2/data/nestedSnap.qml b/tests/auto/quick/qquicklistview2/data/nestedSnap.qml new file mode 100644 index 0000000000..d7f064da01 --- /dev/null +++ b/tests/auto/quick/qquicklistview2/data/nestedSnap.qml @@ -0,0 +1,63 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +pragma ComponentBehavior: Bound + +import QtQuick + +ListView { + id: row + + width: 300 + height: 300 + + orientation: Qt.Horizontal + snapMode: ListView.SnapOneItem + highlightRangeMode: ListView.StrictlyEnforceRange + + model: 3 + delegate: ListView { + id: column + objectName: "vertical column " + index + + required property int index + + width: 300 + height: 300 + + orientation: Qt.Vertical + snapMode: ListView.SnapOneItem + highlightRangeMode: ListView.StrictlyEnforceRange + + model: 3 + delegate: Rectangle { + id: cell + + required property int index + + width: 300 + height: 300 + color: "transparent" + border.color: "#000" + border.width: 5 + radius: 15 + + Text { + anchors.centerIn: parent + text: `Row: ${cell.index}` + } + } + + Text { + anchors.verticalCenterOffset: -height + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: `Column: ${column.index}\ncurrentIndex: ${column.currentIndex}` + } + } + + Text { + x: 10; y: 10 + text: `currentIndex: ${row.currentIndex}` + } +} diff --git a/tests/auto/quick/qquicklistview2/data/visibleBoundToCountGreaterThanZero.qml b/tests/auto/quick/qquicklistview2/data/visibleBoundToCountGreaterThanZero.qml new file mode 100644 index 0000000000..e75c779584 --- /dev/null +++ b/tests/auto/quick/qquicklistview2/data/visibleBoundToCountGreaterThanZero.qml @@ -0,0 +1,31 @@ +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + property alias listView: listView + + ListView { + id: listView + + visible: count > 0 // actual defect. countChanged never fires so this never turns true + + Layout.fillWidth: true + Layout.preferredHeight: contentHeight // grow with content, initially 0 + + model: ListModel { + id: idModel + } + + delegate: Text { + required property string name + text: name + } + + Timer { + running: true + interval: 10 + repeat: true + onTriggered: idModel.append({name:"Hello"}) + } + } +} diff --git a/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp b/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp index c240f36a80..e114cc1591 100644 --- a/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp +++ b/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp @@ -18,6 +18,8 @@ Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests") using namespace QQuickViewTestUtils; using namespace QQuickVisualTestUtils; +static const int oneSecondInMs = 1000; + class tst_QQuickListView2 : public QQmlDataTest { Q_OBJECT @@ -35,6 +37,7 @@ private slots: void delegateModelRefresh(); void wheelSnap(); void wheelSnap_data(); + void nestedWheelSnap(); void sectionsNoOverlap(); void metaSequenceAsModel(); @@ -62,6 +65,7 @@ private slots: void changingOrientationResetsPreviousAxisValues_data(); void changingOrientationResetsPreviousAxisValues(); + void visibleBoundToCountGreaterThanZero(); private: void flickWithTouch(QQuickWindow *window, const QPoint &from, const QPoint &to); @@ -838,6 +842,67 @@ void tst_QQuickListView2::wheelSnap_data() << 210.0; } +void tst_QQuickListView2::nestedWheelSnap() +{ + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("nestedSnap.qml"))); + + quint64 timestamp = 10; + auto sendWheelEvent = [×tamp, &window](const QPoint &pixelDelta, Qt::ScrollPhase phase) { + const QPoint pos(100, 100); + QWheelEvent event(pos, window.mapToGlobal(pos), pixelDelta, pixelDelta, Qt::NoButton, + Qt::NoModifier, phase, false, Qt::MouseEventSynthesizedBySystem); + event.setAccepted(false); + event.setTimestamp(timestamp); + QGuiApplication::sendEvent(&window, &event); + timestamp += 50; + }; + + QQuickListView *outerListView = qobject_cast<QQuickListView *>(window.rootObject()); + QTRY_VERIFY(outerListView); + QSignalSpy outerCurrentIndexSpy(outerListView, &QQuickListView::currentIndexChanged); + int movingAtIndex = -1; + + // send horizontal pixel-delta wheel events with phases; confirm that ListView hits the next item boundary + sendWheelEvent({}, Qt::ScrollBegin); + for (int i = 1; i < 4; ++i) { + sendWheelEvent({-50, 0}, Qt::ScrollUpdate); + if (movingAtIndex < 0 && outerListView->isMoving()) + movingAtIndex = i; + } + QVERIFY(outerListView->isDragging()); + sendWheelEvent({}, Qt::ScrollEnd); + QCOMPARE(outerListView->isDragging(), false); + QTRY_COMPARE(outerListView->isMoving(), false); // wait until it stops + qCDebug(lcTests) << "outer got moving after" << movingAtIndex + << "horizontal events; stopped at" << outerListView->contentX() << outerListView->currentIndex(); + QCOMPARE_GT(movingAtIndex, 0); + QCOMPARE(outerListView->contentX(), 300); + QCOMPARE(outerCurrentIndexSpy.size(), 1); + + movingAtIndex = -1; + QQuickListView *innerListView = qobject_cast<QQuickListView *>(outerListView->currentItem()); + QTRY_VERIFY(innerListView); + QSignalSpy innerCurrentIndexSpy(innerListView, &QQuickListView::currentIndexChanged); + + // send vertical pixel-delta wheel events with phases; confirm that ListView hits the next item boundary + sendWheelEvent({}, Qt::ScrollBegin); + for (int i = 1; i < 4; ++i) { + sendWheelEvent({0, -50}, Qt::ScrollUpdate); + if (movingAtIndex < 0 && innerListView->isMoving()) + movingAtIndex = i; + } + QVERIFY(innerListView->isDragging()); + sendWheelEvent({}, Qt::ScrollEnd); + QCOMPARE(innerListView->isDragging(), false); + QTRY_COMPARE(innerListView->isMoving(), false); // wait until it stops + qCDebug(lcTests) << "inner got moving after" << movingAtIndex + << "vertical events; stopped at" << innerListView->contentY() << innerListView->currentIndex(); + QCOMPARE_GT(movingAtIndex, 0); + QCOMPARE(innerListView->contentY(), 300); + QCOMPARE(innerCurrentIndexSpy.size(), 1); +} + class FriendlyItemView : public QQuickItemView { friend class ItemViewAccessor; @@ -1153,6 +1218,23 @@ void tst_QQuickListView2::changingOrientationResetsPreviousAxisValues() // QTBUG QVERIFY(!listView->property("isYReset").toBool()); } +void tst_QQuickListView2::visibleBoundToCountGreaterThanZero() +{ + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("visibleBoundToCountGreaterThanZero.qml"))); + + auto *listView = window.rootObject()->property("listView").value<QQuickListView *>(); + QVERIFY(listView); + + QSignalSpy countChangedSpy(listView, SIGNAL(countChanged())); + QVERIFY(countChangedSpy.isValid()); + + QTRY_COMPARE_GT_WITH_TIMEOUT(listView->count(), 1, oneSecondInMs); + // Using the TRY variant here as well is necessary. + QTRY_COMPARE_GT_WITH_TIMEOUT(countChangedSpy.count(), 1, oneSecondInMs); + QVERIFY(listView->isVisible()); +} + QTEST_MAIN(tst_QQuickListView2) #include "tst_qquicklistview2.moc" diff --git a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp index 6d2aa267dd..d5b3b75215 100644 --- a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp +++ b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp @@ -939,6 +939,9 @@ void tst_QQuickMouseArea::doubleClick() QCOMPARE(window.rootObject()->property("clicked").toInt(), 1); QCOMPARE(window.rootObject()->property("doubleClicked").toInt(), 1); QCOMPARE(window.rootObject()->property("released").toInt(), 2); + + // wait long enough to avoid affecting the next test function + QTest::qWait(QGuiApplication::styleHints()->mouseDoubleClickInterval()); } void tst_QQuickMouseArea::doubleTap() // QTBUG-112434 @@ -970,6 +973,9 @@ void tst_QQuickMouseArea::doubleTap() // QTBUG-112434 QCOMPARE(mouseArea->pressed(), false); QCOMPARE(window.rootObject()->property("clicked").toInt(), 1); + // avoid getting a double-click event next + QTest::qWait(QGuiApplication::styleHints()->mouseDoubleClickInterval()); + // now tap with two fingers simultaneously: only one of them generates synth-mouse QPoint p2 = p1 + QPoint(50, 5); QTest::touchEvent(&window, device).press(2, p1).press(3, p2); @@ -991,8 +997,8 @@ void tst_QQuickMouseArea::doubleTap() // QTBUG-112434 QTest::touchEvent(&window, device).release(4, p1).release(5, p2); QQuickTouchUtils::flush(&window); QCOMPARE(window.rootObject()->property("released").toInt(), 4); - QCOMPARE(window.rootObject()->property("clicked").toInt(), 2); - QCOMPARE(window.rootObject()->property("doubleClicked").toInt(), 2); + QCOMPARE(window.rootObject()->property("clicked").toInt(), 3); + QCOMPARE(window.rootObject()->property("doubleClicked").toInt(), 1); QCOMPARE(mouseArea->pressed(), false); // make sure it doesn't get stuck } diff --git a/tests/auto/quick/qquickpathview/data/qtbug46487.qml b/tests/auto/quick/qquickpathview/data/qtbug46487.qml new file mode 100644 index 0000000000..840d77ffe4 --- /dev/null +++ b/tests/auto/quick/qquickpathview/data/qtbug46487.qml @@ -0,0 +1,28 @@ +import QtQuick 2.0 + +PathView { + id: view + property int delegatesCreated: 0 + property int delegatesDestroyed: 0 + + width: 400 + height: 400 + preferredHighlightBegin: 0.5 + preferredHighlightEnd: 0.5 + pathItemCount: 5 + currentIndex: 1 + model: customModel + delegate: Text { + text: "item: " + index + " of: " + view.count + Component.onCompleted: view.delegatesCreated++; + Component.onDestruction: view.delegatesDestroyed++; + } + path: Path { + startX: 50 + startY: 0 + PathLine { + x: 50 + y: 400 + } + } +} diff --git a/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp b/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp index 90c3060235..7d41d907fb 100644 --- a/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp +++ b/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp @@ -133,6 +133,7 @@ private slots: void requiredPropertiesInDelegatePreventUnrelated(); void touchMove(); void mousePressAfterFlick(); + void qtbug46487(); private: QScopedPointer<QPointingDevice> touchDevice = QScopedPointer<QPointingDevice>(QTest::createTouchDevice()); @@ -2895,6 +2896,68 @@ void tst_QQuickPathView::mousePressAfterFlick() // QTBUG-115121 QCOMPARE(pressedSpy.size(), 0); } +class CustomModel : public QAbstractListModel +{ +public: + CustomModel(QObject *parent = 0) : QAbstractListModel(parent) { + m_values << 0 << 1 << 2 << 3 << 4; + } + + int rowCount(const QModelIndex &parent = QModelIndex()) const { + Q_UNUSED(parent); + return m_values.count(); + } + QVariant data(const QModelIndex &index, int role) const { + if (index.row() < 0 || m_values.count() <= index.row()) + return QVariant(); + + return m_values[index.row()]; + } + + Q_INVOKABLE void shrink() { + beginResetModel(); + m_values.takeLast(); + m_values.takeLast(); + endResetModel(); + } + +private: + QList<int> m_values; +}; + +void tst_QQuickPathView::qtbug46487() +{ + QScopedPointer<QQuickView> window(createView()); + + CustomModel* model = new CustomModel; + QQmlContext *ctxt = window->rootContext(); + ctxt->setContextProperty("customModel", model); + + window->setSource(testFileUrl("qtbug46487.qml")); + window->show(); + qApp->processEvents(); + + QQuickPathView *pathview = qobject_cast<QQuickPathView*>(window->rootObject()); + QVERIFY(pathview); + + QTest::qWait(500); + + // Should create just pathItemCount amount and not destroy any + QCOMPARE(pathview->count(), 5); + QCOMPARE(pathview->property("delegatesCreated").toInt(), 5); + QCOMPARE(pathview->property("delegatesDestroyed").toInt(), 0); + + // Resets the model and removes 2 items. + model->shrink(); + QTest::qWait(500); + + // Should destroy previous items (begin/endResetModel) and + // (re)create 3 new items. + QCOMPARE(pathview->count(), 3); + QCOMPARE(pathview->property("delegatesCreated").toInt(), 5 + 3); + QCOMPARE(pathview->property("delegatesDestroyed").toInt(), 5); +} + QTEST_MAIN(tst_QQuickPathView) #include "tst_qquickpathview.moc" diff --git a/tests/auto/quick/qquickpixmapcache/tst_qquickpixmapcache.cpp b/tests/auto/quick/qquickpixmapcache/tst_qquickpixmapcache.cpp index 590f022e4e..4e0534edf0 100644 --- a/tests/auto/quick/qquickpixmapcache/tst_qquickpixmapcache.cpp +++ b/tests/auto/quick/qquickpixmapcache/tst_qquickpixmapcache.cpp @@ -108,6 +108,11 @@ void tst_qquickpixmapcache::single() QFETCH(bool, exists); QFETCH(bool, neterror); +#if !QT_CONFIG(qml_network) + if (target.scheme() == "http") + QSKIP("Skipping due to lack of QML network feature"); +#endif + QString expectedError; if (neterror) { expectedError = "Error transferring " + target.toString() + " - server replied: Not found"; @@ -196,6 +201,11 @@ void tst_qquickpixmapcache::parallel() QFETCH(int, incache); QFETCH(int, cancel); +#if !QT_CONFIG(qml_network) + if (target1.scheme() == "http" || target2.scheme() == "http") + QSKIP("Skipping due to lack of QML network feature"); +#endif + QList<QUrl> targets; targets << target1 << target2; diff --git a/tests/auto/quick/qquickstates/data/anchorRewindBug3.qml b/tests/auto/quick/qquickstates/data/anchorRewindBug3.qml new file mode 100644 index 0000000000..6d829be363 --- /dev/null +++ b/tests/auto/quick/qquickstates/data/anchorRewindBug3.qml @@ -0,0 +1,38 @@ +import QtQuick 2.0 + +Rectangle { + id: root + width: 100; height: 100 + + Rectangle { + id: rectangle + objectName: "inner" + color: "green" + // Width and height end up to be 50 + // after root Component.onCompleted + width: 75 + height: 75 + anchors.top: root.top + anchors.left: root.left + } + + // Start with anchored state + state: "anchored" + states: [ + State { + name: "anchored" + AnchorChanges { + target: rectangle + anchors.top: undefined + anchors.left: undefined + anchors.right: root.right + anchors.bottom: root.bottom + } + } + ] + + Component.onCompleted: { + rectangle.width = 50 + rectangle.height = 50 + } +} diff --git a/tests/auto/quick/qquickstates/tst_qquickstates.cpp b/tests/auto/quick/qquickstates/tst_qquickstates.cpp index be1361e4ab..7332db93fd 100644 --- a/tests/auto/quick/qquickstates/tst_qquickstates.cpp +++ b/tests/auto/quick/qquickstates/tst_qquickstates.cpp @@ -154,6 +154,7 @@ private slots: void anchorChangesCrash(); void anchorRewindBug(); void anchorRewindBug2(); + void anchorRewind_keepsSize_whenStateResetsDefaultAnchors(); void script(); void restoreEntryValues(); void explicitChanges(); @@ -1087,6 +1088,30 @@ void tst_qquickstates::anchorRewindBug2() QCOMPARE(mover->width(), qreal(50.0)); } +// QTBUG-126057 +void tst_qquickstates::anchorRewind_keepsSize_whenStateResetsDefaultAnchors() +{ + // Arrange + QQmlEngine engine; + + // NOTE: Contains two nested rectangles, inner is by default anchored to the top left corner of + // its parent. A state is initially "anchored" which removes the default anchoring and anchors + // the inner rectangle to the bottom right corner of the parent. The size of the inner rectangle + // is assigned to 50x50 on Component.onCompleted of outer rectangle. + QQmlComponent rectComponent(&engine, testFileUrl("anchorRewindBug3.qml")); + QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create())); + QVERIFY(rect != nullptr); + QQuickRectangle *mover = rect->findChild<QQuickRectangle*>("inner"); + QVERIFY(mover != nullptr); + + // Act + QQuickItemPrivate::get(rect.get())->setState(""); + + // Assert + QCOMPARE(mover->width(), qreal(50.0)); + QCOMPARE(mover->height(), qreal(50.0)); +} + void tst_qquickstates::script() { QQmlEngine engine; diff --git a/tests/auto/quick/qquicktableview/data/invalidateModelContextObject.qml b/tests/auto/quick/qquicktableview/data/invalidateModelContextObject.qml new file mode 100644 index 0000000000..ff552e856c --- /dev/null +++ b/tests/auto/quick/qquicktableview/data/invalidateModelContextObject.qml @@ -0,0 +1,38 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ApplicationWindow { + width: 720 + height: 480 + visible: true + + property alias tableView: tableView + property int modelData: 15 + + Page { + anchors.fill: parent + header: Rectangle { + height: 40 + color: "red" + } + TableView { + id: tableView + anchors.fill: parent + model: modelData + contentY: Math.max(0, contentHeight - height) + contentHeight: 40 * rows + rowHeightProvider: () => 40 + columnWidthProvider: () => 200 + delegate : Rectangle { + width: 40; + height: 40; + color: "green" + Text { + anchors.fill: parent + text: index + } + } + } + } +} diff --git a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp index 96137877cb..7482367057 100644 --- a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp +++ b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp @@ -162,12 +162,16 @@ private slots: void checkSyncView_childViews_data(); void checkSyncView_childViews(); void checkSyncView_differentSizedModels(); - void checkSyncView_differentGeometry(); + void checkSyncView_differentGeometry_vertical(); + void checkSyncView_differentGeometry_horizontal(); + void checkSyncView_differentGeometry_both_directions(); void checkSyncView_connect_late_data(); void checkSyncView_connect_late(); void checkSyncView_pageFlicking(); void checkSyncView_emptyModel(); void checkSyncView_topLeftChanged(); + void checkSyncView_dontRelayoutWhileFlicking(); + void checkSyncView_detectTopLeftPositionChanged(); void delegateWithRequiredProperties(); void checkThatFetchMoreIsCalledWhenScrolledToTheEndOfTable(); void replaceModel(); @@ -278,6 +282,7 @@ private slots: void checkScroll_data(); void checkScroll(); void checkRebuildJsModel(); + void invalidateTableInstanceModelContextObject(); }; tst_QQuickTableView::tst_QQuickTableView() @@ -3070,7 +3075,7 @@ void tst_QQuickTableView::checkSyncView_differentSizedModels() QVERIFY(tableViewHVPrivate->loadedColumns.isEmpty()); } -void tst_QQuickTableView::checkSyncView_differentGeometry() +void tst_QQuickTableView::checkSyncView_differentGeometry_vertical() { // Check that you can have two tables in a syncView relation, where // the sync "child" is larger than the sync view. This means that the @@ -3083,46 +3088,106 @@ void tst_QQuickTableView::checkSyncView_differentGeometry() GET_QML_TABLEVIEW(tableViewV); GET_QML_TABLEVIEW(tableViewHV); - tableView->setWidth(40); - tableView->setHeight(40); + tableView->setHeight(90); + tableViewH->setSyncView(nullptr); + tableViewHV->setSyncView(nullptr); auto tableViewModel = TestModelAsVariant(100, 100); tableView->setModel(tableViewModel); - tableViewH->setModel(tableViewModel); tableViewV->setModel(tableViewModel); - tableViewHV->setModel(tableViewModel); WAIT_UNTIL_POLISHED; - // Check that the column widths are in sync - for (int column = tableViewH->leftColumn(); column < tableViewH->rightColumn(); ++column) { - QCOMPARE(tableViewH->columnWidth(column), tableView->columnWidth(column)); - QCOMPARE(tableViewHV->columnWidth(column), tableView->columnWidth(column)); - } + // Check that the row heights are in sync + for (int row = tableViewV->topRow(); row < tableViewV->bottomRow(); ++row) + QCOMPARE(tableViewV->rowHeight(row), tableView->rowHeight(row)); + + // Flick in a new row + tableView->setContentY(20); // Check that the row heights are in sync - for (int row = tableViewV->topRow(); row < tableViewV->bottomRow(); ++row) { + for (int row = tableViewV->topRow(); row <= tableViewV->bottomRow(); ++row) QCOMPARE(tableViewV->rowHeight(row), tableView->rowHeight(row)); - QCOMPARE(tableViewHV->rowHeight(row), tableView->rowHeight(row)); - } +} + +void tst_QQuickTableView::checkSyncView_differentGeometry_horizontal() +{ + // Check that you can have two tables in a syncView relation, where + // the sync "child" is larger than the sync view. This means that the + // child will display more rows and columns than the parent. + // In that case, the sync view will anyway need to load the same rows + // and columns as the child, otherwise the column and row sizes + // cannot be determined for the child. + LOAD_TABLEVIEW("syncviewsimple.qml"); + GET_QML_TABLEVIEW(tableViewH); + GET_QML_TABLEVIEW(tableViewV); + GET_QML_TABLEVIEW(tableViewHV); + + tableView->setWidth(90); + tableViewV->setSyncView(nullptr); + tableViewHV->setSyncView(nullptr); + + auto tableViewModel = TestModelAsVariant(100, 100); + + tableView->setModel(tableViewModel); + tableViewH->setModel(tableViewModel); - // Flick a bit, and do the same test again - tableView->setContentX(200); - tableView->setContentY(200); WAIT_UNTIL_POLISHED; // Check that the column widths are in sync - for (int column = tableViewH->leftColumn(); column < tableViewH->rightColumn(); ++column) { + for (int column = tableViewH->leftColumn(); column < tableViewH->rightColumn(); ++column) QCOMPARE(tableViewH->columnWidth(column), tableView->columnWidth(column)); - QCOMPARE(tableViewHV->columnWidth(column), tableView->columnWidth(column)); - } + + // Flick in a new column + tableView->setContentX(20); + + // Check that the column widths are in sync + for (int column = tableViewH->leftColumn(); column < tableViewH->rightColumn(); ++column) + QCOMPARE(tableViewH->columnWidth(column), tableView->columnWidth(column)); +} + +void tst_QQuickTableView::checkSyncView_differentGeometry_both_directions() { + // Check that you can have two tables in a syncView relation, where + // the sync "child" is larger than the sync view. This means that the + // child will display more rows and columns than the parent. + // In that case, the sync view will anyway need to load the same rows + // and columns as the child, otherwise the column and row sizes + // cannot be determined for the child. + LOAD_TABLEVIEW("syncviewsimple.qml"); + GET_QML_TABLEVIEW(tableViewH); + GET_QML_TABLEVIEW(tableViewV); + GET_QML_TABLEVIEW(tableViewHV); + + tableView->setWidth(90); + tableView->setHeight(90); + tableViewHV->setSyncView(nullptr); + + auto tableViewModel = TestModelAsVariant(100, 100); + + tableView->setModel(tableViewModel); + tableViewH->setModel(tableViewModel); + tableViewV->setModel(tableViewModel); + + WAIT_UNTIL_POLISHED; // Check that the row heights are in sync - for (int row = tableViewV->topRow(); row < tableViewV->bottomRow(); ++row) { + for (int row = tableViewV->topRow(); row < tableViewV->bottomRow(); ++row) QCOMPARE(tableViewV->rowHeight(row), tableView->rowHeight(row)); - QCOMPARE(tableViewHV->rowHeight(row), tableView->rowHeight(row)); - } + // Check that the column widths are in sync + for (int column = tableViewH->leftColumn(); column < tableViewH->rightColumn(); ++column) + QCOMPARE(tableViewH->columnWidth(column), tableView->columnWidth(column)); + + // Flick in a new row + tableView->setContentX(20); + tableView->setContentY(20); + + // Check that the row heights are in sync + for (int row = tableViewV->topRow(); row <= tableViewV->bottomRow(); ++row) + QCOMPARE(tableViewV->rowHeight(row), tableView->rowHeight(row)); + // Check that the column widths are in sync + for (int column = tableViewH->leftColumn(); column < tableViewH->rightColumn(); ++column) + QCOMPARE(tableViewH->columnWidth(column), tableView->columnWidth(column)); } void tst_QQuickTableView::checkSyncView_connect_late_data() @@ -3328,6 +3393,85 @@ void tst_QQuickTableView::checkSyncView_topLeftChanged() QCOMPARE(tableViewV->topRow(), tableView->topRow()); } +void tst_QQuickTableView::checkSyncView_dontRelayoutWhileFlicking() +{ + // Check that we don't do a full relayout in a sync child when + // a new row or column is flicked into the view. Normal load + // and unload of edges should suffice, equal to how the main + // TableView (syncView) does it. + LOAD_TABLEVIEW("syncviewsimple.qml"); + GET_QML_TABLEVIEW(tableViewHV); + + auto model = TestModelAsVariant(100, 100); + tableView->setModel(model); + tableViewHV->setModel(model); + + tableView->setColumnWidthProvider(QJSValue()); + tableView->setRowHeightProvider(QJSValue()); + view->rootObject()->setProperty("delegateWidth", 50); + view->rootObject()->setProperty("delegateHeight", 50); + + WAIT_UNTIL_POLISHED; + + // To check that we don't do a relayout when flicking horizontally, we use a "trick" + // where we check the rebuildOptions when we receive the rightColumnChanged + // signal. If this signal is emitted as a part of a relayout, rebuildOptions + // would still be different from RebuildOption::None at that point. + bool columnFlickedIn = false; + connect(tableViewHV, &QQuickTableView::rightColumnChanged, [&] { + columnFlickedIn = true; + QCOMPARE(tableViewHVPrivate->rebuildOptions, QQuickTableViewPrivate::RebuildOption::None); + }); + + // We do the same for vertical flicking + bool rowFlickedIn = false; + connect(tableViewHV, &QQuickTableView::bottomRowChanged, [&] { + rowFlickedIn = true; + QCOMPARE(tableViewHVPrivate->rebuildOptions, QQuickTableViewPrivate::RebuildOption::None); + }); + + // Move the main tableview so that a new column is flicked in + tableView->setContentX(60); + QTRY_VERIFY(columnFlickedIn); + + // Move the main tableview so that a new row is flicked in + tableView->setContentY(60); + QTRY_VERIFY(rowFlickedIn); +} + +void tst_QQuickTableView::checkSyncView_detectTopLeftPositionChanged() +{ + // It can happen that, during a resize of columns or rows from using a float-based + // slider, that the position of the top-left delegate item is shifted a bit left or + // right because of rounding issues. And this again can over time, as you flick, make + // the loadedTableOuterRect get slightly out of sync in the sync child compared to the + // sync view. TableView will detect if this happens (in syncSyncView), and correct for + // it. And this test will test that it works. + LOAD_TABLEVIEW("syncviewsimple.qml"); + GET_QML_TABLEVIEW(tableViewHV); + + auto model = TestModelAsVariant(100, 100); + tableView->setModel(model); + tableViewHV->setModel(model); + + WAIT_UNTIL_POLISHED; + + // Writing an auto test to trigger this rounding issue is very hard. So to keep it + // simple, we cheat by just moving the loadedTableOuterRect directly, and + // check that the syncView child detects it, and corrects it, upon doing a + // forceLayout() + tableViewPrivate->loadedTableOuterRect.moveLeft(20); + tableViewPrivate->loadedTableOuterRect.moveTop(30); + tableViewPrivate->relayoutTableItems(); + tableViewHV->forceLayout(); + + QCOMPARE(tableViewPrivate->loadedTableOuterRect.left(), 20); + QCOMPARE(tableViewHVPrivate->loadedTableOuterRect.left(), 20); + + QCOMPARE(tableViewPrivate->loadedTableOuterRect.top(), 30); + QCOMPARE(tableViewHVPrivate->loadedTableOuterRect.top(), 30); +} + void tst_QQuickTableView::checkThatFetchMoreIsCalledWhenScrolledToTheEndOfTable() { LOAD_TABLEVIEW("plaintableview.qml"); @@ -7523,6 +7667,29 @@ void tst_QQuickTableView::checkRebuildJsModel() QCOMPARE(tableView->property(modelUpdated).toInt(), 1); } +void tst_QQuickTableView::invalidateTableInstanceModelContextObject() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("invalidateModelContextObject.qml")); + + std::unique_ptr<QQuickWindow> window(qobject_cast<QQuickWindow*>(component.create())); + QVERIFY(window); + + auto tableView = window->property("tableView").value<QQuickTableView *>(); + QVERIFY(tableView); + + const int modelData = window->property("modelData").toInt(); + QTRY_COMPARE(tableView->rows(), modelData); + + bool tableViewDestroyed = false; + connect(tableView, &QObject::destroyed, [&] { + tableViewDestroyed = true; + }); + + window.reset(); + QTRY_COMPARE(tableViewDestroyed, true); +} + QTEST_MAIN(tst_QQuickTableView) #include "tst_qquicktableview.moc" diff --git a/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp b/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp index 63d4f24a54..23d2e006ba 100644 --- a/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp +++ b/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp @@ -3,14 +3,17 @@ #include <qtest.h> #include <QtTest/QtTest> +#include <QtQuick/QQuickView> #include <QtQuick/QQuickTextDocument> #include <QtQuick/QQuickItem> #include <QtQuick/private/qquicktextedit_p.h> #include <QtQuick/private/qquicktextdocument_p.h> #include <QtGui/QTextDocument> +#include <QtGui/QTextBlock> #include <QtGui/QTextDocumentWriter> #include <QtQml/QQmlEngine> #include <QtQml/QQmlComponent> +#include <QtQuickTest/QtQuickTest> #include <QtQuickTestUtils/private/qmlutils_p.h> class tst_qquicktextdocument : public QQmlDataTest @@ -22,6 +25,8 @@ public: private slots: void textDocumentWriter(); void textDocumentWithImage(); + void changeCharFormatInRange_data(); + void changeCharFormatInRange(); }; QString text = QStringLiteral("foo bar"); @@ -68,6 +73,53 @@ void tst_qquicktextdocument::textDocumentWithImage() QCOMPARE(image, document.resource(QTextDocument::ImageResource, name).value<QImage>()); } +void tst_qquicktextdocument::changeCharFormatInRange_data() +{ + QTest::addColumn<bool>("editBlock"); + + QTest::newRow("begin/end") << true; + QTest::newRow("no edit block") << false; // QTBUG-126886 : don't crash +} + +void tst_qquicktextdocument::changeCharFormatInRange() +{ + QFETCH(bool, editBlock); + QQuickView window(testFileUrl("text.qml")); + window.showNormal(); + QQuickTextEdit *textEdit = qobject_cast<QQuickTextEdit *>(window.rootObject()); + QVERIFY(textEdit); + QVERIFY(textEdit->textDocument()); + + auto *doc = textEdit->textDocument()->textDocument(); + QVERIFY(doc); + + QSignalSpy contentSpy(doc, &QTextDocument::contentsChanged); + const auto data = QStringLiteral("Format String"); + doc->setPlainText(data); + const auto block = doc->findBlockByNumber(0); + + auto formatText = [block, data] { + QTextLayout::FormatRange formatText; + formatText.start = 0; + formatText.length = data.size(); + formatText.format.setForeground(Qt::green); + block.layout()->setFormats({formatText}); + }; + + // change the char format of this block, and verify visual effect + if (editBlock) { + QTextCursor cursor(doc); + cursor.beginEditBlock(); + formatText(); + cursor.endEditBlock(); + } else { + formatText(); + } + + QVERIFY(QQuickTest::qWaitForPolish(textEdit)); + QCOMPARE(contentSpy.size(), editBlock ? 2 : 1); +} + QTEST_MAIN(tst_qquicktextdocument) #include "tst_qquicktextdocument.moc" diff --git a/tests/auto/quick/qquicktextedit/data/hAlignVisual.qml b/tests/auto/quick/qquicktextedit/data/hAlignVisual.qml index 1280a655f0..f532a9aa36 100644 --- a/tests/auto/quick/qquicktextedit/data/hAlignVisual.qml +++ b/tests/auto/quick/qquicktextedit/data/hAlignVisual.qml @@ -4,11 +4,11 @@ Rectangle { width: 200 height: 100 - Text { - objectName: "textItem" + TextEdit { + objectName: "textEditItem" text: "AA\nBBBBBBB\nCCCCCCCCCCCCCCCC" anchors.centerIn: parent - horizontalAlignment: Text.AlignLeft + horizontalAlignment: TextEdit.AlignLeft font.pointSize: 12 font.family: "Times New Roman" } diff --git a/tests/auto/quick/qquicktextedit/data/inFlickable.qml b/tests/auto/quick/qquicktextedit/data/inFlickable.qml index 7a896db29b..183ddd6701 100644 --- a/tests/auto/quick/qquicktextedit/data/inFlickable.qml +++ b/tests/auto/quick/qquicktextedit/data/inFlickable.qml @@ -1,6 +1,7 @@ import QtQuick 2.0 Flickable { + id: flick width: 320; height: 120; contentHeight: text.height TextEdit { id: text @@ -8,4 +9,10 @@ Flickable { font.pixelSize: 20 text: "several\nlines\nof\ntext\n-\ntry\nto\nflick" } + Text { + color: "red" + parent: flick // stay on top + anchors.right: parent.right + text: flick.contentY.toFixed(1) + } } diff --git a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp index 15f538ac3c..44745f8263 100644 --- a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp +++ b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp @@ -8,6 +8,8 @@ #include <QtQuick/QQuickTextDocument> #include <QtQuickTest/QtQuickTest> #include <QTextDocument> +#include <QtGui/qtextobject.h> +#include <QtGui/QTextTable> #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcontext.h> #include <QtQml/qqmlexpression.h> @@ -43,6 +45,8 @@ DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD) Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests") +// #define DEBUG_WRITE_INPUT + static bool isPlatformWayland() { return !QGuiApplication::platformName().compare(QLatin1String("wayland"), Qt::CaseInsensitive); @@ -143,6 +147,8 @@ private slots: void largeTextObservesViewport(); void largeTextSelection(); void renderingAroundSelection(); + void largeTextTables_data(); + void largeTextTables(); void signal_editingfinished(); @@ -221,7 +227,6 @@ private: void simulateKey(QWindow *, int key, Qt::KeyboardModifiers modifiers = {}); bool isMainFontFixed(); - static bool hasWindowActivation(); QStringList standard; QStringList richText; @@ -972,8 +977,8 @@ void tst_qquicktextedit::hAlignVisual() view.showNormal(); QVERIFY(QTest::qWaitForWindowExposed(&view)); - QQuickText *text = view.rootObject()->findChild<QQuickText*>("textItem"); - QVERIFY(text != nullptr); + QQuickTextEdit *text = view.rootObject()->findChild<QQuickTextEdit*>("textEditItem"); + QVERIFY(text); // Try to check whether alignment works by checking the number of black // pixels in the thirds of the grabbed image. @@ -1000,7 +1005,7 @@ void tst_qquicktextedit::hAlignVisual() } { // HCenter Align - text->setHAlign(QQuickText::AlignHCenter); + text->setHAlign(QQuickTextEdit::AlignHCenter); QImage image = view.grabWindow(); const int left = numberOfNonWhitePixels(centeredSection1, centeredSection2, image); const int mid = numberOfNonWhitePixels(centeredSection2, centeredSection3, image); @@ -1010,7 +1015,7 @@ void tst_qquicktextedit::hAlignVisual() } { // Right Align - text->setHAlign(QQuickText::AlignRight); + text->setHAlign(QQuickTextEdit::AlignRight); QImage image = view.grabWindow(); const int left = numberOfNonWhitePixels(centeredSection1, centeredSection2, image); const int mid = numberOfNonWhitePixels(centeredSection2, centeredSection3, image); @@ -1022,36 +1027,36 @@ void tst_qquicktextedit::hAlignVisual() text->setWidth(200); { - // Left Align + // Right Align QImage image = view.grabWindow(); - 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()); - QCOMPARE(right, 0); + const int x = image.width() - qCeil(text->implicitWidth() * view.devicePixelRatio()); + const int left = numberOfNonWhitePixels(0, x, image); + const int right = numberOfNonWhitePixels(x, image.width() - x, image); + QCOMPARE(left, 0); + QVERIFY2(right > 0, msgNotGreaterThan(left, 0).constData()); } { // HCenter Align - text->setHAlign(QQuickText::AlignHCenter); + text->setHAlign(QQuickTextEdit::AlignHCenter); QImage image = view.grabWindow(); - 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); - int right = numberOfNonWhitePixels(x2, image.width() - x2, image); + const int x1 = qFloor(image.width() - text->implicitWidth() * view.devicePixelRatio()) / 2; + const int x2 = image.width() - x1; + const int left = numberOfNonWhitePixels(0, x1, image); + const int mid = numberOfNonWhitePixels(x1, x2 - x1, image); + const int right = numberOfNonWhitePixels(x2, image.width(), image); QCOMPARE(left, 0); QVERIFY2(mid > 0, msgNotGreaterThan(left, 0).constData()); QCOMPARE(right, 0); } { - // Right Align - text->setHAlign(QQuickText::AlignRight); + // Left Align + text->setHAlign(QQuickTextEdit::AlignLeft); QImage image = view.grabWindow(); - 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); - QVERIFY2(right > 0, msgNotGreaterThan(left, 0).constData()); + const int x = qCeil(text->implicitWidth() * view.devicePixelRatio()); + const int left = numberOfNonWhitePixels(0, x, image); + const int right = numberOfNonWhitePixels(x, image.width() - x, image); + QVERIFY2(left > 0, msgNotGreaterThan(left, 0).constData()); + QCOMPARE(right, 0); } } @@ -3385,11 +3390,6 @@ bool tst_qquicktextedit::isMainFontFixed() return ret; } -bool tst_qquicktextedit::hasWindowActivation() -{ - return (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)); -} - void tst_qquicktextedit::textInput() { QQuickView view(testFileUrl("inputMethodEvent.qml")); @@ -3951,6 +3951,105 @@ void tst_qquicktextedit::renderingAroundSelection() QTRY_COMPARE(textItem->sortedLinePositions, sortedLinePositions); } +struct OffsetAndExpectedBlocks { + int tableIndex; // which nested frame + qreal tableOffset; // fraction of that frame's height to scroll to + int minExpectedBlockCount; + + OffsetAndExpectedBlocks(int i, qreal o, int c) + : tableIndex(i), tableOffset(o), minExpectedBlockCount(c) {} +}; + +typedef QList<OffsetAndExpectedBlocks> OffsetAndExpectedBlocksList; + +void tst_qquicktextedit::largeTextTables_data() +{ + QTest::addColumn<int>("tables"); + QTest::addColumn<int>("tableCols"); + QTest::addColumn<int>("tableRows"); + QTest::addColumn<OffsetAndExpectedBlocksList>("steps"); + + QTest::newRow("one big table") << 1 << 3 << 70 + << OffsetAndExpectedBlocksList{ + OffsetAndExpectedBlocks(1, 0.75, 150), + OffsetAndExpectedBlocks(1, 0.5, 150)}; + QTest::newRow("short tables") << 5 << 3 << 10 + << OffsetAndExpectedBlocksList{ + OffsetAndExpectedBlocks(4, 0.75, 35), + OffsetAndExpectedBlocks(3, 0.25, 50), + OffsetAndExpectedBlocks(2, 0.75, 50)}; +} + +void tst_qquicktextedit::largeTextTables() // QTBUG-118636 +{ + QFETCH(int, tables); + QFETCH(int, tableCols); + QFETCH(int, tableRows); + QFETCH(OffsetAndExpectedBlocksList, steps); + + QStringList lines; + + lines << QLatin1String("<h1>") + QTest::currentDataTag() + "</h1>"; + for (int t = 0; t < tables; ++t) { + if (t > 0) + lines << QString("<p>table %1</p>").arg(t); + lines << "<table border='1'>"; + for (int r = 0; r < tableRows; ++r) { + lines << " <tr>"; + for (int c = 0; c < tableCols; ++c) + lines << QString(" <td>table %1 cell %2, %3</td>").arg(t).arg(c).arg(r); + lines << " </tr>"; + } + lines << "</table>"; + } + lines << "<p>here endeth the tables</p>"; + QString html = lines.join('\n'); + +#ifdef DEBUG_WRITE_INPUT + QFile f(QLatin1String("/tmp/") + QTest::currentDataTag() + ".html"); + f.open(QFile::WriteOnly); + f.write(html.toUtf8()); + f.close(); +#endif + + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("inFlickable.qml"))); + QQuickFlickable *flick = qmlobject_cast<QQuickFlickable *>(window.rootObject()); + QVERIFY(flick); + QQuickTextEdit *te = window.rootObject()->findChild<QQuickTextEdit *>(); + QVERIFY(te); + auto *tePriv = QQuickTextEditPrivate::get(te); + auto font = te->font(); + font.setPixelSize(10); + te->setFont(font); + + te->setTextFormat(QQuickTextEdit::RichText); + te->setText(html); + te->setFlag(QQuickItem::ItemObservesViewport); // this isn't "large text", but test viewporting anyway + + QTextDocument *doc = te->textDocument()->textDocument(); + QList<QTextFrame *> frames = doc->rootFrame()->childFrames(); + frames.prepend(doc->rootFrame()); + qCDebug(lcTests) << "blocks" << doc->blockCount() << "chars" << doc->characterCount() << "frames" << frames; + + for (const OffsetAndExpectedBlocks &oeb : steps) { + QCOMPARE_GT(frames.size(), oeb.tableIndex); + const QTextFrame *textFrame = frames.at(oeb.tableIndex); + const QTextCursor top = textFrame->firstCursorPosition(); + const qreal yTop = te->positionToRectangle(top.position()).top(); + const QTextCursor bottom = textFrame->lastCursorPosition(); + const qreal yBottom = te->positionToRectangle(bottom.position()).bottom(); + const qreal y = yTop + (yBottom - yTop) * oeb.tableOffset; + qCDebug(lcTests) << "frame" << textFrame << "goes from pos" << top.position() << "y" << yTop + << "to pos" << bottom.position() << "y" << yBottom << "; scrolling to" << y + << "which is at" << oeb.tableOffset << "of table height" << (yBottom - yTop); + flick->setContentY(y); + qCDebug(lcTests) << tePriv->renderedRegion << "rendered blocks" << tePriv->renderedBlockCount << ":" + << tePriv->firstBlockInViewport << "to" << tePriv->firstBlockPastViewport; + QTRY_COMPARE_GE(tePriv->renderedBlockCount, oeb.minExpectedBlockCount); + } +} + void tst_qquicktextedit::signal_editingfinished() { QQuickView *window = new QQuickView(nullptr); @@ -6483,8 +6582,8 @@ void tst_qquicktextedit::touchscreenDoesNotSelect() void tst_qquicktextedit::touchscreenSetsFocusAndMovesCursor() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION + QQuickView window; QVERIFY(QQuickTest::showView(window, testFileUrl("twoInAColumn.qml"))); window.requestActivate(); diff --git a/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp b/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp index 5e2d49afb8..c944406e10 100644 --- a/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp +++ b/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp @@ -5,6 +5,7 @@ #include <QtQuickTestUtils/private/qmlutils_p.h> #include <QtQuickTestUtils/private/testhttpserver_p.h> #include <QtQuickTestUtils/private/viewtestutils_p.h> +#include <QtQuickTestUtils/private/visualtestutils_p.h> #include <private/qinputmethod_p.h> #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcomponent.h> @@ -212,7 +213,6 @@ private: #if QT_CONFIG(shortcut) void simulateKeys(QWindow *window, const QKeySequence &sequence); #endif - static bool hasWindowActivation(); QQmlEngine engine; QStringList standard; @@ -238,11 +238,6 @@ void tst_qquicktextinput::simulateKeys(QWindow *window, const QList<Key> &keys) } } -bool tst_qquicktextinput::hasWindowActivation() -{ - return (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)); -} - #if QT_CONFIG(shortcut) void tst_qquicktextinput::simulateKeys(QWindow *window, const QKeySequence &sequence) @@ -7191,17 +7186,15 @@ void tst_qquicktextinput::touchscreenDoesNotSelect() QTest::touchEvent(&window, touchscreen.data()).press(0, QPoint(x2,y), &window); QTest::touchEvent(&window, touchscreen.data()).release(0, QPoint(x2,y), &window); QQuickTouchUtils::flush(&window); - QCOMPARE(textInputObject->selectedText().isEmpty(), !expectDefaultSelectByMouse); - if (expectDefaultSelectByMouse) - QCOMPARE(textInputObject->cursorPosition(), cursorPos); - else - QCOMPARE_NE(textInputObject->cursorPosition(), cursorPos); + QCOMPARE(textInputObject->selectedText().isEmpty(), true); + QCOMPARE_NE(textInputObject->cursorPosition(), cursorPos); + QVERIFY(textInputObject->selectedText().isEmpty()); } void tst_qquicktextinput::touchscreenSetsFocusAndMovesCursor() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION + QQuickView window; QVERIFY(QQuickTest::showView(window, testFileUrl("twoInAColumn.qml"))); window.requestActivate(); diff --git a/tests/auto/quick/qquickview_extra/data/qtbug_87228.qml b/tests/auto/quick/qquickview_extra/data/qtbug_87228.qml index ff10eba23d..9327daae6b 100644 --- a/tests/auto/quick/qquickview_extra/data/qtbug_87228.qml +++ b/tests/auto/quick/qquickview_extra/data/qtbug_87228.qml @@ -15,6 +15,8 @@ Item { Rectangle { ListView { objectName: "listView" + width: 100 + height: 100 delegate: Text { required property string desc text: desc diff --git a/tests/auto/quick/softwarerenderer/tst_softwarerenderer.cpp b/tests/auto/quick/softwarerenderer/tst_softwarerenderer.cpp index 2d2581a3fa..2124a8ee8f 100644 --- a/tests/auto/quick/softwarerenderer/tst_softwarerenderer.cpp +++ b/tests/auto/quick/softwarerenderer/tst_softwarerenderer.cpp @@ -56,10 +56,8 @@ void tst_SoftwareRenderer::renderTarget() rc.polishItems(); - rc.beginFrame(); rc.sync(); rc.render(); - rc.endFrame(); QImage content = window->grabWindow(); QString errorMessage; @@ -74,10 +72,8 @@ void tst_SoftwareRenderer::renderTarget() rc.polishItems(); - rc.beginFrame(); rc.sync(); rc.render(); - rc.endFrame(); content = window->grabWindow(); QVERIFY2(QQuickVisualTestUtils::compareImages(content, renderTarget2, &errorMessage), diff --git a/tests/auto/quick/touchmouse/tst_touchmouse.cpp b/tests/auto/quick/touchmouse/tst_touchmouse.cpp index d7820996be..179acbc728 100644 --- a/tests/auto/quick/touchmouse/tst_touchmouse.cpp +++ b/tests/auto/quick/touchmouse/tst_touchmouse.cpp @@ -244,6 +244,8 @@ private slots: void strayTouchDoesntAutograb(); + void noDoubleClickWithInterveningTouch(); + protected: bool eventFilter(QObject *, QEvent *event) override { @@ -1303,7 +1305,7 @@ void tst_TouchMouse::touchGrabCausesMouseUngrab() QVERIFY(leftItem); EventItem *rightItem = window.rootObject()->findChild<EventItem*>("rightItem"); - QVERIFY(leftItem); + QVERIFY(rightItem); // Send a touch to the leftItem. But leftItem accepts only mouse events, thus // a mouse event will be synthesized out of this touch and will get accepted by @@ -1660,6 +1662,58 @@ void tst_TouchMouse::strayTouchDoesntAutograb() // QTBUG-107867 QTest::touchEvent(&window, device).release(0, p1).release(1, p1); } +void tst_TouchMouse::noDoubleClickWithInterveningTouch() // QTBUG-116442 +{ + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("twosiblingitems.qml"))); + + EventItem *leftItem = window.rootObject()->findChild<EventItem*>("leftItem"); + QVERIFY(leftItem); + // simulate a MouseArea: don't accept touch + leftItem->setAcceptedMouseButtons(Qt::LeftButton); + leftItem->acceptMouse = true; + + EventItem *rightItem = window.rootObject()->findChild<EventItem*>("rightItem"); + QVERIFY(rightItem); + // simulate an item that reacts to either touch or mouse + rightItem->setAcceptedMouseButtons(Qt::LeftButton); + rightItem->acceptMouse = true; + rightItem->setAcceptTouchEvents(true); + rightItem->acceptTouch = true; + + const QPoint pLeft(80, 200); + const QPoint pRight(240, 200); + + // tap left + QTest::touchEvent(&window, device).press(1, pLeft, &window); + QTest::touchEvent(&window, device).release(1, pLeft, &window); + QQuickTouchUtils::flush(&window); + qCDebug(lcTests) << "left tap" << leftItem->eventList; + QCOMPARE(leftItem->eventList.size(), 3); + QCOMPARE(leftItem->eventList.at(0).type, QEvent::MouseButtonPress); + leftItem->eventList.clear(); + + // tap right + QTest::touchEvent(&window, device).press(1, pRight, &window); + QTest::touchEvent(&window, device).release(1, pRight, &window); + QQuickTouchUtils::flush(&window); + qCDebug(lcTests) << "right tap" << rightItem->eventList; + QCOMPARE(rightItem->eventList.size(), 2); + QCOMPARE(rightItem->eventList.at(0).type, QEvent::TouchBegin); + rightItem->eventList.clear(); + + // tap left again: this is NOT a double-click, even though it's within time and space limits + QTest::touchEvent(&window, device).press(3, pLeft, &window); + QTest::touchEvent(&window, device).release(3, pLeft, &window); + QQuickTouchUtils::flush(&window); + qCDebug(lcTests) << "left tap again" << leftItem->eventList; + QCOMPARE(leftItem->eventList.size(), 3); + QCOMPARE(leftItem->eventList.at(0).type, QEvent::MouseButtonPress); + QCOMPARE(leftItem->eventList.at(1).type, QEvent::MouseButtonRelease); + QCOMPARE(leftItem->eventList.at(2).type, QEvent::UngrabMouse); + leftItem->eventList.clear(); +} + QTEST_MAIN(tst_TouchMouse) #include "tst_touchmouse.moc" diff --git a/tests/auto/quickcontrols/CMakeLists.txt b/tests/auto/quickcontrols/CMakeLists.txt index f4f2e6b2c6..b7669d4f54 100644 --- a/tests/auto/quickcontrols/CMakeLists.txt +++ b/tests/auto/quickcontrols/CMakeLists.txt @@ -24,13 +24,16 @@ add_subdirectory(pressandhold) add_subdirectory(qquickapplicationwindow) add_subdirectory(qquickcontrol) add_subdirectory(qquickcolor) +add_subdirectory(qquickcontainer) add_subdirectory(qquickdrawer) add_subdirectory(qquickheaderview) add_subdirectory(qquickiconimage) add_subdirectory(qquickiconlabel) add_subdirectory(qquickimaginestyle) -add_subdirectory(qquickmaterialstyle) -add_subdirectory(qquickmaterialstyleconf) +if (QT_FEATURE_quickcontrols2_material) + add_subdirectory(qquickmaterialstyle) + add_subdirectory(qquickmaterialstyleconf) +endif() add_subdirectory(qquickmenu) add_subdirectory(qquickmenubar) add_subdirectory(qquickninepatchimage) diff --git a/tests/auto/quickcontrols/controls/basic/CMakeLists.txt b/tests/auto/quickcontrols/controls/basic/CMakeLists.txt index dabab76bbe..74bbf2954e 100644 --- a/tests/auto/quickcontrols/controls/basic/CMakeLists.txt +++ b/tests/auto/quickcontrols/controls/basic/CMakeLists.txt @@ -43,6 +43,9 @@ set_source_files_properties(${test_data} HEADER_FILE_ONLY ON ) +set(test_target tst_basic) +include(../shared.cmake) + #### Keys ignored in scope 1:.:.:basic.pro:<TRUE>: # OTHER_FILES = "$$PWD/../data/*.qml" # TEMPLATE = "app" diff --git a/tests/auto/quickcontrols/controls/data/combobox/shader.frag b/tests/auto/quickcontrols/controls/data/combobox/shader.frag new file mode 100644 index 0000000000..fbbef218e6 --- /dev/null +++ b/tests/auto/quickcontrols/controls/data/combobox/shader.frag @@ -0,0 +1,19 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#version 440 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; +layout(binding = 1) uniform sampler2D source; // this item + +layout(std140, binding = 0) uniform buf { + float qt_Opacity; // inherited opacity of this item +}; + + +void main() { + vec4 p = texture(source, qt_TexCoord0); + lowp float g = dot(p.xyz, vec3(0.344, 0.5, 0.156)); + fragColor = vec4(g, g, g, p.a) * qt_Opacity; +} diff --git a/tests/auto/quickcontrols/controls/data/tst_abstractbutton.qml b/tests/auto/quickcontrols/controls/data/tst_abstractbutton.qml index 4560c93dea..296aa2c507 100644 --- a/tests/auto/quickcontrols/controls/data/tst_abstractbutton.qml +++ b/tests/auto/quickcontrols/controls/data/tst_abstractbutton.qml @@ -858,7 +858,7 @@ TestCase { // Ensure that clicked is emitted when no handler is defined for the pressAndHold() signal. // Note that even though signal spies aren't considered in QObject::isSignalConnected(), // we can't use one here to check for pressAndHold(), because otherwise clicked() won't be emitted. - wait(Qt.styleHints.mousePressAndHoldInterval + 100) + wait(Application.styleHints.mousePressAndHoldInterval + 100) mouseRelease(control) compare(clickedSpy.count, 1) } diff --git a/tests/auto/quickcontrols/controls/data/tst_button.qml b/tests/auto/quickcontrols/controls/data/tst_button.qml index 8720b55eff..4ca8ebfd6d 100644 --- a/tests/auto/quickcontrols/controls/data/tst_button.qml +++ b/tests/auto/quickcontrols/controls/data/tst_button.qml @@ -4,6 +4,7 @@ import QtQuick import QtTest import QtQuick.Controls +import Qt.test.controls TestCase { id: testCase @@ -448,12 +449,20 @@ TestCase { verify(!textLabel) compare(iconImage.x, (control.availableWidth - iconImage.width) / 2) compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + if (StyleInfo.styleName === "Material") { + compare(control.leftPadding, Material.buttonLeftPadding(false, true)) + compare(control.rightPadding, Material.buttonRightPadding(false, true, false)) + } break; case Button.TextOnly: verify(!iconImage) verify(textLabel) compare(textLabel.x, (control.availableWidth - textLabel.width) / 2) compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + if (StyleInfo.styleName === "Material") { + compare(control.leftPadding, Material.buttonLeftPadding(false, false)) + compare(control.rightPadding, Material.buttonRightPadding(false, false, true)) + } break; case Button.TextUnderIcon: verify(iconImage) @@ -461,6 +470,10 @@ TestCase { compare(iconImage.x, (control.availableWidth - iconImage.width) / 2) compare(textLabel.x, (control.availableWidth - textLabel.width) / 2) verify(iconImage.y < textLabel.y) + if (StyleInfo.styleName === "Material") { + compare(control.leftPadding, Material.buttonLeftPadding(false, true)) + compare(control.rightPadding, Material.buttonRightPadding(false, true, true)) + } break; case Button.TextBesideIcon: verify(iconImage) @@ -471,6 +484,10 @@ TestCase { verify(iconImage.x < textLabel.x) compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + if (StyleInfo.styleName === "Material") { + compare(control.leftPadding, Material.buttonLeftPadding(false, true)) + compare(control.rightPadding, Material.buttonRightPadding(false, true, true)) + } break; } } diff --git a/tests/auto/quickcontrols/controls/data/tst_buttongroup.qml b/tests/auto/quickcontrols/controls/data/tst_buttongroup.qml index ef97405234..bec2d8ca88 100644 --- a/tests/auto/quickcontrols/controls/data/tst_buttongroup.qml +++ b/tests/auto/quickcontrols/controls/data/tst_buttongroup.qml @@ -304,7 +304,9 @@ TestCase { id: repeater Column { id: column - property ButtonGroup group: ButtonGroup { buttons: column.children } + property ButtonGroup group: ButtonGroup { + buttons: column.children.filter((child) => child !== r) + } property alias repeater: r Repeater { id: r @@ -392,7 +394,7 @@ TestCase { id: checkedButtonColumn Column { id: column - ButtonGroup { buttons: column.children } + ButtonGroup { buttons: column.children.filter((child) => child !== repeater) } Repeater { id: repeater delegate: Button { diff --git a/tests/auto/quickcontrols/controls/data/tst_checkbox.qml b/tests/auto/quickcontrols/controls/data/tst_checkbox.qml index 7d80484123..7a8e563131 100644 --- a/tests/auto/quickcontrols/controls/data/tst_checkbox.qml +++ b/tests/auto/quickcontrols/controls/data/tst_checkbox.qml @@ -201,7 +201,7 @@ TestCase { sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true, "checkState": Qt.Checked }], "pressed"] // Don't want to double-click. - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(sequenceSpy.success) @@ -220,7 +220,7 @@ TestCase { // release outside sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false, "checkState": Qt.Unchecked }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(sequenceSpy.success) diff --git a/tests/auto/quickcontrols/controls/data/tst_combobox.qml b/tests/auto/quickcontrols/controls/data/tst_combobox.qml index 8e671a38a6..0cc4de4d0c 100644 --- a/tests/auto/quickcontrols/controls/data/tst_combobox.qml +++ b/tests/auto/quickcontrols/controls/data/tst_combobox.qml @@ -74,16 +74,7 @@ TestCase { objectName: "ShaderFX" width: rect.width height: rect.height - fragmentShader: " - uniform lowp sampler2D source; // this item - uniform lowp float qt_Opacity; // inherited opacity of this item - varying highp vec2 qt_TexCoord0; - void main() { - lowp vec4 p = texture2D(source, qt_TexCoord0); - lowp float g = dot(p.xyz, vec3(0.344, 0.5, 0.156)); - gl_FragColor = vec4(g, g, g, p.a) * qt_Opacity; - }" - + fragmentShader: "qrc:/data/combobox/shader.frag.qsb" } } } @@ -1956,7 +1947,7 @@ TestCase { // and then that ComboBox loses focus, its currentIndex should change // to the index of the edit text (assuming a match is found). function test_currentIndexChangeOnLostFocus() { - if (Qt.styleHints.tabFocusBehavior !== Qt.TabFocusAllControls) + if (Application.styleHints.tabFocusBehavior !== Qt.TabFocusAllControls) skip("This platform only allows tab focus for text controls") let theModel = [] @@ -2004,11 +1995,16 @@ TestCase { compare(currentIndexSpy.count, 1) } + readonly property font testFont: ({ + family: "Arial", + pixelSize: 12 + }) + Component { - id: appFontTextFieldComponent + id: fixedFontTextFieldComponent TextField { objectName: "appFontTextField" - font: Qt.application.font + font: testCase.testFont // We don't want the background's implicit width to interfere with our tests, // which are about implicit width of the contentItem of ComboBox, which is by default TextField. background: null @@ -2016,14 +2012,14 @@ TestCase { } Component { - id: appFontContentItemComboBoxComponent + id: fixedFontContentItemComboBoxComponent ComboBox { // Override the contentItem so that the font doesn't vary between styles. contentItem: TextField { objectName: "appFontContentItemTextField" // We do this just to be extra sure that the font never comes from the control, - // as we want it to match that of the TextField in the appFontTextFieldComponent. - font: Qt.application.font + // as we want it to match that of the TextField in the fixedFontTextFieldComponent. + font: testCase.testFont background: null } } @@ -2077,14 +2073,14 @@ TestCase { function test_implicitContentWidthPolicy_ContentItemImplicitWidth() { // Set ContentItemImplicitWidth and ensure that implicitContentWidth is as wide as the current item // by comparing it against the implicitWidth of an identical TextField - let control = createTemporaryObject(appFontContentItemComboBoxComponent, testCase, { + let control = createTemporaryObject(fixedFontContentItemComboBoxComponent, testCase, { model: ["Short", "Kinda long"], implicitContentWidthPolicy: ComboBox.ContentItemImplicitWidth }) verify(control) compare(control.implicitContentWidthPolicy, ComboBox.ContentItemImplicitWidth) - let textField = createTemporaryObject(appFontTextFieldComponent, testCase) + let textField = createTemporaryObject(fixedFontTextFieldComponent, testCase) verify(textField) // Don't set any text on textField because we're not accounting for the widest // text here, so we want to compare it against an empty TextField. @@ -2103,14 +2099,14 @@ TestCase { } function test_implicitContentWidthPolicy_WidestText(data) { - let control = createTemporaryObject(appFontContentItemComboBoxComponent, testCase, { + let control = createTemporaryObject(fixedFontContentItemComboBoxComponent, testCase, { model: data.model, implicitContentWidthPolicy: ComboBox.WidestText }) verify(control) compare(control.implicitContentWidthPolicy, ComboBox.WidestText) - let textField = createTemporaryObject(appFontTextFieldComponent, testCase) + let textField = createTemporaryObject(fixedFontTextFieldComponent, testCase) verify(textField) textField.text = "Kinda long" // Note that we don't need to change the current index here, as the implicitContentWidth @@ -2137,7 +2133,7 @@ TestCase { // Changes in font should result in the implicitContentWidth being updated. textField.font.pixelSize *= 2 // We have to change the contentItem's font size manually since we break the - // style's binding to the control's font when we set Qt.application.font to it. + // style's binding to the control's font when we set the fixed font on it. control.contentItem.font.pixelSize *= 2 control.font.pixelSize *= 2 compare(Math.ceil(control.implicitContentWidth), Math.ceil(textField.implicitWidth)) @@ -2148,14 +2144,14 @@ TestCase { } function test_implicitContentWidthPolicy_WidestTextWhenCompleted(data) { - let control = createTemporaryObject(appFontContentItemComboBoxComponent, testCase, { + let control = createTemporaryObject(fixedFontContentItemComboBoxComponent, testCase, { model: data.model, implicitContentWidthPolicy: ComboBox.WidestTextWhenCompleted }) verify(control) compare(control.implicitContentWidthPolicy, ComboBox.WidestTextWhenCompleted) - let textField = createTemporaryObject(appFontTextFieldComponent, testCase) + let textField = createTemporaryObject(fixedFontTextFieldComponent, testCase) verify(textField) textField.text = "Kinda long" compare(Math.ceil(control.implicitContentWidth), Math.ceil(textField.implicitWidth)) diff --git a/tests/auto/quickcontrols/controls/data/tst_control.qml b/tests/auto/quickcontrols/controls/data/tst_control.qml index 4a2bc33b33..5c5e557f11 100644 --- a/tests/auto/quickcontrols/controls/data/tst_control.qml +++ b/tests/auto/quickcontrols/controls/data/tst_control.qml @@ -1064,7 +1064,7 @@ TestCase { verify(control) compare(control.hovered, false) - compare(control.hoverEnabled, Qt.styleHints.useHoverEffects) + compare(control.hoverEnabled, Application.styleHints.useHoverEffects) control.hoverEnabled = false @@ -1098,7 +1098,7 @@ TestCase { function test_hoverEnabled() { let control = createTemporaryObject(component, testCase) - compare(control.hoverEnabled, Qt.styleHints.useHoverEffects) + compare(control.hoverEnabled, Application.styleHints.useHoverEffects) let child = component.createObject(control) let grandChild = component.createObject(child) diff --git a/tests/auto/quickcontrols/controls/data/tst_delaybutton.qml b/tests/auto/quickcontrols/controls/data/tst_delaybutton.qml index 27145cd2ab..82d4f56495 100644 --- a/tests/auto/quickcontrols/controls/data/tst_delaybutton.qml +++ b/tests/auto/quickcontrols/controls/data/tst_delaybutton.qml @@ -172,7 +172,7 @@ TestCase { "pressed", "activated"] // Don't want to double-click. - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) tryVerify(function() { return sequenceSpy.success}) @@ -190,7 +190,7 @@ TestCase { sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], ["downChanged", { "down": true }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(sequenceSpy.success) @@ -208,7 +208,7 @@ TestCase { sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], ["downChanged", { "down": true }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(sequenceSpy.success) diff --git a/tests/auto/quickcontrols/controls/data/tst_radiobutton.qml b/tests/auto/quickcontrols/controls/data/tst_radiobutton.qml index 08caac977d..47cbc7b72b 100644 --- a/tests/auto/quickcontrols/controls/data/tst_radiobutton.qml +++ b/tests/auto/quickcontrols/controls/data/tst_radiobutton.qml @@ -154,7 +154,7 @@ TestCase { sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], "pressed"] // Don't want to double-click. - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(sequenceSpy.success) @@ -169,7 +169,7 @@ TestCase { // release outside sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(sequenceSpy.success) diff --git a/tests/auto/quickcontrols/controls/data/tst_splitview.qml b/tests/auto/quickcontrols/controls/data/tst_splitview.qml index e835a8440f..a2054000b6 100644 --- a/tests/auto/quickcontrols/controls/data/tst_splitview.qml +++ b/tests/auto/quickcontrols/controls/data/tst_splitview.qml @@ -74,20 +74,19 @@ TestCase { // Note that the indices mentioned here account for handles; they do not // match the indices reported by QQuickSplitView's logging categories. compare(item.x, expectedGeometry.x, "Mismatch in actual vs expected x value of " - + itemType + " at index " + typeSpecificIndex + context) + + itemType + " " + item + " at index " + typeSpecificIndex + context) compare(item.y, expectedGeometry.y, "Mismatch in actual vs expected y value of " - + itemType + " at index " + typeSpecificIndex + context) + + itemType + " " + item + " at index " + typeSpecificIndex + context) compare(item.width, expectedGeometry.width, "Mismatch in actual vs expected width value of " - + itemType + " at index " + typeSpecificIndex + context) + + itemType + " " + item + " at index " + typeSpecificIndex + context) compare(item.height, expectedGeometry.height, "Mismatch in actual vs expected height value of " - + itemType + " at index " + typeSpecificIndex + context) + + itemType + " " + item + " at index " + typeSpecificIndex + context) } } property real defaultHorizontalHandleWidth: 10 property real defaultVerticalHandleHeight: 10 - Component { id: signalSpyComponent SignalSpy {} @@ -96,14 +95,14 @@ TestCase { Component { id: handleComponent Rectangle { - objectName: "handle" + objectName: `handle ${x},${y} ${width}x${height} visible: ${visible}` implicitWidth: defaultHorizontalHandleWidth implicitHeight: defaultVerticalHandleHeight color: "#444" Text { - objectName: "handleText_" + text - text: parent.x + "," + parent.y + " " + parent.width + "x" + parent.height + objectName: text + "_Text" + text: parent.objectName color: "white" anchors.centerIn: parent rotation: 90 @@ -2672,4 +2671,88 @@ TestCase { verify(!firstHandle.SplitHandle.pressed) compare(firstItem.width, 125) } + + Component { + id: hiddenItemComponent + + SplitView { + anchors.fill: parent + handle: handleComponent + orientation: Qt.Horizontal + + component SplitItem: Rectangle { + objectName: labelText + + SplitView.preferredWidth: 50 + SplitView.fillHeight: true + + required property string labelText + + Text { + anchors.fill: parent + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + text: `${parent.labelText} - width: ${parent.width.toFixed(2)}` + } + } + + SplitItem { + color: "blue" + labelText: "View 1" + } + SplitItem { + color: "red" + labelText: "View 2 (hidden)" + visible: false + } + SplitItem { + color: "purple" + labelText: "View 3" + } + SplitItem { + color: "yellow" + labelText: "View 4" + } + } + } + + function test_resizeHiddenItem() { + let control = createTemporaryObject(hiddenItemComponent, testCase) + verify(control) + + const standardItemWidth = 50 + let expectedGeometries = [ + // First item. + { x: 0, y: 0, width: standardItemWidth, height: control.height }, + // First handle. + { x: standardItemWidth, y: 0, width: defaultHorizontalHandleWidth, height: control.height }, + // The second item and its handle are hidden. + { hidden: true }, + { hidden: true }, + // Third item. + { x: standardItemWidth + defaultHorizontalHandleWidth, y: 0, width: standardItemWidth, height: control.height }, + // Third handle. + { x: (standardItemWidth * 2) + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, height: control.height }, + // Fourth item. + { x: (standardItemWidth * 2) + (defaultHorizontalHandleWidth * 2), y: 0, + width: control.width - (standardItemWidth * 2) - (defaultHorizontalHandleWidth * 2), height: control.height } + ] + compareSizes(control, expectedGeometries, "before dragging handle") + + // Drag the third handle to the right. + let handles = findHandles(control) + let thirdHandle = handles[2] + // The third (index 4 here) item should get one pixel bigger, and the fourth one pixel smaller. + ++expectedGeometries[4].width + ++expectedGeometries[5].x // handle + ++expectedGeometries[6].x + --expectedGeometries[6].width + // Use individual events rather than mouseDrag because that will move it past the drag threshold, + // which we don't want, since we only want to move by 1 pixel. + mousePress(thirdHandle) + mouseMove(thirdHandle, thirdHandle.width / 2 + 1, thirdHandle.height / 2, 16) + mouseRelease(thirdHandle) + compareSizes(control, expectedGeometries, "after dragging handle") + } } diff --git a/tests/auto/quickcontrols/controls/data/tst_swipedelegate.qml b/tests/auto/quickcontrols/controls/data/tst_swipedelegate.qml index 8d57e37575..ddbd283c87 100644 --- a/tests/auto/quickcontrols/controls/data/tst_swipedelegate.qml +++ b/tests/auto/quickcontrols/controls/data/tst_swipedelegate.qml @@ -14,7 +14,7 @@ TestCase { when: windowShown name: "SwipeDelegate" - readonly property int dragDistance: Math.max(20, Qt.styleHints.startDragDistance + 5) + readonly property int dragDistance: Math.max(20, Application.styleHints.startDragDistance + 5) Component { id: backgroundFillComponent @@ -655,6 +655,20 @@ TestCase { width: 100 height: 120 + property int rotation: 0 + + transform: [ + Rotation { + angle: rotation + origin.x: 0 + origin.y: 0 + }, + Translate { + x: (rotation === 90) ? parent.width : 0 + y: 0 + } + ] + model: ListModel { ListElement { name: "Apple" } ListElement { name: "Orange" } @@ -712,15 +726,20 @@ TestCase { function test_removableDelegates_data() { return [ { tag: "mouse", touch: false }, - { tag: "touch", touch: true } + { tag: "touch", touch: true }, + { tag: "mouse_rotation_90", touch: false, rotation: 90 }, + { tag: "touch_rotation_90", touch: true, rotation: 90 }, ] } - function test_removableDelegates() { - var listView = createTemporaryObject(removableDelegatesComponent, testCase); + function test_removableDelegates(data) { + let listView = createTemporaryObject(removableDelegatesComponent, testCase); verify(listView); compare(listView.count, 3); + if (data.rotation) + listView.rotation = data.rotation; + let touch = data.touch ? touchEvent(listView) : null // Expose the remove button. diff --git a/tests/auto/quickcontrols/controls/data/tst_swipeview.qml b/tests/auto/quickcontrols/controls/data/tst_swipeview.qml index 0c69e26f12..694741ce45 100644 --- a/tests/auto/quickcontrols/controls/data/tst_swipeview.qml +++ b/tests/auto/quickcontrols/controls/data/tst_swipeview.qml @@ -561,7 +561,7 @@ TestCase { } function test_focus() { - if (Qt.styleHints.tabFocusBehavior !== Qt.TabFocusAllControls) + if (Application.styleHints.tabFocusBehavior !== Qt.TabFocusAllControls) skip("This platform only allows tab focus for text controls") var control = createTemporaryObject(focusSwipeViewComponent, testCase) diff --git a/tests/auto/quickcontrols/controls/data/tst_switch.qml b/tests/auto/quickcontrols/controls/data/tst_switch.qml index 407513b914..8d6160cef1 100644 --- a/tests/auto/quickcontrols/controls/data/tst_switch.qml +++ b/tests/auto/quickcontrols/controls/data/tst_switch.qml @@ -210,7 +210,7 @@ TestCase { spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], "pressed"] // Don't want to double-click. - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(spy.success) @@ -227,7 +227,7 @@ TestCase { // release on the right spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(spy.success) @@ -246,7 +246,7 @@ TestCase { // release on the left spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(spy.success) @@ -265,7 +265,7 @@ TestCase { // release in the middle spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, 0, 0).commit() compare(control.pressed, true) verify(spy.success) @@ -426,7 +426,7 @@ TestCase { spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], "pressed"] // Don't want to double-click. - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, 0).commit() compare(control.position, 1.0) compare(control.checked, true) @@ -462,7 +462,7 @@ TestCase { // press-drag-release from and to outside the indicator spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width - 1).commit() compare(control.position, 0.0) compare(control.checked, false) diff --git a/tests/auto/quickcontrols/controls/data/tst_switchdelegate.qml b/tests/auto/quickcontrols/controls/data/tst_switchdelegate.qml index 42d58b9668..570e89a531 100644 --- a/tests/auto/quickcontrols/controls/data/tst_switchdelegate.qml +++ b/tests/auto/quickcontrols/controls/data/tst_switchdelegate.qml @@ -201,7 +201,7 @@ TestCase { spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], "pressed"] // Don't want to double-click. - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(spy.success) @@ -218,7 +218,7 @@ TestCase { // release on the right spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(spy.success) @@ -237,7 +237,7 @@ TestCase { // release on the left spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(spy.success) @@ -256,7 +256,7 @@ TestCase { // release in the middle spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, 0, 0).commit() compare(control.pressed, true) verify(spy.success) @@ -417,7 +417,7 @@ TestCase { spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], "pressed"] // Don't want to double-click. - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, 0).commit() compare(control.position, 1.0) compare(control.checked, true) @@ -453,7 +453,7 @@ TestCase { // press-drag-release from and to outside the indicator spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width - 1).commit() compare(control.position, 0.0) compare(control.checked, false) diff --git a/tests/auto/quickcontrols/controls/data/tst_tumbler.qml b/tests/auto/quickcontrols/controls/data/tst_tumbler.qml index 0bbade9c43..0491ab039e 100644 --- a/tests/auto/quickcontrols/controls/data/tst_tumbler.qml +++ b/tests/auto/quickcontrols/controls/data/tst_tumbler.qml @@ -1149,25 +1149,46 @@ TestCase { } } - function test_setCurrentIndexOnImperativeModelChange() { - var tumbler = createTemporaryObject(setCurrentIndexOnImperativeModelChangeComponent, testCase); - verify(tumbler); + function test_setCurrentIndexOnImperativeModelChange_data() { + return [ + { tag: "default wrap", setWrap: false, initialWrap: false, newWrap: true }, + { tag: "wrap=false", setWrap: true, initialWrap: false, newWrap: false }, + { tag: "wrap=true", setWrap: true, initialWrap: true, newWrap: true }, + ] + } - tumbler.model = 4 - compare(tumbler.count, 4); - tumblerView = findView(tumbler); - tryCompare(tumblerView, "count", 4); + function test_setCurrentIndexOnImperativeModelChange(data) { + let tumbler = createTemporaryObject(setCurrentIndexOnImperativeModelChangeComponent, testCase, + data.setWrap ? {wrap: data.initialWrap} : {}) + verify(tumbler) - // 4 - 2 = 2 - compare(tumbler.currentIndex, 2); + let model = 4 + let expectedCurrentIndex = model - 2 - ++tumbler.model; - compare(tumbler.count, 5); - compare(tumbler.wrap, true); - tumblerView = findView(tumbler); - tryCompare(tumblerView, "count", 5); - // 5 - 2 = 3 - compare(tumbler.currentIndex, 3); + tumbler.model = model + + compare(tumbler.count, model) + compare(tumbler.wrap, data.initialWrap) + compare(tumbler.currentIndex, expectedCurrentIndex) + + let tumblerView = findView(tumbler) + verify(tumblerView) + tryCompare(tumblerView, "count", model) + tryCompare(tumblerView, "currentIndex", expectedCurrentIndex) + + model = 5 + expectedCurrentIndex = model - 2 + + tumbler.model = model + + compare(tumbler.count, model) + compare(tumbler.wrap, data.newWrap) + compare(tumbler.currentIndex, expectedCurrentIndex) + + tumblerView = findView(tumbler) + verify(tumblerView) + tryCompare(tumblerView, "count", model) + tryCompare(tumblerView, "currentIndex", expectedCurrentIndex) } Component { @@ -1176,35 +1197,59 @@ TestCase { Item { property alias tumbler: tumbler - property int setting: 4 + required property int modelValue + property alias tumblerWrap: tumbler.wrap Tumbler { id: tumbler - model: setting + model: modelValue onModelChanged: currentIndex = model - 2 } } } - function test_setCurrentIndexOnDeclarativeModelChange() { - var root = createTemporaryObject(setCurrentIndexOnDeclarativeModelChangeComponent, testCase); - verify(root); + function test_setCurrentIndexOnDeclarativeModelChange_data() { + return [ + { tag: "default wrap", setWrap: false, initialWrap: false, newWrap: true }, + { tag: "wrap=false", setWrap: true, initialWrap: false, newWrap: false }, + { tag: "wrap=true", setWrap: true, initialWrap: true, newWrap: true }, + ] + } - var tumbler = root.tumbler; - compare(tumbler.count, 4); - compare(tumbler.wrap, false); - tumblerView = findView(tumbler); - tryCompare(tumblerView, "count", 4); - // 4 - 2 = 2 - compare(tumbler.currentIndex, 2); + function test_setCurrentIndexOnDeclarativeModelChange(data) { + let model = 4 + let expectedCurrentIndex = model - 2 - ++root.setting; - compare(tumbler.count, 5); - compare(tumbler.wrap, true); - tumblerView = findView(tumbler); - tryCompare(tumblerView, "count", 5); - // 5 - 2 = 3 - compare(tumbler.currentIndex, 3); + let root = createTemporaryObject(setCurrentIndexOnDeclarativeModelChangeComponent, testCase, + data.setWrap ? {modelValue: model, tumblerWrap: data.initialWrap} + : {modelValue: model}) + verify(root) + + let tumbler = root.tumbler + verify(tumbler) + + compare(tumbler.count, model) + compare(tumbler.wrap, data.initialWrap) + compare(tumbler.currentIndex, expectedCurrentIndex) + + let tumberView = findView(tumbler) + verify(tumbler) + tryCompare(tumberView, "count", model) + tryCompare(tumberView, "currentIndex", expectedCurrentIndex) + + model = 5 + expectedCurrentIndex = model - 2 + + root.modelValue = model + + compare(tumbler.count, model) + compare(tumbler.wrap, data.newWrap) + compare(tumbler.currentIndex, expectedCurrentIndex) + + tumberView = findView(tumbler) + verify(tumbler) + tryCompare(tumberView, "count", model) + tryCompare(tumberView, "currentIndex", expectedCurrentIndex) } function test_displacementAfterResizing() { @@ -1237,15 +1282,46 @@ TestCase { height: 200 delegate: Text {text: modelData} model: 10 - currentIndex: 4 + currentIndex: 1 } } + //QTBUG-127315 + Component { + id: initialCurrentIndexTumblerNoDelegate - function test_initialCurrentIndex() { - var tumbler = createTemporaryObject(initialCurrentIndexTumbler, testCase, {wrap: true}); - compare(tumbler.currentIndex, 4); - tumbler = createTemporaryObject(initialCurrentIndexTumbler, testCase, {wrap: false}); - compare(tumbler.currentIndex, 4); + Tumbler { + anchors.centerIn: parent + width: 60 + height: 200 + model: 10 + } + } + + function test_initialCurrentIndex_data() { + return [ + { tag: "delegate: true, wrap: true, currentIndex: 1", + component: initialCurrentIndexTumbler, wrap: true, currentIndex: 1 }, + { tag: "delegate: true, wrap: false, currentIndex: 1", + component: initialCurrentIndexTumbler, wrap: false, currentIndex: 1 }, + { tag: "delegate: false, wrap: true, currentIndex: 1", + component: initialCurrentIndexTumblerNoDelegate, wrap: true, currentIndex: 1 }, + { tag: "delegate: false, wrap: false, currentIndex: 1", + component: initialCurrentIndexTumblerNoDelegate, wrap: false, currentIndex: 1 }, + { tag: "delegate: true, wrap: true, currentIndex: 4", + component: initialCurrentIndexTumbler, wrap: true, currentIndex: 4 }, + { tag: "delegate: true, wrap: false, currentIndex: 4", + component: initialCurrentIndexTumbler, wrap: false, currentIndex: 4 }, + { tag: "delegate: false, wrap: true, currentIndex: 4", + component: initialCurrentIndexTumblerNoDelegate, wrap: true, currentIndex: 4 }, + { tag: "delegate: false, wrap: false, currentIndex: 4", + component: initialCurrentIndexTumblerNoDelegate, wrap: false, currentIndex: 4 }, + ] + } + + function test_initialCurrentIndex(data) { + let tumbler = createTemporaryObject(data.component, testCase, + {wrap: data.wrap, currentIndex: data.currentIndex}); + compare(tumbler.currentIndex, data.currentIndex); } // QTBUG-109995 diff --git a/tests/auto/quickcontrols/controls/fusion/CMakeLists.txt b/tests/auto/quickcontrols/controls/fusion/CMakeLists.txt index 5495efd858..e3d38e1cf3 100644 --- a/tests/auto/quickcontrols/controls/fusion/CMakeLists.txt +++ b/tests/auto/quickcontrols/controls/fusion/CMakeLists.txt @@ -43,6 +43,9 @@ set_source_files_properties(${test_data} HEADER_FILE_ONLY ON ) +set(test_target tst_fusion) +include(../shared.cmake) + #### Keys ignored in scope 1:.:.:fusion.pro:<TRUE>: # OTHER_FILES = "$$PWD/../data/*.qml" # TEMPLATE = "app" diff --git a/tests/auto/quickcontrols/controls/imagine/CMakeLists.txt b/tests/auto/quickcontrols/controls/imagine/CMakeLists.txt index 6efb1a0a45..c296a5b1a4 100644 --- a/tests/auto/quickcontrols/controls/imagine/CMakeLists.txt +++ b/tests/auto/quickcontrols/controls/imagine/CMakeLists.txt @@ -43,6 +43,9 @@ set_source_files_properties(${test_data} HEADER_FILE_ONLY ON ) +set(test_target tst_imagine) +include(../shared.cmake) + #### Keys ignored in scope 1:.:.:imagine.pro:<TRUE>: # OTHER_FILES = "$$PWD/../data/*.qml" # TEMPLATE = "app" diff --git a/tests/auto/quickcontrols/controls/ios/CMakeLists.txt b/tests/auto/quickcontrols/controls/ios/CMakeLists.txt index fe6839990f..4e0cb72e68 100644 --- a/tests/auto/quickcontrols/controls/ios/CMakeLists.txt +++ b/tests/auto/quickcontrols/controls/ios/CMakeLists.txt @@ -26,3 +26,5 @@ qt_internal_add_test(tst_ios TESTDATA ${test_data} ) +set(test_target tst_ios) +include(../shared.cmake) diff --git a/tests/auto/quickcontrols/controls/macos/CMakeLists.txt b/tests/auto/quickcontrols/controls/macos/CMakeLists.txt index 04d186c63f..3202601316 100644 --- a/tests/auto/quickcontrols/controls/macos/CMakeLists.txt +++ b/tests/auto/quickcontrols/controls/macos/CMakeLists.txt @@ -36,3 +36,6 @@ set_source_files_properties(${test_data} PROPERTIES HEADER_FILE_ONLY ON ) + +set(test_target tst_macos) +include(../shared.cmake) diff --git a/tests/auto/quickcontrols/controls/material/CMakeLists.txt b/tests/auto/quickcontrols/controls/material/CMakeLists.txt index 3e87bf1609..506509c774 100644 --- a/tests/auto/quickcontrols/controls/material/CMakeLists.txt +++ b/tests/auto/quickcontrols/controls/material/CMakeLists.txt @@ -6,7 +6,7 @@ if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) cmake_minimum_required(VERSION 3.16) project(tst_material LANGUAGES C CXX ASM) - find_package(Qt6BuildInternals COMPONENTS STANDALONE_TEST) + find_package(Qt6BuildInternals REQUIRED COMPONENTS ShaderTools STANDALONE_TEST) endif() ##################################################################### @@ -43,6 +43,9 @@ set_source_files_properties(${test_data} HEADER_FILE_ONLY ON ) +set(test_target tst_material) +include(../shared.cmake) + #### Keys ignored in scope 1:.:.:material.pro:<TRUE>: # OTHER_FILES = "$$PWD/../data/*.qml" # TEMPLATE = "app" diff --git a/tests/auto/quickcontrols/controls/shared.cmake b/tests/auto/quickcontrols/controls/shared.cmake new file mode 100644 index 0000000000..ca62b88db6 --- /dev/null +++ b/tests/auto/quickcontrols/controls/shared.cmake @@ -0,0 +1,12 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt6_add_shaders(${test_target} "${test_target}_shaders" + BATCHABLE + PREFIX + "/" + BASE + "../" + FILES + "../data/combobox/shader.frag" +) diff --git a/tests/auto/quickcontrols/controls/universal/CMakeLists.txt b/tests/auto/quickcontrols/controls/universal/CMakeLists.txt index f0c7d2ed80..f39975ab15 100644 --- a/tests/auto/quickcontrols/controls/universal/CMakeLists.txt +++ b/tests/auto/quickcontrols/controls/universal/CMakeLists.txt @@ -43,6 +43,9 @@ set_source_files_properties(${test_data} HEADER_FILE_ONLY ON ) +set(test_target tst_universal) +include(../shared.cmake) + #### Keys ignored in scope 1:.:.:universal.pro:<TRUE>: # OTHER_FILES = "$$PWD/../data/*.qml" # TEMPLATE = "app" diff --git a/tests/auto/quickcontrols/controls/windows/CMakeLists.txt b/tests/auto/quickcontrols/controls/windows/CMakeLists.txt index a5ba30a51b..1d6ab1fc57 100644 --- a/tests/auto/quickcontrols/controls/windows/CMakeLists.txt +++ b/tests/auto/quickcontrols/controls/windows/CMakeLists.txt @@ -36,3 +36,6 @@ set_source_files_properties(${test_data} PROPERTIES HEADER_FILE_ONLY ON ) + +set(test_target tst_windows) +include(../shared.cmake) diff --git a/tests/auto/quickcontrols/focus/tst_focus.cpp b/tests/auto/quickcontrols/focus/tst_focus.cpp index ca70146885..78db22ffbc 100644 --- a/tests/auto/quickcontrols/focus/tst_focus.cpp +++ b/tests/auto/quickcontrols/focus/tst_focus.cpp @@ -29,6 +29,7 @@ public: tst_focus(); private slots: + void init() override; void initTestCase() override; void navigation_data(); @@ -50,8 +51,14 @@ tst_focus::tst_focus() { } +void tst_focus::init() +{ + QTest::failOnWarning(QRegularExpression(".?")); +} + void tst_focus::initTestCase() { + SKIP_IF_NO_WINDOW_ACTIVATION QQuickStyle::setStyle("Basic"); QQmlDataTest::initTestCase(); } diff --git a/tests/auto/quickcontrols/palette/data/comboBoxPopupWithApplicationWindow.qml b/tests/auto/quickcontrols/palette/data/comboBoxPopupWithApplicationWindow.qml new file mode 100644 index 0000000000..436d3cdad6 --- /dev/null +++ b/tests/auto/quickcontrols/palette/data/comboBoxPopupWithApplicationWindow.qml @@ -0,0 +1,32 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + width: 400 + height: 400 + + property alias topLevelComboBox: topLevelComboBox + property alias popup: popup + property alias comboBoxInPopup: comboBoxInPopup + + ComboBox { + id: topLevelComboBox + model: ["ONE", "TWO", "THREE"] + } + + Popup { + id: popup + width: 200 + height: 200 + visible: true + palette.window: "red" + + ComboBox { + id: comboBoxInPopup + model: ["ONE", "TWO", "THREE"] + } + } +} diff --git a/tests/auto/quickcontrols/palette/data/comboBoxPopupWithThemeDefault.qml b/tests/auto/quickcontrols/palette/data/comboBoxPopupWithThemeDefault.qml new file mode 100644 index 0000000000..592793fa3f --- /dev/null +++ b/tests/auto/quickcontrols/palette/data/comboBoxPopupWithThemeDefault.qml @@ -0,0 +1,17 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + width: 400 + height: 400 + + property alias comboBox: comboBox + + ComboBox { + id: comboBox + model: 1 + } +} diff --git a/tests/auto/quickcontrols/palette/data/comboBoxPopupWithWindow.qml b/tests/auto/quickcontrols/palette/data/comboBoxPopupWithWindow.qml new file mode 100644 index 0000000000..d806f30d01 --- /dev/null +++ b/tests/auto/quickcontrols/palette/data/comboBoxPopupWithWindow.qml @@ -0,0 +1,33 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Window +import QtQuick.Controls + +Window { + width: 400 + height: 400 + + property alias topLevelComboBox: topLevelComboBox + property alias popup: popup + property alias comboBoxInPopup: comboBoxInPopup + + ComboBox { + id: topLevelComboBox + model: ["ONE", "TWO", "THREE"] + } + + Popup { + id: popup + width: 200 + height: 200 + visible: true + palette.window: "red" + + ComboBox { + id: comboBoxInPopup + model: ["ONE", "TWO", "THREE"] + } + } +} diff --git a/tests/auto/quickcontrols/palette/data/toolTipPaletteUpdate.qml b/tests/auto/quickcontrols/palette/data/toolTipPaletteUpdate.qml new file mode 100644 index 0000000000..3968c54cd0 --- /dev/null +++ b/tests/auto/quickcontrols/palette/data/toolTipPaletteUpdate.qml @@ -0,0 +1,19 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + id: window + + palette { toolTipBase: "white"; toolTipText: "black"} + + Button { + objectName: "button" + text: qsTr("Button with Tooltip") + + ToolTip.visible: false + ToolTip.text: qsTr("This is a tool tip.") + } +} diff --git a/tests/auto/quickcontrols/palette/tst_palette.cpp b/tests/auto/quickcontrols/palette/tst_palette.cpp index d8f4bfd804..621475b86e 100644 --- a/tests/auto/quickcontrols/palette/tst_palette.cpp +++ b/tests/auto/quickcontrols/palette/tst_palette.cpp @@ -7,17 +7,21 @@ #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcomponent.h> #include <QtQuickTestUtils/private/qmlutils_p.h> +#include <QtQuickTestUtils/private/visualtestutils_p.h> #include <QtQuickControlsTestUtils/private/controlstestutils_p.h> #include <QtQuick/private/qquickitem_p.h> #include <QtQuickTemplates2/private/qquickapplicationwindow_p.h> +#include <QtQuickTemplates2/private/qquickcombobox_p.h> #include <QtQuickTemplates2/private/qquickcontrol_p.h> #include <QtQuickTemplates2/private/qquickcontrol_p_p.h> #include <QtQuickTemplates2/private/qquickpopup_p.h> #include <QtQuickTemplates2/private/qquickpopup_p_p.h> #include <QtQuickTemplates2/private/qquicktheme_p_p.h> #include <QtQuickTemplates2/private/qquickbutton_p.h> +#include <QtQuickTemplates2/private/qquicktooltip_p.h> #include <QtQuickControls2/qquickstyle.h> +using namespace QQuickVisualTestUtils; using namespace QQuickControlsTestUtils; // Need a more descriptive failure message: QTBUG-87039 @@ -55,6 +59,13 @@ private slots: void resolve(); void updateBindingPalette(); + + void comboBoxPopup_data(); + void comboBoxPopup(); + void comboBoxPopupWithThemeDefault_data(); + void comboBoxPopupWithThemeDefault(); + + void toolTipPaletteUpdate(); }; tst_palette::tst_palette() @@ -485,6 +496,120 @@ void tst_palette::updateBindingPalette() QCOMPARE(windowPalette->buttonText(), customPalette->buttonText()); } +void tst_palette::comboBoxPopup_data() +{ + QTest::addColumn<QString>("style"); + QTest::addColumn<QString>("qmlFilePath"); + + QTest::newRow("Window, Basic") << "Basic" << "comboBoxPopupWithWindow.qml"; + QTest::newRow("ApplicationWindow, Basic") << "Basic" << "comboBoxPopupWithApplicationWindow.qml"; + QTest::newRow("Window, Fusion") << "Fusion" << "comboBoxPopupWithWindow.qml"; + QTest::newRow("ApplicationWindow, Fusion") << "Fusion" << "comboBoxPopupWithApplicationWindow.qml"; +} + +// Unlike regular popups, which should inherit their palette from the window and not the parent popup, +// combo box popups should inherit their palette from the combo box itself. +void tst_palette::comboBoxPopup() +{ + QFETCH(QString, style); + QFETCH(QString, qmlFilePath); + + qmlClearTypeRegistrations(); + QQuickStyle::setStyle(style); + + QQuickApplicationHelper helper(this, qmlFilePath); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + const auto *windowPalette = window->property("palette").value<QQuickPalette *>(); + QVERIFY(windowPalette); + + const auto *popup = window->property("popup").value<QQuickPopup *>(); + QVERIFY(popup); + const auto *popupBackground = popup->background(); + QCOMPARE(popupBackground->property("color"), QColorConstants::Red); + QCOMPARE(popupBackground->property("palette").value<QQuickPalette*>()->toQPalette().window().color(), + QColorConstants::Red); + + // This has the default palette. + const auto *topLevelComboBox = window->property("topLevelComboBox").value<QQuickComboBox *>(); + QVERIFY(topLevelComboBox); + const auto *topLevelComboBoxBackground = topLevelComboBox->popup()->background(); + QCOMPARE_NE(topLevelComboBoxBackground->property("color"), QColorConstants::Red); + QCOMPARE_NE(topLevelComboBoxBackground->property("palette").value<QQuickPalette*>()->toQPalette().window().color(), + QColorConstants::Red); + + // The popup that this combo box is in has its window role set to red, + // so the combo box's popup background should be red too. + const auto *comboBoxInPopup = window->property("comboBoxInPopup").value<QQuickComboBox *>(); + QVERIFY(comboBoxInPopup); + const auto *comboBoxInPopupBackground = comboBoxInPopup->popup()->background(); + QCOMPARE(comboBoxInPopupBackground->property("color"), QColorConstants::Red); + QCOMPARE(comboBoxInPopupBackground->property("palette").value<QQuickPalette*>()->toQPalette().window().color(), + QColorConstants::Red); +} + +void tst_palette::comboBoxPopupWithThemeDefault_data() +{ + QTest::addColumn<QString>("style"); + QTest::addColumn<QColor>("expectedComboBoxPopupBackgroundColor"); + + QTest::newRow("Basic") << "Basic" << QColor::fromRgb(0xFFFFFF); + + // We can't test Fusion because it uses the default application palette, + // which is the default-constructed QPalette, so the test would always pass. +} + +void tst_palette::comboBoxPopupWithThemeDefault() +{ + QFETCH(QString, style); + QFETCH(QColor, expectedComboBoxPopupBackgroundColor); + + qmlClearTypeRegistrations(); + QQuickStyle::setStyle(style); + + QQuickApplicationHelper helper(this, "comboBoxPopupWithThemeDefault.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + const auto *comboBox = window->property("comboBox").value<QQuickComboBox *>(); + QVERIFY(comboBox); + const auto *comboBoxBackground = comboBox->popup()->background(); + QCOMPARE(comboBoxBackground->property("color"), expectedComboBoxPopupBackgroundColor); +} + +void tst_palette::toolTipPaletteUpdate() +{ + QQuickApplicationHelper helper(this, "toolTipPaletteUpdate.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + auto *button = window->findChild<QQuickButton *>("button"); + QVERIFY(button); + auto *attachedToolTip = button->findChild<QQuickToolTipAttached *>(); + QVERIFY(attachedToolTip); + auto *toolTip = attachedToolTip->toolTip(); + QVERIFY(toolTip); + + auto windowPalette = QQuickWindowPrivate::get(window)->palette(); + auto toolTipPalette = QQuickPopupPrivate::get(toolTip)->palette(); + + QCOMPARE(toolTipPalette->toolTipBase(), windowPalette->toolTipBase()); + QCOMPARE(toolTipPalette->toolTipText(), windowPalette->toolTipText()); + + windowPalette->setToolTipBase(Qt::blue); + windowPalette->setToolTipText(Qt::red); + + QCOMPARE(toolTipPalette->toolTipBase(), windowPalette->toolTipBase()); + QCOMPARE(toolTipPalette->toolTipText(), windowPalette->toolTipText()); +} + QTEST_MAIN(tst_palette) #include "tst_palette.moc" diff --git a/tests/auto/quickcontrols/qquickapplicationwindow/data/backgroundSize.qml b/tests/auto/quickcontrols/qquickapplicationwindow/data/backgroundSize.qml index 0e4ed277d0..db5470986f 100644 --- a/tests/auto/quickcontrols/qquickapplicationwindow/data/backgroundSize.qml +++ b/tests/auto/quickcontrols/qquickapplicationwindow/data/backgroundSize.qml @@ -9,6 +9,7 @@ ApplicationWindow { height: 400 background: Item { + objectName: "background" implicitWidth: 123 implicitHeight: 456 } diff --git a/tests/auto/quickcontrols/qquickapplicationwindow/data/explicitBackgroundSizeBinding.qml b/tests/auto/quickcontrols/qquickapplicationwindow/data/explicitBackgroundSizeBinding.qml new file mode 100644 index 0000000000..67fdf7808c --- /dev/null +++ b/tests/auto/quickcontrols/qquickapplicationwindow/data/explicitBackgroundSizeBinding.qml @@ -0,0 +1,21 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + id: window + width: 600 + height: 400 + + property real scaleFactor: 1 + + background: Rectangle { + objectName: "background" + color: "green" + width: window.width * window.scaleFactor + height: window.height * window.scaleFactor + anchors.centerIn: parent + } +} diff --git a/tests/auto/quickcontrols/qquickapplicationwindow/tst_qquickapplicationwindow.cpp b/tests/auto/quickcontrols/qquickapplicationwindow/tst_qquickapplicationwindow.cpp index c7a5df4b68..a02ab9c595 100644 --- a/tests/auto/quickcontrols/qquickapplicationwindow/tst_qquickapplicationwindow.cpp +++ b/tests/auto/quickcontrols/qquickapplicationwindow/tst_qquickapplicationwindow.cpp @@ -12,6 +12,7 @@ #include <QtQuickTestUtils/private/qmlutils_p.h> #include <QtQuickTestUtils/private/visualtestutils_p.h> #include <QtGui/private/qguiapplication_p.h> +#include <QtQuickTemplates2/private/qquickabstractbutton_p.h> #include <QtQuickTemplates2/private/qquickapplicationwindow_p.h> #include <QtQuickTemplates2/private/qquickoverlay_p.h> #include <QtQuickTemplates2/private/qquickcontrol_p.h> @@ -53,6 +54,7 @@ private slots: void componentComplete(); void opacity(); void backgroundSize(); + void explicitBackgroundSizeBinding(); }; tst_QQuickApplicationWindow::tst_QQuickApplicationWindow() @@ -978,6 +980,23 @@ void tst_QQuickApplicationWindow::backgroundSize() QCOMPARE(background->height(), 678); } +void tst_QQuickApplicationWindow::explicitBackgroundSizeBinding() +{ + QQuickControlsApplicationHelper helper(this, QLatin1String("explicitBackgroundSizeBinding.qml")); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickApplicationWindow *window = helper.appWindow; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + auto *background = window->background(); + QCOMPARE(background->width(), window->width()); + QCOMPARE(background->height(), window->height()); + + window->setProperty("scaleFactor", 0.5); + QCOMPARE(background->width(), window->width() / 2); + QCOMPARE(background->height(), window->height() / 2); +} + QTEST_MAIN(tst_QQuickApplicationWindow) #include "tst_qquickapplicationwindow.moc" diff --git a/tests/auto/quickcontrols/qquickcontainer/CMakeLists.txt b/tests/auto/quickcontrols/qquickcontainer/CMakeLists.txt new file mode 100644 index 0000000000..c79e508cd1 --- /dev/null +++ b/tests/auto/quickcontrols/qquickcontainer/CMakeLists.txt @@ -0,0 +1,39 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qquickcontainer LANGUAGES C CXX ASM) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +# 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_qquickcontainer + SOURCES + tst_qquickcontainer.cpp + LIBRARIES + Qt::CorePrivate + Qt::Gui + Qt::QuickControls2 + Qt::QuickControlsTestUtilsPrivate + Qt::QuickPrivate + Qt::QuickTemplates2Private + Qt::QuickTest + TESTDATA ${test_data} +) + +qt_internal_extend_target(tst_qquickcontainer CONDITION ANDROID OR IOS + DEFINES + QT_QMLTEST_DATADIR=":/data" +) + +qt_internal_extend_target(tst_qquickcontainer CONDITION NOT ANDROID AND NOT IOS + DEFINES + QT_QMLTEST_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/data" +) diff --git a/tests/auto/quickcontrols/qquickcontainer/data/zeroSizeWithListView.qml b/tests/auto/quickcontrols/qquickcontainer/data/zeroSizeWithListView.qml new file mode 100644 index 0000000000..43c3614866 --- /dev/null +++ b/tests/auto/quickcontrols/qquickcontainer/data/zeroSizeWithListView.qml @@ -0,0 +1,58 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + width: 640 + height: 480 + + property alias container: container + property alias text1: text1 + property alias text2: text2 + property alias text3: text3 + + component TextItem: Text { + font.pointSize: 24 + width: container.width + height: container.height + } + + Component { + id: textComponent + TextItem {} + } + + function addTextItem() { + container.addItem(textComponent.createObject(container, { text: " 4 " })) + } + + Item { + id: root + objectName: "root" + + Container { + id: container + anchors.fill: parent + contentItem: ListView { + model: container.contentModel + snapMode: ListView.SnapOneItem + orientation: ListView.Horizontal + } + + TextItem { + id: text1 + text: "1 " + } + TextItem { + id: text2 + text: " 2 " + } + TextItem { + id: text3 + text: " 3 " + } + } + } +} diff --git a/tests/auto/quickcontrols/qquickcontainer/data/zeroSizeWithRepeater.qml b/tests/auto/quickcontrols/qquickcontainer/data/zeroSizeWithRepeater.qml new file mode 100644 index 0000000000..f78b6e273c --- /dev/null +++ b/tests/auto/quickcontrols/qquickcontainer/data/zeroSizeWithRepeater.qml @@ -0,0 +1,58 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + width: 640 + height: 480 + + property alias container: container + property alias text1: text1 + property alias text2: text2 + property alias text3: text3 + + component TextItem: Text { + font.pointSize: 24 + width: container.width + height: container.height + } + + Component { + id: textComponent + TextItem {} + } + + function addTextItem() { + container.addItem(textComponent.createObject(container, { text: " 4 " })) + } + + Item { + id: root + objectName: "root" + + Container { + id: container + anchors.fill: parent + contentItem: Row { + Repeater { + model: container.contentModel + } + } + + TextItem { + id: text1 + text: "1 " + } + TextItem { + id: text2 + text: " 2 " + } + TextItem { + id: text3 + text: " 3 " + } + } + } +} diff --git a/tests/auto/quickcontrols/qquickcontainer/tst_qquickcontainer.cpp b/tests/auto/quickcontrols/qquickcontainer/tst_qquickcontainer.cpp new file mode 100644 index 0000000000..ce95f2cc37 --- /dev/null +++ b/tests/auto/quickcontrols/qquickcontainer/tst_qquickcontainer.cpp @@ -0,0 +1,107 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtTest> +#include <QtQuickTemplates2/private/qquickcontainer_p.h> +#include <QtQuickControls2/qquickstyle.h> +#include <QtQuickTest/QtQuickTest> +#include <QtQuickTestUtils/private/qmlutils_p.h> +#include <QtQuickTestUtils/private/visualtestutils_p.h> +#include <QtQuickControlsTestUtils/private/controlstestutils_p.h> + +using namespace QQuickVisualTestUtils; +using namespace QQuickControlsTestUtils; + +class tst_qquickcontainer : public QQmlDataTest +{ + Q_OBJECT + +public: + tst_qquickcontainer(); + +private slots: + void zeroSize_data(); + void zeroSize(); +}; + +tst_qquickcontainer::tst_qquickcontainer() + : QQmlDataTest(QT_QMLTEST_DATADIR) +{ + qputenv("QML_NO_TOUCH_COMPRESSION", "1"); + + QQuickStyle::setStyle("Basic"); +} + +void tst_qquickcontainer::zeroSize_data() +{ + QTest::addColumn<QString>("qmlFileName"); + QTest::addColumn<bool>("isItemView"); + + QTest::newRow("ListView") << "zeroSizeWithListView.qml" << true; + // See QQuickContainerPrivate::maybeCullItem for why this is false. + QTest::newRow("Repeater") << "zeroSizeWithRepeater.qml" << false; +} + +// Tests that a zero-size Container with a QQuickItemView sub-class culls its items. +// Based on a use case involving SwipeView: QTBUG-125416 +void tst_qquickcontainer::zeroSize() +{ + QFETCH(QString, qmlFileName); + QFETCH(bool, isItemView); + + QQuickControlsApplicationHelper helper(this, qmlFileName); + QVERIFY2(helper.ready, helper.failureMessage()); + centerOnScreen(helper.window); + helper.window->show(); + QVERIFY(QTest::qWaitForWindowExposed(helper.window)); + + auto *text1 = helper.window->property("text1").value<QQuickItem *>(); + QVERIFY(text1); + QCOMPARE(QQuickItemPrivate::get(text1)->culled, isItemView); + + auto *text2 = helper.window->property("text2").value<QQuickItem *>(); + QVERIFY(text2); + QCOMPARE(QQuickItemPrivate::get(text2)->culled, isItemView); + + auto *text3 = helper.window->property("text3").value<QQuickItem *>(); + QVERIFY(text3); + QCOMPARE(QQuickItemPrivate::get(text3)->culled, isItemView); + + // Add an item and check that it's culled appropriately. + QVERIFY(QMetaObject::invokeMethod(helper.window, "addTextItem")); + auto *container = helper.window->property("container").value<QQuickContainer *>(); + QVERIFY(container); + auto *text4 = container->itemAt(3); + QVERIFY(text4); + QCOMPARE(QQuickItemPrivate::get(text4)->culled, isItemView); + + // Give it a non-zero size (via its parent, which it fills). + container->parentItem()->setWidth(text1->implicitWidth()); + container->parentItem()->setHeight(text1->implicitHeight()); + if (isItemView) { + QVERIFY(QQuickTest::qIsPolishScheduled(helper.window)); + QVERIFY(QQuickTest::qWaitForPolish(helper.window)); + } + QCOMPARE(QQuickItemPrivate::get(text1)->culled, false); + // This one won't be culled for views either, because of cacheBuffer (and + // clipping apparently doesn't affect culling, if we were to set clip to true). + QCOMPARE(QQuickItemPrivate::get(text2)->culled, false); + QCOMPARE(QQuickItemPrivate::get(text3)->culled, isItemView); + QCOMPARE(QQuickItemPrivate::get(text4)->culled, isItemView); + + // Go back to a zero size. + container->parentItem()->setWidth(0); + container->parentItem()->setHeight(0); + if (isItemView) { + QVERIFY(QQuickTest::qIsPolishScheduled(helper.window)); + QVERIFY(QQuickTest::qWaitForPolish(helper.window)); + } + QCOMPARE(QQuickItemPrivate::get(text1)->culled, isItemView); + QCOMPARE(QQuickItemPrivate::get(text2)->culled, isItemView); + QCOMPARE(QQuickItemPrivate::get(text3)->culled, isItemView); + QCOMPARE(QQuickItemPrivate::get(text4)->culled, isItemView); +} + +QTEST_MAIN(tst_qquickcontainer) + +#include "tst_qquickcontainer.moc" diff --git a/tests/auto/quickcontrols/qquickdrawer/tst_qquickdrawer.cpp b/tests/auto/quickcontrols/qquickdrawer/tst_qquickdrawer.cpp index ee0c6262d8..3e88bdb2b0 100644 --- a/tests/auto/quickcontrols/qquickdrawer/tst_qquickdrawer.cpp +++ b/tests/auto/quickcontrols/qquickdrawer/tst_qquickdrawer.cpp @@ -1051,8 +1051,7 @@ void tst_QQuickDrawer::interactive_data() void tst_QQuickDrawer::interactive() { - if (!(QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QFETCH(QString, source); QQuickControlsApplicationHelper helper(this, source); diff --git a/tests/auto/quickcontrols/qquickmenu/data/mnemonics.qml b/tests/auto/quickcontrols/qquickmenu/data/mnemonics.qml index 8929b00275..091d087dbb 100644 --- a/tests/auto/quickcontrols/qquickmenu/data/mnemonics.qml +++ b/tests/auto/quickcontrols/qquickmenu/data/mnemonics.qml @@ -5,14 +5,18 @@ import QtQuick import QtQuick.Controls ApplicationWindow { + id: root width: 400 height: 400 + property bool enabled: true + property bool checkable: false property alias menu: menu property alias action: action property alias menuItem: menuItem property alias subMenu: subMenu property alias subMenuItem: subMenuItem + property alias subMenuAction: subMenuAction Menu { id: menu @@ -20,20 +24,33 @@ ApplicationWindow { Action { id: action text: "&Action" + checkable: root.checkable + enabled: root.enabled } MenuItem { id: menuItem text: "Menu &Item" + checkable: root.checkable + enabled: root.enabled } Menu { id: subMenu title: "Sub &Menu" + Action { + id: subMenuAction + text: "S&ub Menu Action" + checkable: root.checkable + enabled: root.enabled + } + MenuItem { id: subMenuItem text: "&Sub Menu Item" + checkable: root.checkable + enabled: root.enabled } } } diff --git a/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp b/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp index fbb4e7d5f9..f54678b686 100644 --- a/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp +++ b/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp @@ -47,6 +47,10 @@ private slots: void contextMenuKeyboard(); void disabledMenuItemKeyNavigation(); void mnemonics(); +#if QT_CONFIG(shortcut) + void checkableMnemonics_data(); + void checkableMnemonics(); +#endif void menuButton(); void addItem(); void menuSeparator(); @@ -87,9 +91,6 @@ private slots: void customMenuCullItems(); void customMenuUseRepeaterAsTheContentItem(); void invalidUrlInImgTag(); - -private: - static bool hasWindowActivation(); }; tst_QQuickMenu::tst_QQuickMenu() @@ -97,11 +98,6 @@ tst_QQuickMenu::tst_QQuickMenu() { } -bool tst_QQuickMenu::hasWindowActivation() -{ - return (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)); -} - void tst_QQuickMenu::defaults() { QQuickControlsApplicationHelper helper(this, QLatin1String("applicationwindow.qml")); @@ -146,8 +142,7 @@ void tst_QQuickMenu::count() void tst_QQuickMenu::mouse() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION if ((QGuiApplication::platformName() == QLatin1String("offscreen")) || (QGuiApplication::platformName() == QLatin1String("minimal"))) @@ -278,8 +273,7 @@ void tst_QQuickMenu::pressAndHold() void tst_QQuickMenu::contextMenuKeyboard() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls) QSKIP("This platform only allows tab focus for text controls"); @@ -468,8 +462,7 @@ void tst_QQuickMenu::contextMenuKeyboard() // QTBUG-70181 void tst_QQuickMenu::disabledMenuItemKeyNavigation() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls) QSKIP("This platform only allows tab focus for text controls"); @@ -535,8 +528,7 @@ void tst_QQuickMenu::disabledMenuItemKeyNavigation() void tst_QQuickMenu::mnemonics() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION #ifdef Q_OS_MACOS QSKIP("Mnemonics are not used on macOS"); @@ -591,10 +583,207 @@ void tst_QQuickMenu::mnemonics() QCOMPARE(subMenuItemSpy.size(), 1); } +#if QT_CONFIG(shortcut) +namespace CheckableMnemonics { +using MnemonicKey = std::pair<Qt::Key, QString>; + +enum class MenuItemType { + Action, + MenuItem +}; + +enum class SignalName { + CheckedChanged = 0x01, + Triggered = 0x02, +}; +Q_DECLARE_FLAGS(SignalNames, SignalName); + +class ItemSignalSpy +{ +public: + ItemSignalSpy(MenuItemType type, QObject *item) : item(item) + { + switch (type) { + case MenuItemType::Action: + initSignals<QQuickAction>(qobject_cast<QQuickAction *>(item)); + break; + case MenuItemType::MenuItem: + initSignals<QQuickMenuItem>(qobject_cast<QQuickMenuItem *>(item)); + break; + } + } + + [[nodiscard]] bool isValid() const + { + return ((checkedChangedSpy && checkedChangedSpy->isValid()) && + (triggeredSpy && triggeredSpy->isValid())); + } + + [[nodiscard]] int signalSize(SignalName signal) const + { + constexpr int INVALID_SIZE = -1; // makes the test fail even when the signal is not expected + switch (signal) { + case SignalName::CheckedChanged: + return checkedChangedSpy ? checkedChangedSpy->size() : INVALID_SIZE; + case SignalName::Triggered: + return triggeredSpy ? triggeredSpy->size() : INVALID_SIZE; + } + Q_UNREACHABLE_RETURN(INVALID_SIZE); + } + +private: + template<typename Item> + void initSignals(Item *item) + { + checkedChangedSpy = std::make_unique<QSignalSpy>(item, &Item::checkedChanged); + triggeredSpy = std::make_unique<QSignalSpy>(item, &Item::triggered); + } + +private: + QPointer<QObject> item; + std::unique_ptr<QSignalSpy> checkedChangedSpy = nullptr; + std::unique_ptr<QSignalSpy> triggeredSpy = nullptr; +}; + +} + +void tst_QQuickMenu::checkableMnemonics_data() +{ + if (QKeySequence::mnemonic("&A").isEmpty()) + QSKIP("Mnemonics are not enabled"); + + using namespace CheckableMnemonics; + + QTest::addColumn<bool>("checkable"); + QTest::addColumn<bool>("enabled"); + QTest::addColumn<bool>("isSubMenu"); + QTest::addColumn<MenuItemType>("itemType"); + QTest::addColumn<MnemonicKey>("mnemonicKey"); + QTest::addColumn<SignalNames>("expectedSignals"); + + QTest::addRow("checkable_enabled_action") + << true << true << false << MenuItemType::Action << MnemonicKey{Qt::Key_A, "A"} + << SignalNames{SignalName::Triggered, SignalName::CheckedChanged}; + QTest::addRow("checkable_disabled_action") + << true << false << false << MenuItemType::Action << MnemonicKey{Qt::Key_A, "A"} + << SignalNames{}; + QTest::addRow("uncheckable_enabled_action") + << false << true << false << MenuItemType::Action << MnemonicKey{Qt::Key_A, "A"} + << SignalNames{SignalName::Triggered}; + QTest::addRow("uncheckable_disabled_action") + << false << false << false << MenuItemType::Action << MnemonicKey{Qt::Key_A, "A"} + << SignalNames{}; + + QTest::addRow("checkable_enabled_menuItem") + << true << true << false << MenuItemType::MenuItem << MnemonicKey{Qt::Key_I, "I"} + << SignalNames{SignalName::Triggered, SignalName::CheckedChanged}; + QTest::addRow("checkable_disabled_menuItem") + << true << false << false << MenuItemType::MenuItem << MnemonicKey{Qt::Key_I, "I"} + << SignalNames{}; + QTest::addRow("uncheckable_enabled_menuItem") + << false << true << false << MenuItemType::MenuItem << MnemonicKey{Qt::Key_I, "I"} + << SignalNames{SignalName::Triggered}; + QTest::addRow("uncheckable_disabled_menuItem") + << false << false << false << MenuItemType::MenuItem << MnemonicKey{Qt::Key_I, "I"} + << SignalNames{}; + + QTest::addRow("checkable_enabled_subMenuItem") + << true << true << true << MenuItemType::MenuItem << MnemonicKey{Qt::Key_S, "S"} + << SignalNames{SignalName::Triggered, SignalName::CheckedChanged}; + QTest::addRow("checkable_disabled_subMenuItem") + << true << false << true << MenuItemType::MenuItem << MnemonicKey{Qt::Key_S, "S"} + << SignalNames{}; + QTest::addRow("uncheckable_enabled_subMenuItem") + << false << true << true << MenuItemType::MenuItem << MnemonicKey{Qt::Key_S, "S"} + << SignalNames{SignalName::Triggered}; + QTest::addRow("uncheckable_disabled_subMenuItem") + << false << false << true << MenuItemType::MenuItem << MnemonicKey{Qt::Key_S, "S"} + << SignalNames{}; + + QTest::addRow("checkable_enabled_subMenuAction") + << true << true << true << MenuItemType::Action << MnemonicKey{Qt::Key_U, "U"} + << SignalNames{SignalName::Triggered, SignalName::CheckedChanged}; + QTest::addRow("checkable_disabled_subMenuAction") + << true << false << true << MenuItemType::Action << MnemonicKey{Qt::Key_U, "U"} + << SignalNames{}; + QTest::addRow("uncheckable_enabled_subMenuAction") + << false << true << true << MenuItemType::Action << MnemonicKey{Qt::Key_U, "U"} + << SignalNames{SignalName::Triggered}; + QTest::addRow("uncheckable_disabled_subMenuAction") + << false << false << true << MenuItemType::Action << MnemonicKey{Qt::Key_U, "U"} + << SignalNames{}; +} + +// QTBUG-96630 +void tst_QQuickMenu::checkableMnemonics() +{ + using namespace CheckableMnemonics; + + QFETCH(bool, checkable); + QFETCH(bool, enabled); + QFETCH(bool, isSubMenu); + QFETCH(MenuItemType, itemType); + QFETCH(MnemonicKey, mnemonicKey); + QFETCH(SignalNames, expectedSignals); + + QQuickControlsApplicationHelper helper(this, QLatin1String("mnemonics.qml")); + QVERIFY2(helper.ready, helper.failureMessage()); + + QQuickWindow *window = helper.window; + window->show(); + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(window)); + + window->setProperty("checkable", checkable); + window->setProperty("enabled", enabled); + + QQuickMenu *menu = window->property("menu").value<QQuickMenu *>(); + QVERIFY(menu); + + auto clickKey = [window](const MnemonicKey &mnemonic) mutable { + QTest::simulateEvent(window, true, mnemonic.first, Qt::NoModifier, mnemonic.second, false); + QTest::simulateEvent(window, false, mnemonic.first, Qt::NoModifier, mnemonic.second, false); + }; + + constexpr auto EMPTY_ITEM_NAME = ""; + const char *itemName = EMPTY_ITEM_NAME; + switch (itemType) { + case MenuItemType::Action: + itemName = isSubMenu ? "subMenuAction" : "action"; + break; + case MenuItemType::MenuItem: + itemName = isSubMenu ? "subMenuItem" : "menuItem"; + break; + } + QCOMPARE_NE(itemName, EMPTY_ITEM_NAME); + + QObject *menuItem = window->property(itemName).value<QObject*>(); + QVERIFY(menuItem); + + menu->open(); + QTRY_VERIFY(menu->isOpened()); + + if (isSubMenu) { + QQuickMenu *subMenu = window->property("subMenu").value<QQuickMenu *>(); + QVERIFY(subMenu); + clickKey(MnemonicKey{Qt::Key_M, "M"}); // "Sub &Menu" + QTRY_VERIFY(subMenu->isOpened()); + } + + const ItemSignalSpy itemSignalSpy(itemType, menuItem); + QVERIFY(itemSignalSpy.isValid()); + + clickKey(mnemonicKey); + QCOMPARE(itemSignalSpy.signalSize(SignalName::CheckedChanged), + expectedSignals & SignalName::CheckedChanged ? 1 : 0); + QCOMPARE(itemSignalSpy.signalSize(SignalName::Triggered), + expectedSignals & SignalName::Triggered ? 1 : 0); +} +#endif + void tst_QQuickMenu::menuButton() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls) QSKIP("This platform only allows tab focus for text controls"); @@ -648,8 +837,7 @@ void tst_QQuickMenu::addItem() void tst_QQuickMenu::menuSeparator() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QQuickControlsApplicationHelper helper(this, QLatin1String("menuSeparator.qml")); QVERIFY2(helper.ready, helper.failureMessage()); @@ -1037,8 +1225,7 @@ void tst_QQuickMenu::actions() #if QT_CONFIG(shortcut) void tst_QQuickMenu::actionShortcuts() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QQuickControlsApplicationHelper helper(this, QLatin1String("actionShortcuts.qml")); QVERIFY2(helper.ready, helper.failureMessage()); @@ -1332,8 +1519,7 @@ void tst_QQuickMenu::subMenuKeyboard_data() void tst_QQuickMenu::subMenuKeyboard() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QFETCH(bool, cascade); QFETCH(bool, mirrored); @@ -1461,8 +1647,7 @@ void tst_QQuickMenu::subMenuDisabledKeyboard_data() // QTBUG-69540 void tst_QQuickMenu::subMenuDisabledKeyboard() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QFETCH(bool, cascade); QFETCH(bool, mirrored); @@ -2050,8 +2235,7 @@ void tst_QQuickMenu::menuItemWidthAfterRetranslate() void tst_QQuickMenu::giveMenuItemFocusOnButtonPress() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QQuickControlsApplicationHelper helper(this, QLatin1String("giveMenuItemFocusOnButtonPress.qml")); QVERIFY2(helper.ready, helper.failureMessage()); diff --git a/tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp b/tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp index bbace286a8..b331fda1a3 100644 --- a/tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp +++ b/tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp @@ -38,8 +38,6 @@ private slots: void checkHighlightWhenMenuDismissed(); private: - static bool hasWindowActivation(); - QScopedPointer<QPointingDevice> touchScreen = QScopedPointer<QPointingDevice>(QTest::createTouchDevice()); }; @@ -54,11 +52,6 @@ tst_qquickmenubar::tst_qquickmenubar() qputenv("QML_NO_TOUCH_COMPRESSION", "1"); } -bool tst_qquickmenubar::hasWindowActivation() -{ - return (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)); -} - void tst_qquickmenubar::delegate() { QQmlApplicationEngine engine(testFileUrl("empty.qml")); @@ -74,8 +67,7 @@ void tst_qquickmenubar::delegate() void tst_qquickmenubar::mouse() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION if ((QGuiApplication::platformName() == QLatin1String("offscreen")) || (QGuiApplication::platformName() == QLatin1String("minimal"))) @@ -286,8 +278,7 @@ void tst_qquickmenubar::touch() void tst_qquickmenubar::keys() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QQmlApplicationEngine engine(testFileUrl("menubar.qml")); @@ -478,8 +469,7 @@ void tst_qquickmenubar::keys() void tst_qquickmenubar::mnemonics() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION #if defined(Q_OS_MACOS) or defined(Q_OS_WEBOS) QSKIP("Mnemonics are not used on this platform"); diff --git a/tests/auto/quickcontrols/qquickpopup/BLACKLIST b/tests/auto/quickcontrols/qquickpopup/BLACKLIST index aa31440328..ed8288b5c9 100644 --- a/tests/auto/quickcontrols/qquickpopup/BLACKLIST +++ b/tests/auto/quickcontrols/qquickpopup/BLACKLIST @@ -12,3 +12,8 @@ opensuse-leap [cursorShape] opensuse-leap + +# QTBUG-125237 +[activeFocusItemAfterWindowInactive] +android + diff --git a/tests/auto/quickcontrols/qquickpopup/data/activeFocusAfterWindowInactive.qml b/tests/auto/quickcontrols/qquickpopup/data/activeFocusAfterWindowInactive.qml new file mode 100644 index 0000000000..1d4dc87e41 --- /dev/null +++ b/tests/auto/quickcontrols/qquickpopup/data/activeFocusAfterWindowInactive.qml @@ -0,0 +1,27 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + width: 400 + height: 400 + + property alias popup: popup + property alias button: button + + Button { + id: button + text: "button" + focus: true + } + + Popup { + id: popup + focus: true + width: 100 + height: 100 + anchors.centerIn: Overlay.overlay + } +} diff --git a/tests/auto/quickcontrols/qquickpopup/data/resetHoveredForItemsWithinOverlay.qml b/tests/auto/quickcontrols/qquickpopup/data/resetHoveredForItemsWithinOverlay.qml new file mode 100644 index 0000000000..a7e66718ee --- /dev/null +++ b/tests/auto/quickcontrols/qquickpopup/data/resetHoveredForItemsWithinOverlay.qml @@ -0,0 +1,28 @@ +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + id: root + width: 100 + height: 100 + property alias controlsPopup: _controlsPopup + property alias blockInputPopup: _blockInputPopup + Popup { + id: _controlsPopup + width: parent.width + height: parent.height + modal: true + Control { + id: controls + anchors.fill: parent + hoverEnabled: true + contentItem: Text { text: "Test Control" } + } + } + Popup { + id: _blockInputPopup + width: parent.width + height: parent.height + modal: true + } +} diff --git a/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp b/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp index 09c0b29d2f..ee535caa8e 100644 --- a/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp +++ b/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp @@ -65,6 +65,7 @@ private slots: void activeFocusAfterExit(); void activeFocusOnDelayedEnter(); void activeFocusDespiteLowerStackingOrder(); + void activeFocusItemAfterWindowInactive(); void hover_data(); void hover(); void wheel_data(); @@ -102,9 +103,9 @@ private slots: void focusMultiplePopup(); void contentChildrenChange(); void doubleClickInMouseArea(); + void resetHoveredStateForItemsWithinPopup(); private: - static bool hasWindowActivation(); QScopedPointer<QPointingDevice> touchScreen = QScopedPointer<QPointingDevice>(QTest::createTouchDevice()); }; @@ -128,11 +129,6 @@ 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); @@ -511,8 +507,7 @@ void tst_QQuickPopup::closePolicy_data() void tst_QQuickPopup::closePolicy() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QFETCH(QString, source); QFETCH(const QPointingDevice *, device); @@ -654,8 +649,7 @@ void tst_QQuickPopup::closePolicy_grabberInside() void tst_QQuickPopup::activeFocusOnClose1() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION // Test that a popup that never sets focus: true (e.g. ToolTip) doesn't affect // the active focus item when it closes. @@ -700,8 +694,7 @@ void tst_QQuickPopup::activeFocusOnClose1() void tst_QQuickPopup::activeFocusOnClose2() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION // Test that a popup that sets focus: true but relinquishes focus (e.g. by // calling forceActiveFocus() on another item) before it closes doesn't @@ -742,8 +735,7 @@ void tst_QQuickPopup::activeFocusOnClose2() void tst_QQuickPopup::activeFocusOnClose3() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION // Test that a closing popup that had focus doesn't steal focus from // another popup that the focus was transferred to. @@ -778,8 +770,7 @@ void tst_QQuickPopup::activeFocusOnClose3() void tst_QQuickPopup::activeFocusOnClosingSeveralPopups() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION // Test that active focus isn't lost when multiple popup closing simultaneously QQuickControlsApplicationHelper helper(this, QStringLiteral("activeFocusOnClosingSeveralPopups.qml")); @@ -830,8 +821,7 @@ void tst_QQuickPopup::activeFocusOnClosingSeveralPopups() void tst_QQuickPopup::activeFocusAfterExit() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION // Test that after closing a popup the highest one in z-order receives it instead. QQuickControlsApplicationHelper helper(this, QStringLiteral("activeFocusAfterExit.qml")); @@ -882,8 +872,7 @@ void tst_QQuickPopup::activeFocusAfterExit() void tst_QQuickPopup::activeFocusOnDelayedEnter() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION // 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. @@ -911,8 +900,7 @@ void tst_QQuickPopup::activeFocusOnDelayedEnter() // key events due to having active focus. void tst_QQuickPopup::activeFocusDespiteLowerStackingOrder() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QQuickControlsApplicationHelper helper(this, QStringLiteral("activeFocusOnClose3.qml")); QVERIFY2(helper.ready, helper.failureMessage()); @@ -951,6 +939,51 @@ void tst_QQuickPopup::activeFocusDespiteLowerStackingOrder() QVERIFY(!popup1->hasActiveFocus()); } +void tst_QQuickPopup::activeFocusItemAfterWindowInactive() +{ + SKIP_IF_NO_WINDOW_ACTIVATION + + QQuickControlsApplicationHelper helper(this, QStringLiteral("activeFocusAfterWindowInactive.qml")); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickApplicationWindow *window = helper.appWindow; + window->show(); + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(window)); + + QQuickPopup *popup = helper.appWindow->property("popup").value<QQuickPopup*>(); + QQuickButton *button = helper.appWindow->property("button").value<QQuickButton*>(); + QVERIFY(popup); + QVERIFY(button); + + popup->open(); + QTRY_VERIFY(popup->isVisible()); + QVERIFY(popup->hasActiveFocus()); + QVERIFY(!button->hasActiveFocus()); + + popup->close(); + QTRY_VERIFY(!popup->isVisible()); + QVERIFY(button->hasActiveFocus()); + QCOMPARE(window->activeFocusItem(), button); + + popup->open(); + QTRY_VERIFY(popup->isVisible()); + + QQuickWindow newWindow; + newWindow.setTitle("newFocusWindow"); + newWindow.show(); + newWindow.requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(&newWindow)); + + popup->close(); + QTRY_VERIFY(!popup->isVisible()); + QTRY_VERIFY(!popup->isOpened()); + QCOMPARE(QGuiApplication::focusWindow(), &newWindow); + + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(window)); + QCOMPARE(window->activeFocusItem(), button); +} + void tst_QQuickPopup::hover_data() { QTest::addColumn<QString>("source"); @@ -1389,8 +1422,7 @@ void tst_QQuickPopup::componentComplete() void tst_QQuickPopup::closeOnEscapeWithNestedPopups() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION // 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. @@ -1459,8 +1491,7 @@ void tst_QQuickPopup::closeOnEscapeWithNestedPopups() void tst_QQuickPopup::closeOnEscapeWithVisiblePopup() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QQuickControlsApplicationHelper helper(this, QStringLiteral("closeOnEscapeWithVisiblePopup.qml")); QVERIFY2(helper.ready, helper.failureMessage()); @@ -1587,8 +1618,7 @@ void tst_QQuickPopup::qquickview() // QTBUG-73447 void tst_QQuickPopup::disabledPalette() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QQuickControlsApplicationHelper helper(this, "disabledPalette.qml"); QVERIFY2(helper.ready, helper.failureMessage()); @@ -1627,8 +1657,7 @@ void tst_QQuickPopup::disabledPalette() void tst_QQuickPopup::disabledParentPalette() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QQuickControlsApplicationHelper helper(this, "disabledPalette.qml"); QVERIFY2(helper.ready, helper.failureMessage()); @@ -1736,8 +1765,7 @@ void tst_QQuickPopup::setOverlayParentToNull() void tst_QQuickPopup::tabFence() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls) QSKIP("This platform only allows tab focus for text controls"); @@ -1849,8 +1877,7 @@ void tst_QQuickPopup::centerInOverlayWithinStackViewItem() void tst_QQuickPopup::destroyDuringExitTransition() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QQuickControlsApplicationHelper helper(this, "destroyDuringExitTransition.qml"); QVERIFY2(helper.ready, helper.failureMessage()); @@ -2287,6 +2314,40 @@ void tst_QQuickPopup::doubleClickInMouseArea() QCOMPARE(longPressSpy.count(), 0); } +void tst_QQuickPopup::resetHoveredStateForItemsWithinPopup() +{ + QQuickControlsApplicationHelper helper(this, "resetHoveredForItemsWithinOverlay.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + QQuickPopup *controlsPopup = window->property("controlsPopup").value<QQuickPopup*>(); + QVERIFY(controlsPopup); + + QQuickPopup *blockInputPopup = window->property("blockInputPopup").value<QQuickPopup*>(); + QVERIFY(controlsPopup); + + controlsPopup->open(); + QTRY_VERIFY(controlsPopup->isOpened()); + + QTest::mouseMove(window, QPoint(window->width() / 2 + 2, window->height() / 2 + 2)); + QTest::mouseMove(window, QPoint(window->width() / 2, window->height() / 2)); + + auto *controlItem = qobject_cast<QQuickControl *>(controlsPopup->contentItem()->childItems().at(0)); + QVERIFY(controlItem); + // Check hover enabled for the control item within the popup + QTRY_VERIFY(controlItem->isHovered()); + + // Open the modal popup window over the existing control item + blockInputPopup->open(); + QTRY_VERIFY(blockInputPopup->isOpened()); + + // Control item hovered shall be disabled once we open the modal popup + QTRY_VERIFY(!controlItem->isHovered()); +} + QTEST_QUICKCONTROLS_MAIN(tst_QQuickPopup) #include "tst_qquickpopup.moc" diff --git a/tests/auto/quickcontrols/qquicktextarea/tst_qquicktextarea.cpp b/tests/auto/quickcontrols/qquicktextarea/tst_qquicktextarea.cpp index 3788799f8a..1e3e0e46ee 100644 --- a/tests/auto/quickcontrols/qquicktextarea/tst_qquicktextarea.cpp +++ b/tests/auto/quickcontrols/qquicktextarea/tst_qquicktextarea.cpp @@ -12,6 +12,7 @@ #include <QtQuick/qquickview.h> #include <QtQuickTestUtils/private/qmlutils_p.h> #include <QtQuickTestUtils/private/viewtestutils_p.h> +#include <QtQuickTestUtils/private/visualtestutils_p.h> #include <QtQuickTemplates2/private/qquicktextarea_p.h> #include <QtQuickControlsTestUtils/private/qtest_quickcontrols_p.h> @@ -29,7 +30,6 @@ private slots: void touchscreenSetsFocusAndMovesCursor(); private: - static bool hasWindowActivation(); QScopedPointer<QPointingDevice> touchDevice = QScopedPointer<QPointingDevice>(QTest::createTouchDevice()); }; @@ -102,8 +102,7 @@ void tst_QQuickTextArea::touchscreenDoesNotSelect() void tst_QQuickTextArea::touchscreenSetsFocusAndMovesCursor() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION qunsetenv("QT_QUICK_CONTROLS_TEXT_SELECTION_BEHAVIOR"); QQuickView window; @@ -158,11 +157,6 @@ void tst_QQuickTextArea::touchscreenSetsFocusAndMovesCursor() QCOMPARE_GT(top->selectedText().size(), 0); } -bool tst_QQuickTextArea::hasWindowActivation() -{ - return (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)); -} - QTEST_QUICKCONTROLS_MAIN(tst_QQuickTextArea) #include "tst_qquicktextarea.moc" diff --git a/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp b/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp index 27334f06bb..a42ea91b6d 100644 --- a/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp +++ b/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp @@ -304,6 +304,7 @@ bool FileDialogTestHelper::openDialog() void tst_QQuickFileDialogImpl::defaults() { + QTest::failOnWarning(QRegularExpression(".*")); FileDialogTestHelper dialogHelper(this, "fileDialog.qml"); QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); QVERIFY(dialogHelper.waitForWindowActive()); diff --git a/tests/auto/quickdialogs/qquickmessagedialogimpl/tst_qquickmessagedialogimpl.cpp b/tests/auto/quickdialogs/qquickmessagedialogimpl/tst_qquickmessagedialogimpl.cpp index 6fd64acc1b..b2edbb1cd8 100644 --- a/tests/auto/quickdialogs/qquickmessagedialogimpl/tst_qquickmessagedialogimpl.cpp +++ b/tests/auto/quickdialogs/qquickmessagedialogimpl/tst_qquickmessagedialogimpl.cpp @@ -277,6 +277,10 @@ void tst_QQuickMessageDialogImpl::emitCorrectAcceptedAndRejectedSignals() QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); QVERIFY(dialogHelper.waitForWindowActive()); QVERIFY(dialogHelper.openDialog()); + + if (QQuickTest::qIsPolishScheduled(dialogHelper.window())) + QVERIFY(QQuickTest::qWaitForPolish(dialogHelper.window())); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); auto *buttonBox = dialogHelper.quickDialog->findChild<QQuickDialogButtonBox *>("buttonBox"); diff --git a/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp b/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp index 175bec4cb8..73411e5b8a 100644 --- a/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp +++ b/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp @@ -14,6 +14,7 @@ #include <QtQuick/private/qquicktaphandler_p.h> #include <QtQuickTemplates2/private/qquickbutton_p.h> #include <QtQuickTestUtils/private/qmlutils_p.h> +#include <QtQuickTestUtils/private/visualtestutils_p.h> #include <QtGui/QWindow> #include <QtGui/QScreen> #include <QtGui/QImage> @@ -142,6 +143,7 @@ private slots: void focusPreserved(); void accessibilityHandlesViewChange(); void cleanupRhi(); + void dontRecreateRootElementOnWindowChange(); private: QPointingDevice *device = QTest::createTouchDevice(); @@ -991,8 +993,7 @@ void tst_qquickwidget::focusOnClickInProxyWidget() void tst_qquickwidget::focusPreserved() { - if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) - QSKIP("Window Activation is not supported."); + SKIP_IF_NO_WINDOW_ACTIVATION if (QGuiApplication::platformName() == "android") QSKIP("Test doesn't exit cleanly on Android and generates many warnings - QTBUG-112696"); @@ -1096,6 +1097,21 @@ void tst_qquickwidget::cleanupRhi() topLevel.create(); } +void tst_qquickwidget::dontRecreateRootElementOnWindowChange() +{ + auto *quickWidget = new QQuickWidget(); + quickWidget->setSource(testFileUrl("rectangle.qml")); + QObject *item = quickWidget->rootObject(); + + bool wasDestroyed = false; + QObject::connect(item, &QObject::destroyed, this, [&] { wasDestroyed = true; }); + + QEvent event(QEvent::WindowChangeInternal); + QCoreApplication::sendEvent(quickWidget, &event); + + QVERIFY(!wasDestroyed); +} + QTEST_MAIN(tst_qquickwidget) #include "tst_qquickwidget.moc" |
