diff options
Diffstat (limited to 'tests')
271 files changed, 12120 insertions, 1157 deletions
diff --git a/tests/auto/cmake/CMakeLists.txt b/tests/auto/cmake/CMakeLists.txt index 0e168cf6ad..9d2ef3bd88 100644 --- a/tests/auto/cmake/CMakeLists.txt +++ b/tests/auto/cmake/CMakeLists.txt @@ -129,6 +129,7 @@ endif() if(TARGET Qt::Quick) if(NOT CMAKE_CROSSCOMPILING) + _qt_internal_test_expect_pass(shared_qml_module) _qt_internal_test_expect_pass(qtquickcompiler BINARY qqc_test) if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.21") _qt_internal_test_expect_pass(test_common_import_path diff --git a/tests/auto/cmake/shared_qml_module/CMakeLists.txt b/tests/auto/cmake/shared_qml_module/CMakeLists.txt new file mode 100644 index 0000000000..867caa4aad --- /dev/null +++ b/tests/auto/cmake/shared_qml_module/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.16) + +project(scheduler VERSION 0.1 LANGUAGES CXX) + +find_package(Qt6 REQUIRED COMPONENTS Qml) +qt_standard_project_setup(REQUIRES 6.8) + +add_custom_target(custom_qmllint_target ALL) +set(QT_QMLLINT_ALL_TARGET custom_qmllint_target) + +add_subdirectory(external) +add_subdirectory(Scheduler) +add_subdirectory(SchedulerApp) +add_subdirectory(tests) diff --git a/tests/auto/cmake/shared_qml_module/Scheduler/CMakeLists.txt b/tests/auto/cmake/shared_qml_module/Scheduler/CMakeLists.txt new file mode 100644 index 0000000000..a40688d3a3 --- /dev/null +++ b/tests/auto/cmake/shared_qml_module/Scheduler/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.16) + +project(scheduler VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 6.5 REQUIRED COMPONENTS Quick) + +qt_add_qml_module(scheduler + URI Scheduler + VERSION 1.0 + SOURCES + task.h + task.cpp + schedulerglobal.h + QML_FILES + MainScreen.qml +) + +target_compile_definitions(scheduler + PRIVATE + SCHEDULER_LIBRARY +) + +target_include_directories(scheduler + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries(scheduler + PRIVATE + Qt6::Quick +) diff --git a/tests/auto/cmake/shared_qml_module/Scheduler/MainScreen.qml b/tests/auto/cmake/shared_qml_module/Scheduler/MainScreen.qml new file mode 100644 index 0000000000..6260b4f918 --- /dev/null +++ b/tests/auto/cmake/shared_qml_module/Scheduler/MainScreen.qml @@ -0,0 +1,5 @@ +import QtQuick + +Rectangle { + color: "tomato" +} diff --git a/tests/auto/cmake/shared_qml_module/Scheduler/schedulerglobal.h b/tests/auto/cmake/shared_qml_module/Scheduler/schedulerglobal.h new file mode 100644 index 0000000000..cd24bcc8b9 --- /dev/null +++ b/tests/auto/cmake/shared_qml_module/Scheduler/schedulerglobal.h @@ -0,0 +1,15 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef SCHEDULEGLOBAL_H +#define SCHEDULEGLOBAL_H + +#include <QtGlobal> + +#if defined(SCHEDULER_LIBRARY) +#define SCHEDULER_EXPORT Q_DECL_EXPORT +#else +#define SCHEDULER_EXPORT Q_DECL_IMPORT +#endif + +#endif // SCHEDULEGLOBAL_H diff --git a/tests/auto/cmake/shared_qml_module/Scheduler/task.cpp b/tests/auto/cmake/shared_qml_module/Scheduler/task.cpp new file mode 100644 index 0000000000..534a614139 --- /dev/null +++ b/tests/auto/cmake/shared_qml_module/Scheduler/task.cpp @@ -0,0 +1,47 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "task.h" + +Task::Task() +{ +} + +Task::Task(const QString &name, int durationInMinutes) + : mName(name) + , mDurationInMinutes(durationInMinutes) +{ +} + +QString Task::name() const +{ + return mName; +} + +void Task::setName(const QString &name) +{ + mName = name; +} + +int Task::durationInMinutes() const +{ + return mDurationInMinutes; +} + +void Task::setDurationInMinutes(int durationInMinutes) +{ + mDurationInMinutes = durationInMinutes; +} + +bool Task::read(const QJsonObject &json) +{ + mName = json.value("name").toString(); + mDurationInMinutes = json.value("durationInMinutes").toInt(); + return true; +} + +void Task::write(QJsonObject &json) const +{ + json["name"] = mName; + json["durationInMinutes"] = mDurationInMinutes; +} diff --git a/tests/auto/cmake/shared_qml_module/Scheduler/task.h b/tests/auto/cmake/shared_qml_module/Scheduler/task.h new file mode 100644 index 0000000000..916f939508 --- /dev/null +++ b/tests/auto/cmake/shared_qml_module/Scheduler/task.h @@ -0,0 +1,34 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef TASK_H +#define TASK_H + +#include <QObject> +#include <QJsonObject> + +#include "schedulerglobal.h" + +class SCHEDULER_EXPORT Task : public QObject +{ + Q_OBJECT + +public: + Task(); + Task(const QString &name, int durationInMinutes); + + bool read(const QJsonObject &json); + void write(QJsonObject &json) const; + + QString name() const; + void setName(const QString &name); + + int durationInMinutes() const; + void setDurationInMinutes(int durationInMinutes); + +private: + QString mName; + int mDurationInMinutes = 0; +}; + +#endif // TASK_H diff --git a/tests/auto/cmake/shared_qml_module/SchedulerApp/CMakeLists.txt b/tests/auto/cmake/shared_qml_module/SchedulerApp/CMakeLists.txt new file mode 100644 index 0000000000..fdbb1df701 --- /dev/null +++ b/tests/auto/cmake/shared_qml_module/SchedulerApp/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 3.16) + +project(schedulerapp VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 6.5 REQUIRED COMPONENTS Quick) + +qt_add_executable(schedulerapp + main.cpp +) + +qt_add_qml_module(schedulerapp + URI SchedulerApp + DEPENDENCIES + TARGET scheduler + IMPORTS + TARGET nested_module + VERSION 1.0 + QML_FILES + Main.qml +) + +# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. +# If you are developing for iOS or macOS you should consider setting an +# explicit, fixed bundle identifier manually though. +set_target_properties(schedulerapp PROPERTIES +# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.schedulerapp + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +target_link_libraries(schedulerapp + PRIVATE Qt6::Quick +) + +include(GNUInstallDirs) +install(TARGETS schedulerapp + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff --git a/tests/auto/cmake/shared_qml_module/SchedulerApp/Main.qml b/tests/auto/cmake/shared_qml_module/SchedulerApp/Main.qml new file mode 100644 index 0000000000..4dddb35ae8 --- /dev/null +++ b/tests/auto/cmake/shared_qml_module/SchedulerApp/Main.qml @@ -0,0 +1,12 @@ +import QtQuick.Controls +import Scheduler + +ApplicationWindow { + width: 640 + height: 480 + visible: true + title: qsTr("Scheduler") + + MainScreen {} + Test {} +} diff --git a/tests/auto/cmake/shared_qml_module/SchedulerApp/main.cpp b/tests/auto/cmake/shared_qml_module/SchedulerApp/main.cpp new file mode 100644 index 0000000000..6422d91bfa --- /dev/null +++ b/tests/auto/cmake/shared_qml_module/SchedulerApp/main.cpp @@ -0,0 +1,21 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +// +#include <QGuiApplication> +#include <QQmlApplicationEngine> + + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + qputenv("QT_QUICK_CONTROLS_STYLE", "Material"); + + QQmlApplicationEngine engine; + QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, + &app, []() { QCoreApplication::exit(-1); }, + Qt::QueuedConnection); + engine.loadFromModule("SchedulerApp", "Main"); + + return app.exec(); +} diff --git a/tests/auto/cmake/shared_qml_module/external/CMakeLists.txt b/tests/auto/cmake/shared_qml_module/external/CMakeLists.txt new file mode 100644 index 0000000000..798a23e1a5 --- /dev/null +++ b/tests/auto/cmake/shared_qml_module/external/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(nested/module) diff --git a/tests/auto/cmake/shared_qml_module/external/nested/module/CMakeLists.txt b/tests/auto/cmake/shared_qml_module/external/nested/module/CMakeLists.txt new file mode 100644 index 0000000000..da75d2333a --- /dev/null +++ b/tests/auto/cmake/shared_qml_module/external/nested/module/CMakeLists.txt @@ -0,0 +1,15 @@ + +cmake_minimum_required(VERSION 3.16) + +project(scheduler VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 6.5 REQUIRED COMPONENTS Quick) + +qt_add_qml_module(nested_module + URI nested.module + VERSION 1.0 + QML_FILES + Test.qml +) diff --git a/tests/auto/cmake/shared_qml_module/external/nested/module/Test.qml b/tests/auto/cmake/shared_qml_module/external/nested/module/Test.qml new file mode 100644 index 0000000000..3052615aef --- /dev/null +++ b/tests/auto/cmake/shared_qml_module/external/nested/module/Test.qml @@ -0,0 +1,3 @@ +import QtQuick + +Item {} diff --git a/tests/auto/cmake/shared_qml_module/tests/CMakeLists.txt b/tests/auto/cmake/shared_qml_module/tests/CMakeLists.txt new file mode 100644 index 0000000000..0653827192 --- /dev/null +++ b/tests/auto/cmake/shared_qml_module/tests/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(auto) diff --git a/tests/auto/cmake/shared_qml_module/tests/auto/CMakeLists.txt b/tests/auto/cmake/shared_qml_module/tests/auto/CMakeLists.txt new file mode 100644 index 0000000000..269aea0c60 --- /dev/null +++ b/tests/auto/cmake/shared_qml_module/tests/auto/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(unit) diff --git a/tests/auto/cmake/shared_qml_module/tests/auto/unit/CMakeLists.txt b/tests/auto/cmake/shared_qml_module/tests/auto/unit/CMakeLists.txt new file mode 100644 index 0000000000..9d5f1d9f5e --- /dev/null +++ b/tests/auto/cmake/shared_qml_module/tests/auto/unit/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.5) + +project(tst_models LANGUAGES CXX) + +enable_testing() + +find_package(Qt6 REQUIRED COMPONENTS Gui Test Quick) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_executable(tst_models tst_models.cpp) +add_test(NAME tst_models COMMAND tst_models) + +target_link_libraries(tst_models + PRIVATE + scheduler + Qt6::Gui + Qt6::Test + Qt6::Quick +) + + +qt_add_qml_module(tst_models + URI unittest + DEPENDENCIES + TARGET scheduler +) + + +qt_add_executable(tst_models_dummy_helper dummy.cpp) +qt_add_qml_module(tst_models_dummy_helper + URI unimportant + DEPENDENCIES + TARGET nested_module +) diff --git a/tests/auto/cmake/shared_qml_module/tests/auto/unit/dummy.cpp b/tests/auto/cmake/shared_qml_module/tests/auto/unit/dummy.cpp new file mode 100644 index 0000000000..237c8ce181 --- /dev/null +++ b/tests/auto/cmake/shared_qml_module/tests/auto/unit/dummy.cpp @@ -0,0 +1 @@ +int main() {} diff --git a/tests/auto/cmake/shared_qml_module/tests/auto/unit/tst_models.cpp b/tests/auto/cmake/shared_qml_module/tests/auto/unit/tst_models.cpp new file mode 100644 index 0000000000..0a7119fbcc --- /dev/null +++ b/tests/auto/cmake/shared_qml_module/tests/auto/unit/tst_models.cpp @@ -0,0 +1,45 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtTest> + + +class tst_models : public QObject +{ + Q_OBJECT + +public: + tst_models(); + ~tst_models(); + +private slots: + void initTestCase(); + void cleanupTestCase(); + + void qtconf(); +}; + +tst_models::tst_models() {} + +tst_models::~tst_models() {} + +void tst_models::initTestCase() {} + +void tst_models::cleanupTestCase() {} + +void tst_models::qtconf() +{ + auto importPaths = QLibraryInfo::paths(QLibraryInfo::QmlImportsPath); + QCOMPARE_GE(importPaths.size(), 2); + if (importPaths.at(0).endsWith("shared_qml_module")) { + QVERIFY(importPaths.at(1).endsWith("external/nested")); + } else if (importPaths.at(0).endsWith("external/nested")) { + QVERIFY(importPaths.at(1).endsWith("shared_qml_module")); + } else { + QFAIL("Expected import paths were not found"); + } +} + +QTEST_MAIN(tst_models) + +#include "tst_models.moc" diff --git a/tests/auto/cmake/test_generate_qmlls_ini/main.cpp b/tests/auto/cmake/test_generate_qmlls_ini/main.cpp index a7bdbf1e18..2d663cdc23 100644 --- a/tests/auto/cmake/test_generate_qmlls_ini/main.cpp +++ b/tests/auto/cmake/test_generate_qmlls_ini/main.cpp @@ -5,6 +5,7 @@ #include <QtCore/qstring.h> #include <QtCore/qdir.h> #include <QtCore/qfile.h> +#include <QtCore/qlibraryinfo.h> #include <QtQml/qqml.h> #include <QtTest/qtest.h> @@ -33,6 +34,7 @@ void tst_generate_qmlls_ini::qmllsIniAreCorrect() QSKIP(u"Cannot find source directory '%1', skipping test..."_s.arg(SOURCE_DIRECTORY) .toLatin1()); + const QString &docPath = QLibraryInfo::path(QLibraryInfo::DocumentationPath); { auto file = QFile(source.absoluteFilePath(qmllsIniName)); QVERIFY(file.exists()); @@ -41,8 +43,8 @@ void tst_generate_qmlls_ini::qmllsIniAreCorrect() auto secondFolder = QDir(build.absolutePath().append(u"/qml/hello/subfolders"_s)); QVERIFY(secondFolder.exists()); QCOMPARE(fileContent, - u"[General]\nbuildDir=%1%2%3\nno-cmake-calls=false\n"_s.arg(build.absolutePath(), QDir::listSeparator(), - secondFolder.absolutePath())); + u"[General]\nbuildDir=%1%2%3\nno-cmake-calls=false\ndocDir=%4\n"_s.arg(build.absolutePath(), QDir::listSeparator(), + secondFolder.absolutePath(), docPath)); } { @@ -55,7 +57,7 @@ void tst_generate_qmlls_ini::qmllsIniAreCorrect() QVERIFY(file.open(QFile::ReadOnly | QFile::Text)); const auto fileContent = QString::fromUtf8(file.readAll()); QCOMPARE(fileContent, - u"[General]\nbuildDir=%1\nno-cmake-calls=false\n"_s.arg(buildSubfolder.absolutePath())); + u"[General]\nbuildDir=%1\nno-cmake-calls=false\ndocDir=%2\n"_s.arg(buildSubfolder.absolutePath(), docPath)); } } @@ -70,7 +72,7 @@ void tst_generate_qmlls_ini::qmllsIniAreCorrect() const auto fileContent = QString::fromUtf8(file.readAll()); QCOMPARE( fileContent, - u"[General]\nbuildDir=%1\nno-cmake-calls=false\n"_s.arg(build.absolutePath())); + u"[General]\nbuildDir=%1\nno-cmake-calls=false\ndocDir=%2\n"_s.arg(build.absolutePath(), docPath)); } } { @@ -86,7 +88,7 @@ void tst_generate_qmlls_ini::qmllsIniAreCorrect() const auto fileContent = QString::fromUtf8(file.readAll()); QCOMPARE( fileContent, - u"[General]\nbuildDir=%1\nno-cmake-calls=false\n"_s.arg(build.absolutePath())); + u"[General]\nbuildDir=%1\nno-cmake-calls=false\ndocDir=%2\n"_s.arg(build.absolutePath(), docPath)); } } } diff --git a/tests/auto/qml/debugger/shared/qqmldebugprocess.cpp b/tests/auto/qml/debugger/shared/qqmldebugprocess.cpp index 9e5bc17623..97477370c8 100644 --- a/tests/auto/qml/debugger/shared/qqmldebugprocess.cpp +++ b/tests/auto/qml/debugger/shared/qqmldebugprocess.cpp @@ -56,7 +56,7 @@ QString QQmlDebugProcess::stateString() const void QQmlDebugProcess::start(const QStringList &arguments) { -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS // make sure m_executable points to the actual binary even if it's inside an app bundle QFileInfo binFile(m_executable); if (!binFile.isExecutable()) { diff --git a/tests/auto/qml/ecmascripttests/TestExpectations b/tests/auto/qml/ecmascripttests/TestExpectations index 2e96de7819..cc5eae456d 100644 --- a/tests/auto/qml/ecmascripttests/TestExpectations +++ b/tests/auto/qml/ecmascripttests/TestExpectations @@ -98,7 +98,6 @@ built-ins/Array/prototype/slice/length-exceeding-integer-limit-proxied-array.js built-ins/Array/prototype/slice/length-exceeding-integer-limit.js fails built-ins/Array/prototype/some/15.4.4.17-3-28.js fails built-ins/Array/prototype/some/15.4.4.17-3-29.js fails -built-ins/Array/prototype/sort/comparefn-nonfunction-call-throws.js fails built-ins/Array/prototype/splice/S15.4.4.12_A3_T1.js fails built-ins/Array/prototype/splice/clamps-length-to-integer-limit.js fails built-ins/Array/prototype/splice/create-ctor-non-object.js fails @@ -254,8 +253,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/ecmascripttests/test262runner.cpp b/tests/auto/qml/ecmascripttests/test262runner.cpp index 90ac10a38b..d87a8a9552 100644 --- a/tests/auto/qml/ecmascripttests/test262runner.cpp +++ b/tests/auto/qml/ecmascripttests/test262runner.cpp @@ -222,10 +222,20 @@ void Test262Runner::createProcesses() } }); - QObject::connect(&p, &QProcess::finished, this, [&, i](int, QProcess::ExitStatus status) { - if (status != QProcess::NormalExit) { - qDebug() << QStringLiteral("Process %1 of %2 exited with a non-normal status") - .arg(i).arg(processCount - 1); + QObject::connect(&p, &QProcess::finished, this, + [this, processCount, i](int exitCode, QProcess::ExitStatus status) { + if (status != QProcess::NormalExit || exitCode != 0) { + TestData &testData(currentTasks[i]); + + auto &result = testData.stillNeedStrictRun + ? testData.sloppyResult + : testData.strictResult; + result = TestCase::Result( + TestCase::Crashes, + QStringLiteral("Process %1 of %2 exited with a non-normal status") + .arg(i).arg(processCount - 1)); + + addResult(testData); } --runningCount; diff --git a/tests/auto/qml/linebylinelex/BLACKLIST b/tests/auto/qml/linebylinelex/BLACKLIST deleted file mode 100644 index 0fd7f604e3..0000000000 --- a/tests/auto/qml/linebylinelex/BLACKLIST +++ /dev/null @@ -1,5 +0,0 @@ -# QTBUG-105697 -[testFormatter] -android -[testLineByLineLex] -android diff --git a/tests/auto/qml/linebylinelex/CMakeLists.txt b/tests/auto/qml/linebylinelex/CMakeLists.txt index 8b05ca0527..92d956a972 100644 --- a/tests/auto/qml/linebylinelex/CMakeLists.txt +++ b/tests/auto/qml/linebylinelex/CMakeLists.txt @@ -10,12 +10,12 @@ endif() # Collect linebyline test data file(GLOB_RECURSE test_data_glob - RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/.. - linebylinelex/data/*) + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + data/*) # Collect qmlformat test data file(GLOB_RECURSE test_data_glob2 - RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/.. - qmlformat/data/*) + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + ../qmlformat/data/*) list(APPEND test_data ${test_data_glob} ${test_data_glob2}) qt_internal_add_test(tst_linebylinelex @@ -25,17 +25,5 @@ qt_internal_add_test(tst_linebylinelex Qt::Qml Qt::QuickTestUtilsPrivate TESTDATA ${test_data} -) - -## Scopes: -##################################################################### - -qt_internal_extend_target(tst_linebylinelex CONDITION ANDROID OR IOS - DEFINES - QT_QMLTEST_DATADIR=":/" -) - -qt_internal_extend_target(tst_linebylinelex CONDITION NOT ANDROID AND NOT IOS - DEFINES - QT_QMLTEST_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/.." + BUILTIN_TESTDATA ) diff --git a/tests/auto/qml/linebylinelex/tst_linebylinelex.cpp b/tests/auto/qml/linebylinelex/tst_linebylinelex.cpp index d94f325020..040ccfa9a6 100644 --- a/tests/auto/qml/linebylinelex/tst_linebylinelex.cpp +++ b/tests/auto/qml/linebylinelex/tst_linebylinelex.cpp @@ -13,7 +13,7 @@ QT_USE_NAMESPACE using namespace Qt::StringLiterals; using namespace QQmlJS; -class TestLineByLineLex : public QQmlDataTest +class TestLineByLineLex : public QObject { Q_OBJECT @@ -21,7 +21,7 @@ public: TestLineByLineLex(); private Q_SLOTS: - void initTestCase() override; + void initTestCase(); void testLineByLineLex_data(); void testLineByLineLex(); @@ -34,17 +34,14 @@ private: QString m_qmljsrootgenPath; QString m_qmltyperegistrarPath; - QString m_baseDir; }; TestLineByLineLex::TestLineByLineLex() - : QQmlDataTest(QT_QMLTEST_DATADIR), m_baseDir(QString::fromLocal8Bit(QT_QMLTEST_DATADIR)) { } void TestLineByLineLex::initTestCase() { - QQmlDataTest::initTestCase(); } void TestLineByLineLex::testLineByLineLex_data() @@ -59,22 +56,18 @@ void TestLineByLineLex::testLineByLineLex() { QFETCH(QString, filename); - QString filePath = m_baseDir + u"/linebylinelex/data/"_s + filename; + QString filePath = QFINDTESTDATA("data/" + filename); runLex(filePath); } void TestLineByLineLex::testFormatter_data() { QTest::addColumn<QString>("filename"); - QDir formatData(m_baseDir + u"/qmlformat/data"_s); - bool hasTestData = false; // ### TODO: fix test to always have data + QDir formatData(QFINDTESTDATA("qmlformat/data")); for (const QFileInfo &fInfo : formatData.entryInfoList(QStringList({ u"*.qml"_s, u"*.js"_s }), QDir::Files)) { QTest::newRow(qPrintable(fInfo.fileName())) << fInfo.absoluteFilePath(); - hasTestData = true; } - if (!hasTestData) - QSKIP("No test data found!"); } void TestLineByLineLex::testFormatter() diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index daa16eba72..7bf3b34f86 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -5948,7 +5948,7 @@ void tst_QJSEngine::functionCtorGeneratedCUIsNotCollectedByGc() const QString program = "new Function('a', 'b', 'let x = \"Hello\"; return a + b');"; auto sumFunc = engine.evaluate(program); QVERIFY(sumFunc.isCallable()); - auto *function = QJSValuePrivate::asManagedType<QV4::FunctionObject>(&sumFunc); + auto *function = QJSValuePrivate::asManagedType<QV4::JavaScriptFunctionObject>(&sumFunc); auto *cu = function->d()->function->executableCompilationUnit(); QVERIFY(cu->runtimeStrings); // should exist for "Hello" QVERIFY(cu->runtimeStrings[0]->isMarked()); diff --git a/tests/auto/qml/qmlcachegen/CMakeLists.txt b/tests/auto/qml/qmlcachegen/CMakeLists.txt index 2dacff35b5..d88de460e9 100644 --- a/tests/auto/qml/qmlcachegen/CMakeLists.txt +++ b/tests/auto/qml/qmlcachegen/CMakeLists.txt @@ -28,6 +28,7 @@ qt_internal_add_test(tst_qmlcachegen Qt::Gui Qt::QmlPrivate Qt::QuickTestUtilsPrivate + Qt::QmlCompilerPrivate TESTDATA ${test_data} ) diff --git a/tests/auto/qml/qmlcachegen/data/aotstats/AotstatsClean.qml b/tests/auto/qml/qmlcachegen/data/aotstats/AotstatsClean.qml new file mode 100644 index 0000000000..b48004dc87 --- /dev/null +++ b/tests/auto/qml/qmlcachegen/data/aotstats/AotstatsClean.qml @@ -0,0 +1,11 @@ +import QtQml + +QtObject { + property int i: 100 + property int j: i * 2 + + function s() : string { + let s = "abc" + return s + "def " + } +} diff --git a/tests/auto/qml/qmlcachegen/data/aotstats/AotstatsMixed.qml b/tests/auto/qml/qmlcachegen/data/aotstats/AotstatsMixed.qml new file mode 100644 index 0000000000..3d96760e96 --- /dev/null +++ b/tests/auto/qml/qmlcachegen/data/aotstats/AotstatsMixed.qml @@ -0,0 +1,7 @@ +import QtQml + +QtObject { + property int i: Math.max(1, 2) // OK + function f() { return 1 } // Error: No specified return type + property string s: g() // Error: g? +} diff --git a/tests/auto/qml/qmlcachegen/data/aotstats/cachegentest.qrc b/tests/auto/qml/qmlcachegen/data/aotstats/cachegentest.qrc new file mode 100644 index 0000000000..4f156ad2ef --- /dev/null +++ b/tests/auto/qml/qmlcachegen/data/aotstats/cachegentest.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/cachegentest"> + <file alias="qmldir">qmldir</file> + </qresource> +</RCC> diff --git a/tests/auto/qml/qmlcachegen/data/aotstats/qmldir b/tests/auto/qml/qmlcachegen/data/aotstats/qmldir new file mode 100644 index 0000000000..72047cc99d --- /dev/null +++ b/tests/auto/qml/qmlcachegen/data/aotstats/qmldir @@ -0,0 +1,3 @@ +module cachegentest +AotstatsClean 254.0 AotstatsClean.qml +AotstatsMixed 254.0 AotstatsMixed.qml diff --git a/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp b/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp index 17a914c1dd..34ad3bbc98 100644 --- a/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp +++ b/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp @@ -3,6 +3,7 @@ #include <qtest.h> +#include <QJsonDocument> #include <QQmlComponent> #include <QQmlEngine> #include <QProcess> @@ -11,6 +12,7 @@ #include <QSysInfo> #include <QLoggingCategory> #include <private/qqmlcomponent_p.h> +#include <private/qqmljscompilerstats_p.h> #include <private/qqmlscriptdata_p.h> #include <private/qv4compileddata_p.h> #include <qtranslator.h> @@ -67,6 +69,10 @@ private slots: void scriptStringCachegenInteraction(); void saveableUnitPointer(); + + void aotstatsSerialization(); + void aotstatsGeneration_data(); + void aotstatsGeneration(); }; // A wrapper around QQmlComponent to ensure the temporary reference counts @@ -895,6 +901,133 @@ void tst_qmlcachegen::saveableUnitPointer() QCOMPARE(unit.flags, flags); } +void tst_qmlcachegen::aotstatsSerialization() +{ + const auto createEntry = [](const auto &d, const auto &n, const auto &e, const auto &l, + const auto &c, const auto &s) -> QQmlJS::AotStatsEntry { + QQmlJS::AotStatsEntry entry; + entry.codegenDuration = d; + entry.functionName = n; + entry.errorMessage = e; + entry.line = l; + entry.column = c; + entry.codegenSuccessful = s; + return entry; + }; + + const auto equal = [](const auto &e1, const auto &e2) -> bool { + return e1.codegenDuration == e2.codegenDuration && e1.functionName == e2.functionName + && e1.errorMessage == e2.errorMessage && e1.line == e2.line + && e1.column == e2.column && e1.codegenSuccessful == e2.codegenSuccessful; + }; + + // AotStats + // +-ModuleA + // | +-File1 + // | | +-e1 + // | | +-e2 + // | +-File2 + // | | +-e3 + // +-ModuleB + // | +-File3 + // | | +-e4 + + QQmlJS::AotStats original; + QQmlJS::AotStatsEntry e1 = createEntry(std::chrono::microseconds(500), "f1", "", 1, 1, true); + QQmlJS::AotStatsEntry e2 = createEntry(std::chrono::microseconds(200), "f2", "err1", 5, 4, false); + QQmlJS::AotStatsEntry e3 = createEntry(std::chrono::microseconds(750), "f3", "", 20, 4, true); + QQmlJS::AotStatsEntry e4 = createEntry(std::chrono::microseconds(300), "f4", "err2", 5, 8, false); + original.addEntry("ModuleA", "File1", e1); + original.addEntry("ModuleA", "File1", e2); + original.addEntry("ModuleA", "File2", e3); + original.addEntry("ModuleB", "File3", e4); + + const auto parsed = QQmlJS::AotStats::fromJsonDocument(original.toJsonDocument()); + QCOMPARE(parsed.entries().size(), original.entries().size()); + + const auto &parsedA = parsed.entries()["ModuleA"]; + const auto &originalA = original.entries()["ModuleA"]; + QCOMPARE(parsedA.size(), originalA.size()); + QCOMPARE(parsedA["File1"].size(), originalA["File1"].size()); + QVERIFY(equal(parsedA["File1"][0], originalA["File1"][0])); + QVERIFY(equal(parsedA["File1"][1], originalA["File1"][1])); + QCOMPARE(parsedA["File2"].size(), originalA["File2"].size()); + QVERIFY(equal(parsedA["File2"][0], originalA["File2"][0])); + + const auto &parsedB = parsed.entries()["ModuleB"]; + const auto &originalB = original.entries()["ModuleB"]; + QCOMPARE(parsedB.size(), originalB.size()); + QCOMPARE(parsedB["File3"].size(), originalB["File3"].size()); + QVERIFY(equal(parsedB["File3"][0], originalB["File3"][0])); +} + +struct FunctionEntry +{ + QString name; + QString errorMessage; + bool codegenSuccessful; +}; + +void tst_qmlcachegen::aotstatsGeneration_data() +{ + QTest::addColumn<QString>("qmlFile"); + QTest::addColumn<QList<FunctionEntry>>("entries"); + + QTest::addRow("clean") << "AotstatsClean.qml" + << QList<FunctionEntry>{ { "j", "", true }, { "s", "", true } }; + + const QString fError = "function without return type annotation returns int. This may prevent " + "proper compilation to Cpp."; + const QString sError = "method g cannot be resolved."; + QTest::addRow("mixed") << "AotstatsMixed.qml" + << QList<FunctionEntry>{ { "i", "", true }, + { "f", fError, false }, + { "s", sError, false } }; +} + +void tst_qmlcachegen::aotstatsGeneration() +{ +#if defined(QTEST_CROSS_COMPILED) + QSKIP("Cannot call qmlcachegen on cross-compiled target."); +#endif + QFETCH(QString, qmlFile); + QFETCH(QList<FunctionEntry>, entries); + + QTemporaryDir dir; + QProcess proc; + proc.setProgram(QLibraryInfo::path(QLibraryInfo::LibraryExecutablesPath) + "/qmlcachegen"_L1); + const QString cppOutput = dir.filePath(qmlFile + ".cpp"); + const QString aotstatsOutput = cppOutput + ".aotstats"; + proc.setArguments({ "--bare", + "--resource-path", "/cachegentest/data/aotstats/" + qmlFile, + "-i", testFile("aotstats/qmldir"), + "--resource", testFile("aotstats/cachegentest.qrc"), + "--dump-aot-stats", + "--module-id=Aotstats", + "-o", cppOutput, + testFile("aotstats/" + qmlFile) }); + proc.start(); + QVERIFY(proc.waitForFinished() && proc.exitStatus() == QProcess::NormalExit); + + QVERIFY(QFileInfo::exists(aotstatsOutput)); + QFile aotstatsFile(aotstatsOutput); + QVERIFY(aotstatsFile.open(QIODevice::Text | QIODevice::ReadOnly)); + const auto document = QJsonDocument::fromJson(aotstatsFile.readAll()); + const auto aotstats = QQmlJS::AotStats::fromJsonDocument(document); + QVERIFY(aotstats.entries().size() == 1); // One module + const auto &moduleEntries = aotstats.entries()["Aotstats"]; + QVERIFY(moduleEntries.size() == 1); // Only one qml file was compiled + const auto &fileEntries = moduleEntries[moduleEntries.keys().first()]; + + for (const auto &entry : entries) { + const auto it = std::find_if(fileEntries.cbegin(), fileEntries.cend(), + [&](const auto &e) { return e.functionName == entry.name; }); + QVERIFY(it != fileEntries.cend()); + QVERIFY(it->codegenSuccessful == entry.codegenSuccessful); + QVERIFY(it->errorMessage == entry.errorMessage); + } +} + const QQmlScriptString &ScriptStringProps::undef() const { return m_undef; diff --git a/tests/auto/qml/qmlcppcodegen/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/CMakeLists.txt index 42ad6d23d6..715ad6162a 100644 --- a/tests/auto/qml/qmlcppcodegen/CMakeLists.txt +++ b/tests/auto/qml/qmlcppcodegen/CMakeLists.txt @@ -19,6 +19,8 @@ qt_internal_add_test(tst_qmlcppcodegen codegen_test_moduleplugin codegen_test_hidden codegen_test_hiddenplugin + codegen_test_stringbuilder + codegen_test_stringbuilderplugin ) qt_internal_add_test(tst_qmlcppcodegen_interpreted @@ -31,6 +33,8 @@ qt_internal_add_test(tst_qmlcppcodegen_interpreted codegen_test_moduleplugin codegen_test_hidden codegen_test_hiddenplugin + codegen_test_stringbuilder + codegen_test_stringbuilderplugin DEFINES QT_TEST_FORCE_INTERPRETER ) diff --git a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt index 8c5449d192..7f2e6ad967 100644 --- a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt +++ b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt @@ -346,6 +346,33 @@ add_dependencies(codegen_test_hidden Qt::Quick) qt_autogen_tools_initial_setup(codegen_test_hiddenplugin) +qt_policy(SET QTP0004 NEW) + +qt_add_library(codegen_test_stringbuilder STATIC) +qt_autogen_tools_initial_setup(codegen_test_stringbuilder) + +set_target_properties(codegen_test_stringbuilder PROPERTIES + # We really want qmlcachegen here, even if qmlsc is available + QT_QMLCACHEGEN_EXECUTABLE qmlcachegen + QT_QMLCACHEGEN_ARGUMENTS --validate-basic-blocks +) + +target_compile_definitions(codegen_test_stringbuilder PRIVATE + -DGENERATED_CPP_FOLDER="${CMAKE_CURRENT_BINARY_DIR}/.rcc/qmlcache" + QT_USE_QSTRINGBUILDER +) + +qt6_add_qml_module(codegen_test_stringbuilder + URI StringBuilderTestTypes + SOURCES + writableVariantMap.h + QML_FILES + writeVariantMap.qml + OUTPUT_DIRECTORY stringbuilderTestTypes + __QT_INTERNAL_DISAMBIGUATE_QMLDIR_RESOURCE +) + +qt_autogen_tools_initial_setup(codegen_test_stringbuilderplugin) qt_add_library(codegen_test_module STATIC) qt_autogen_tools_initial_setup(codegen_test_module) @@ -356,7 +383,7 @@ set_target_properties(codegen_test_module PROPERTIES QT_QMLCACHEGEN_ARGUMENTS --validate-basic-blocks ) -qt_policy(SET QTP0004 NEW) + target_compile_definitions(codegen_test_module PUBLIC -DGENERATED_CPP_FOLDER="${CMAKE_CURRENT_BINARY_DIR}/.rcc/qmlcache" diff --git a/tests/auto/qml/qmlcppcodegen/data/writableVariantMap.h b/tests/auto/qml/qmlcppcodegen/data/writableVariantMap.h new file mode 100644 index 0000000000..3c0fedd28b --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/writableVariantMap.h @@ -0,0 +1,31 @@ +#pragma once +#include <QObject> +#include <QVariantMap> +#include <QtQml/qqmlregistration.h> + +class WritableVariantMap : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(QVariantMap data READ data WRITE setData NOTIFY dataChanged) + +public: + WritableVariantMap(QObject *parent = nullptr) : QObject(parent) { } + + QVariantMap data() const { return m_data; } + void setData(const QVariantMap &data) + { + if (m_data != data) { + m_data = data; + emit dataChanged(); + } + } + +signals: + void dataChanged(); + +private: + QVariantMap m_data; +}; + + diff --git a/tests/auto/qml/qmlcppcodegen/data/writeVariantMap.qml b/tests/auto/qml/qmlcppcodegen/data/writeVariantMap.qml new file mode 100644 index 0000000000..536e53b408 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/writeVariantMap.qml @@ -0,0 +1,10 @@ +pragma Strict +import StringBuilderTestTypes + +WritableVariantMap { + id: dragSource + property string modelData: "Drag Me" + data: ({ + "text/plain": "%" + dragSource.modelData + "%" + }) +} diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp index 9b66143f62..53cc068e8c 100644 --- a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp +++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp @@ -256,6 +256,7 @@ private slots: void voidConversion(); void voidFunction(); void writeBack(); + void writeVariantMap(); }; static QByteArray arg1() @@ -5107,6 +5108,22 @@ void tst_QmlCppCodegen::writeBack() QCOMPARE(person->property("ints"), QVariant::fromValue(QList<int>({12, 22, 2, 1, 0, 0, 33}))); } +void tst_QmlCppCodegen::writeVariantMap() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/StringBuilderTestTypes/writeVariantMap.qml"_s)); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + const QVariantMap v = object->property("data").toMap(); + QCOMPARE(v.size(), 1); + const QVariant textPlain = v[u"text/plain"_s]; + QCOMPARE(textPlain.metaType(), QMetaType::fromType<QString>()); + QCOMPARE(textPlain.toString(), u"%Drag Me%"_s); + +} + QTEST_MAIN(tst_QmlCppCodegen) #include "tst_qmlcppcodegen.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 da3ebc69a2..11d4c9d2c7 100644 --- a/tests/auto/qml/qmlformat/tst_qmlformat.cpp +++ b/tests/auto/qml/qmlformat/tst_qmlformat.cpp @@ -390,7 +390,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; //plainJS QTest::newRow("nestedLambdaWithIfElse") << "lambdaWithIfElseInsideLambda.js" diff --git a/tests/auto/qml/qmlimportscanner/data/CompositeSingleton.json b/tests/auto/qml/qmlimportscanner/data/CompositeSingleton.json index 028685f566..8860b629d1 100644 --- a/tests/auto/qml/qmlimportscanner/data/CompositeSingleton.json +++ b/tests/auto/qml/qmlimportscanner/data/CompositeSingleton.json @@ -10,29 +10,25 @@ "name": "QtQuick", "plugin": "qtquick2plugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQuick/", "relativePath": "QtQuick", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQuick/" + "type": "module" }, { - "classname": "QtQmlMetaPlugin", - "linkTarget": "Qt6::QmlMeta", + "classname": "QtQmlPlugin", + "linkTarget": "Qt6::qmlplugin", "name": "QtQml", - "plugin": "qmlmetaplugin", + "plugin": "qmlplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/", "relativePath": "QtQml", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/" + "type": "module" }, { - "classname": "QtQmlPlugin", - "linkTarget": "Qt6::qmlplugin", - "name": "QtQml.Base", - "plugin": "qmlplugin", - "pluginIsOptional": true, - "relativePath": "QtQml/Base", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Base/" + "name": "QML", + "prefer": ":/qt-project.org/imports/QML/", + "relativePath": "QML", + "type": "module" }, { "classname": "QtQmlModelsPlugin", @@ -40,9 +36,9 @@ "name": "QtQml.Models", "plugin": "modelsplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/Models/", "relativePath": "QtQml/Models", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Models/" + "type": "module" }, { "classname": "QtQmlWorkerScriptPlugin", @@ -50,12 +46,8 @@ "name": "QtQml.WorkerScript", "plugin": "workerscriptplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/", "relativePath": "QtQml/WorkerScript", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/" - }, - { - "name": "QML", "type": "module" } ] diff --git a/tests/auto/qml/qmlimportscanner/data/CompositeWithEnum.json b/tests/auto/qml/qmlimportscanner/data/CompositeWithEnum.json index 77faf99e6c..085d0bf6d4 100644 --- a/tests/auto/qml/qmlimportscanner/data/CompositeWithEnum.json +++ b/tests/auto/qml/qmlimportscanner/data/CompositeWithEnum.json @@ -5,24 +5,20 @@ "type": "module" }, { - "classname": "QtQmlMetaPlugin", - "linkTarget": "Qt6::QmlMeta", + "classname": "QtQmlPlugin", + "linkTarget": "Qt6::qmlplugin", "name": "QtQml", - "plugin": "qmlmetaplugin", + "plugin": "qmlplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/", "relativePath": "QtQml", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/" + "type": "module" }, { - "classname": "QtQmlPlugin", - "linkTarget": "Qt6::qmlplugin", - "name": "QtQml.Base", - "plugin": "qmlplugin", - "pluginIsOptional": true, - "relativePath": "QtQml/Base", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Base/" + "name": "QML", + "prefer": ":/qt-project.org/imports/QML/", + "relativePath": "QML", + "type": "module" }, { "classname": "QtQmlModelsPlugin", @@ -30,9 +26,9 @@ "name": "QtQml.Models", "plugin": "modelsplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/Models/", "relativePath": "QtQml/Models", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Models/" + "type": "module" }, { "classname": "QtQmlWorkerScriptPlugin", @@ -40,12 +36,8 @@ "name": "QtQml.WorkerScript", "plugin": "workerscriptplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/", "relativePath": "QtQml/WorkerScript", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/" - }, - { - "name": "QML", "type": "module" } ] diff --git a/tests/auto/qml/qmlimportscanner/data/CompositeWithinSingleton.json b/tests/auto/qml/qmlimportscanner/data/CompositeWithinSingleton.json index cf446c33bd..c0cad1e3a1 100644 --- a/tests/auto/qml/qmlimportscanner/data/CompositeWithinSingleton.json +++ b/tests/auto/qml/qmlimportscanner/data/CompositeWithinSingleton.json @@ -10,29 +10,25 @@ "name": "QtQuick", "plugin": "qtquick2plugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQuick/", "relativePath": "QtQuick", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQuick/" + "type": "module" }, { - "classname": "QtQmlMetaPlugin", - "linkTarget": "Qt6::QmlMeta", + "classname": "QtQmlPlugin", + "linkTarget": "Qt6::qmlplugin", "name": "QtQml", - "plugin": "qmlmetaplugin", + "plugin": "qmlplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/", "relativePath": "QtQml", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/" + "type": "module" }, { - "classname": "QtQmlPlugin", - "linkTarget": "Qt6::qmlplugin", - "name": "QtQml.Base", - "plugin": "qmlplugin", - "pluginIsOptional": true, - "relativePath": "QtQml/Base", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Base/" + "name": "QML", + "prefer": ":/qt-project.org/imports/QML/", + "relativePath": "QML", + "type": "module" }, { "classname": "QtQmlModelsPlugin", @@ -40,9 +36,9 @@ "name": "QtQml.Models", "plugin": "modelsplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/Models/", "relativePath": "QtQml/Models", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Models/" + "type": "module" }, { "classname": "QtQmlWorkerScriptPlugin", @@ -50,12 +46,8 @@ "name": "QtQml.WorkerScript", "plugin": "workerscriptplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/", "relativePath": "QtQml/WorkerScript", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/" - }, - { - "name": "QML", "type": "module" } ] diff --git a/tests/auto/qml/qmlimportscanner/data/Drawer.qml.json b/tests/auto/qml/qmlimportscanner/data/Drawer.qml.json index 0a885f058e..3abcd58b1c 100644 --- a/tests/auto/qml/qmlimportscanner/data/Drawer.qml.json +++ b/tests/auto/qml/qmlimportscanner/data/Drawer.qml.json @@ -1,23 +1,19 @@ [ { - "classname": "QtQmlMetaPlugin", - "linkTarget": "Qt6::QmlMeta", + "classname": "QtQmlPlugin", + "linkTarget": "Qt6::qmlplugin", "name": "QtQml", - "plugin": "qmlmetaplugin", + "plugin": "qmlplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/", "relativePath": "QtQml", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/" + "type": "module" }, { - "classname": "QtQmlPlugin", - "linkTarget": "Qt6::qmlplugin", - "name": "QtQml.Base", - "plugin": "qmlplugin", - "pluginIsOptional": true, - "relativePath": "QtQml/Base", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Base/" + "name": "QML", + "prefer": ":/qt-project.org/imports/QML/", + "relativePath": "QML", + "type": "module" }, { "classname": "QtQmlModelsPlugin", @@ -25,9 +21,9 @@ "name": "QtQml.Models", "plugin": "modelsplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/Models/", "relativePath": "QtQml/Models", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Models/" + "type": "module" }, { "classname": "QtQmlWorkerScriptPlugin", @@ -35,12 +31,8 @@ "name": "QtQml.WorkerScript", "plugin": "workerscriptplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/", "relativePath": "QtQml/WorkerScript", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/" - }, - { - "name": "QML", "type": "module" } ] diff --git a/tests/auto/qml/qmlimportscanner/data/Imports.json b/tests/auto/qml/qmlimportscanner/data/Imports.json index 20b9c524c4..ee6903b1da 100644 --- a/tests/auto/qml/qmlimportscanner/data/Imports.json +++ b/tests/auto/qml/qmlimportscanner/data/Imports.json @@ -10,29 +10,25 @@ "name": "QtQuick", "plugin": "qtquick2plugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQuick/", "relativePath": "QtQuick", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQuick/" + "type": "module" }, { - "classname": "QtQmlMetaPlugin", - "linkTarget": "Qt6::QmlMeta", + "classname": "QtQmlPlugin", + "linkTarget": "Qt6::qmlplugin", "name": "QtQml", - "plugin": "qmlmetaplugin", + "plugin": "qmlplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/", "relativePath": "QtQml", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/" + "type": "module" }, { - "classname": "QtQmlPlugin", - "linkTarget": "Qt6::qmlplugin", - "name": "QtQml.Base", - "plugin": "qmlplugin", - "pluginIsOptional": true, - "relativePath": "QtQml/Base", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Base/" + "name": "QML", + "prefer": ":/qt-project.org/imports/QML/", + "relativePath": "QML", + "type": "module" }, { "classname": "QtQmlModelsPlugin", @@ -40,9 +36,9 @@ "name": "QtQml.Models", "plugin": "modelsplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/Models/", "relativePath": "QtQml/Models", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Models/" + "type": "module" }, { "classname": "QtQmlWorkerScriptPlugin", @@ -50,12 +46,8 @@ "name": "QtQml.WorkerScript", "plugin": "workerscriptplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/", "relativePath": "QtQml/WorkerScript", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/" - }, - { - "name": "QML", "type": "module" } ] diff --git a/tests/auto/qml/qmlimportscanner/data/ListProperty.qml.json b/tests/auto/qml/qmlimportscanner/data/ListProperty.qml.json index f07b7e8494..0e733b551f 100644 --- a/tests/auto/qml/qmlimportscanner/data/ListProperty.qml.json +++ b/tests/auto/qml/qmlimportscanner/data/ListProperty.qml.json @@ -1,39 +1,29 @@ [ { "classname": "QtQuick2Plugin", - "name": "QtQuick", "linkTarget": "Qt6::qtquick2plugin", + "name": "QtQuick", "plugin": "qtquick2plugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQuick/", "relativePath": "QtQuick", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQuick/" - }, - { - "name": "Things", - "plugin": "doesNotExistPlugin", - "relativePath": "Things", "type": "module" }, { - "classname": "QtQmlMetaPlugin", - "linkTarget": "Qt6::QmlMeta", + "classname": "QtQmlPlugin", + "linkTarget": "Qt6::qmlplugin", "name": "QtQml", - "plugin": "qmlmetaplugin", + "plugin": "qmlplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/", "relativePath": "QtQml", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/" + "type": "module" }, { - "classname": "QtQmlPlugin", - "linkTarget": "Qt6::qmlplugin", - "name": "QtQml.Base", - "plugin": "qmlplugin", - "pluginIsOptional": true, - "relativePath": "QtQml/Base", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Base/" + "name": "QML", + "prefer": ":/qt-project.org/imports/QML/", + "relativePath": "QML", + "type": "module" }, { "classname": "QtQmlModelsPlugin", @@ -41,9 +31,9 @@ "name": "QtQml.Models", "plugin": "modelsplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/Models/", "relativePath": "QtQml/Models", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Models/" + "type": "module" }, { "classname": "QtQmlWorkerScriptPlugin", @@ -51,12 +41,14 @@ "name": "QtQml.WorkerScript", "plugin": "workerscriptplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/", "relativePath": "QtQml/WorkerScript", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/" + "type": "module" }, { - "name": "QML", + "name": "Things", + "plugin": "doesNotExistPlugin", + "relativePath": "Things", "type": "module" } ] diff --git a/tests/auto/qml/qmlimportscanner/data/QTBUG-45916.js.json b/tests/auto/qml/qmlimportscanner/data/QTBUG-45916.js.json index 3aa7bc8282..d0d427f595 100644 --- a/tests/auto/qml/qmlimportscanner/data/QTBUG-45916.js.json +++ b/tests/auto/qml/qmlimportscanner/data/QTBUG-45916.js.json @@ -5,29 +5,25 @@ "name": "QtQuick", "plugin": "qtquick2plugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQuick/", "relativePath": "QtQuick", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQuick/" + "type": "module" }, { - "classname": "QtQmlMetaPlugin", - "linkTarget": "Qt6::QmlMeta", + "classname": "QtQmlPlugin", + "linkTarget": "Qt6::qmlplugin", "name": "QtQml", - "plugin": "qmlmetaplugin", + "plugin": "qmlplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/", "relativePath": "QtQml", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/" + "type": "module" }, { - "classname": "QtQmlPlugin", - "linkTarget": "Qt6::qmlplugin", - "name": "QtQml.Base", - "plugin": "qmlplugin", - "pluginIsOptional": true, - "relativePath": "QtQml/Base", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Base/" + "name": "QML", + "prefer": ":/qt-project.org/imports/QML/", + "relativePath": "QML", + "type": "module" }, { "classname": "QtQmlModelsPlugin", @@ -35,9 +31,9 @@ "name": "QtQml.Models", "plugin": "modelsplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/Models/", "relativePath": "QtQml/Models", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Models/" + "type": "module" }, { "classname": "QtQmlWorkerScriptPlugin", @@ -45,12 +41,8 @@ "name": "QtQml.WorkerScript", "plugin": "workerscriptplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/", "relativePath": "QtQml/WorkerScript", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/" - }, - { - "name": "QML", "type": "module" } ] diff --git a/tests/auto/qml/qmlimportscanner/data/Simple.qml.json b/tests/auto/qml/qmlimportscanner/data/Simple.qml.json index 3aa7bc8282..d0d427f595 100644 --- a/tests/auto/qml/qmlimportscanner/data/Simple.qml.json +++ b/tests/auto/qml/qmlimportscanner/data/Simple.qml.json @@ -5,29 +5,25 @@ "name": "QtQuick", "plugin": "qtquick2plugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQuick/", "relativePath": "QtQuick", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQuick/" + "type": "module" }, { - "classname": "QtQmlMetaPlugin", - "linkTarget": "Qt6::QmlMeta", + "classname": "QtQmlPlugin", + "linkTarget": "Qt6::qmlplugin", "name": "QtQml", - "plugin": "qmlmetaplugin", + "plugin": "qmlplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/", "relativePath": "QtQml", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/" + "type": "module" }, { - "classname": "QtQmlPlugin", - "linkTarget": "Qt6::qmlplugin", - "name": "QtQml.Base", - "plugin": "qmlplugin", - "pluginIsOptional": true, - "relativePath": "QtQml/Base", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Base/" + "name": "QML", + "prefer": ":/qt-project.org/imports/QML/", + "relativePath": "QML", + "type": "module" }, { "classname": "QtQmlModelsPlugin", @@ -35,9 +31,9 @@ "name": "QtQml.Models", "plugin": "modelsplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/Models/", "relativePath": "QtQml/Models", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Models/" + "type": "module" }, { "classname": "QtQmlWorkerScriptPlugin", @@ -45,12 +41,8 @@ "name": "QtQml.WorkerScript", "plugin": "workerscriptplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/", "relativePath": "QtQml/WorkerScript", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/" - }, - { - "name": "QML", "type": "module" } ] diff --git a/tests/auto/qml/qmlimportscanner/data/Singleton.json b/tests/auto/qml/qmlimportscanner/data/Singleton.json index 90f0ff19ad..1813d01efe 100644 --- a/tests/auto/qml/qmlimportscanner/data/Singleton.json +++ b/tests/auto/qml/qmlimportscanner/data/Singleton.json @@ -10,29 +10,25 @@ "name": "QtQuick", "plugin": "qtquick2plugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQuick/", "relativePath": "QtQuick", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQuick/" + "type": "module" }, { - "classname": "QtQmlMetaPlugin", - "linkTarget": "Qt6::QmlMeta", + "classname": "QtQmlPlugin", + "linkTarget": "Qt6::qmlplugin", "name": "QtQml", - "plugin": "qmlmetaplugin", + "plugin": "qmlplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/", "relativePath": "QtQml", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/" + "type": "module" }, { - "classname": "QtQmlPlugin", - "linkTarget": "Qt6::qmlplugin", - "name": "QtQml.Base", - "plugin": "qmlplugin", - "pluginIsOptional": true, - "relativePath": "QtQml/Base", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Base/" + "name": "QML", + "prefer": ":/qt-project.org/imports/QML/", + "relativePath": "QML", + "type": "module" }, { "classname": "QtQmlModelsPlugin", @@ -40,9 +36,9 @@ "name": "QtQml.Models", "plugin": "modelsplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/Models/", "relativePath": "QtQml/Models", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Models/" + "type": "module" }, { "classname": "QtQmlWorkerScriptPlugin", @@ -50,12 +46,8 @@ "name": "QtQml.WorkerScript", "plugin": "workerscriptplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/", "relativePath": "QtQml/WorkerScript", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/" - }, - { - "name": "QML", "type": "module" } ] diff --git a/tests/auto/qml/qmlimportscanner/data/Things.json b/tests/auto/qml/qmlimportscanner/data/Things.json index 7782dd7c5f..a6081c89d8 100644 --- a/tests/auto/qml/qmlimportscanner/data/Things.json +++ b/tests/auto/qml/qmlimportscanner/data/Things.json @@ -11,29 +11,25 @@ "name": "QtQuick", "plugin": "qtquick2plugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQuick/", "relativePath": "QtQuick", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQuick/" + "type": "module" }, { - "classname": "QtQmlMetaPlugin", - "linkTarget": "Qt6::QmlMeta", + "classname": "QtQmlPlugin", + "linkTarget": "Qt6::qmlplugin", "name": "QtQml", - "plugin": "qmlmetaplugin", + "plugin": "qmlplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/", "relativePath": "QtQml", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/" + "type": "module" }, { - "classname": "QtQmlPlugin", - "linkTarget": "Qt6::qmlplugin", - "name": "QtQml.Base", - "plugin": "qmlplugin", - "pluginIsOptional": true, - "relativePath": "QtQml/Base", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Base/" + "name": "QML", + "prefer": ":/qt-project.org/imports/QML/", + "relativePath": "QML", + "type": "module" }, { "classname": "QtQmlModelsPlugin", @@ -41,9 +37,9 @@ "name": "QtQml.Models", "plugin": "modelsplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/Models/", "relativePath": "QtQml/Models", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Models/" + "type": "module" }, { "classname": "QtQmlWorkerScriptPlugin", @@ -51,12 +47,8 @@ "name": "QtQml.WorkerScript", "plugin": "workerscriptplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/", "relativePath": "QtQml/WorkerScript", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/" - }, - { - "name": "QML", "type": "module" } ] diff --git a/tests/auto/qml/qmlimportscanner/data/javascriptMethods.qml.json b/tests/auto/qml/qmlimportscanner/data/javascriptMethods.qml.json index 8fe2da0078..7094d1c8e3 100644 --- a/tests/auto/qml/qmlimportscanner/data/javascriptMethods.qml.json +++ b/tests/auto/qml/qmlimportscanner/data/javascriptMethods.qml.json @@ -1,27 +1,19 @@ [ { - "classname": "QtQmlMetaPlugin", - "linkTarget": "Qt6::QmlMeta", - "name": "QtQml", - "plugin": "qmlmetaplugin", - "pluginIsOptional": true, - "relativePath": "QtQml", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/" - }, - { "classname": "QtQmlPlugin", "linkTarget": "Qt6::qmlplugin", - "name": "QtQml.Base", + "name": "QtQml", "plugin": "qmlplugin", "pluginIsOptional": true, - "relativePath": "QtQml/Base", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Base/" + "prefer": ":/qt-project.org/imports/QtQml/", + "relativePath": "QtQml", + "type": "module" }, { - "name": "Methods.js", - "type": "javascript" + "name": "QML", + "prefer": ":/qt-project.org/imports/QML/", + "relativePath": "QML", + "type": "module" }, { "classname": "QtQmlModelsPlugin", @@ -29,9 +21,9 @@ "name": "QtQml.Models", "plugin": "modelsplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/Models/", "relativePath": "QtQml/Models", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Models/" + "type": "module" }, { "classname": "QtQmlWorkerScriptPlugin", @@ -39,12 +31,12 @@ "name": "QtQml.WorkerScript", "plugin": "workerscriptplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/", "relativePath": "QtQml/WorkerScript", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/" + "type": "module" }, { - "name": "QML", - "type": "module" + "name": "Methods.js", + "type": "javascript" } ] diff --git a/tests/auto/qml/qmlimportscanner/data/localImport.qml.json b/tests/auto/qml/qmlimportscanner/data/localImport.qml.json index 7782dd7c5f..a6081c89d8 100644 --- a/tests/auto/qml/qmlimportscanner/data/localImport.qml.json +++ b/tests/auto/qml/qmlimportscanner/data/localImport.qml.json @@ -11,29 +11,25 @@ "name": "QtQuick", "plugin": "qtquick2plugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQuick/", "relativePath": "QtQuick", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQuick/" + "type": "module" }, { - "classname": "QtQmlMetaPlugin", - "linkTarget": "Qt6::QmlMeta", + "classname": "QtQmlPlugin", + "linkTarget": "Qt6::qmlplugin", "name": "QtQml", - "plugin": "qmlmetaplugin", + "plugin": "qmlplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/", "relativePath": "QtQml", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/" + "type": "module" }, { - "classname": "QtQmlPlugin", - "linkTarget": "Qt6::qmlplugin", - "name": "QtQml.Base", - "plugin": "qmlplugin", - "pluginIsOptional": true, - "relativePath": "QtQml/Base", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Base/" + "name": "QML", + "prefer": ":/qt-project.org/imports/QML/", + "relativePath": "QML", + "type": "module" }, { "classname": "QtQmlModelsPlugin", @@ -41,9 +37,9 @@ "name": "QtQml.Models", "plugin": "modelsplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/Models/", "relativePath": "QtQml/Models", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Models/" + "type": "module" }, { "classname": "QtQmlWorkerScriptPlugin", @@ -51,12 +47,8 @@ "name": "QtQml.WorkerScript", "plugin": "workerscriptplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/", "relativePath": "QtQml/WorkerScript", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/" - }, - { - "name": "QML", "type": "module" } ] diff --git a/tests/auto/qml/qmlimportscanner/data/parentEnum.qml.json b/tests/auto/qml/qmlimportscanner/data/parentEnum.qml.json index 3aa7bc8282..d0d427f595 100644 --- a/tests/auto/qml/qmlimportscanner/data/parentEnum.qml.json +++ b/tests/auto/qml/qmlimportscanner/data/parentEnum.qml.json @@ -5,29 +5,25 @@ "name": "QtQuick", "plugin": "qtquick2plugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQuick/", "relativePath": "QtQuick", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQuick/" + "type": "module" }, { - "classname": "QtQmlMetaPlugin", - "linkTarget": "Qt6::QmlMeta", + "classname": "QtQmlPlugin", + "linkTarget": "Qt6::qmlplugin", "name": "QtQml", - "plugin": "qmlmetaplugin", + "plugin": "qmlplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/", "relativePath": "QtQml", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/" + "type": "module" }, { - "classname": "QtQmlPlugin", - "linkTarget": "Qt6::qmlplugin", - "name": "QtQml.Base", - "plugin": "qmlplugin", - "pluginIsOptional": true, - "relativePath": "QtQml/Base", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Base/" + "name": "QML", + "prefer": ":/qt-project.org/imports/QML/", + "relativePath": "QML", + "type": "module" }, { "classname": "QtQmlModelsPlugin", @@ -35,9 +31,9 @@ "name": "QtQml.Models", "plugin": "modelsplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/Models/", "relativePath": "QtQml/Models", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Models/" + "type": "module" }, { "classname": "QtQmlWorkerScriptPlugin", @@ -45,12 +41,8 @@ "name": "QtQml.WorkerScript", "plugin": "workerscriptplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/", "relativePath": "QtQml/WorkerScript", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/" - }, - { - "name": "QML", "type": "module" } ] diff --git a/tests/auto/qml/qmlimportscanner/data/qmldirImportAndDepend.qml.json b/tests/auto/qml/qmlimportscanner/data/qmldirImportAndDepend.qml.json index 7782dd7c5f..a6081c89d8 100644 --- a/tests/auto/qml/qmlimportscanner/data/qmldirImportAndDepend.qml.json +++ b/tests/auto/qml/qmlimportscanner/data/qmldirImportAndDepend.qml.json @@ -11,29 +11,25 @@ "name": "QtQuick", "plugin": "qtquick2plugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQuick/", "relativePath": "QtQuick", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQuick/" + "type": "module" }, { - "classname": "QtQmlMetaPlugin", - "linkTarget": "Qt6::QmlMeta", + "classname": "QtQmlPlugin", + "linkTarget": "Qt6::qmlplugin", "name": "QtQml", - "plugin": "qmlmetaplugin", + "plugin": "qmlplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/", "relativePath": "QtQml", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/" + "type": "module" }, { - "classname": "QtQmlPlugin", - "linkTarget": "Qt6::qmlplugin", - "name": "QtQml.Base", - "plugin": "qmlplugin", - "pluginIsOptional": true, - "relativePath": "QtQml/Base", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Base/" + "name": "QML", + "prefer": ":/qt-project.org/imports/QML/", + "relativePath": "QML", + "type": "module" }, { "classname": "QtQmlModelsPlugin", @@ -41,9 +37,9 @@ "name": "QtQml.Models", "plugin": "modelsplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/Models/", "relativePath": "QtQml/Models", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Models/" + "type": "module" }, { "classname": "QtQmlWorkerScriptPlugin", @@ -51,12 +47,8 @@ "name": "QtQml.WorkerScript", "plugin": "workerscriptplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/", "relativePath": "QtQml/WorkerScript", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/" - }, - { - "name": "QML", "type": "module" } ] diff --git a/tests/auto/qml/qmlimportscanner/data/qtQmlOnly.qml.json b/tests/auto/qml/qmlimportscanner/data/qtQmlOnly.qml.json index 0a885f058e..3abcd58b1c 100644 --- a/tests/auto/qml/qmlimportscanner/data/qtQmlOnly.qml.json +++ b/tests/auto/qml/qmlimportscanner/data/qtQmlOnly.qml.json @@ -1,23 +1,19 @@ [ { - "classname": "QtQmlMetaPlugin", - "linkTarget": "Qt6::QmlMeta", + "classname": "QtQmlPlugin", + "linkTarget": "Qt6::qmlplugin", "name": "QtQml", - "plugin": "qmlmetaplugin", + "plugin": "qmlplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/", "relativePath": "QtQml", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/" + "type": "module" }, { - "classname": "QtQmlPlugin", - "linkTarget": "Qt6::qmlplugin", - "name": "QtQml.Base", - "plugin": "qmlplugin", - "pluginIsOptional": true, - "relativePath": "QtQml/Base", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Base/" + "name": "QML", + "prefer": ":/qt-project.org/imports/QML/", + "relativePath": "QML", + "type": "module" }, { "classname": "QtQmlModelsPlugin", @@ -25,9 +21,9 @@ "name": "QtQml.Models", "plugin": "modelsplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/Models/", "relativePath": "QtQml/Models", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Models/" + "type": "module" }, { "classname": "QtQmlWorkerScriptPlugin", @@ -35,12 +31,8 @@ "name": "QtQml.WorkerScript", "plugin": "workerscriptplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/", "relativePath": "QtQml/WorkerScript", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/" - }, - { - "name": "QML", "type": "module" } ] diff --git a/tests/auto/qml/qmlimportscanner/data/rootPath.json b/tests/auto/qml/qmlimportscanner/data/rootPath.json index b468a8acb1..41ce0f9a45 100644 --- a/tests/auto/qml/qmlimportscanner/data/rootPath.json +++ b/tests/auto/qml/qmlimportscanner/data/rootPath.json @@ -5,29 +5,25 @@ "name": "QtQuick", "plugin": "qtquick2plugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQuick/", "relativePath": "QtQuick", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQuick/" + "type": "module" }, { - "classname": "QtQmlMetaPlugin", - "linkTarget": "Qt6::QmlMeta", + "classname": "QtQmlPlugin", + "linkTarget": "Qt6::qmlplugin", "name": "QtQml", - "plugin": "qmlmetaplugin", + "plugin": "qmlplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/", "relativePath": "QtQml", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/" + "type": "module" }, { - "classname": "QtQmlPlugin", - "linkTarget": "Qt6::qmlplugin", - "name": "QtQml.Base", - "plugin": "qmlplugin", - "pluginIsOptional": true, - "relativePath": "QtQml/Base", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Base/" + "name": "QML", + "prefer": ":/qt-project.org/imports/QML/", + "relativePath": "QML", + "type": "module" }, { "classname": "QtQmlModelsPlugin", @@ -35,9 +31,9 @@ "name": "QtQml.Models", "plugin": "modelsplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/Models/", "relativePath": "QtQml/Models", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/Models/" + "type": "module" }, { "classname": "QtQmlWorkerScriptPlugin", @@ -45,12 +41,8 @@ "name": "QtQml.WorkerScript", "plugin": "workerscriptplugin", "pluginIsOptional": true, + "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/", "relativePath": "QtQml/WorkerScript", - "type": "module", - "prefer": ":/qt-project.org/imports/QtQml/WorkerScript/" - }, - { - "name": "QML", "type": "module" }, { @@ -72,12 +64,12 @@ "type": "module" }, { - "name": "Imports", - "relativePath": "Imports", + "name": "Module", "type": "module" }, { - "name": "Module", + "name": "Imports", + "relativePath": "Imports", "type": "module" } ] diff --git a/tests/auto/qml/qmllint/data/Qtbug111015/qmldir b/tests/auto/qml/qmllint/data/Qtbug111015/qmldir new file mode 100644 index 0000000000..3bf1d48e13 --- /dev/null +++ b/tests/auto/qml/qmllint/data/Qtbug111015/qmldir @@ -0,0 +1,3 @@ +module Qtbug111015 +typeinfo qtbug111015.qmltypes +import QtQml diff --git a/tests/auto/qml/qmllint/data/Qtbug111015/qtbug111015.qmltypes b/tests/auto/qml/qmllint/data/Qtbug111015/qtbug111015.qmltypes new file mode 100644 index 0000000000..7de521a379 --- /dev/null +++ b/tests/auto/qml/qmllint/data/Qtbug111015/qtbug111015.qmltypes @@ -0,0 +1,20 @@ +import QtQuick.tooling 1.2 + +Module { + Component { + file: "typewithjsonobjectlist.h" + name: "TypeWithJsonObjectList" + exports: ["QmlLintTestLib/TypeWithJsonObjectList 1.0"] + accessSemantics: "reference" + prototype: "QObject" + Property { name: "jsonObjectList"; type: "QJsonObject"; isList: true; read: "getJsonObjectList"; index: 0; isReadonly: true } + } + Component { + file: "typewithjsonarray.h" + name: "TypeWithJsonArray" + exports: ["QmlLintTestLib/TypeWithJsonArray 1.0"] + accessSemantics: "reference" + prototype: "QObject" + Property { name: "jsonArray"; type: "QJsonArray"; read: "getJsonArray"; index: 0; isReadonly: true } + } +} diff --git a/tests/auto/qml/qmllint/data/jsonArrayIsRecognized.qml b/tests/auto/qml/qmllint/data/jsonArrayIsRecognized.qml new file mode 100644 index 0000000000..89c52e0e52 --- /dev/null +++ b/tests/auto/qml/qmllint/data/jsonArrayIsRecognized.qml @@ -0,0 +1,8 @@ +import QtQuick +import Qtbug111015 1.0 + +Item { + TypeWithJsonArray { + jsonArray: [] + } +} diff --git a/tests/auto/qml/qmllint/data/jsonObjectIsRecognized.qml b/tests/auto/qml/qmllint/data/jsonObjectIsRecognized.qml new file mode 100644 index 0000000000..0a96fa9f92 --- /dev/null +++ b/tests/auto/qml/qmllint/data/jsonObjectIsRecognized.qml @@ -0,0 +1,8 @@ +import QtQuick +import Qtbug111015 1.0 + +Item { + TypeWithJsonObjectList { + jsonObjectList: [] + } +} 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/lintplugin.cpp b/tests/auto/qml/qmllint/lintplugin.cpp index 65795c103c..58b174cb6b 100644 --- a/tests/auto/qml/qmllint/lintplugin.cpp +++ b/tests/auto/qml/qmllint/lintplugin.cpp @@ -23,7 +23,7 @@ public: void run(const QQmlSA::Element &element) override { auto property = element.property(u"radius"_s); - if (!property.isValid() || element.property(u"radius"_s).typeName() != u"qreal") { + if (!property.isValid() || element.property(u"radius"_s).typeName() != u"double") { emitWarning(u"Failed to verify radius property", plugin, element.sourceLocation()); return; } diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp index 57cb7228d8..02b0ea69d2 100644 --- a/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -105,6 +105,7 @@ private Q_SLOTS: void valueTypesFromString(); void ignoreSettingsNotCommandLineOptions(); + void backslashedQmldirPath(); void environment_data(); void environment(); @@ -113,6 +114,7 @@ private Q_SLOTS: void testPlugin(); void quickPlugin(); #endif + private: enum DefaultImportOption { NoDefaultImports, UseDefaultImports }; enum ContainOption { StringNotContained, StringContained }; @@ -1026,7 +1028,7 @@ expression: \${expr} \${expr} \\\${expr} \\\${expr}`)", { Message { QStringLiteral("Ready") } } } }; QTest::newRow("nullBinding") << QStringLiteral("nullBinding.qml") << Result{ { Message{ QStringLiteral( - "Cannot assign literal of type null to qreal") } } }; + "Cannot assign literal of type null to double") } } }; QTest::newRow("missingRequiredAlias") << QStringLiteral("missingRequiredAlias.qml") << Result { { Message { @@ -1345,6 +1347,8 @@ void TestQmllint::cleanQmlCode_data() QTest::newRow("locale") << QStringLiteral("locale.qml"); QTest::newRow("constInvokable") << QStringLiteral("useConstInvokable.qml"); QTest::newRow("dontCheckJSTypes") << QStringLiteral("dontCheckJSTypes.qml"); + QTest::newRow("jsonObjectIsRecognized") << QStringLiteral("jsonObjectIsRecognized.qml"); + QTest::newRow("jsonArrayIsRecognized") << QStringLiteral("jsonArrayIsRecognized.qml"); } void TestQmllint::cleanQmlCode() @@ -1799,7 +1803,7 @@ void TestQmllint::settingsFile() .arg(testFile("settings/unusedImportWarning/unused.qml")))); QVERIFY(runQmllint("settings/bare/bare.qml", false, {}, false, false) .contains(QStringLiteral("Failed to find the following builtins: " - "builtins.qmltypes, jsroot.qmltypes"))); + "jsroot.qmltypes, builtins.qmltypes"))); QVERIFY(runQmllint("settings/qmltypes/qmltypes.qml", false, QStringList(), false) .contains(QStringLiteral("not a qmldir file. Assuming qmltypes."))); QVERIFY(runQmllint("settings/qmlimports/qmlimports.qml", true, QStringList(), false).isEmpty()); @@ -1907,7 +1911,7 @@ void TestQmllint::missingBuiltinsNoCrash() checkResult(warnings, Result { { Message { QStringLiteral("Failed to find the following builtins: " - "builtins.qmltypes, jsroot.qmltypes") } } }); + "jsroot.qmltypes, builtins.qmltypes") } } }); } void TestQmllint::absolutePath() @@ -2120,7 +2124,7 @@ void TestQmllint::quickPlugin() Message { u"SplitView attached property only works with Items"_s }, Message { u"ScrollIndicator must be attached to a Flickable"_s }, Message { u"ScrollBar must be attached to a Flickable or ScrollView"_s }, - Message { u"Accessible must be attached to an Item"_s }, + Message { u"Accessible must be attached to an Item or an Action"_s }, Message { u"EnterKey attached property only works with Items"_s }, Message { u"LayoutDirection attached property only works with Items and Windows"_s }, @@ -2252,5 +2256,14 @@ 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()); +} + QTEST_GUILESS_MAIN(TestQmllint) #include "tst_qmllint.moc" diff --git a/tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp b/tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp index 31c27c3cd7..cdbd9695fd 100644 --- a/tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp +++ b/tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp @@ -153,7 +153,7 @@ void tst_qmltc_qprocess::noBuiltins() QVERIFY(file.rename(original)); }; - for (QString builtin : { u"builtins.qmltypes"_s, u"jsroot.qmltypes"_s }) { + for (QString builtin : { u"jsroot.qmltypes"_s, u"builtins.qmltypes"_s }) { const auto path = QLibraryInfo::path(QLibraryInfo::QmlImportsPath) + u"/"_s + builtin; QScopeGuard scope(std::bind(renameBack, path)); diff --git a/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp b/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp index 822caea0d0..282ad3eba0 100644 --- a/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp +++ b/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp @@ -682,7 +682,6 @@ void tst_qmltyperegistrar::constructibleValueType() name: "Constructible" accessSemantics: "value" exports: ["QmlTypeRegistrarTest/constructible 1.0"] - isCreatable: true exportMetaObjectRevisions: [256] Method { name: "Constructible" @@ -701,7 +700,6 @@ void tst_qmltyperegistrar::structuredValueType() name: "Structured" accessSemantics: "value" exports: ["QmlTypeRegistrarTest/structured 1.0"] - isCreatable: true isStructured: true exportMetaObjectRevisions: [256] Property { name: "i"; type: "int"; index: 0; isFinal: true } @@ -735,51 +733,50 @@ void tst_qmltyperegistrar::typedEnum() accessSemantics: "reference" prototype: "QObject" exports: ["QmlTypeRegistrarTest/TypedEnum 1.0"] - isCreatable: true exportMetaObjectRevisions: [256] Enum { name: "UChar" - type: "uchar" + type: "quint8" values: ["V0"] } Enum { name: "Int8_T" - type: "int8_t" + type: "qint8" values: ["V1"] } Enum { name: "UInt8_T" - type: "uint8_t" + type: "quint8" values: ["V2"] } Enum { name: "Int16_T" - type: "int16_t" + type: "short" values: ["V3"] } Enum { name: "UInt16_T" - type: "uint16_t" + type: "ushort" values: ["V4"] } Enum { name: "Int32_T" - type: "int32_t" + type: "int" values: ["V5"] } Enum { name: "UInt32_T" - type: "uint32_t" + type: "uint" values: ["V6"] } Enum { name: "S" - type: "qint16" + type: "short" values: ["A", "B", "C"] } Enum { name: "T" - type: "quint16" + type: "ushort" values: ["D", "E", "F"] } Enum { @@ -805,7 +802,7 @@ void tst_qmltyperegistrar::listSignal() prototype: "QObject" Signal { name: "objectListHappened" - Parameter { type: "QList<QObject*>" } + Parameter { type: "QObjectList" } } })")); } @@ -833,7 +830,6 @@ void tst_qmltyperegistrar::withNamespace() accessSemantics: "reference" prototype: "Testing::Foo" exports: ["QmlTypeRegistrarTest/Bar 1.0"] - isCreatable: true exportMetaObjectRevisions: [256] Property { name: "barProp"; type: "int"; read: "bar"; index: 0; isReadonly: true; isConstant: true } })")); @@ -853,7 +849,6 @@ void tst_qmltyperegistrar::withNamespace() prototype: "Testing::Bar" extension: "Bar" exports: ["QmlTypeRegistrarTest/Baz 1.0"] - isCreatable: true exportMetaObjectRevisions: [256] attachedType: "Testing::Foo" })")); @@ -937,7 +932,6 @@ void tst_qmltyperegistrar::nameExplosion() "QmlTypeRegistrarTest/Name2 1.0", "QmlTypeRegistrarTest/NameExplosion 1.0" ] - isCreatable: true exportMetaObjectRevisions: [256] })")); @@ -962,7 +956,6 @@ void tst_qmltyperegistrar::javaScriptExtension() extension: "SymbolPrototype" extensionIsJavaScript: true exports: ["QmlTypeRegistrarTest/JavaScriptExtension 1.0"] - isCreatable: true exportMetaObjectRevisions: [256] })")); } @@ -978,7 +971,6 @@ void tst_qmltyperegistrar::relatedAddedInVersion() "QmlTypeRegistrarTest/AddedIn1_0 1.0", "QmlTypeRegistrarTest/AddedIn1_0 1.5" ] - isCreatable: true exportMetaObjectRevisions: [256, 261] })")); } @@ -991,12 +983,11 @@ void tst_qmltyperegistrar::longNumberTypes() accessSemantics: "reference" prototype: "QObject" exports: ["QmlTypeRegistrarTest/LongNumberTypes 1.0"] - isCreatable: true exportMetaObjectRevisions: [256] - Property { name: "a"; type: "qint64"; index: 0 } - Property { name: "b"; type: "int64_t"; index: 1 } - Property { name: "c"; type: "quint64"; index: 2 } - Property { name: "d"; type: "uint64_t"; index: 3 } + Property { name: "a"; type: "qlonglong"; index: 0 } + Property { name: "b"; type: "qlonglong"; index: 1 } + Property { name: "c"; type: "qulonglong"; index: 2 } + Property { name: "d"; type: "qulonglong"; index: 3 } })")); } @@ -1017,10 +1008,67 @@ void tst_qmltyperegistrar::constReturnType() accessSemantics: "reference" prototype: "QObject" exports: ["QmlTypeRegistrarTest/ConstInvokable 1.0"] - isCreatable: true exportMetaObjectRevisions: [256] Method { name: "getObject"; type: "QObject"; isPointer: true; isConstant: true } })")); } +void tst_qmltyperegistrar::usingDeclaration() +{ + QVERIFY(qmltypesData.contains(R"(Component { + file: "tst_qmltyperegistrar.h" + name: "WithMyInt" + accessSemantics: "reference" + prototype: "QObject" + exports: ["QmlTypeRegistrarTest/WithMyInt 1.0"] + exportMetaObjectRevisions: [256] + Property { name: "a"; type: "int"; read: "a"; index: 0; isReadonly: true; isConstant: true } + })")); +} + +void tst_qmltyperegistrar::enumsRegistered() +{ + QCOMPARE(QMetaType::fromName("SizeEnums::Unit"), QMetaType::fromType<SizeEnums::Unit>()); + QCOMPARE(QMetaType::fromName("Local::Flag"), QMetaType::fromType<Local::Flag>()); + QCOMPARE(QMetaType::fromName("Local::Flags"), QMetaType::fromType<Local::Flags>()); + QCOMPARE(QMetaType::fromName("ValueTypeWithEnum1::Quality"), + QMetaType::fromType<ValueTypeWithEnum1::Quality>()); + QCOMPARE(QMetaType::fromName("ValueTypeWithEnum2::Quality"), + QMetaType::fromType<ValueTypeWithEnum2::Quality>()); + QCOMPARE(QMetaType::fromName("BaseNamespace::BBB"), QMetaType::fromType<BaseNamespace::BBB>()); + QCOMPARE(QMetaType::fromName("ExtensionValueType::EEE"), + QMetaType::fromType<ExtensionValueType::EEE>()); + QCOMPARE(QMetaType::fromName("TypedEnum::UChar"), QMetaType::fromType<TypedEnum::UChar>()); + QCOMPARE(QMetaType::fromName("TypedEnum::Int8_T"), QMetaType::fromType<TypedEnum::Int8_T>()); + QCOMPARE(QMetaType::fromName("TypedEnum::UInt8_T"), QMetaType::fromType<TypedEnum::UInt8_T>()); + QCOMPARE(QMetaType::fromName("TypedEnum::Int16_T"), QMetaType::fromType<TypedEnum::Int16_T>()); + QCOMPARE(QMetaType::fromName("TypedEnum::UInt16_T"), QMetaType::fromType<TypedEnum::UInt16_T>()); + QCOMPARE(QMetaType::fromName("TypedEnum::Int32_T"), QMetaType::fromType<TypedEnum::Int32_T>()); + QCOMPARE(QMetaType::fromName("TypedEnum::UInt32_T"), QMetaType::fromType<TypedEnum::UInt32_T>()); + QCOMPARE(QMetaType::fromName("TypedEnum::S"), QMetaType::fromType<TypedEnum::S>()); + QCOMPARE(QMetaType::fromName("TypedEnum::T"), QMetaType::fromType<TypedEnum::T>()); + QCOMPARE(QMetaType::fromName("TypedEnum::U"), QMetaType::fromType<TypedEnum::U>()); + QCOMPARE(QMetaType::fromName("TypedEnum::V"), QMetaType::fromType<TypedEnum::V>()); + QCOMPARE(QMetaType::fromName("NetworkManager::NM"), QMetaType::fromType<NetworkManager::NM>()); + QCOMPARE(QMetaType::fromName("NotNamespace::Abc"), QMetaType::fromType<NotNamespace::Abc>()); +} + +void tst_qmltyperegistrar::doNotDuplicateQtNamespace() +{ + QVERIFY(!qmltypesData.contains(R"(file: "qnamespace.h")")); +} + +void tst_qmltyperegistrar::slotsBeforeInvokables() +{ + QVERIFY(qmltypesData.contains(R"(Component { + file: "tst_qmltyperegistrar.h" + name: "SlotsBeforeInvokables" + accessSemantics: "reference" + prototype: "QObject" + Method { name: "bar" } + Method { name: "foo" } + Method { name: "baz" } + })")); +} + QTEST_MAIN(tst_qmltyperegistrar) diff --git a/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h b/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h index 371fb840d1..1eff2af024 100644 --- a/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h +++ b/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h @@ -17,6 +17,7 @@ #include <QtQml/qqmlcomponent.h> #include <QtCore/qabstractitemmodel.h> +#include <QtCore/qnamespace.h> #include <QtCore/qproperty.h> #include <QtCore/qrect.h> #include <QtCore/qtemporaryfile.h> @@ -792,6 +793,45 @@ public: Q_INVOKABLE const QObject *getObject() { return nullptr; } }; +using myint = int; + +struct IntAlias +{ + Q_GADGET + QML_FOREIGN(myint) + QML_USING(int); +}; + +class WithMyInt : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(myint a READ a CONSTANT) +public: + myint a() const { return 10; } +}; + +class UsesQtNamespace : public QObject +{ + Q_OBJECT + QML_ANONYMOUS + Q_PROPERTY(Qt::Key key READ key CONSTANT) +public: + Qt::Key key() const { return Qt::Key_Escape; } +}; + +class SlotsBeforeInvokables : public QObject +{ + Q_OBJECT + QML_ANONYMOUS +public: + Q_INVOKABLE void foo() {} +public Q_SLOTS: + void bar() {} +public: + Q_INVOKABLE void baz() {} +}; + class tst_qmltyperegistrar : public QObject { Q_OBJECT @@ -865,6 +905,11 @@ private slots: void enumList(); void constReturnType(); + void usingDeclaration(); + void enumsRegistered(); + void doNotDuplicateQtNamespace(); + void slotsBeforeInvokables(); + private: QByteArray qmltypesData; }; diff --git a/tests/auto/qml/qqmlbinding/CMakeLists.txt b/tests/auto/qml/qqmlbinding/CMakeLists.txt index 0419240921..0249bb0f25 100644 --- a/tests/auto/qml/qqmlbinding/CMakeLists.txt +++ b/tests/auto/qml/qqmlbinding/CMakeLists.txt @@ -28,6 +28,7 @@ qt_internal_add_test(tst_qqmlbinding Qt::Gui Qt::GuiPrivate Qt::QmlPrivate + Qt::QmlMetaPrivate Qt::QuickPrivate Qt::QuickTestUtilsPrivate TESTDATA ${test_data} @@ -42,6 +43,7 @@ qt_internal_add_test(tst_qqmlbinding_no_deferred_properties Qt::Gui Qt::GuiPrivate Qt::QmlPrivate + Qt::QmlMetaPrivate Qt::QuickPrivate Qt::QuickTestUtilsPrivate TESTDATA ${test_data} diff --git a/tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp b/tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp index 494d765798..b13379a103 100644 --- a/tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp +++ b/tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp @@ -1,13 +1,17 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only -#include <qtest.h> + +#include "WithBindableProperties.h" + +#include <private/qmlutils_p.h> +#include <private/qqmlbind_p.h> +#include <private/qqmlcomponentattached_p.h> +#include <private/qquickrectangle_p.h> + +#include <QtTest/qtest.h> + #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcomponent.h> -#include <QtQml/private/qqmlbind_p.h> -#include <QtQml/private/qqmlcomponentattached_p.h> -#include <QtQuick/private/qquickrectangle_p.h> -#include <QtQuickTestUtils/private/qmlutils_p.h> -#include "WithBindableProperties.h" class tst_qqmlbinding : public QQmlDataTest { diff --git a/tests/auto/qml/qqmlconnections/CMakeLists.txt b/tests/auto/qml/qqmlconnections/CMakeLists.txt index dd1814fed1..0e6947370d 100644 --- a/tests/auto/qml/qqmlconnections/CMakeLists.txt +++ b/tests/auto/qml/qqmlconnections/CMakeLists.txt @@ -27,6 +27,7 @@ qt_internal_add_test(tst_qqmlconnections Qt::Gui Qt::GuiPrivate Qt::QmlPrivate + Qt::QmlMetaPrivate Qt::QuickPrivate Qt::QuickTestUtilsPrivate TESTDATA ${test_data} diff --git a/tests/auto/qml/qqmlimport/CMakeLists.txt b/tests/auto/qml/qqmlimport/CMakeLists.txt index 803234787b..b8b720f5dc 100644 --- a/tests/auto/qml/qqmlimport/CMakeLists.txt +++ b/tests/auto/qml/qqmlimport/CMakeLists.txt @@ -35,6 +35,7 @@ qt_internal_add_test(tst_qqmlimport SOURCES tst_qqmlimport.cpp LIBRARIES + Qt::CorePrivate Qt::Gui Qt::Qml Qt::QmlPrivate @@ -58,6 +59,13 @@ qt_internal_add_resource(tst_qqmlimport "preferred2" "qmldir" ) +qt_internal_add_resource(tst_qqmlimport "qtconf" + PREFIX + "/" + FILES + "qmlimports.qt.conf" +) + ## Scopes: ##################################################################### diff --git a/tests/auto/qml/qqmlimport/qmlimports.qt.conf b/tests/auto/qml/qqmlimport/qmlimports.qt.conf new file mode 100644 index 0000000000..3a63cc797f --- /dev/null +++ b/tests/auto/qml/qqmlimport/qmlimports.qt.conf @@ -0,0 +1,3 @@ +[Paths] +Prefix = "" +QmlImports = ":/a/path", ":/another/path", ":/even/more/path" diff --git a/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp b/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp index ff1513d0d6..fe14281387 100644 --- a/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp +++ b/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp @@ -17,6 +17,7 @@ #include <QtCore/qscopeguard.h> #include <QtCore/qlibraryinfo.h> +#include <QtCore/private/qlibraryinfo_p.h> class TheThing : public QObject { @@ -68,6 +69,7 @@ private slots: void qualifiedScriptImport(); void invalidImportUrl(); void registerTypesFromImplicitImport(); + void containsAllQtConfEntries(); private: QQmlModuleRegistration noimportRegistration; @@ -202,6 +204,22 @@ void tst_QQmlImport::registerTypesFromImplicitImport() QCOMPARE(t->m_width, 640); } +void tst_QQmlImport::containsAllQtConfEntries() +{ + QString qtConfPath(u":/qmlimports.qt.conf"); + QLibraryInfoPrivate::setQtconfManualPath(&qtConfPath); + QLibraryInfoPrivate::reload(); + auto cleanup = qScopeGuard([](){ + QLibraryInfoPrivate::setQtconfManualPath(nullptr); + QLibraryInfoPrivate::reload(); + }); + QQmlEngine engine; + auto importPaths = engine.importPathList(); + QVERIFY(importPaths.contains(u"qrc:/a/path")); + QVERIFY(importPaths.contains(u"qrc:/another/path")); + QVERIFY(importPaths.contains(u"qrc:/even/more/path")); +} + void tst_QQmlImport::testDesignerSupported() { std::unique_ptr<QQuickView> window = std::make_unique<QQuickView>(); diff --git a/tests/auto/qml/qqmllanguage/data/asValueType.qml b/tests/auto/qml/qqmllanguage/data/asValueType.qml index 6a5500e344..b51dc9c6ec 100644 --- a/tests/auto/qml/qqmllanguage/data/asValueType.qml +++ b/tests/auto/qml/qqmllanguage/data/asValueType.qml @@ -10,4 +10,11 @@ QtObject { property var e: ({x: 10, y: 20}) as point property var f: "red" as withString property var g: "green" as string + property rect bb + property var p: bb as size; + property var q: this as size; + property var r: ({}) as size; + property var s: 11 as size; + property var t: Component as size; + property var u: Qt as size; } diff --git a/tests/auto/qml/qqmllanguage/data/asValueTypeGood.qml b/tests/auto/qml/qqmllanguage/data/asValueTypeGood.qml new file mode 100644 index 0000000000..777ada3848 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/asValueTypeGood.qml @@ -0,0 +1,35 @@ +pragma ValueTypeBehavior: Assertable +import QtQml as Q +import StaticTest as S + +Q.QtObject { + property var a + property rect b: a as Q.rect + property bool c: a instanceof Q.rect + property bool d: ({x: 10, y: 20}) instanceof Q.point + property var e: ({x: 10, y: 20}) as Q.point + property var f: "red" as S.withString + property var g: "green" as Q.string + + property var h: new S.withString("red") + property var i: { + let p = new Q.point; + p.x = 10 + p.y = 20 + return p + } + + property var j: 4.0 as Q.int + property var k: (4.5 / 1.5) as Q.int + property var l: 5 as Q.double + property var m: "something" as Q.var + property var n: 1 as Q.bool + property var o: Infinity as Q.int + + property var p: b as Q.size; + property var q: this as Q.size; + property var r: ({}) as Q.size; + property var s: 11 as Q.size; + property var t: Q.Component as Q.size; + property var u: Q.Qt as Q.size; +} diff --git a/tests/auto/qml/qqmllanguage/data/invokableCtors.qml b/tests/auto/qml/qqmllanguage/data/invokableCtors.qml new file mode 100644 index 0000000000..35a8d7bf08 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/invokableCtors.qml @@ -0,0 +1,12 @@ +import QtQml as QQ +import Test as VV + +QQ.QtObject { + property QQ.QtObject oo: new QQ.QtObject() + property QQ.QtObject pp: new QQ.QtObject(oo) + property VV.vv v: new VV.vv("green") + + property VV.InvokableSingleton i: new VV.InvokableSingleton(5, oo) + property VV.InvokableExtended k: new VV.InvokableExtended() + property VV.InvokableUncreatable l: new VV.InvokableUncreatable() +} diff --git a/tests/auto/qml/qqmllanguage/data/jsonArrayProperty.qml b/tests/auto/qml/qqmllanguage/data/jsonArrayProperty.qml new file mode 100644 index 0000000000..5bd563a288 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/jsonArrayProperty.qml @@ -0,0 +1,191 @@ +import QtQml +import TypeWithQJsonArrayProperty + +TypeWithQJsonArrayProperty { + function jsArray() { return [1, 2, 3] } + + jsonArray: jsArray() + + property list<int> concatenatedJsonArray: jsonArray.concat([4, 5, 6]) + property list<int> concatenatedJsArray: jsArray().concat([4, 5, 6]) + + property bool entriesMatch: { + var iterator = jsonArray.entries(); + for (var [index, element] of jsArray().entries()) { + var v = iterator.next().value; + if (index !== v[0] || element !== v[1]) { + console.log(index, v[0], element, v[1]); + return false; + } + } + + var iterator = jsArray().entries(); + for (var [index, element] of jsonArray.entries()) { + var v = iterator.next().value; + if (index !== v[0] || element !== v[1]) { + console.log(index, v[0], element, v[1]); + return false; + } + } + + return true; + } + + property bool jsonArrayEvery: jsonArray.every(element => element != 0) + property bool jsArrayEvery: jsArray().every(element => element != 0) + + property list<int> jsonArrayFiltered: jsonArray.filter(element => element > 2) + property list<int> jsArrayFiltered: jsArray().filter(element => element > 2) + + property int jsonArrayFind: jsonArray.find(element => element === 2) + property int jsArrayFind: jsArray().find(element => element === 2) + + property int jsonArrayFindIndex: jsonArray.findIndex(element => element === 1) + property int jsArrayFindIndex: jsArray().findIndex(element => element === 1) + + property string jsonArrayForEach + property string jsArrayForEach + + property bool jsonArrayIncludes: jsonArray.includes(3) + property bool jsArrayIncludes: jsArray().includes(3) + + property int jsonArrayIndexOf: jsonArray.indexOf(2) + property int jsArrayIndexOf: jsArray().indexOf(2) + + property string jsonArrayJoin: jsonArray.join() + property string jsArrayJoin: jsArray().join() + + property bool keysMatch: { + var iterator = jsonArray.keys(); + for (var index of jsArray().keys()) { + var v = iterator.next().value; + if (index !== v) { + console.log(index, v); + return false; + } + } + + var iterator = jsArray().keys(); + for (var index of jsonArray.keys()) { + var v = iterator.next().value; + if (index !== v) { + console.log(index, v); + return false; + } + } + + return true; + } + + property int jsonArrayLastIndexOf: jsonArray.lastIndexOf(1) + property int jsArrayLastIndexOf: jsArray().lastIndexOf(1) + + property list<string> jsonArrayMap: jsonArray.map(element => element.toString()) + property list<string> jsArrayMap: jsArray().map(element => element.toString()) + + property int jsonArrayReduce: jsonArray.reduce((acc, element) => acc - element, 40) + property int jsArrayReduce: jsArray().reduce((acc, element) => acc - element, 40) + + property string jsonArrayReduceRight: jsonArray.reduceRight((acc, element) => acc + element.toString(), "") + property string jsArrayReduceRight: jsArray().reduceRight((acc, element) => acc + element.toString(), "") + + property list<int> jsonArraySlice: jsonArray.slice(0, 1) + property list<int> jsArraySlice: jsArray().slice(0, 1) + + property bool jsonArraySome: jsonArray.some(element => element === 1) + property bool jsArraySome: jsArray().some(element => element === 1) + + property string stringifiedLocaleJsonArray: jsonArray.toLocaleString() + property string stringifiedLocaleJsArray: jsArray().toLocaleString() + + property string stringifiedJsonArray: jsonArray.toString() + property string stringifiedJsArray: jsArray().toString() + + property bool valuesMatch: { + var iterator = jsonArray.values(); + for (var obj of jsArray().values()) { + var v = iterator.next().value; + if (obj !== v) { + console.log(obj, v); + return false; + } + } + + var iterator = jsArray().values(); + for (var obj of jsonArray.values()) { + var v = iterator.next().value; + if (obj !== v) { + console.log(obj, v); + return false; + } + } + + return true; + } + + // In-place mutation methods. + // Set by onCompleted if mutating jsonArray and then accessing it + // respects the mutation and the mutation behaves as for an array. + property bool jsonArrayWasCopiedWithin: false + property bool jsonArrayWasFilled: false + property bool jsonArrayWasPopped: false + property bool jsonArrayWasPushed: false + property bool jsonArrayWasReversed: false + property bool jsonArrayWasShifted: false + property bool jsonArrayWasSpliced: false + property bool jsonArrayWasUnshifted: false + property bool jsonArrayWasSorted: false + + Component.onCompleted: { + function equals(lhs, rhs) { + return lhs.toString() === rhs.toString() + } + + jsonArray.forEach(element => jsonArrayForEach += "-" + element + "-"); + jsArray().forEach(element => jsArrayForEach += "-" + element + "-"); + + var array = jsArray() + + jsonArray.copyWithin(1, 0, 1) + array.copyWithin(1, 0, 1) + jsonArrayWasCopiedWithin = equals(jsonArray, array) + + jsonArray.fill(7, 0, 1) + array.fill(7, 0, 1) + jsonArrayWasFilled = equals(jsonArray, array) + + jsonArray.pop() + array.pop() + jsonArrayWasPopped = equals(jsonArray, array) + + jsonArray.push(23) + jsonArray.push(11) + jsonArray.push(54) + jsonArray.push(42) + array.push(23) + array.push(11) + array.push(54) + array.push(42) + jsonArrayWasPushed = equals(jsonArray, array) + + jsonArray.reverse() + array.reverse() + jsonArrayWasReversed = equals(jsonArray, array) + + jsonArray.shift() + array.shift() + jsonArrayWasShifted = equals(jsonArray, array) + + jsonArray.splice(2, 1, [1, 2], 7, [1, 5]) + array.splice(2, 1, [1, 2], 7, [1, 5]) + jsonArrayWasSpliced = equals(jsonArray, array) + + jsonArray.unshift(4, 71) + array.unshift(4, 71) + jsonArrayWasUnshifted = equals(jsonArray, array) + + jsonArray.sort() + array.sort() + jsonArrayWasSorted = equals(jsonArray, array) + } +} 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/data/optimizedSequenceShift.qml b/tests/auto/qml/qqmllanguage/data/optimizedSequenceShift.qml new file mode 100644 index 0000000000..32765895a0 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/optimizedSequenceShift.qml @@ -0,0 +1,14 @@ +import QtQml + +QtObject { + id: root + + property int changes: 0 + + property list<int> numbers: [1, 2, 3, 4, 5] + onNumbersChanged: ++changes + + property var one: numbers.shift.bind([1,2,3])() + + Component.onCompleted: root.numbers.shift() +} diff --git a/tests/auto/qml/qqmllanguage/testtypes.cpp b/tests/auto/qml/qqmllanguage/testtypes.cpp index ffff0a6979..526cca4b5b 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.cpp +++ b/tests/auto/qml/qqmllanguage/testtypes.cpp @@ -174,6 +174,14 @@ void registerTypes() qmlRegisterTypesAndRevisions<NonSingleton>("EnumScopeTest", 1); qmlRegisterTypesAndRevisions<EnumProviderSingletonQml>("EnumScopeTest", 1); + qmlRegisterTypesAndRevisions<TypeWithQJsonArrayProperty>("TypeWithQJsonArrayProperty", 1); + qmlRegisterTypesAndRevisions< + InvokableSingleton, + InvokableExtended, + InvokableUncreatable, + InvokableValueType + >("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 bcf02c1cf9..ce6abf3504 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.h +++ b/tests/auto/qml/qqmllanguage/testtypes.h @@ -6,6 +6,7 @@ #include <QtCore/qobject.h> #include <QtCore/qrect.h> #include <QtCore/qdatetime.h> +#include <QtCore/qjsonarray.h> #include <QtGui/qtransform.h> #include <QtGui/qcolor.h> #include <QtGui/qvector2d.h> @@ -2942,4 +2943,104 @@ public: } }; +class TypeWithQJsonArrayProperty : public QObject { + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(QJsonArray jsonArray READ jsonArray WRITE setJsonArray NOTIFY jsonArrayChanged) + +public: + TypeWithQJsonArrayProperty(QObject *parent = nullptr) : QObject(parent) {} + + const QJsonArray& jsonArray() { return m_jsonArray; } + void setJsonArray(const QJsonArray& a) { m_jsonArray = a; } + +signals: + void jsonArrayChanged(); + +private: + QJsonArray m_jsonArray; +}; + +class InvokableSingleton : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_SINGLETON +public: + InvokableSingleton() = default; + Q_INVOKABLE InvokableSingleton(int a, QObject *parent) : QObject(parent), m_a(a) {} + + int m_a = 0; +}; + +class InvokableExtension : public QObject +{ + Q_OBJECT +public: + Q_INVOKABLE InvokableExtension(QObject *parent = nullptr) : QObject(parent) {} +}; + +class InvokableExtended : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_EXTENDED(InvokableExtension) + +public: + Q_INVOKABLE InvokableExtended() = default; +}; + +class InvokableUncreatable : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("no") + +public: + Q_INVOKABLE InvokableUncreatable() = default; +}; + +class InvokableValueType +{ + Q_GADGET + QML_VALUE_TYPE(vv) +public: + Q_INVOKABLE InvokableValueType() = default; + Q_INVOKABLE InvokableValueType(const QString &s) : m_s(s) {} + QString m_s; +}; + +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 2f382e8d8e..01caa18cce 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -38,7 +38,7 @@ #include <deque> -#if defined(Q_OS_MAC) +#if defined(Q_OS_DARWIN) #include <unistd.h> #endif @@ -48,7 +48,7 @@ DEFINE_BOOL_CONFIG_OPTION(qmlCheckTypes, QML_CHECK_TYPES) static inline bool isCaseSensitiveFileSystem(const QString &path) { Q_UNUSED(path); -#if defined(Q_OS_MAC) +#if defined(Q_OS_DARWIN) return pathconf(path.toLatin1().constData(), _PC_CASE_SENSITIVE); #elif defined(Q_OS_WIN) return false; @@ -410,7 +410,9 @@ private slots: void objectAndGadgetMethodCallsRejectThisObject(); void objectAndGadgetMethodCallsAcceptThisObject(); + void asValueType(); + void asValueTypeGood(); void longConversion(); @@ -453,6 +455,12 @@ private slots: void enumScopes(); void typedObjectList(); + void invokableCtors(); + + void jsonArrayPropertyBehavesLikeAnArray(); + + void nestedVectors(); + void optimizedSequenceShift(); private: QQmlEngine engine; @@ -5411,24 +5419,30 @@ void tst_qqmllanguage::namespacedPropertyTypes() void tst_qqmllanguage::qmlTypeCanBeResolvedByName_data() { QTest::addColumn<QUrl>("componentUrl"); + QTest::addColumn<QString>("name"); // Built-in C++ types - QTest::newRow("C++ - Anonymous") << testFileUrl("quickTypeByName_anon.qml"); - QTest::newRow("C++ - Named") << testFileUrl("quickTypeByName_named.qml"); + QTest::newRow("C++ - Anonymous") << testFileUrl("quickTypeByName_anon.qml") + << QStringLiteral("QtQuick/Item"); + QTest::newRow("C++ - Named") << testFileUrl("quickTypeByName_named.qml") + << QStringLiteral("QtQuick/Item"); // Composite types with a qmldir - QTest::newRow("QML - Anonymous - qmldir") << testFileUrl("compositeTypeByName_anon_qmldir.qml"); - QTest::newRow("QML - Named - qmldir") << testFileUrl("compositeTypeByName_named_qmldir.qml"); + QTest::newRow("QML - Anonymous - qmldir") << testFileUrl("compositeTypeByName_anon_qmldir.qml") + << QStringLiteral("SimpleType"); + QTest::newRow("QML - Named - qmldir") << testFileUrl("compositeTypeByName_named_qmldir.qml") + << QStringLiteral("SimpleType"); } void tst_qqmllanguage::qmlTypeCanBeResolvedByName() { QFETCH(QUrl, componentUrl); + QFETCH(QString, name); QQmlEngine engine; QQmlComponent component(&engine, componentUrl); VERIFY_ERRORS(0); - QTest::ignoreMessage(QtMsgType::QtWarningMsg, "[object Object]"); // a bit crude, but it will do + QTest::ignoreMessage(QtMsgType::QtWarningMsg, qPrintable(name)); QScopedPointer<QObject> o(component.create()); QVERIFY(!o.isNull()); @@ -8059,7 +8073,60 @@ void tst_qqmllanguage::asValueType() QTest::ignoreMessage( QtWarningMsg, + "Could not find any constructor for value type QQmlRectFValueType " + "to call with value undefined"); + + QTest::ignoreMessage( + QtWarningMsg, qPrintable(url.toString() + ":7:5: Unable to assign [undefined] to QRectF"_L1)); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":10: Coercing a value to QML/point using a type " + "assertion. This behavior is deprecated. Add 'pragma " + "ValueTypeBehavior: Assertable' to prevent it."_L1)); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":14: Coercing between incompatible value types mistakenly " + "yields null rather than undefined. Add 'pragma " + "ValueTypeBehavior: Assertable' to prevent this."_L1)); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":15: Coercing from instances of object types to value " + "types mistakenly yields null rather than undefined. Add " + "'pragma ValueTypeBehavior: Assertable' to prevent " + "this."_L1)); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":16: Coercing a value to QML/size using a type " + "assertion. This behavior is deprecated. Add 'pragma " + "ValueTypeBehavior: Assertable' to prevent it."_L1)); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":11: Coercing a value to StaticTest/withString using a " + "type assertion. This behavior is deprecated. Add 'pragma " + "ValueTypeBehavior: Assertable' to prevent it."_L1)); + QTest::ignoreMessage( + QtWarningMsg, + "Could not find any constructor for value type QQmlSizeFValueType to call " + "with value 11"); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":18: Coercing a value to QML/size using a type " + "assertion. This behavior is deprecated. Add 'pragma " + "ValueTypeBehavior: Assertable' to prevent it."_L1)); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":19: Coercing a value to QML/size using a type " + "assertion. This behavior is deprecated. Add 'pragma " + "ValueTypeBehavior: Assertable' to prevent it."_L1)); + QScopedPointer<QObject> o(c.create()); QCOMPARE(o->property("a"), QVariant()); @@ -8082,6 +8149,84 @@ void tst_qqmllanguage::asValueType() const QVariant string = o->property("g"); QCOMPARE(string.metaType(), QMetaType::fromType<QString>()); QCOMPARE(string.toString(), u"green"); + + const QVariant p = o->property("p"); + QCOMPARE(p.metaType(), QMetaType::fromType<std::nullptr_t>()); + + const QVariant q = o->property("q"); + QCOMPARE(q.metaType(), QMetaType::fromType<std::nullptr_t>()); + + const QVariant r = o->property("r"); + QCOMPARE(r.metaType(), QMetaType::fromType<QSizeF>()); + QCOMPARE(r.value<QSizeF>(), QSizeF()); + + const QVariant s = o->property("s"); + QCOMPARE(s.metaType(), QMetaType()); + + const QVariant t = o->property("t"); + QCOMPARE(t.metaType(), QMetaType::fromType<QSizeF>()); + QCOMPARE(t.value<QSizeF>(), QSizeF()); + + const QVariant u = o->property("u"); + QCOMPARE(u.metaType(), QMetaType::fromType<QSizeF>()); + QCOMPARE(u.value<QSizeF>(), QSizeF()); +} + +void tst_qqmllanguage::asValueTypeGood() +{ + QQmlEngine engine; + const QUrl url = testFileUrl("asValueTypeGood.qml"); + QQmlComponent c(&engine, url); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":7:5: Unable to assign [undefined] to QRectF"_L1)); + QScopedPointer<QObject> o(c.create()); + + QCOMPARE(o->property("a"), QVariant()); + QCOMPARE(o->property("b").value<QRectF>(), QRectF()); + QVERIFY(!o->property("c").toBool()); + + const QRectF rect(1, 2, 3, 4); + o->setProperty("a", QVariant(rect)); + QCOMPARE(o->property("b").value<QRectF>(), rect); + QVERIFY(o->property("c").toBool()); + + QVERIFY(!o->property("d").toBool()); + QVERIFY(!o->property("e").isValid()); + QVERIFY(!o->property("f").isValid()); + + const QVariant string = o->property("g"); + QCOMPARE(string.metaType(), QMetaType::fromType<QString>()); + QCOMPARE(string.toString(), u"green"); + + const ValueTypeWithString withString = o->property("h").value<ValueTypeWithString>(); + QCOMPARE(withString.toString(), u"red"); + + const QPointF point = o->property("i").value<QPointF>(); + QCOMPARE(point.x(), 10.0); + QCOMPARE(point.y(), 20.0); + + const QVariant j = o->property("j"); + QCOMPARE(j.metaType(), QMetaType::fromType<int>()); + QCOMPARE(j.toInt(), 4); + + QVERIFY(!o->property("k").isValid()); + QVERIFY(!o->property("l").isValid()); + + const QVariant m = o->property("m"); + QCOMPARE(m.metaType(), QMetaType::fromType<QString>()); + QCOMPARE(m.toString(), u"something"); + + QVERIFY(!o->property("n").isValid()); + QVERIFY(!o->property("o").isValid()); + QVERIFY(!o->property("p").isValid()); + QVERIFY(!o->property("q").isValid()); + QVERIFY(!o->property("r").isValid()); + QVERIFY(!o->property("s").isValid()); + QVERIFY(!o->property("t").isValid()); + QVERIFY(!o->property("u").isValid()); } void tst_qqmllanguage::typedEnums_data() @@ -8659,6 +8804,128 @@ void tst_qqmllanguage::typedObjectList() QVERIFY(list.at(&list, 0) != nullptr); } +void tst_qqmllanguage::jsonArrayPropertyBehavesLikeAnArray() { + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("jsonArrayProperty.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("concatenatedJsonArray"), o->property("concatenatedJsArray")); + QVERIFY(o->property("entriesMatch").toBool()); + QCOMPARE(o->property("jsonArrayEvery"), o->property("jsArrayEvery")); + QCOMPARE(o->property("jsonArrayFiltered"), o->property("jsArrayFiltered")); + QCOMPARE(o->property("jsonArrayFind"), o->property("jsArrayFind")); + QCOMPARE(o->property("jsonArrayFindIndex"), o->property("jsArrayFindIndex")); + QCOMPARE(o->property("jsonArrayForEach"), o->property("jsArrayForEach")); + QCOMPARE(o->property("jsonArrayIncludes"), o->property("jsArrayIncludes")); + QCOMPARE(o->property("jsonArrayIndexOf"), o->property("jsArrayIndexOf")); + QCOMPARE(o->property("jsonArrayJoin"), o->property("jsArrayJoin")); + QVERIFY(o->property("keysMatch").toBool()); + QCOMPARE(o->property("jsonArrayLastIndexOf"), o->property("jsArrayLastIndexOf")); + QCOMPARE(o->property("jsonArrayMap"), o->property("jsArrayMap")); + QCOMPARE(o->property("jsonArrayReduce"), o->property("jsArrayReduce")); + QCOMPARE(o->property("jsonArrayReduceRight"), o->property("jsArrayReduceRight")); + QCOMPARE(o->property("jsonArraySlice"), o->property("jsArraySlice")); + QCOMPARE(o->property("jsonArraySome"), o->property("jsArraySome")); + QCOMPARE(o->property("stringifiedLocaleJsonArray"), o->property("stringifiedLocaleJsArray")); + QCOMPARE(o->property("stringifiedJsonArray"), o->property("stringifiedJsArray")); + QVERIFY(o->property("valuesMatch").toBool()); + + QVERIFY(o->property("jsonArrayWasCopiedWithin").toBool()); + QVERIFY(o->property("jsonArrayWasFilled").toBool()); + QVERIFY(o->property("jsonArrayWasPopped").toBool()); + QVERIFY(o->property("jsonArrayWasPushed").toBool()); + QVERIFY(o->property("jsonArrayWasReversed").toBool()); + QVERIFY(o->property("jsonArrayWasShifted").toBool()); + QVERIFY(o->property("jsonArrayWasSpliced").toBool()); + QVERIFY(o->property("jsonArrayWasUnshifted").toBool()); + QVERIFY(o->property("jsonArrayWasSorted").toBool()); +} + +void tst_qqmllanguage::invokableCtors() +{ + QQmlEngine e; + + const QUrl url = testFileUrl("invokableCtors.qml"); + + QQmlComponent c(&e, url); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + const QString urlString = url.toString(); + QTest::ignoreMessage(QtWarningMsg, qPrintable( + urlString + ":9: You are calling a Q_INVOKABLE constructor of " + "InvokableSingleton which is a singleton in QML.")); + + // Extended types look like types without any constructors. + // Therefore they aren't even FunctionObjects. + QTest::ignoreMessage(QtWarningMsg, qPrintable( + urlString + ":10: TypeError: Type error")); + + QTest::ignoreMessage(QtWarningMsg, qPrintable( + urlString + ":11: You are calling a Q_INVOKABLE constructor of " + "InvokableUncreatable which is uncreatable in QML.")); + + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QObject *oo = qvariant_cast<QObject *>(o->property("oo")); + QVERIFY(oo); + QObject *pp = qvariant_cast<QObject *>(o->property("pp")); + QVERIFY(pp); + QCOMPARE(pp->parent(), oo); + + InvokableValueType vv = qvariant_cast<InvokableValueType>(o->property("v")); + QCOMPARE(vv.m_s, "green"); + + InvokableSingleton *i = qvariant_cast<InvokableSingleton *>(o->property("i")); + QVERIFY(i); + QCOMPARE(i->m_a, 5); + QCOMPARE(i->parent(), oo); + + QVariant k = o->property("k"); + QCOMPARE(k.metaType(), QMetaType::fromType<InvokableExtended *>()); + QCOMPARE(k.value<InvokableExtended *>(), nullptr); + + InvokableUncreatable *l = qvariant_cast<InvokableUncreatable *>(o->property("l")); + QVERIFY(l); +} + +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(list1.metaType(), QMetaType::fromType<std::vector<std::vector<int>>>()); + QCOMPARE(list1.value<std::vector<std::vector<int>>>(), expected1); + + const std::vector<std::vector<int>> expected2 { { 2, 3, 4 }, { 5, 6 } }; + QCOMPARE(n->getList(), expected2); +} + +void tst_qqmllanguage::optimizedSequenceShift() +{ + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("optimizedSequenceShift.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("changes").toInt(), 2); + + const QVariant one = o->property("one"); + QCOMPARE(one.metaType(), QMetaType::fromType<int>()); + QCOMPARE(one.toInt(), 1); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" diff --git a/tests/auto/qml/qqmlmoduleplugin/tst_qqmlmoduleplugin.cpp b/tests/auto/qml/qqmlmoduleplugin/tst_qqmlmoduleplugin.cpp index e57eb1b65a..67e1591b8b 100644 --- a/tests/auto/qml/qqmlmoduleplugin/tst_qqmlmoduleplugin.cpp +++ b/tests/auto/qml/qqmlmoduleplugin/tst_qqmlmoduleplugin.cpp @@ -17,7 +17,7 @@ #include <QtQuickShapes/private/qquickshapesglobal_p.h> -#if defined(Q_OS_MAC) +#if defined(Q_OS_DARWIN) // For _PC_CASE_SENSITIVE #include <unistd.h> #endif @@ -242,9 +242,9 @@ void tst_qqmlmoduleplugin::incorrectPluginCase() QString expectedError = QLatin1String("module \"org.qtproject.WrongCase\" plugin \"PluGin\" not found"); -#if defined(Q_OS_MAC) || defined(Q_OS_WIN32) +#if defined(Q_OS_DARWIN) || defined(Q_OS_WIN32) bool caseSensitive = true; -#if defined(Q_OS_MAC) +#if defined(Q_OS_DARWIN) int res = pathconf(QDir::currentPath().toLatin1().constData(), _PC_CASE_SENSITIVE); if (res == -1) QSKIP("Could not establish case sensitivity of file system"); diff --git a/tests/auto/qml/qqmlparser/tst_qqmlparser.cpp b/tests/auto/qml/qqmlparser/tst_qqmlparser.cpp index eb8c6c260f..0a8411ddcf 100644 --- a/tests/auto/qml/qqmlparser/tst_qqmlparser.cpp +++ b/tests/auto/qml/qqmlparser/tst_qqmlparser.cpp @@ -34,6 +34,10 @@ private slots: void codeLocationsWithContinuationStringLiteral_data(); void noSubstitutionTemplateLiteral(); void templateLiteral(); + void numericSeparator_data(); + void numericSeparator(); + void invalidNumericSeparator_data(); + void invalidNumericSeparator(); void leadingSemicolonInClass(); void templatedReadonlyProperty(); void qmlImportInJS(); @@ -496,6 +500,74 @@ void tst_qqmlparser::templateLiteral() QVERIFY(e); } +void tst_qqmlparser::numericSeparator_data() { + QTest::addColumn<QString>("code"); + QTest::addColumn<double>("expected_value"); + + QTest::newRow("Separator in decimal literal") << "1_000_000_000" << 1000000000.0; + QTest::newRow("Separator in fractional part") << "1000.22_33" << 1000.2233; + QTest::newRow("Separator in exponent part") << "1e1_0_0" << std::pow(10, 100); + QTest::newRow("Separator in positive exponent part") << "1e+1_0_0" << 1e100; + QTest::newRow("Separator in negative exponent part") << "1e-1_0_0" << 1e-100; + QTest::newRow("Separator in binary literal with b prefix") << "0b1010_0001_1000_0101" << static_cast<double>(0b1010000110000101); + QTest::newRow("Separator in binary literal with B prefix") << "0B01_10_01_10" << static_cast<double>(0b01100110); + QTest::newRow("Separator in octal literal with o prefix") << "0o1234_5670" << static_cast<double>(012345670); + QTest::newRow("Separator in octal literal with O prefix") << "0O7777_0000" << static_cast<double>(077770000); + QTest::newRow("Separator in hex literal with x prefix") << "0xA0_B0_C0" << static_cast<double>(0xA0B0C0); + QTest::newRow("Separator in hex literal with X prefix") << "0X1000_AAAA" << static_cast<double>(0x1000AAAA); +} + +void tst_qqmlparser::numericSeparator() { + using namespace QQmlJS; + + QFETCH(QString, code); + QFETCH(double, expected_value); + + QQmlJS::Engine engine; + + QQmlJS::Lexer lexer(&engine); + lexer.setCode(code, 1); + + QQmlJS::Parser parser(&engine); + QVERIFY(parser.parseExpression()); + + AST::ExpressionNode *expression = parser.expression(); + QVERIFY(expression); + + auto *literal = QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(expression); + QVERIFY(literal); + + QCOMPARE(literal->value, expected_value); + QCOMPARE(literal->firstSourceLocation().begin(), 0u); + QCOMPARE(literal->lastSourceLocation().end(), quint32(code.size())); +} + +void tst_qqmlparser::invalidNumericSeparator_data() { + QTest::addColumn<QString>("code"); + QTest::addColumn<QString>("error"); + + QTest::newRow("Trailing numeric separator") << "1_" << "A trailing numeric separator is not allowed in numeric literals"; + QTest::newRow("Multiple numeric separators") << "1__2" << "There can be at most one numeric separator beetwen digits"; +} + +void tst_qqmlparser::invalidNumericSeparator() { + using namespace QQmlJS; + + QFETCH(QString, code); + QFETCH(QString, error); + + QQmlJS::Engine engine; + + QQmlJS::Lexer lexer(&engine); + lexer.setCode(code, 1); + + QQmlJS::Parser parser(&engine); + QVERIFY(!parser.parseExpression()); + + QVERIFY(lexer.errorCode() != Lexer::NoError); + QCOMPARE(lexer.errorMessage(), error); +} + void tst_qqmlparser::leadingSemicolonInClass() { QQmlJS::Engine engine; diff --git a/tests/auto/qml/qqmlqt/data/qtbug_125495.qml b/tests/auto/qml/qqmlqt/data/qtbug_125495.qml new file mode 100644 index 0000000000..88f9cb259f --- /dev/null +++ b/tests/auto/qml/qqmlqt/data/qtbug_125495.qml @@ -0,0 +1,5 @@ +import QtQuick 2.0 + +Item { + property font fontProperty: Qt.font({ styleName: "Some Style" }); +} diff --git a/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp b/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp index 9fea41104d..8139ec48d7 100644 --- a/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp +++ b/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp @@ -85,6 +85,8 @@ private slots: void timeRoundtrip_data(); void timeRoundtrip(); + void fontSetsStyleName(); + private: QQmlEngine engine; }; @@ -1469,6 +1471,18 @@ void tst_qqmlqt::timeRoundtrip() QCOMPARE(tp.m_getTime, tp.m_putTime); } +void tst_qqmlqt::fontSetsStyleName() { + QQmlComponent component(&engine, testFileUrl("qtbug_125495.qml")); + + QScopedPointer<QObject> object(component.create()); + QVERIFY(object != nullptr); + + QFont f; + f.setStyleName("Some Style"); + + QCOMPARE(qvariant_cast<QFont>(object->property("fontProperty")), f); +} + QTEST_MAIN(tst_qqmlqt) #include "tst_qqmlqt.moc" diff --git a/tests/auto/qml/qqmltimer/CMakeLists.txt b/tests/auto/qml/qqmltimer/CMakeLists.txt index f66e054dc6..88d596be85 100644 --- a/tests/auto/qml/qqmltimer/CMakeLists.txt +++ b/tests/auto/qml/qqmltimer/CMakeLists.txt @@ -21,6 +21,7 @@ qt_internal_add_test(tst_qqmltimer Qt::Gui Qt::GuiPrivate Qt::QmlPrivate + Qt::QmlMetaPrivate Qt::QuickPrivate ) diff --git a/tests/auto/qml/qqmltimer/tst_qqmltimer.cpp b/tests/auto/qml/qqmltimer/tst_qqmltimer.cpp index a6c61abd57..495f7044f6 100644 --- a/tests/auto/qml/qqmltimer/tst_qqmltimer.cpp +++ b/tests/auto/qml/qqmltimer/tst_qqmltimer.cpp @@ -1,14 +1,18 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only -#include <QtTest/QSignalSpy> -#include <qtest.h> -#include <QtQml/qqmlengine.h> -#include <QtQml/qqmlcomponent.h> -#include <QtQml/private/qqmltimer_p.h> -#include <QtQuick/qquickitem.h> -#include <QDebug> -#include <QtCore/QPauseAnimation> + #include <private/qabstractanimation_p.h> +#include <private/qqmltimer_p.h> + +#include <QtQuick/qquickitem.h> + +#include <QtQml/qqmlcomponent.h> +#include <QtQml/qqmlengine.h> + +#include <QtTest/qsignalspy.h> +#include <QtTest/qtest.h> + +#include <QtCore/qpauseanimation.h> void consistentWait(int ms) { diff --git a/tests/auto/qml/qqmlvaluetypes/data/constructors.qml b/tests/auto/qml/qqmlvaluetypes/data/constructors.qml new file mode 100644 index 0000000000..d94d6d8ad4 --- /dev/null +++ b/tests/auto/qml/qqmlvaluetypes/data/constructors.qml @@ -0,0 +1,14 @@ +import QtQuick as Q + +Q.QtObject { + property var point: new Q.point() + property var size: new Q.size() + property var rect: new Q.rect() + property var color: new Q.color() + property var vector2d: new Q.vector2d() + property var vector3d: new Q.vector3d() + property var vector4d: new Q.vector4d() + property var quaternion: new Q.quaternion() + property var matrix4x4: new Q.matrix4x4() + property var font: new Q.font() +} diff --git a/tests/auto/qml/qqmlvaluetypes/data/matrix4x4_invokables.qml b/tests/auto/qml/qqmlvaluetypes/data/matrix4x4_invokables.qml index c28901956d..1827b57ca9 100644 --- a/tests/auto/qml/qqmlvaluetypes/data/matrix4x4_invokables.qml +++ b/tests/auto/qml/qqmlvaluetypes/data/matrix4x4_invokables.qml @@ -1,4 +1,4 @@ -import QtQuick 2.0 +import QtQuick Item { property bool success: false @@ -6,6 +6,7 @@ Item { property variant m1: Qt.matrix4x4(1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4) property variant m2: Qt.matrix4x4(5,5,5,5,6,6,6,6,7,7,7,7,8,8,8,8) property variant m3: Qt.matrix4x4(123,22,6,42,55,54,67,77,777,1,112,22,55,6696,77,777) + property matrix4x4 m4: PlanarTransform.fromAffineMatrix(1, 2, 3, 4, 5, 6) property variant v1: Qt.vector4d(1,2,3,4) property variant v2: Qt.vector3d(1,2,3) property real factor: 2.23 @@ -101,6 +102,7 @@ Item { if (m1.transposed() != Qt.matrix4x4(1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4)) success = false; if (m1.fuzzyEquals(m2)) success = false; if (!m1.fuzzyEquals(m2, 10)) success = false; + if (m4 != Qt.matrix4x4(1, 3, 0, 5, 2, 4, 0, 6, 0, 0, 1, 0, 0, 0, 0, 1)) success = false; if (!testTransformation()) success = false; if (!testMatrixMapping()) success = false; } diff --git a/tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp b/tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp index 2634044238..ea521053ae 100644 --- a/tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp +++ b/tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp @@ -78,6 +78,7 @@ private slots: void writeBackOnFunctionCall(); void valueTypeConversions(); void readReferenceOnGetOwnProperty(); + void constructors(); private: QQmlEngine engine; @@ -1832,6 +1833,28 @@ void tst_qqmlvaluetypes::readReferenceOnGetOwnProperty() QVERIFY(o->property("allo").toBool()); } +void tst_qqmlvaluetypes::constructors() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("constructors.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("point"), QVariant(QPointF())); + QCOMPARE(o->property("size"), QVariant(QSizeF())); + QCOMPARE(o->property("rect"), QVariant(QRectF())); + QCOMPARE(o->property("color"), QVariant(QColor())); + QCOMPARE(o->property("vector2d"), QVariant(QVector2D())); + QCOMPARE(o->property("vector3d"), QVariant(QVector3D())); + QCOMPARE(o->property("vector4d"), QVariant(QVector4D())); + QCOMPARE(o->property("quaternion"), QVariant(QQuaternion())); + QCOMPARE(o->property("matrix4x4"), QVariant(QMatrix4x4())); + QCOMPARE(o->property("font"), QVariant(QFont())); + +} + #undef CHECK_TYPE_IS_NOT_VALUETYPE QTEST_MAIN(tst_qqmlvaluetypes) diff --git a/tests/auto/qml/qqmlxmllistmodel/tst_qqmlxmllistmodel.cpp b/tests/auto/qml/qqmlxmllistmodel/tst_qqmlxmllistmodel.cpp index eebe4a6c05..bb6e59cb17 100644 --- a/tests/auto/qml/qqmlxmllistmodel/tst_qqmlxmllistmodel.cpp +++ b/tests/auto/qml/qqmlxmllistmodel/tst_qqmlxmllistmodel.cpp @@ -321,14 +321,12 @@ void tst_QQmlXmlListModel::headers() QTRY_COMPARE_WITH_TIMEOUT(qvariant_cast<QQmlXmlListModel::Status>(model->property("status")), QQmlXmlListModel::Error, 10000); - QVariantMap expectedHeaders; - expectedHeaders["Accept"] = "application/xml,*/*"; + QLatin1String expectedAcceptHeader = "application/xml,*/*"_L1; - QCOMPARE(factory.lastSentHeaders.size(), expectedHeaders.size()); - for (auto it = expectedHeaders.cbegin(), end = expectedHeaders.cend(); it != end; ++it) { - QVERIFY(factory.lastSentHeaders.contains(it.key())); - QCOMPARE(factory.lastSentHeaders[it.key()].toString(), it.value().toString()); - } + QCOMPARE(factory.lastSentHeaders.size(), 1); + QVariant acceptHeader = factory.lastSentHeaders["accept"]; + QVERIFY(acceptHeader.isValid()); + QCOMPARE(acceptHeader.toString(), expectedAcceptHeader); } void tst_QQmlXmlListModel::source() 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/qml/qv4mm/tst_qv4mm.cpp b/tests/auto/qml/qv4mm/tst_qv4mm.cpp index 5bcdcd4624..a414199181 100644 --- a/tests/auto/qml/qv4mm/tst_qv4mm.cpp +++ b/tests/auto/qml/qv4mm/tst_qv4mm.cpp @@ -13,6 +13,8 @@ #include <private/qv4identifiertable_p.h> #include <private/qv4arraydata_p.h> #include <private/qqmlcomponentattached_p.h> +#include <private/qv4mapobject_p.h> +#include <private/qv4setobject_p.h> #include <QtQuickTestUtils/private/qmlutils_p.h> @@ -37,6 +39,7 @@ private slots: void sharedInternalClassDataMarking(); void gcTriggeredInOnDestroyed(); void weakValuesAssignedAfterThePhaseThatShouldHandleWeakValues(); + void mapAndSetKeepValuesAlive(); }; tst_qv4mm::tst_qv4mm() @@ -502,6 +505,149 @@ void tst_qv4mm::weakValuesAssignedAfterThePhaseThatShouldHandleWeakValues() QVERIFY(ddata->jsWrapper.valueRef()->heapObject()->inUse()); } +void tst_qv4mm::mapAndSetKeepValuesAlive() +{ + { + QJSEngine jsEngine; + QV4::ExecutionEngine &engine = *jsEngine.handle(); + + QV4::Scope scope(&engine); + auto map = jsEngine.evaluate("new Map()"); + QV4::ScopedFunctionObject afunction(scope, engine.memoryManager->alloc<QV4::FunctionObject>()); // hack, we just need about any function object + QV4::Value thisObject = QJSValuePrivate::asReturnedValue(&map); + + QVERIFY(!engine.memoryManager->gcBlocked); + // no scoped classes, as that would defeat the point of the test + // we block the gc instead so that the allocation can't trigger the gc + engine.memoryManager->gcBlocked = QV4::MemoryManager::InCriticalSection; + QV4::Heap::String *key = engine.newString(QString::fromLatin1("key")); + QV4::Heap::String *value = engine.newString(QString::fromLatin1("value")); + QV4::Value values[2] = { QV4::Value::fromHeapObject(key), QV4::Value::fromHeapObject(value) }; + engine.memoryManager->gcBlocked = QV4::MemoryManager::Unblocked; + QVERIFY(!key->isMarked()); + QVERIFY(!value->isMarked()); + + auto sm = engine.memoryManager->gcStateMachine.get(); + sm->reset(); + while (sm->state != QV4::GCState::HandleQObjectWrappers) { + QV4::GCStateInfo& stateInfo = sm->stateInfoMap[int(sm->state)]; + sm->state = stateInfo.execute(sm, sm->stateData); + } + QV4::MapPrototype::method_set(afunction.getPointer(), &thisObject, values, 2); + QVERIFY(key->isMarked()); + QVERIFY(value->isMarked()); + bool gcComplete = engine.memoryManager->tryForceGCCompletion(); + QVERIFY(gcComplete); + QVERIFY(key->inUse()); + QVERIFY(value->inUse()); + gc(engine); + QCOMPARE(map.property("size").toInt(), 1); + } + { + QJSEngine jsEngine; + QV4::ExecutionEngine &engine = *jsEngine.handle(); + + QV4::Scope scope(&engine); + auto map = jsEngine.evaluate("new WeakMap()"); + QV4::ScopedFunctionObject afunction(scope, engine.memoryManager->alloc<QV4::FunctionObject>()); // hack, we just need about any function object + QV4::Value thisObject = QJSValuePrivate::asReturnedValue(&map); + + QVERIFY(!engine.memoryManager->gcBlocked); + // no scoped classes, as that would defeat the point of the test + // we block the gc instead so that the allocation can't trigger the gc + engine.memoryManager->gcBlocked = QV4::MemoryManager::InCriticalSection; + QV4::Heap::Object *key = engine.newObject(); + QV4::Heap::String *value = engine.newString(QString::fromLatin1("value")); + QV4::Value values[2] = { QV4::Value::fromHeapObject(key), QV4::Value::fromHeapObject(value) }; + engine.memoryManager->gcBlocked = QV4::MemoryManager::Unblocked; + QVERIFY(!key->isMarked()); + QVERIFY(!value->isMarked()); + + auto sm = engine.memoryManager->gcStateMachine.get(); + sm->reset(); + while (sm->state != QV4::GCState::HandleQObjectWrappers) { + QV4::GCStateInfo& stateInfo = sm->stateInfoMap[int(sm->state)]; + sm->state = stateInfo.execute(sm, sm->stateData); + } + QV4::WeakMapPrototype::method_set(afunction.getPointer(), &thisObject, values, 2); + QVERIFY(!engine.hasException); + QVERIFY(key->isMarked()); + QVERIFY(value->isMarked()); + bool gcComplete = engine.memoryManager->tryForceGCCompletion(); + QVERIFY(gcComplete); + QVERIFY(key->inUse()); + QVERIFY(value->inUse()); + gc(engine); + QCOMPARE(map.property("size").toInt(), 0); + } + { + QJSEngine jsEngine; + QV4::ExecutionEngine &engine = *jsEngine.handle(); + + QV4::Scope scope(&engine); + auto map = jsEngine.evaluate("new Set()"); + QV4::ScopedFunctionObject afunction(scope, engine.memoryManager->alloc<QV4::FunctionObject>()); // hack, we just need about any function object + QV4::Value thisObject = QJSValuePrivate::asReturnedValue(&map); + + QVERIFY(!engine.memoryManager->gcBlocked); + // no scoped classes, as that would defeat the point of the test + // we block the gc instead so that the allocation can't trigger the gc + engine.memoryManager->gcBlocked = QV4::MemoryManager::InCriticalSection; + QV4::Heap::Object *key = engine.newObject(); + QV4::Value values[1] = { QV4::Value::fromHeapObject(key) }; + engine.memoryManager->gcBlocked = QV4::MemoryManager::Unblocked; + QVERIFY(!key->isMarked()); + + auto sm = engine.memoryManager->gcStateMachine.get(); + sm->reset(); + while (sm->state != QV4::GCState::HandleQObjectWrappers) { + QV4::GCStateInfo& stateInfo = sm->stateInfoMap[int(sm->state)]; + sm->state = stateInfo.execute(sm, sm->stateData); + } + QV4::SetPrototype::method_add(afunction.getPointer(), &thisObject, values, 1); + QVERIFY(!engine.hasException); + QVERIFY(key->isMarked()); + bool gcComplete = engine.memoryManager->tryForceGCCompletion(); + QVERIFY(gcComplete); + QVERIFY(key->inUse()); + gc(engine); + QCOMPARE(map.property("size").toInt(), 1); + } + { + QJSEngine jsEngine; + QV4::ExecutionEngine &engine = *jsEngine.handle(); + + QV4::Scope scope(&engine); + auto map = jsEngine.evaluate("new WeakSet()"); + QV4::ScopedFunctionObject afunction(scope, engine.memoryManager->alloc<QV4::FunctionObject>()); // hack, we just need about any function object + QV4::Value thisObject = QJSValuePrivate::asReturnedValue(&map); + + QVERIFY(!engine.memoryManager->gcBlocked); + // no scoped classes, as that would defeat the point of the test + // we block the gc instead so that the allocation can't trigger the gc + engine.memoryManager->gcBlocked = QV4::MemoryManager::InCriticalSection; + QV4::Heap::Object *key = engine.newObject(); + QV4::Value values[1] = { QV4::Value::fromHeapObject(key) }; + engine.memoryManager->gcBlocked = QV4::MemoryManager::Unblocked; + QVERIFY(!key->isMarked()); + + auto sm = engine.memoryManager->gcStateMachine.get(); + sm->reset(); + while (sm->state != QV4::GCState::HandleQObjectWrappers) { + QV4::GCStateInfo& stateInfo = sm->stateInfoMap[int(sm->state)]; + sm->state = stateInfo.execute(sm, sm->stateData); + } + QV4::WeakSetPrototype::method_add(afunction.getPointer(), &thisObject, values, 1); + QVERIFY(!engine.hasException); + QVERIFY(key->isMarked()); + bool gcComplete = engine.memoryManager->tryForceGCCompletion(); + QVERIFY(gcComplete); + QVERIFY(key->inUse()); + gc(engine); + QCOMPARE(map.property("size").toInt(), 0); + } +} + QTEST_MAIN(tst_qv4mm) #include "tst_qv4mm.moc" diff --git a/tests/auto/qmldom/domdata/domitem/crashes/bracketsInBinding.qml b/tests/auto/qmldom/domdata/domitem/crashes/bracketsInBinding.qml new file mode 100644 index 0000000000..bef28ffc45 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/crashes/bracketsInBinding.qml @@ -0,0 +1,5 @@ +import QtQuick + +Item { + property int foo: {} +} diff --git a/tests/auto/qmldom/domdata/domitem/fileLocationRegions/comments.qml b/tests/auto/qmldom/domdata/domitem/fileLocationRegions/comments.qml new file mode 100644 index 0000000000..a69505c544 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/fileLocationRegions/comments.qml @@ -0,0 +1,12 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQml + +/* +splitting +multiline +*/ +// single line +/* another comment */ +QtObject {} diff --git a/tests/auto/qmldom/domdata/domitem/lambdas.qml b/tests/auto/qmldom/domdata/domitem/lambdas.qml new file mode 100644 index 0000000000..c241bb77ae --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/lambdas.qml @@ -0,0 +1,41 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + signal helloSignal + + function method() { + console.log("helloMethod"); + let myLambda = function(a, b) { return a + b }; + let myArrow = (v, w) => a + b; + } + + onHelloSignal: function(x, y, z) { console.log("HelloLambda"); } + + function testNestedFunctions() { + function nested(tic, tac, toe) { return tic + tac/3 + toe/2} + nested() + } + + function generators() { + function *myGeneratorDeclaration(a, b) { yield 5 }; + let myGenerator = function*(tic, tac, toe) { yield tic + tac - toe }; + } + + function *generatorInQmlObject() { + function nested(q,w,e,r) { return q + w + e - r; } + function *nested2(a,z,e,r) { yield a + z + e - r; yield 42; } + yield 4; + yield* nested2(1,2,3,4); + const t = (function (a) { + return a + 100; + }); + } + function traditionalLambda() { + const tradition = (function (a) { + return a + 100; + }); + } +} diff --git a/tests/auto/qmldom/domitem/tst_qmldomitem.h b/tests/auto/qmldom/domitem/tst_qmldomitem.h index 4464333b6e..3291cc0585 100644 --- a/tests/auto/qmldom/domitem/tst_qmldomitem.h +++ b/tests/auto/qmldom/domitem/tst_qmldomitem.h @@ -2874,6 +2874,9 @@ private slots: QTest::addRow("lambda") << baseDir + u"/crashes/lambda.qml"_s; + + QTest::addRow("bracketsInBinding") + << baseDir + u"/crashes/bracketsInBinding.qml"_s; } void crashes() { @@ -3189,6 +3192,157 @@ private slots: envPtrChild->loadPendingDependencies(); } + void populateLazyFileBeforeCommitToBase() + { + DomItem qmlObject; + DomCreationOptions options; + options.setFlag(DomCreationOption::WithScriptExpressions); + options.setFlag(DomCreationOption::WithSemanticAnalysis); + options.setFlag(DomCreationOption::WithRecovery); + + std::shared_ptr<DomEnvironment> envPtr = DomEnvironment::create( + qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded, options); + + const QString fileName{ QDir::cleanPath(baseDir + u"/propertyBindings.qml"_s) }; + + { + DomItem envChild = DomItem(envPtr).makeCopy(DomItem::CopyOption::EnvConnected).item(); + auto envPtrChild = envChild.ownerAs<DomEnvironment>(); + envPtrChild->loadFile( + FileToLoad::fromFileSystem(envPtrChild, fileName), + [&qmlObject](Path, const DomItem &, const DomItem &newIt) { + qmlObject = newIt.fileObject(); + }); + envPtrChild->loadPendingDependencies(); + + const DomItem childEnv = DomItem(envPtrChild->shared_from_this()); + // populate the lazy file by accessing it via the DomItem interface + const DomItem mainComponent = + childEnv.field(Fields::qmlFileWithPath) + .key(fileName) + .field(Fields::currentItem) + .field(Fields::components) + .key(QString()); + QVERIFY(mainComponent); + + envPtrChild->commitToBase(DomItem(envPtrChild)); + } // destroy the temporary environment that the file was loaded into + + // also make sure that the main component also exists in the base environment after the + // commitToBase call. + const DomItem env = DomItem(envPtr->shared_from_this()); + const DomItem mainComponent = env.field(Fields::qmlFileWithPath) + .key(fileName) + .field(Fields::currentItem) + .field(Fields::components) + .key(QString()); + QVERIFY(mainComponent); + } + + void populateLazyFileAfterCommitToBase() + { + DomItem qmlObject; + DomCreationOptions options; + options.setFlag(DomCreationOption::WithScriptExpressions); + options.setFlag(DomCreationOption::WithSemanticAnalysis); + options.setFlag(DomCreationOption::WithRecovery); + + std::shared_ptr<DomEnvironment> envPtr = DomEnvironment::create( + qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded, options); + + const QString fileName{ QDir::cleanPath(baseDir + u"/propertyBindings.qml"_s) }; + + { + DomItem envChild = DomItem(envPtr).makeCopy(DomItem::CopyOption::EnvConnected).item(); + auto envPtrChild = envChild.ownerAs<DomEnvironment>(); + envPtrChild->loadFile( + FileToLoad::fromFileSystem(envPtrChild, fileName), + [&qmlObject](Path, const DomItem &, const DomItem &newIt) { + qmlObject = newIt.fileObject(); + }); + envPtrChild->loadPendingDependencies(); + envPtrChild->commitToBase(DomItem(envPtrChild)); + } // destroy the temporary environment that the file was loaded into + + const DomItem env = DomItem(envPtr->shared_from_this()); + // populate the lazy file by accessing it via the DomItem interface + const DomItem mainComponent = env.field(Fields::qmlFileWithPath) + .key(fileName) + .field(Fields::currentItem) + .field(Fields::components) + .key(QString()); + QVERIFY(mainComponent); + } + + void qtbug_124799() + { + // reproduces the completion crash in QTBUG-124799 that was actually not completion related: + // triggering the completion was triggering the population of a file, that led to a + // heap-use-after-free. The steps to reproduce the crash are following: + // 1. load a file in a temporary environment + // 2. grab an unpopulated qqmljsscope from the type resolver of the loaded file + // 3. destroy the temporary environment + // 4. update the loaded file with new content, to make sure the QQmlJSImporter (used to + // populate of qmlfiles) has no more strong references in the QmlFile. + // 5. populate the unpopulated qqmljsscope: its factory should have kept track that its + // environment is not the temporary one but the base one (because of the commitToBase() + // call) and use the correct QQmlJSImporter (if its the one from the temporary environment + // this will lead to the heap-use-after-free memory error you get when triggering + // completions before this fix) + + DomItem qmlObject; + DomCreationOptions options; + options.setFlag(DomCreationOption::WithScriptExpressions); + options.setFlag(DomCreationOption::WithSemanticAnalysis); + options.setFlag(DomCreationOption::WithRecovery); + + std::shared_ptr<DomEnvironment> envPtr = DomEnvironment::create( + qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded, options); + + const QString fileName{ QDir::cleanPath(baseDir + u"/propertyBindings.qml"_s) }; + + QQmlJSScope::ConstPtr populateAfterEnvironmentDestruction; + + { + DomItem envChild = DomItem(envPtr).makeCopy(DomItem::CopyOption::EnvConnected).item(); + auto envPtrChild = envChild.ownerAs<DomEnvironment>(); + envPtrChild->loadFile( + FileToLoad::fromFileSystem(envPtrChild, fileName), + [&qmlObject](Path, const DomItem &, const DomItem &newIt) { + qmlObject = newIt.fileObject(); + }); + envPtrChild->loadPendingDependencies(); + + auto qmlFilePtr = qmlObject.ownerAs<QmlFile>(); + auto resolver = qmlFilePtr->typeResolver(); + // simulate completion by grabbing some type from the resolver + populateAfterEnvironmentDestruction = resolver->importedTypes()[u"Derived"_s].scope; + envPtrChild->commitToBase(DomItem(envPtrChild)); + } + + // update the file + { + DomItem envChild = DomItem(envPtr).makeCopy(DomItem::CopyOption::EnvConnected).item(); + auto envPtrChild = envChild.ownerAs<DomEnvironment>(); + + // simulate user typing something + QFile file(fileName); + QVERIFY(file.open(QFile::ReadOnly)); + const QString content = file.readAll(); + const QString newContent = content + "\n // important comment here\n"; + envPtrChild->loadFile(FileToLoad::fromMemory(envPtrChild, fileName, newContent), + [&qmlObject](Path, const DomItem &, const DomItem &newIt) { + qmlObject = newIt.fileObject(); + }); + envPtrChild->loadPendingDependencies(); + envPtrChild->commitToBase(DomItem(envPtrChild)); + } + + // step 3: populate the lazy qqmljsscope, it should not crash + QCOMPARE(populateAfterEnvironmentDestruction->filePath(), + QDir::cleanPath(baseDir + u"/Derived.qml"_s)); + } + void visitTreeFilter() { DomItem qmlObject; @@ -3404,6 +3558,355 @@ private slots: QVERIFY(comments.contains(u"/*Ast Comment*/ "_s)); } + void commentLocations() + { + auto envPtr = DomEnvironment::create( + QStringList(), + QQmlJS::Dom::DomEnvironment::Option::SingleThreaded + | QQmlJS::Dom::DomEnvironment::Option::NoDependencies); + + const auto filePath = baseDir + u"/fileLocationRegions/comments.qml"_s; + QFile f(filePath); + QVERIFY(f.open(QIODevice::ReadOnly | QIODevice::Text)); + QString code = f.readAll(); + DomItem file; + envPtr->loadFile(FileToLoad::fromMemory(envPtr, filePath, code), + [&file](Path, const DomItem &, const DomItem &newIt) { + file = newIt.fileObject(); + }); + envPtr->loadPendingDependencies(); + + const auto expctedCommentLocations = QSet { + QQmlJS::SourceLocation(0, 41, 1, 1), + QQmlJS::SourceLocation(42,68, 2, 1), + QQmlJS::SourceLocation(126,25, 6, 1), + QQmlJS::SourceLocation(152,14, 10, 1), + QQmlJS::SourceLocation(167,21, 11, 1) + }; + + QSet<SourceLocation> locs; + file.fileObject(GoTo::MostLikely).visitTree(Path(), [&locs](Path, const DomItem &item, bool){ + if (item.internalKind() == DomType::Comment) { + const auto comment = item.as<Comment>(); + if (comment) { + locs << comment->info().sourceLocation(); + } + } + return true; + }, VisitOption::Default, emptyChildrenVisitor, emptyChildrenVisitor); + + + QCOMPARE(locs, expctedCommentLocations); + } + + void lambdas() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/lambdas.qml"_s; + const DomItem fileObject = rootQmlObjectFromFile(testFile, qmltypeDirs).fileObject(); + const DomItem mainObject = fileObject.field(Fields::components) + .key(QString()) + .index(0) + .field(Fields::objects) + .index(0); + { + const DomItem lambda = mainObject.field(Fields::methods) + .key(u"method"_s) + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements) + .index(1) + .field(Fields::declarations) + .index(0) + .field(Fields::initializer); + QVERIFY(lambda); + QCOMPARE(lambda.internalKind(), DomType::ScriptFunctionExpression); + QCOMPARE(lambda.field(Fields::name).value().toString(), u"myLambda"_s); + QCOMPARE(lambda.field(Fields::parameters).indexes(), 2); + QCOMPARE(lambda.field(Fields::parameters).index(0).field(Fields::identifier).value().toString(), u"a"); + QCOMPARE(lambda.field(Fields::parameters).index(1).field(Fields::identifier).value().toString(), u"b"); + + auto scope = lambda.semanticScope(); + QVERIFY(scope); + QVERIFY(scope->jsIdentifier(u"b"_s)); + + const DomItem body = lambda.field(Fields::body); + QCOMPARE(body.internalKind(), DomType::ScriptBlockStatement); + } + } + void arrow() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/lambdas.qml"_s; + const DomItem fileObject = rootQmlObjectFromFile(testFile, qmltypeDirs).fileObject(); + const DomItem mainObject = fileObject.field(Fields::components) + .key(QString()) + .index(0) + .field(Fields::objects) + .index(0); + { + const DomItem arrow = mainObject.field(Fields::methods) + .key(u"method"_s) + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements) + .index(2) + .field(Fields::declarations) + .index(0) + .field(Fields::initializer); + QVERIFY(arrow); + QCOMPARE(arrow.internalKind(), DomType::ScriptFunctionExpression); + QCOMPARE(arrow.field(Fields::name).value().toString(), u"myArrow"_s); + QCOMPARE(arrow.field(Fields::parameters).indexes(), 2); + QCOMPARE(arrow.field(Fields::parameters) + .index(0) + .field(Fields::identifier) + .value() + .toString(), + u"v"); + QCOMPARE(arrow.field(Fields::parameters) + .index(1) + .field(Fields::identifier) + .value() + .toString(), + u"w"); + + auto scope = arrow.semanticScope(); + QVERIFY(scope); + QVERIFY(scope->jsIdentifier(u"w"_s)); + + const DomItem body = arrow.field(Fields::body); + QCOMPARE(body.internalKind(), DomType::ScriptBlockStatement); + QCOMPARE(body.field(Fields::statements).indexes(), 1); + QCOMPARE(body.field(Fields::statements).index(0).internalKind(), + DomType::ScriptReturnStatement); + } + } + void lamdbaInBinding() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/lambdas.qml"_s; + const DomItem fileObject = rootQmlObjectFromFile(testFile, qmltypeDirs).fileObject(); + const DomItem mainObject = fileObject.field(Fields::components) + .key(QString()) + .index(0) + .field(Fields::objects) + .index(0); + { + const DomItem lambda = mainObject.field(Fields::bindings) + .key(u"onHelloSignal"_s) + .index(0) + .field(Fields::value) + .field(Fields::scriptElement); + QVERIFY(lambda); + QCOMPARE(lambda.internalKind(), DomType::ScriptFunctionExpression); + QCOMPARE(lambda.field(Fields::name).value().toString(), QString()); + QCOMPARE(lambda.field(Fields::parameters).indexes(), 3); + QCOMPARE(lambda.field(Fields::parameters).index(0).field(Fields::identifier).value().toString(), u"x"); + QCOMPARE(lambda.field(Fields::parameters).index(2).field(Fields::identifier).value().toString(), u"z"); + auto scope = lambda.semanticScope(); + QVERIFY(scope); + QVERIFY(scope->jsIdentifier(u"z"_s)); + const DomItem body = lambda.field(Fields::body); + QCOMPARE(body.internalKind(), DomType::ScriptBlockStatement); + } + } + void nestedFunction() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/lambdas.qml"_s; + const DomItem fileObject = rootQmlObjectFromFile(testFile, qmltypeDirs).fileObject(); + const DomItem mainObject = fileObject.field(Fields::components) + .key(QString()) + .index(0) + .field(Fields::objects) + .index(0); + { + const DomItem nested = mainObject.field(Fields::methods) + .key(u"testNestedFunctions"_s) + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements) + .index(0); + QVERIFY(nested); + QCOMPARE(nested.internalKind(), DomType::ScriptFunctionExpression); + QCOMPARE(nested.field(Fields::name).value().toString(), u"nested"_s); + QCOMPARE(nested.field(Fields::parameters).indexes(), 3); + QCOMPARE(nested.field(Fields::parameters) + .index(0) + .field(Fields::identifier) + .value() + .toString(), + u"tic"); + QCOMPARE(nested.field(Fields::parameters) + .index(2) + .field(Fields::identifier) + .value() + .toString(), + u"toe"); + const DomItem body = nested.field(Fields::body); + QCOMPARE(body.internalKind(), DomType::ScriptBlockStatement); + auto scope = nested.semanticScope(); + QVERIFY(scope); + QVERIFY(scope->jsIdentifier(u"toe"_s)); + } + } + void generatorDeclaration() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/lambdas.qml"_s; + const DomItem fileObject = rootQmlObjectFromFile(testFile, qmltypeDirs).fileObject(); + const DomItem mainObject = fileObject.field(Fields::components) + .key(QString()) + .index(0) + .field(Fields::objects) + .index(0); + { + const DomItem generator = mainObject.field(Fields::methods) + .key(u"generators"_s) + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements) + .index(0); + QVERIFY(generator); + QCOMPARE(generator.internalKind(), DomType::ScriptFunctionExpression); + QCOMPARE(generator.field(Fields::name).value().toString(), u"myGeneratorDeclaration"_s); + QCOMPARE(generator.field(Fields::parameters).indexes(), 2); + QCOMPARE(generator.field(Fields::parameters) + .index(0) + .field(Fields::identifier) + .value() + .toString(), + u"a"); + QCOMPARE(generator.field(Fields::parameters) + .index(1) + .field(Fields::identifier) + .value() + .toString(), + u"b"); + const DomItem body = generator.field(Fields::body); + QCOMPARE(body.internalKind(), DomType::ScriptBlockStatement); + auto scope = generator.semanticScope(); + QVERIFY(scope); + QVERIFY(scope->jsIdentifier(u"b"_s)); + + const DomItem yieldExpression = + generator.field(Fields::body).field(Fields::statements).index(0); + QCOMPARE(yieldExpression.internalKind(), DomType::ScriptYieldExpression); + QCOMPARE(yieldExpression.field(Fields::expression).value().toInteger(), 5); + } + } + void generatorExpression() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/lambdas.qml"_s; + const DomItem fileObject = rootQmlObjectFromFile(testFile, qmltypeDirs).fileObject(); + const DomItem mainObject = fileObject.field(Fields::components) + .key(QString()) + .index(0) + .field(Fields::objects) + .index(0); + { + const DomItem generator = mainObject.field(Fields::methods) + .key(u"generators"_s) + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements) + .index(1) + .field(Fields::declarations) + .index(0) + .field(Fields::initializer); + QVERIFY(generator); + QCOMPARE(generator.internalKind(), DomType::ScriptFunctionExpression); + QCOMPARE(generator.field(Fields::name).value().toString(), u"myGenerator"_s); + QCOMPARE(generator.field(Fields::parameters).indexes(), 3); + QCOMPARE(generator.field(Fields::parameters) + .index(0) + .field(Fields::identifier) + .value() + .toString(), + u"tic"); + QCOMPARE(generator.field(Fields::parameters) + .index(2) + .field(Fields::identifier) + .value() + .toString(), + u"toe"); + const DomItem body = generator.field(Fields::body); + QCOMPARE(body.internalKind(), DomType::ScriptBlockStatement); + auto scope = generator.semanticScope(); + QVERIFY(scope); + QVERIFY(scope->jsIdentifier(u"toe"_s)); + } + } + void generatorDeclarationInQmlObject() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/lambdas.qml"_s; + const DomItem fileObject = rootQmlObjectFromFile(testFile, qmltypeDirs).fileObject(); + const DomItem statements = fileObject.field(Fields::components) + .key(QString()) + .index(0) + .field(Fields::objects) + .index(0) + .field(Fields::methods) + .key(u"generatorInQmlObject"_s) + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements); + { + const DomItem nested = statements.index(0); + QVERIFY(nested); + QCOMPARE(nested.internalKind(), DomType::ScriptFunctionExpression); + + const DomItem nested2 = statements.index(1); + QVERIFY(nested2); + QCOMPARE(nested2.internalKind(), DomType::ScriptFunctionExpression); + + const DomItem yield = statements.index(2); + QVERIFY(yield); + QCOMPARE(yield.internalKind(), DomType::ScriptYieldExpression); + + const DomItem yieldStar = statements.index(3); + QVERIFY(yieldStar); + QCOMPARE(yieldStar.internalKind(), DomType::ScriptYieldExpression); + + } + } + void traditionalLambda() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/lambdas.qml"_s; + const DomItem fileObject = rootQmlObjectFromFile(testFile, qmltypeDirs).fileObject(); + const DomItem initializer = fileObject.field(Fields::components) + .key(QString()) + .index(0) + .field(Fields::objects) + .index(0) + .field(Fields::methods) + .key(u"traditionalLambda"_s) + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements) + .index(0) + .field(Fields::declarations) + .index(0) + .field(Fields::initializer); + QVERIFY(initializer); + QCOMPARE(initializer.internalKind(), DomType::ScriptParenthesizedExpression); + const DomItem lambda = initializer.field(Fields::expression); + QVERIFY(lambda); + QCOMPARE(lambda.internalKind(), DomType::ScriptFunctionExpression); + } + + private: QString baseDir; QStringList qmltypeDirs; diff --git a/tests/auto/qmldom/reformatter/tst_reformatter.h b/tests/auto/qmldom/reformatter/tst_reformatter.h index 31d80097c1..5cf800c80c 100644 --- a/tests/auto/qmldom/reformatter/tst_reformatter.h +++ b/tests/auto/qmldom/reformatter/tst_reformatter.h @@ -461,6 +461,13 @@ private slots: << QStringLiteral(u"function *g(a,b){}") << QStringLiteral(u"function* g(a, b) {}"); QTest::newRow("AnonymousGenerator") << QStringLiteral(u"let g=function * (a,b){}") << QStringLiteral(u"let g = function* (a, b) {}"); + QTest::newRow("yield") << QStringLiteral(u"let g=function*(a,b){yield a;}") + << QStringLiteral(u"let g = function* (a, b) {\nyield a;\n}"); + QTest::newRow("yield*") << QStringLiteral(u"let g=function*(a,b){yield*a;}") + << QStringLiteral(u"let g = function* (a, b) {\nyield* a;\n}"); + QTest::newRow("yield*NoSemicolon") + << QStringLiteral(u"let g=function*(a,b){yield*a}") + << QStringLiteral(u"let g = function* (a, b) {\nyield* a;\n}"); } // https://262.ecma-international.org/7.0/#prod-HoistableDeclaration diff --git a/tests/auto/qmlls/modules/data/highlighting/basic.qml b/tests/auto/qmlls/modules/data/highlighting/basic.qml new file mode 100644 index 0000000000..264f553b22 --- /dev/null +++ b/tests/auto/qmlls/modules/data/highlighting/basic.qml @@ -0,0 +1,11 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + id: rootId + function a() { + + } +} diff --git a/tests/auto/qmlls/modules/data/highlighting/bigFile.qml b/tests/auto/qmlls/modules/data/highlighting/bigFile.qml new file mode 100644 index 0000000000..9832e8e98a --- /dev/null +++ b/tests/auto/qmlls/modules/data/highlighting/bigFile.qml @@ -0,0 +1,351 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + id: rootId + function f() { + let sum = 0, sum2 = 0; + for (let i = 1; i < 42; i = i + 2) { + sum = sum + i; + { + let sum = 42; // another unrelated sum + } + } + } + + readonly property int helloProperty: 0 + property int p2: 1 + + function withProperty() { + let sum = 0, sum2 = 0; + for (const i = 1; i < 42; i = i + 2) { + sum = sum + i; + helloProperty = helloProperty + sum - i * p2; + { + let helloProperty = "evil"; + } + } + } + Item { + function f() { + return helloProperty + p2; + } + property string helloProperty + } + component IC: Item { + property var helloProperty + function f() { + return helloProperty + p2; + } + } + component NestedComponent: Item { + property NestedComponent2 inner: NestedComponent2 {} + property int p2 + } + component NestedComponent2: Item { + property NestedComponent3 inner + property int p2 + inner: NestedComponent3 {} + } + component NestedComponent3: Item { + property NestedComponent4 inner + property int p2 + inner: NestedComponent4 {} + } + component NestedComponent4: Item { + property int helloProperty + property int p2 + } + NestedComponent { + id: myNested + } + function nestedUsages() { + let x = myNested.inner.inner.inner.helloProperty + helloProperty; + let a = myNested.p2 + p2; + let b = myNested.inner.p2 + p2; + let c = myNested.inner.inner.p2 + p2; + let d = myNested.inner.inner.inner.p2 + p2; + } + + function recursive(n: int): int { + if (n > 3) + return 1 + recursive(recursive(x - 1) + recursive(x - 2) - recursive(x - 3)); + else + return recursive(0); + } + + property int helloRecursive: recursive(42) + Rectangle { + function f() { + return rootId.recursive(123); + } + } + + signal helloSignal + + function callSignals() { + helloSignal(); + if (false) { + helloSignal(); + } else { + // helloSignal() // not a usage btw + if (true) + helloSignal(); + } + } + function callSignals2() { + helloSignal(); + if (false) { + widthChanged(); + } else { + // helloSignal() // not a usage btw + if (true) + widthChanged(); + rootId.widthChanged(); + } + } + Item { + function callSignalsInChild() { + widthChanged(); + rootId.widthChanged(); + } + } + + function myHelloHandler() { + let x = 32; + } + onHelloSignal: myHelloHandler + + property int helloPropertyBinding + helloPropertyBinding: 123 + + property int checkHandlers + onCheckHandlersChanged: myHelloHandler + onChildrenChanged: myHelloHandler + function callChanged() { + checkHandlersChanged(); + childrenChanged(); + } + property int _: 48 + property int ______42: 48 + property int _123a: 48 + on_Changed: myHelloHandler + on______42Changed: myHelloHandler + on_123AChanged: myHelloHandler + function weirdPropertynames() { + _Changed(); + ______42Changed(); + _123aChanged(); + } + + TapHandler { + onTapped: myHelloHandler + function f() { + tapped(); + } + } + + function anotherF() { + helloPropertyChanged(); + } + onHelloPropertyChanged: myHelloHandler + // Type {} + function foo(mouse) { + } + + MouseArea { + id: area1 + onClicked: foo + property int insideMouseArea1 + } + + MouseArea { + id: area2 + Connections { + function onClicked(mouse) { + area1.clicked(); + area3.clicked(); + } + } + property int insideMouseArea2 + + MouseArea { + id: area3 + } + } + + property Connections c: Connections { + target: area3 + onClicked: function (mouse) {} + } + function useMouseAreas() { + area1.clicked(); + area2.clicked(); + area3.clicked(); + } + + function checkParameters(a: int, b: double, { + x, + y = {}, + z = [x, y] + }) { + return a + b + c + x + y + z; + } + + function deconstructingUsages(xxx) { + let { + a, + b + } = xxx; + let c = a + b; + } + + function k() { + } + + function mafik() { + var patron = 34; + const upperLimit = 42; + do { + ++patron; + if (patron < 2) + continue; + else + ++patron; + } while (patron < upperLimit) + switch (patron) { + case 1: + return 23; + default: + break; + } + try { + {} + } catch (error) { + {} + } finally {} + for (const a in [1, 2, 3]) { + throw 2; + } + } + + enum Test { + LOG + } + + readonly property int t: 34 + signal tt + required property int k + + signal kkk(string a) + signal yyy(string a) + + function ttt() { + } + + function createComplexExpression(...objects) { + // Create an object that holds some data + let data = { + a: 5, + b: 10, + c: 3 + }; + + // Create a complex expression using the data object + let expression = ((data.a + data.b * data.c) / (data.a - data.b)) ** data.c; + + return expression; + } + + function set1() { + const array = [1, 2, 3, 4]; + const [a, b] = [1, 2]; + const [aa, , bb] = array; + const [aaa = 23, bbb] = array; + const [a1, b1, ...rest1] = array; + const [a2, , b2, ...rest2] = array; + const [a3, b3, ...{ + pop, + push + }] = array; + const [a4, b4, ...[c, d]] = array; + + const obj = { + _a: 1, + _b: 2 + }; + const { + a5, + b5 + } = obj; + const { + a6: a_, + b6: b1_ + } = obj; + const { + a7: a11 = 4, + b11 = 34, + c1: b111, + d1 + } = obj; + let key = a; + const { + [key]: a___ + } = obj; + } + + function set2(s: int): int { + // declare first + let a, b, a1, b1, c, d, rest, pop, push; + const array = [1, 2, 3, 4]; + [a, b] = array; + [a, , b] = array; + [a = aDefault, b] = array; + [a, b, ...rest] = array; + [a, , b, ...rest] = array; + [a, b, ...{ + pop, + push + }] = array; + [a, b, ...[c, d]] = array; + + const obj = { + _a: 1, + _b: 2 + }; + ({ + a, + b + } = obj); // brackets are required + ({ + a: a1, + b: b1 + } = obj); + + const complicatedObject = { + a: 1, + b: { + c: 2, + d: { + e: 3, + f: [4, 5, 6] + } + }, + g: [7, 8, 9] + }; + + const { + patron, + b: { + mafik, + d: { + e, + f: [, secondF, ...restF] + } + }, + g: [firstG, ...restG] + } = complicatedObject; + } +} diff --git a/tests/auto/qmlls/modules/data/renameUsages/RenameMe.qml b/tests/auto/qmlls/modules/data/renameUsages/RenameMe.qml new file mode 100644 index 0000000000..7680c63f95 --- /dev/null +++ b/tests/auto/qmlls/modules/data/renameUsages/RenameMe.qml @@ -0,0 +1,5 @@ +import QtQuick + +Item { + property int helloProperty +}
\ No newline at end of file diff --git a/tests/auto/qmlls/modules/data/renameUsages/RenameMe2.ui.qml b/tests/auto/qmlls/modules/data/renameUsages/RenameMe2.ui.qml new file mode 100644 index 0000000000..b9197def63 --- /dev/null +++ b/tests/auto/qmlls/modules/data/renameUsages/RenameMe2.ui.qml @@ -0,0 +1,5 @@ +import QtQuick + +Item { + property int helloProperty +} diff --git a/tests/auto/qmlls/modules/data/renameUsages/main.qml b/tests/auto/qmlls/modules/data/renameUsages/main.qml new file mode 100644 index 0000000000..b59508f92f --- /dev/null +++ b/tests/auto/qmlls/modules/data/renameUsages/main.qml @@ -0,0 +1,6 @@ +import QtQuick + +Item { + RenameMe {} + RenameMe2 {} +} diff --git a/tests/auto/qmlls/modules/tst_qmlls_modules.cpp b/tests/auto/qmlls/modules/tst_qmlls_modules.cpp index 9145bb7566..fa050b0727 100644 --- a/tests/auto/qmlls/modules/tst_qmlls_modules.cpp +++ b/tests/auto/qmlls/modules/tst_qmlls_modules.cpp @@ -3,6 +3,7 @@ #include "tst_qmlls_modules.h" #include "QtQmlLS/private/qqmllsutils_p.h" +#include "QtQmlLS/private/qqmlsemantictokens_p.h" #include <algorithm> #include <memory> #include <optional> @@ -97,8 +98,10 @@ void tst_qmlls_modules::cleanup() } m_uriToClose.clear(); - disconnect(&m_server, nullptr, this, nullptr); - m_server.closeWriteChannel(); + // note: properly exit the language server + m_protocol->requestShutdown(nullptr, []() {}); + m_protocol->notifyExit(nullptr); + m_server.waitForFinished(); QTRY_COMPARE(m_server.state(), QProcess::NotRunning); QCOMPARE(m_server.exitStatus(), QProcess::NormalExit); @@ -372,7 +375,7 @@ void tst_qmlls_modules::buildDir() didChange.textDocument.uri = *uri; didChange.textDocument.version = 2; - // change the file content to force qqmlcodemodel to recreate a new DomItem + // change the file content to force qqml— to recreate a new DomItem // if it reuses the old DomItem then it will not know about the added build directory TextDocumentContentChangeEvent change; change.range = Range{ Position{ 4, 0 }, Position{ 4, 0 } }; @@ -870,6 +873,12 @@ void tst_qmlls_modules::renameUsages_data() QVERIFY(file.open(QIODeviceBase::ReadOnly)); jsIdentifierUsagesContent = QString::fromUtf8(file.readAll()); } + QString renamingContent; + { + QFile file(testFile("renameUsages/main.qml").toUtf8()); + QVERIFY(file.open(QIODeviceBase::ReadOnly)); + renamingContent = QString::fromUtf8(file.readAll()); + } // TODO: create workspace edit for the tests QLspSpecification::WorkspaceEdit sumRenames{ @@ -905,6 +914,51 @@ void tst_qmlls_modules::renameUsages_data() "Invalid EcmaScript identifier!", std::nullopt, }; + + const QString renameUsagesPath = u"renameUsages/main.qml"_s; + const QByteArray renameUsagesUri = testFileUrl("renameUsages/main.qml").toEncoded(); + const QByteArray renameMeUri = testFileUrl("renameUsages/RenameMe.qml").toEncoded(); + const QByteArray renameMe2Uri = testFileUrl("renameUsages/RenameMe2.ui.qml").toEncoded(); + + const QByteArray newFileUri = testFileUrl("renameUsages/HelloWorld.qml").toEncoded(); + const QByteArray newFileUri2 = testFileUrl("renameUsages/HelloWorld.ui.qml").toEncoded(); + + { + + const QLspSpecification::WorkspaceEdit qmlComponentRename{ + std::nullopt, + QList<QLspSpecification::WorkspaceEdit::DocumentChange>{ + TextDocumentEdit{ + OptionalVersionedTextDocumentIdentifier{ { renameUsagesUri } }, + { + TextEdit{ rangeFrom(renamingContent, 4, 5, + strlen("RenameMe")), + "HelloWorld" }, + } }, + RenameFile{ "rename", renameMeUri, newFileUri } } + }; + + QTest::addRow("renameQmlComponent") + << renameUsagesPath << 4 << 8 << u"HelloWorld"_s << qmlComponentRename << noError; + } + + { + QLspSpecification::WorkspaceEdit qmlComponentRename{ + std::nullopt, + QList<QLspSpecification::WorkspaceEdit::DocumentChange>{ + TextDocumentEdit{ + OptionalVersionedTextDocumentIdentifier{ { renameUsagesUri } }, + { + TextEdit{ rangeFrom(renamingContent, 5, 5, + strlen("RenameMe2")), + "HelloWorld" }, + } }, + RenameFile{ "rename", renameMe2Uri, newFileUri2 } } + }; + + QTest::addRow("renameUiQmlComponent") + << renameUsagesPath << 5 << 8 << u"HelloWorld"_s << qmlComponentRename << noError; + } } void tst_qmlls_modules::compareQTextDocumentEdit(const TextDocumentEdit &a, @@ -965,7 +1019,7 @@ void tst_qmlls_modules::renameUsages() auto clean = [didFinish]() { *didFinish = true; }; m_protocol->requestRename( params, - [&](auto res) { + [&](auto &&res) { QScopeGuard cleanup(clean); auto *result = std::get_if<QLspSpecification::WorkspaceEdit>(&res); @@ -990,6 +1044,22 @@ void tst_qmlls_modules::renameUsages() compareQTextDocumentEdit( std::get<TextDocumentEdit>(documentChanges[i]), std::get<TextDocumentEdit>(expectedDocumentChanges[i])); + } else if (std::holds_alternative<RenameFile>(documentChanges[i])) { + const auto &actual = std::get<RenameFile>(documentChanges[i]); + const auto &expected = std::get<RenameFile>(expectedDocumentChanges[i]); + + QCOMPARE(actual.kind, expected.kind); + QCOMPARE(expected.kind, "rename"); + QCOMPARE(actual.oldUri, expected.oldUri); + QCOMPARE(actual.newUri, expected.newUri); + QCOMPARE(actual.options.has_value(), expected.options.has_value()); + if (expected.options.has_value()) { + QCOMPARE(actual.options->overwrite, expected.options->overwrite); + QCOMPARE(actual.options->ignoreIfExists, + expected.options->ignoreIfExists); + } + QCOMPARE(actual.annotationId, expected.annotationId); + } else { QFAIL("TODO: implement me!"); } @@ -1494,4 +1564,202 @@ void tst_qmlls_modules::quickFixes() QTRY_VERIFY_WITH_TIMEOUT(codeActionOk, 5000); } +static QQmlJS::Dom::DomItem fileObject(const QString &filePath) +{ + QFile f(filePath); + QQmlJS::Dom::DomItem file; + if (!f.open(QIODevice::ReadOnly)) + return file; + QString code = f.readAll(); + QQmlJS::Dom::DomCreationOptions options; + options.setFlag(QQmlJS::Dom::DomCreationOption::WithScriptExpressions); + options.setFlag(QQmlJS::Dom::DomCreationOption::WithSemanticAnalysis); + options.setFlag(QQmlJS::Dom::DomCreationOption::WithRecovery); + + QStringList dirs = {QLibraryInfo::path(QLibraryInfo::Qml2ImportsPath)}; + auto envPtr = QQmlJS::Dom::DomEnvironment::create(dirs, + QQmlJS::Dom::DomEnvironment::Option::SingleThreaded + | QQmlJS::Dom::DomEnvironment::Option::NoDependencies, options); + envPtr->loadBuiltins(); + envPtr->loadFile(QQmlJS::Dom::FileToLoad::fromMemory(envPtr, filePath, code), + [&file](QQmlJS::Dom::Path, const QQmlJS::Dom::DomItem &, const QQmlJS::Dom::DomItem &newIt) { + file = newIt.fileObject(); + }); + envPtr->loadPendingDependencies(); + return file; +}; + +void tst_qmlls_modules::semanticHighlightingFull_data() +{ + QTest::addColumn<QString>("filePath"); + QTest::addRow("bigfile") << u"highlighting/bigFile.qml"_s; +} + +void tst_qmlls_modules::semanticHighlightingFull() +{ + QFETCH(QString, filePath); + const auto item = fileObject(testFile(filePath)); + Highlights highlights; + const auto expectedData = highlights.collectTokens(item, std::nullopt); + + const auto uri = openFile(filePath); + QVERIFY(uri); + QLspSpecification::SemanticTokensParams params; + params.textDocument.uri = *uri; + std::shared_ptr<bool> didFinish = std::make_shared<bool>(false); + const auto cleanup = [didFinish]() { *didFinish = true; }; + + auto &&responseHandler = [&](auto res) { + QScopeGuard callAtExit(cleanup); + const auto *const result = std::get_if<QLspSpecification::SemanticTokens>(&res); + QVERIFY(result); + QList<int> data = result->data; + QCOMPARE(data.size(), expectedData.size()); + QCOMPARE(data, expectedData); + }; + + auto &&errorHandler = [&](auto &error) { + QScopeGuard callAtExit(cleanup); + ProtocolBase::defaultResponseErrorHandler(error); + QVERIFY2(false, "error occurred on full semantic tokens"); + }; + + m_protocol->requestSemanticTokens(params, std::move(responseHandler), std::move(errorHandler)); + QTRY_VERIFY_WITH_TIMEOUT(*didFinish, 10000); +} + +void tst_qmlls_modules::semanticHighlightingRange_data() +{ + QTest::addColumn<QString>("filePath"); + QTest::addColumn<QLspSpecification::Range>("range"); + QTest::addRow("bigfile") << u"highlighting/bigFile.qml"_s + << QLspSpecification::Range{ { 6, 0 }, { 15, 0 } }; +} + +void tst_qmlls_modules::semanticHighlightingRange() +{ + QFETCH(QString, filePath); + QFETCH(QLspSpecification::Range, range); + + const auto item = fileObject(testFile(filePath)); + Highlights highlights; + const auto qmlFile = item.as<QQmlJS::Dom::QmlFile>(); + const auto code = qmlFile->code(); + const int startOffset = int(QQmlLSUtils::textOffsetFrom(code, range.start.line, range.end.character)); + const int endOffset = int(QQmlLSUtils::textOffsetFrom(code, range.end.line, range.end.character)); + const auto expectedData = highlights.collectTokens(item, HighlightsRange{startOffset, endOffset}); + + const auto uri = openFile(filePath); + QVERIFY(uri); + QLspSpecification::SemanticTokensRangeParams params; + params.textDocument.uri = *uri; + params.range = range; + + std::shared_ptr<bool> didFinish = std::make_shared<bool>(false); + const auto cleanup = [didFinish]() { *didFinish = true; }; + + auto &&responseHandler = [&](auto res) { + QScopeGuard callAtExit(cleanup); + const auto *const result = std::get_if<QLspSpecification::SemanticTokens>(&res); + QVERIFY(result); + QList<int> data = result->data; + QCOMPARE(data.size(), expectedData.size()); + QCOMPARE(data, expectedData); + }; + + auto &&errorHandler = [&](auto &error) { + QScopeGuard callAtExit(cleanup); + ProtocolBase::defaultResponseErrorHandler(error); + QVERIFY2(false, "error occurred on full semantic tokens"); + }; + + m_protocol->requestSemanticTokensRange(params, std::move(responseHandler), + std::move(errorHandler)); + QTRY_VERIFY_WITH_TIMEOUT(*didFinish, 10000); +} + +void tst_qmlls_modules::semanticHighlightingDelta_data() +{ + QTest::addColumn<QString>("filePath"); + QTest::addRow("basicDelta") << u"highlighting/basic.qml"_s; +} + +void tst_qmlls_modules::semanticHighlightingDelta() +{ + QSKIP("This test should be skipped until QTBUG-124870 is fixed"); + QFETCH(QString, filePath); + QFETCH(QString, deltaFilePath); + + const auto fileItem = fileObject(testFile(filePath)); + const auto deltaFileItem = fileObject(testFile(deltaFilePath)); + Highlights highlights; + auto fullDocumentSemanticTokensData = highlights.collectTokens(fileItem, std::nullopt); + auto editedDocumentSemanticTokensData = highlights.collectTokens(deltaFileItem, std::nullopt); + const auto expectedEdits = HighlightingUtils::computeDiff(fullDocumentSemanticTokensData, editedDocumentSemanticTokensData); + + const auto uri = openFile(filePath); + QVERIFY(uri); + const auto deltaUri = openFile(deltaFilePath); + QVERIFY(deltaUri); + + std::shared_ptr<bool> didFinish = std::make_shared<bool>(false); + const auto cleanup = [didFinish]() { *didFinish = true; }; + + QLspSpecification::SemanticTokensDeltaParams params; + QLspSpecification::Responses::SemanticTokensDeltaResultType result; + + auto &&errorHandler = [&](auto &error) { + QScopeGuard callAtExit(cleanup); + ProtocolBase::defaultResponseErrorHandler(error); + QVERIFY2(false, "error occurred on semantic tokens/delta"); + }; + + QLspSpecification::SemanticTokensParams fullParams; + fullParams.textDocument.uri = *uri; + m_protocol->requestSemanticTokens(fullParams, + [&](auto res) { + QScopeGuard callAtExit(cleanup); + if (auto r = std::get_if<QLspSpecification::SemanticTokens>(&res)) { + params.previousResultId = r->resultId.value(); + fullDocumentSemanticTokensData = r->data; + } + }, errorHandler); + QTRY_VERIFY_WITH_TIMEOUT(*didFinish, 10000); + + // Change the file + DidChangeTextDocumentParams didChange; + didChange.textDocument.uri = *uri; + didChange.textDocument.version = 2; + + TextDocumentContentChangeEvent change; + change.range = Range{ Position{ 8, 4 }, Position{ 8, 4 } }; + change.text = "const Patron = 42"; + + didChange.contentChanges.append(change); + m_protocol->notifyDidChangeTextDocument(didChange); + + *didFinish = false; + params.textDocument.uri = *uri; + m_protocol->requestSemanticTokensDelta(params, + [&](auto res) { + QScopeGuard callAtExit(cleanup); + result = res; + }, std::move(errorHandler)); + QTRY_VERIFY_WITH_TIMEOUT(*didFinish, 10000); + + if (const auto *const delta = std::get_if<QLspSpecification::SemanticTokensDelta>(&result)) { + QVERIFY(delta); + const auto data = delta->edits.front().data; + const auto start = delta->edits.front().start; + const auto deleteCount = delta->edits.front().deleteCount; + QCOMPARE(start, expectedEdits.front().start); + QCOMPARE(deleteCount, expectedEdits.front().deleteCount); + QCOMPARE(data, expectedEdits.front().data); + } else { + const auto *const full = std::get_if<QLspSpecification::SemanticTokens>(&result); + QVERIFY(full); + QCOMPARE(full->data, expectedEdits.front().data); + } +} + QTEST_MAIN(tst_qmlls_modules) diff --git a/tests/auto/qmlls/modules/tst_qmlls_modules.h b/tests/auto/qmlls/modules/tst_qmlls_modules.h index d48bc99407..d7b601cf5a 100644 --- a/tests/auto/qmlls/modules/tst_qmlls_modules.h +++ b/tests/auto/qmlls/modules/tst_qmlls_modules.h @@ -69,7 +69,12 @@ private slots: void hover_data(); void hover(); void checkQuickSnippets(); - + void semanticHighlightingFull_data(); + void semanticHighlightingFull(); + void semanticHighlightingRange_data(); + void semanticHighlightingRange(); + void semanticHighlightingDelta_data(); + void semanticHighlightingDelta(); private: QProcess m_server; std::unique_ptr<QLanguageServerProtocol> m_protocol; diff --git a/tests/auto/qmlls/qqmlcodemodel/data/FileA.qml b/tests/auto/qmlls/qqmlcodemodel/data/FileA.qml new file mode 100644 index 0000000000..5560aee727 --- /dev/null +++ b/tests/auto/qmlls/qqmlcodemodel/data/FileA.qml @@ -0,0 +1,5 @@ +import QtQuick + +Item { + +} diff --git a/tests/auto/qmlls/qqmlcodemodel/data/FileA2.qml b/tests/auto/qmlls/qqmlcodemodel/data/FileA2.qml new file mode 100644 index 0000000000..7680c63f95 --- /dev/null +++ b/tests/auto/qmlls/qqmlcodemodel/data/FileA2.qml @@ -0,0 +1,5 @@ +import QtQuick + +Item { + property int helloProperty +}
\ No newline at end of file diff --git a/tests/auto/qmlls/qqmlcodemodel/data/FileB.qml b/tests/auto/qmlls/qqmlcodemodel/data/FileB.qml new file mode 100644 index 0000000000..03cd2f9fb3 --- /dev/null +++ b/tests/auto/qmlls/qqmlcodemodel/data/FileB.qml @@ -0,0 +1,5 @@ +import QtQuick + +FileA { + property int helloPropertyInB +} diff --git a/tests/auto/qmlls/qqmlcodemodel/tst_qmlls_qqmlcodemodel.cpp b/tests/auto/qmlls/qqmlcodemodel/tst_qmlls_qqmlcodemodel.cpp index f56839d99a..a3293769e5 100644 --- a/tests/auto/qmlls/qqmlcodemodel/tst_qmlls_qqmlcodemodel.cpp +++ b/tests/auto/qmlls/qqmlcodemodel/tst_qmlls_qqmlcodemodel.cpp @@ -128,4 +128,58 @@ void tst_qmlls_qqmlcodemodel::fileNamesToWatch() QVERIFY(fileNames.contains(u"helloworld.h"_s)); } +QString tst_qmlls_qqmlcodemodel::readFile(const QString &filename) const +{ + QFile f(testFile(filename)); + if (!f.open(QFile::ReadOnly)) { + QTest::qFail("Can't read test file", __FILE__, __LINE__); + return {}; + } + return f.readAll(); +} + +void tst_qmlls_qqmlcodemodel::openFiles() +{ + QmlLsp::QQmlCodeModel model; + + const QByteArray fileAUrl = testFileUrl(u"FileA.qml"_s).toEncoded(); + const QString fileAPath = testFile(u"FileA.qml"_s); + + // open file A + model.newOpenFile(fileAUrl, 0, readFile(u"FileA.qml"_s)); + + QTRY_VERIFY_WITH_TIMEOUT(model.validEnv().field(Fields::qmlFileWithPath).key(fileAPath), 3000); + + { + const DomItem fileAComponents = model.validEnv() + .field(Fields::qmlFileWithPath) + .key(fileAPath) + .field(Fields::currentItem) + .field(Fields::components); + // if there is no component then the lazy qml file was not loaded correctly. + QCOMPARE(fileAComponents.size(), 1); + } + + model.newDocForOpenFile(fileAUrl, 1, readFile(u"FileA2.qml"_s)); + + { + const DomItem fileAComponents = model.validEnv() + .field(Fields::qmlFileWithPath) + .key(fileAPath) + .field(Fields::currentItem) + .field(Fields::components); + // if there is no component then the lazy qml file was not loaded correctly. + QCOMPARE(fileAComponents.size(), 1); + + // also check if the property is there + const DomItem properties = fileAComponents.key(QString()) + .index(0) + .field(Fields::objects) + .index(0) + .field(Fields::propertyDefs); + QVERIFY(properties); + QVERIFY(properties.key(u"helloProperty"_s)); + } +} + QTEST_MAIN(tst_qmlls_qqmlcodemodel) diff --git a/tests/auto/qmlls/qqmlcodemodel/tst_qmlls_qqmlcodemodel.h b/tests/auto/qmlls/qqmlcodemodel/tst_qmlls_qqmlcodemodel.h index 45c88d908e..a913f4bd19 100644 --- a/tests/auto/qmlls/qqmlcodemodel/tst_qmlls_qqmlcodemodel.h +++ b/tests/auto/qmlls/qqmlcodemodel/tst_qmlls_qqmlcodemodel.h @@ -23,6 +23,7 @@ class tst_qmlls_qqmlcodemodel : public QQmlDataTest Q_OBJECT public: tst_qmlls_qqmlcodemodel(); + QString readFile(const QString &filename) const; private slots: void buildPathsForFileUrl_data(); @@ -30,6 +31,7 @@ private slots: void fileNamesToWatch(); void findFilePathsFromFileNames_data(); void findFilePathsFromFileNames(); + void openFiles(); }; #endif // TST_QMLLS_QQMLCODEMODEL_H diff --git a/tests/auto/qmlls/utils/CMakeLists.txt b/tests/auto/qmlls/utils/CMakeLists.txt index a8a4e9f0c4..ba81707b30 100644 --- a/tests/auto/qmlls/utils/CMakeLists.txt +++ b/tests/auto/qmlls/utils/CMakeLists.txt @@ -27,6 +27,36 @@ qt_internal_add_test(tst_qmlls_utils TESTDATA ${test_data} ) +qt_internal_add_test(tst_qmlls_highlighting + SOURCES + tst_qmlls_highlighting.cpp + DEFINES + QT_QMLLS_HIGHLIGHTS_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/data" + LIBRARIES + Qt::Core + Qt::QmlDomPrivate + Qt::LanguageServerPrivate + Qt::Test + Qt::QuickTestUtilsPrivate + Qt::QmlLSPrivate + TESTDATA ${test_data} +) + +qt_internal_add_test(tst_qmlls_documentationHints + SOURCES + tst_qmlls_documentationHints.cpp + DEFINES + QT_QMLLS_DOCUMENTATION_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/data" + LIBRARIES + Qt::Core + Qt::QmlDomPrivate + Qt::LanguageServerPrivate + Qt::Test + Qt::QuickTestUtilsPrivate + Qt::QmlLSPrivate + TESTDATA ${test_data} +) + qt_internal_extend_target(tst_qmlls_utils CONDITION ANDROID OR IOS DEFINES QT_QMLLS_UTILS_DATADIR=":/domdata" diff --git a/tests/auto/qmlls/utils/data/highlights/Identifiers.qml b/tests/auto/qmlls/utils/data/highlights/Identifiers.qml new file mode 100644 index 0000000000..7725b6d5e4 --- /dev/null +++ b/tests/auto/qmlls/utils/data/highlights/Identifiers.qml @@ -0,0 +1,37 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + readonly property int test: 34 + signal pressed() + function f() { + let sum = 0, sum2 = 0 + for(let i = 1; i < 42; i = i + 2) { + sum = test + i + { + let sum = 42; // another unrelated sum + } + } + // signal and property changed + testChanged(); + pressed(); + } + + // attached + Keys.onPressed: { + } + + // propertychanged handler + onTestChanged: { + f(); // method identifier + } + + // signal handler + onPressed: {} + + enum K { Plus} + property int tt: Identifiers.Plus // component and enum value + +} diff --git a/tests/auto/qmlls/utils/data/highlights/bindings.qml b/tests/auto/qmlls/utils/data/highlights/bindings.qml new file mode 100644 index 0000000000..ac1592e778 --- /dev/null +++ b/tests/auto/qmlls/utils/data/highlights/bindings.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 + +Item { + property int x: 45 + + Behavior on width {} + + x: width +} diff --git a/tests/auto/qmlls/utils/data/highlights/comments.qml b/tests/auto/qmlls/utils/data/highlights/comments.qml new file mode 100644 index 0000000000..351aaee36c --- /dev/null +++ b/tests/auto/qmlls/utils/data/highlights/comments.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 +Item { +/* + multiline comment +*/ + +/* single line comment */ +// another + + function inc() { + // in + + /* + inside js + */ + } +} diff --git a/tests/auto/qmlls/utils/data/highlights/enums.qml b/tests/auto/qmlls/utils/data/highlights/enums.qml new file mode 100644 index 0000000000..22183bf37f --- /dev/null +++ b/tests/auto/qmlls/utils/data/highlights/enums.qml @@ -0,0 +1,11 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQml + +QtObject { + enum Osc { + Sin, + Saw = 1 + } +} diff --git a/tests/auto/qmlls/utils/data/highlights/identifiers.qml b/tests/auto/qmlls/utils/data/highlights/identifiers.qml new file mode 100644 index 0000000000..7725b6d5e4 --- /dev/null +++ b/tests/auto/qmlls/utils/data/highlights/identifiers.qml @@ -0,0 +1,37 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + readonly property int test: 34 + signal pressed() + function f() { + let sum = 0, sum2 = 0 + for(let i = 1; i < 42; i = i + 2) { + sum = test + i + { + let sum = 42; // another unrelated sum + } + } + // signal and property changed + testChanged(); + pressed(); + } + + // attached + Keys.onPressed: { + } + + // propertychanged handler + onTestChanged: { + f(); // method identifier + } + + // signal handler + onPressed: {} + + enum K { Plus} + property int tt: Identifiers.Plus // component and enum value + +} diff --git a/tests/auto/qmlls/utils/data/highlights/imports.qml b/tests/auto/qmlls/utils/data/highlights/imports.qml new file mode 100644 index 0000000000..1e69077070 --- /dev/null +++ b/tests/auto/qmlls/utils/data/highlights/imports.qml @@ -0,0 +1,9 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQml 2.15 +import "X" as Patron + +Item { +} diff --git a/tests/auto/qmlls/utils/data/highlights/literals.qml b/tests/auto/qmlls/utils/data/highlights/literals.qml new file mode 100644 index 0000000000..520ed5d2ef --- /dev/null +++ b/tests/auto/qmlls/utils/data/highlights/literals.qml @@ -0,0 +1,14 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + property int a: 123 + property string b: "single" + property string c: "multi + line string"; + property bool d: true + property var e: null + +} diff --git a/tests/auto/qmlls/utils/data/highlights/methodAndSignal.qml b/tests/auto/qmlls/utils/data/highlights/methodAndSignal.qml new file mode 100644 index 0000000000..4e8319f049 --- /dev/null +++ b/tests/auto/qmlls/utils/data/highlights/methodAndSignal.qml @@ -0,0 +1,11 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + signal p() + signal q(int a) + signal r(a: int) + function a(b: int) : int {} +} diff --git a/tests/auto/qmlls/utils/data/highlights/objectAndComponent.qml b/tests/auto/qmlls/utils/data/highlights/objectAndComponent.qml new file mode 100644 index 0000000000..9165e4b1b5 --- /dev/null +++ b/tests/auto/qmlls/utils/data/highlights/objectAndComponent.qml @@ -0,0 +1,11 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + component Patron: Item {} + Item { + id: inner + } +} diff --git a/tests/auto/qmlls/utils/data/highlights/pragmas.qml b/tests/auto/qmlls/utils/data/highlights/pragmas.qml new file mode 100644 index 0000000000..cf99c93584 --- /dev/null +++ b/tests/auto/qmlls/utils/data/highlights/pragmas.qml @@ -0,0 +1,10 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +pragma Singleton +pragma FunctionSignatureBehavior: Enforced +pragma ValueTypeBehavior: Copy,Addressable + +import QtQml + +QtObject {} diff --git a/tests/auto/qmlls/utils/data/highlights/properties.qml b/tests/auto/qmlls/utils/data/highlights/properties.qml new file mode 100644 index 0000000000..bde60915ca --- /dev/null +++ b/tests/auto/qmlls/utils/data/highlights/properties.qml @@ -0,0 +1,13 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + Item { + property int k + readonly property int kk + required property int kkk + default property int kkkk + } +} diff --git a/tests/auto/qmlls/utils/data/highlights/scriptExpressions.qml b/tests/auto/qmlls/utils/data/highlights/scriptExpressions.qml new file mode 100644 index 0000000000..ee0b4ff5f8 --- /dev/null +++ b/tests/auto/qmlls/utils/data/highlights/scriptExpressions.qml @@ -0,0 +1,116 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + function k() { + } + + function mafik() { + var patron = 34; + const upperLimit = 42; + do { + ++patron; + if (patron < 2) + continue; + else + ++patron; + } while (patron < upperLimit) + switch (patron) { + case 1: + return 23; + default: + break; + } + try { + {} + } catch (error) { + {} + } finally {} + for (const a in [1, 2, 3]) { + throw 2; + } + } + + enum Test { + LOG + } + + readonly property int t: 34 + signal tt + required property int k + + signal kkk(string a) + signal yyy(a: string) + + function ttt() { + + } + +function createComplexExpression(...objects) { + // Create an object that holds some data + let data = { + a: 5, + b: 10, + c: 3 + }; + + // Create a complex expression using the data object + let expression = ((data.a + data.b * data.c) / (data.a - data.b)) ** data.c; + + return expression; +} + + function set1() { + const array = [1,2,3,4]; + const [a, b] = [1,2]; + const [aa, , bb] = array; + const [aaa = 23, bbb] = array; + const [a1, b1, ...rest1] = array; + const [a2, , b2, ...rest2] = array; + const [a3, b3, ...{ pop, push }] = array; + const [a4, b4, ...[c, d]] = array; + + const obj = {_a:1,_b:2}; + const { a5, b5 } = obj; + const { a6: a_, b6: b1_ } = obj; + const { a7: a11 = 4, b11 = 34, c1: b111, d1 } = obj; + let key = a; + const { [key]: a___ } = obj; + } + + function set2(s : int) : int { + // declare first + let a, b, a1, b1, c, d, rest, pop, push; + const array = [1,2,3,4]; + [a, b] = array; + [a, , b] = array; + [a = aDefault, b] = array; + [a, b, ...rest] = array; + [a, , b, ...rest] = array; + [a, b, ...{ pop, push }] = array; + [a, b, ...[c, d]] = array; + + const obj = {_a:1,_b:2}; + ({ a, b } = obj); // brackets are required + ({ a: a1, b: b1 } = obj); + + const complicatedObject = { + a: 1, + b: { + c: 2, + d: { + e: 3, + f: [4, 5, 6] + } + }, + g: [7, 8, 9] + }; + + const { patron, b: { mafik, d: { e, f: [ , secondF, ...restF ] } }, g: [ firstG, ...restG ] } = complicatedObject; + } + + +} + diff --git a/tests/auto/qmlls/utils/data/renaming/RenameMe.qml b/tests/auto/qmlls/utils/data/renaming/RenameMe.qml new file mode 100644 index 0000000000..adc3da9800 --- /dev/null +++ b/tests/auto/qmlls/utils/data/renaming/RenameMe.qml @@ -0,0 +1,5 @@ +import QtQuick + +Item { + property int i42 +}
\ No newline at end of file diff --git a/tests/auto/qmlls/utils/data/renaming/RenameMe2.ui.qml b/tests/auto/qmlls/utils/data/renaming/RenameMe2.ui.qml new file mode 100644 index 0000000000..35320e03ff --- /dev/null +++ b/tests/auto/qmlls/utils/data/renaming/RenameMe2.ui.qml @@ -0,0 +1,5 @@ +import QtQuick + +Item { + property string i42 +}
\ No newline at end of file diff --git a/tests/auto/qmlls/utils/data/renaming/RenamedByQmldir.qml b/tests/auto/qmlls/utils/data/renaming/RenamedByQmldir.qml new file mode 100644 index 0000000000..f97cbcf115 --- /dev/null +++ b/tests/auto/qmlls/utils/data/renaming/RenamedByQmldir.qml @@ -0,0 +1,4 @@ +import QtQuick + +Item { +} diff --git a/tests/auto/qmlls/utils/data/renaming/UnrelatedFile.qml b/tests/auto/qmlls/utils/data/renaming/UnrelatedFile.qml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/auto/qmlls/utils/data/renaming/UnrelatedFile.qml diff --git a/tests/auto/qmlls/utils/data/renaming/main.qml b/tests/auto/qmlls/utils/data/renaming/main.qml new file mode 100644 index 0000000000..10afda9773 --- /dev/null +++ b/tests/auto/qmlls/utils/data/renaming/main.qml @@ -0,0 +1,7 @@ +import QtQuick + +Item { + RenameMe {} + RenameMe2 {} + HelloWorld {} +} diff --git a/tests/auto/qmlls/utils/data/renaming/qmldir b/tests/auto/qmlls/utils/data/renaming/qmldir new file mode 100644 index 0000000000..8cff297e26 --- /dev/null +++ b/tests/auto/qmlls/utils/data/renaming/qmldir @@ -0,0 +1,6 @@ +module renaming +RenameMe 254.0 RenameMe.qml +RenameMe2 254.0 RenameMe2.ui.qml +RenameMe3 254.0 subfolder/RenameMe3.qml +main 254.0 main.qml +HelloWorld 254.0 RenamedByQmldir.qml
\ No newline at end of file diff --git a/tests/auto/qmlls/utils/tst_qmlls_documentationHints.cpp b/tests/auto/qmlls/utils/tst_qmlls_documentationHints.cpp new file mode 100644 index 0000000000..dcb6f47df9 --- /dev/null +++ b/tests/auto/qmlls/utils/tst_qmlls_documentationHints.cpp @@ -0,0 +1,134 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "tst_qmlls_documentationHints.h" + +#include <QtQmlLS/private/qdochtmlparser_p.h> +#include <QtQmlDom/private/qqmldomtop_p.h> + +tst_qmlls_documentationHints::tst_qmlls_documentationHints() + : QQmlDataTest(QT_QMLLS_DOCUMENTATION_DATADIR) , m_documentationDataDir(QT_QMLLS_DOCUMENTATION_DATADIR + "/documentationHints"_L1) +{ +} + +void tst_qmlls_documentationHints::qdochtmlparser_data() +{ + using namespace QQmlJS::Dom; + QTest::addColumn<QString>("filePath"); + QTest::addColumn<QString>("keyword"); + QTest::addColumn<DomType>("domType"); + QTest::addColumn<HtmlExtractor::ExtractionMode>("extractionMode"); + QTest::addColumn<QString>("expectedDocumentation"); + + QTest::addRow("qml-object-type-extended-plaintext") + << testFile("qdochtmlparser/qml-qtqml-qtobject.html") + << "QtObject" + << DomType::QmlObject + << HtmlExtractor::ExtractionMode::Extended + << R"(The QtObject type is a non-visual element which contains only the objectName property. +It can be useful to create a QtObject if you need an extremely lightweight type to enclose a set of custom properties: + + import QtQuick + + Item { + QtObject { + id: attributes + property string name + property int size + property variant attributes + } + + Text { text: attributes.name } + } + +It can also be useful for C++ integration, as it is just a plain QObject. See the QObject documentation for further details.)"; + + QTest::addRow("qml-object-type-simplified-plaintext") + << testFile("qdochtmlparser/qml-qtqml-qtobject.html") + << "QtObject" + << DomType::QmlObject + << HtmlExtractor::ExtractionMode::Simplified + << R"(A basic QML type.)"; + + QTest::addRow("qml-property-simplified-plaintext") + << testFile("qdochtmlparser/qml-qtqml-qtobject.html") + << "objectName" + << DomType::PropertyDefinition + << HtmlExtractor::ExtractionMode::Simplified + << R"(This property holds the QObject::objectName for this specific object instance.)"; + + QTest::addRow("qml-property-simplified-plaintext-from-Qt5") + << testFile("qdochtmlparser/qml-qtqml-qtobject-qt-5.html") + << "objectName" + << DomType::PropertyDefinition + << HtmlExtractor::ExtractionMode::Simplified + << R"(This property holds the QObject::objectName for this specific object instance.)"; + + QTest::addRow("qml-property-simplified-plaintext") + << testFile("qdochtmlparser/qml-qtquick-item.html") + << "width" + << DomType::PropertyDefinition + << HtmlExtractor::ExtractionMode::Simplified + << R"(Defines the item's position and size. The default value is 0.)"; + + QTest::addRow("qml-group-property-simplified-plaintext") + << testFile("qdochtmlparser/qml-qtquick-item.html") + << "anchors.fill" + << DomType::PropertyDefinition + << HtmlExtractor::ExtractionMode::Simplified + << R"(Anchors provide a way to position an item by specifying its relationship with other items.)"; + QTest::addRow("qml-functions") + << testFile("qdochtmlparser/qml-qtquick-item.html") + << "mapFromGlobal" + << DomType::MethodInfo + << HtmlExtractor::ExtractionMode::Simplified + << "Maps the point (x, y), which is in the global coordinate system, to the item's coordinate system," + " and returns a point matching the mapped coordinate."; + + QTest::addRow("qml-functions-list") + << testFile("qdochtmlparser/qml-qtquick-item.html") + << "mapFromItem" + << DomType::MethodInfo + << HtmlExtractor::ExtractionMode::Simplified + << "Maps the point (x, y) or rect (x, y, width, height), which is in item's coordinate system," + " to this item's coordinate system, and returns a point or rect matching the mapped coordinate."; + QTest::addRow("qml-signal") + << testFile("qdochtmlparser/qml-qtquick-mousearea.html") + << "pressAndHold" + << DomType::MethodInfo + << HtmlExtractor::ExtractionMode::Simplified + << "This signal is emitted when there is a long press (currently 800ms). The mouse parameter provides information about the press, " + "including the x and y position of the press, and which button is pressed."; + + // Some properties and methods can be shown as in groups in qt-docs, like width and height of Item. + QTest::addRow("multiple-entries") + << testFile("qdochtmlparser/qml-qtquick-mousearea.html") + << "pressAndHold" + << DomType::MethodInfo + << HtmlExtractor::ExtractionMode::Simplified + << "This signal is emitted when there is a long press (currently 800ms). The mouse parameter provides information about the press, " + "including the x and y position of the press, and which button is pressed."; +} + +void tst_qmlls_documentationHints::qdochtmlparser() +{ + using namespace QQmlJS::Dom; + QFETCH(QString, filePath); + QFETCH(QString, keyword); + QFETCH(DomType, domType); + QFETCH(HtmlExtractor::ExtractionMode, extractionMode); + QFETCH(QString, expectedDocumentation); + + const auto htmlCode = [](const QString &testFileName) { + QFile file(testFileName); + if (file.open(QIODeviceBase::ReadOnly | QIODevice::Text)) + return QString::fromUtf8(file.readAll()); + return QString{}; + }(filePath); + + ExtractDocumentation extractor(domType); + const auto actual = extractor.execute(htmlCode, keyword, extractionMode); + QCOMPARE(actual, expectedDocumentation); +} + +QTEST_MAIN(tst_qmlls_documentationHints) diff --git a/tests/auto/qmlls/utils/tst_qmlls_documentationHints.h b/tests/auto/qmlls/utils/tst_qmlls_documentationHints.h new file mode 100644 index 0000000000..f3b0f4ddd1 --- /dev/null +++ b/tests/auto/qmlls/utils/tst_qmlls_documentationHints.h @@ -0,0 +1,24 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef TST_QMLLS_DOCUMENTATION_H +#define TST_QMLLS_DOCUMENTATION_H + +#include <QtQuickTestUtils/private/qmlutils_p.h> +#include <QtCore/qobject.h> +#include <QtTest/qtest.h> + +class tst_qmlls_documentationHints : public QQmlDataTest +{ + Q_OBJECT +public: + tst_qmlls_documentationHints(); +private slots: + void qdochtmlparser_data(); + void qdochtmlparser(); + +private: + QString m_documentationDataDir; +}; + +#endif // TST_QMLLS_DOCUMENTATION_H diff --git a/tests/auto/qmlls/utils/tst_qmlls_highlighting.cpp b/tests/auto/qmlls/utils/tst_qmlls_highlighting.cpp new file mode 100644 index 0000000000..f21de11990 --- /dev/null +++ b/tests/auto/qmlls/utils/tst_qmlls_highlighting.cpp @@ -0,0 +1,650 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "tst_qmlls_highlighting.h" + +#include <QtQml/private/qqmljsengine_p.h> +#include <QtQml/private/qqmljslexer_p.h> +#include <QtQml/private/qqmljsparser_p.h> +#include <QtQmlDom/private/qqmldomitem_p.h> +#include <QtQmlDom/private/qqmldomtop_p.h> +#include <QtQmlLS/private/qqmlsemantictokens_p.h> +#include <QtCore/qlibraryinfo.h> +#include <QtLanguageServer/private/qlanguageserverspectypes_p.h> + +#include <qlist.h> + +using namespace QLspSpecification; + +tst_qmlls_highlighting::tst_qmlls_highlighting() + : QQmlDataTest(QT_QMLLS_HIGHLIGHTS_DATADIR) , m_highlightingDataDir(QT_QMLLS_HIGHLIGHTS_DATADIR + "/highlights"_L1) +{ +} + +// Token encoding as in: +// https://microsoft.github.io/language-server-protocol/specifications/specification-3-16/#textDocument_semanticTokens +void tst_qmlls_highlighting::encodeSemanticTokens_data() +{ + QTest::addColumn<Highlights>("highlights"); + QTest::addColumn<QList<int>>("expectedMemoryLayout"); + + { + Highlights c; + c.highlights().insert(0, Token()); + QTest::addRow("empty-token-single") << c << QList {0, 0, 0, 0, 0}; + } + { + Highlights c; + QQmlJS::SourceLocation loc(0, 1, 1, 1); + c.highlights().insert(0, Token(loc, 0, 0)); + QTest::addRow("single-token") << c << QList {0, 0, 1, 0, 0}; + } + { + Highlights c; + Token t1(QQmlJS::SourceLocation(0, 1, 1, 1), 0, 0); + Token t2(QQmlJS::SourceLocation(1, 1, 3, 3), 0, 0); + c.highlights().insert(t1.offset, t1); + c.highlights().insert(t2.offset, t2); + QTest::addRow("different-lines") << c << QList {0, 0, 1, 0, 0, 2, 2, 1, 0, 0}; + } + { + Highlights c; + Token t1(QQmlJS::SourceLocation(0, 1, 1, 1), 0, 0); + Token t2(QQmlJS::SourceLocation(1, 1, 1, 3), 0, 0); + c.highlights().insert(t1.offset, t1); + c.highlights().insert(t2.offset, t2); + QTest::addRow("same-line-different-column") << c << QList {0, 0, 1, 0, 0, 0, 2, 1, 0, 0}; + } + { + Highlights c; + Token t1(QQmlJS::SourceLocation(0, 1, 1, 1), 1, 0); + c.highlights().insert(t1.offset, t1); + QTest::addRow("token-type") << c << QList {0, 0, 1, 1, 0}; + } + { + Highlights c; + Token t1(QQmlJS::SourceLocation(0, 1, 1, 1), 1, 1); + c.highlights().insert(t1.offset, t1); + QTest::addRow("token-modifier") << c << QList {0, 0, 1, 1, 1}; + } +} + +void tst_qmlls_highlighting::encodeSemanticTokens() +{ + QFETCH(Highlights, highlights); + QFETCH(QList<int>, expectedMemoryLayout); + const auto encoded = HighlightingUtils::encodeSemanticTokens(highlights); + QCOMPARE(encoded, expectedMemoryLayout); +} + +struct LineLength +{ + quint32 startLine; + quint32 length; +}; + +void tst_qmlls_highlighting::sourceLocationsFromMultiLineToken_data() +{ + QTest::addColumn<QString>("source"); + QTest::addColumn<QList<LineLength>>("expectedLines"); + + QTest::addRow("multilineComment1") << R"("line 1 +line 2 +line 3 ")" << QList{ LineLength{ 1, 7 }, LineLength{ 2, 6 }, LineLength{ 3, 8 } }; + + QTest::addRow("prePostNewlines") << + R"(" + +")" << QList{ LineLength{ 1, 1 }, LineLength{ 2, 0 }, LineLength{ 3, 1 } }; + QTest::addRow("windows-newline") + << QString::fromUtf8("\"test\r\nwindows\r\nnewline\"") + << QList{ LineLength{ 1, 5 }, LineLength{ 2, 7 }, LineLength{ 3, 8 } }; +} + +void tst_qmlls_highlighting::sourceLocationsFromMultiLineToken() +{ + QFETCH(QString, source); + QFETCH(QList<LineLength>, expectedLines); + using namespace QQmlJS::AST; + + QQmlJS::Engine jsEngine; + QQmlJS::Lexer lexer(&jsEngine); + lexer.setCode(source, 1, true); + QQmlJS::Parser parser(&jsEngine); + parser.parseExpression(); + const auto expression = parser.expression(); + + auto *literal = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(expression); + const auto locs = + HighlightingUtils::sourceLocationsFromMultiLineToken(source, literal->literalToken); + + [&]() { + QCOMPARE(locs.size(), expectedLines.size()); + + for (auto i = 0; i < locs.size(); ++i) { + QCOMPARE(locs[i].startLine, expectedLines[i].startLine); + QCOMPARE(locs[i].length, expectedLines[i].length); + } + }(); + + if (QTest::currentTestFailed()) { + + qDebug() << "Actual locations"; + for (auto i = 0; i < locs.size(); ++i) { + qDebug() << "Startline :" << locs[i].startLine << "Length " << locs[i].length; + } + + qDebug() << "Expected locations"; + for (auto i = 0; i < expectedLines.size(); ++i) { + qDebug() << "Startline :" << expectedLines[i].startLine + << "Length :" << expectedLines[i].length; + } + } +} + +void tst_qmlls_highlighting::highlights_data() +{ + using namespace QQmlJS::Dom; + QTest::addColumn<DomItem>("fileItem"); + QTest::addColumn<Token>("expectedHighlightedToken"); + + const auto fileObject = [](const QString &filePath){ + QFile f(filePath); + DomItem file; + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) + return file; + QString code = f.readAll(); + DomCreationOptions options; + options.setFlag(DomCreationOption::WithScriptExpressions); + options.setFlag(DomCreationOption::WithSemanticAnalysis); + options.setFlag(DomCreationOption::WithRecovery); + + QStringList dirs = {QLibraryInfo::path(QLibraryInfo::Qml2ImportsPath)}; + auto envPtr = DomEnvironment::create(dirs, + QQmlJS::Dom::DomEnvironment::Option::SingleThreaded + | QQmlJS::Dom::DomEnvironment::Option::NoDependencies, options); + envPtr->loadBuiltins(); + envPtr->loadFile(FileToLoad::fromMemory(envPtr, filePath, code), + [&file](Path, const DomItem &, const DomItem &newIt) { + file = newIt.fileObject(); + }); + envPtr->loadPendingDependencies(); + return file; + }; + + { // Comments + const auto filePath = m_highlightingDataDir + "/comments.qml"; + const auto fileItem = fileObject(filePath); + // Copyright (C) 2023 The Qt Company Ltd. + QTest::addRow("single-line-1") + << fileItem + << Token(QQmlJS::SourceLocation(0, 41, 1, 1), int(SemanticTokenTypes::Comment), 0); + + /* single line comment */ + QTest::addRow("single-line-2") << fileItem + << Token(QQmlJS::SourceLocation(162, 28, 9, 1), + int(SemanticTokenTypes::Comment), 0); + + // Multiline comments are split into multiple locations + QTest::addRow("multiline-first-line") + << fileItem + << Token(QQmlJS::SourceLocation(133, 2, 5, 1), int(SemanticTokenTypes::Comment), 0); + QTest::addRow("multiline-second-line") << fileItem + << Token(QQmlJS::SourceLocation(136, 21, 6, 1), + int(SemanticTokenTypes::Comment), 0); + QTest::addRow("multiline-third-line") + << fileItem + << Token(QQmlJS::SourceLocation(158, 2, 7, 1), int(SemanticTokenTypes::Comment), 0); + + // Comments Inside Js blocks + QTest::addRow("inside-js") << fileItem + << Token(QQmlJS::SourceLocation(232, 5, 13, 9), + int(SemanticTokenTypes::Comment), 0); + } + { // Imports + const auto filePath = m_highlightingDataDir + "/imports.qml"; + const auto fileItem = fileObject(filePath); + QTest::addRow("import-keyword") + << fileItem + << Token(QQmlJS::SourceLocation(112, 6, 4, 1), int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("module-uri") << fileItem + << Token(QQmlJS::SourceLocation(119, 7, 4, 8), + int(SemanticTokenTypes::Namespace), 0); + QTest::addRow("directory-uri") + << fileItem + << Token(QQmlJS::SourceLocation(152, 3, 6, 8), int(SemanticTokenTypes::String), 0); + QTest::addRow("as-keyword") << fileItem + << Token(QQmlJS::SourceLocation(156, 2, 6, 12), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("version-number") + << fileItem + << Token(QQmlJS::SourceLocation(140, 4, 5, 14), int(SemanticTokenTypes::Number), 0); + QTest::addRow("qualified-namespace") << fileItem + << Token(QQmlJS::SourceLocation(159, 6, 6, 15), + int(SemanticTokenTypes::Namespace), 0); + } + { // Bindings + const auto filePath = m_highlightingDataDir + "/bindings.qml"; + const auto fileItem = fileObject(filePath); + + // normal binding + QTest::addRow("normalBinding") << fileItem + << Token(QQmlJS::SourceLocation(189, 1, 11, 5), + int(SemanticTokenTypes::Property), 0); + // on binding + QTest::addRow("on-binding") << fileItem + << Token(QQmlJS::SourceLocation(175, 5, 9, 17), + int(SemanticTokenTypes::Property), 0); + QTest::addRow("on-keyword") << fileItem + << Token(QQmlJS::SourceLocation(172, 2, 9, 14), + int(SemanticTokenTypes::Keyword), 0); + } + { // Pragmas + const auto filePath = m_highlightingDataDir + "/pragmas.qml"; + const auto fileItem = fileObject(filePath); + QTest::addRow("pragma-keyword") + << fileItem + << Token(QQmlJS::SourceLocation(112, 6, 4, 1), int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("pragma-name") << fileItem + << Token(QQmlJS::SourceLocation(136, 25, 5, 8), + int(SemanticTokenTypes::Variable), 0); + QTest::addRow("pragma-value") << fileItem + << Token(QQmlJS::SourceLocation(198, 4, 6, 27), + int(SemanticTokenTypes::Variable), 0); + } + { // Enums + const auto filePath = m_highlightingDataDir + "/enums.qml"; + const auto fileItem = fileObject(filePath); + QTest::addRow("enum-keyword") + << fileItem + << Token(QQmlJS::SourceLocation(141, 4, 7, 5), int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("enum-name") + << fileItem + << Token(QQmlJS::SourceLocation(146, 3, 7, 10), int(SemanticTokenTypes::Enum), 0); + QTest::addRow("enum-item") << fileItem + << Token(QQmlJS::SourceLocation(160, 3, 8, 9), + int(SemanticTokenTypes::EnumMember), 0); + QTest::addRow("enum-value") + << fileItem + << Token(QQmlJS::SourceLocation(179, 1, 9, 15), int(SemanticTokenTypes::Number), 0); + } + { // objects and inline components + const auto filePath = m_highlightingDataDir + "/objectAndComponent.qml"; + const auto fileItem = fileObject(filePath); + + // object + QTest::addRow("object-identifier") + << fileItem + << Token(QQmlJS::SourceLocation(169, 4, 8, 5), int(SemanticTokenTypes::Type), 0); + QTest::addRow("object-id-property") << fileItem + << Token(QQmlJS::SourceLocation(184, 2, 9, 9), + int(SemanticTokenTypes::Property), 0); + QTest::addRow("object-id-name") << fileItem + << Token(QQmlJS::SourceLocation(188, 5, 9, 13), + int(SemanticTokenTypes::Variable), 0); + + // component + QTest::addRow("component-keyword") + << fileItem + << Token(QQmlJS::SourceLocation(139, 9, 7, 5), int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("component-name") + << fileItem + << Token(QQmlJS::SourceLocation(149, 6, 7, 15), int(SemanticTokenTypes::Type), 0); + } + { // property definition + const auto filePath = m_highlightingDataDir + "/properties.qml"; + const auto fileItem = fileObject(filePath); + + int definitionModifier = 1 << int(SemanticTokenModifiers::Definition); + QTest::addRow("property-keyword") + << fileItem + << Token(QQmlJS::SourceLocation(154, 8, 8, 9), int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("property-type") + << fileItem + << Token(QQmlJS::SourceLocation(163, 3, 8, 18), int(SemanticTokenTypes::Type), 0); + QTest::addRow("property-name") + << fileItem + << Token(QQmlJS::SourceLocation(167, 1, 8, 22), int(SemanticTokenTypes::Property), + definitionModifier); + int readOnlyModifier = definitionModifier | (1 << int(SemanticTokenModifiers::Readonly)); + QTest::addRow("readonly-keyword") + << fileItem + << Token(QQmlJS::SourceLocation(177, 8, 9, 9), int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("readonly-modifier") + << fileItem + << Token(QQmlJS::SourceLocation(199, 2, 9, 31), int(SemanticTokenTypes::Property), + readOnlyModifier); + int requiredModifier = definitionModifier | (1 << int(SemanticTokenModifiers::Abstract)); + QTest::addRow("required-keyword") << fileItem + << Token(QQmlJS::SourceLocation(210, 8, 10, 9), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("required-modifier") + << fileItem + << Token(QQmlJS::SourceLocation(232, 3, 10, 31), int(SemanticTokenTypes::Property), + requiredModifier); + int defaultModifier = + definitionModifier | (1 << int(SemanticTokenModifiers::DefaultLibrary)); + QTest::addRow("default-keyword") << fileItem + << Token(QQmlJS::SourceLocation(244, 7, 11, 9), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("default-modifier") + << fileItem + << Token(QQmlJS::SourceLocation(265, 4, 11, 30), int(SemanticTokenTypes::Property), + defaultModifier); + } + { + // methods and signals + const auto filePath = m_highlightingDataDir + "/methodAndSignal.qml"; + const auto fileItem = fileObject(filePath); + + QTest::addRow("signal-keyword") + << fileItem + << Token(QQmlJS::SourceLocation(139, 6, 7, 5), int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("signal-name") + << fileItem + << Token(QQmlJS::SourceLocation(146, 1, 7, 12), int(SemanticTokenTypes::Method), 0); + QTest::addRow("signal-type") + << fileItem + << Token(QQmlJS::SourceLocation(163, 3, 8, 14), int(SemanticTokenTypes::Type), 0); + QTest::addRow("signal-type-2") + << fileItem + << Token(QQmlJS::SourceLocation(186, 3, 9, 17), int(SemanticTokenTypes::Type), 0); + QTest::addRow("function-keyword") << fileItem + << Token(QQmlJS::SourceLocation(195, 9, 10, 5), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("function-name") << fileItem + << Token(QQmlJS::SourceLocation(204, 1, 10, 14), + int(SemanticTokenTypes::Method), 0); + QTest::addRow("function-prm-type") + << fileItem + << Token(QQmlJS::SourceLocation(209, 3, 10, 19), int(SemanticTokenTypes::Type), 0); + QTest::addRow("function-prm-name") << fileItem + << Token(QQmlJS::SourceLocation(206, 1, 10, 16), + int(SemanticTokenTypes::Parameter), 0); + QTest::addRow("function-rtn-type") + << fileItem + << Token(QQmlJS::SourceLocation(216, 3, 10, 26), int(SemanticTokenTypes::Type), 0); + } + { // literals + const auto filePath = m_highlightingDataDir + "/literals.qml"; + const auto fileItem = fileObject(filePath); + + QTest::addRow("number") << fileItem + << Token(QQmlJS::SourceLocation(155, 3, 7, 21), + int(SemanticTokenTypes::Number), 0); + QTest::addRow("singleline-string") + << fileItem + << Token(QQmlJS::SourceLocation(182, 8, 8, 24), int(SemanticTokenTypes::String), 0); + QTest::addRow("multiline-string-first") + << fileItem + << Token(QQmlJS::SourceLocation(214, 6, 9, 24), int(SemanticTokenTypes::String), 0); + QTest::addRow("multiline-string-second") << fileItem + << Token(QQmlJS::SourceLocation(221, 16, 10, 1), + int(SemanticTokenTypes::String), 0); + QTest::addRow("boolean") << fileItem + << Token(QQmlJS::SourceLocation(260, 4, 11, 22), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("null") << fileItem + << Token(QQmlJS::SourceLocation(285, 4, 12, 21), + int(SemanticTokenTypes::Keyword), 0); + } + { // identifiers + const auto filePath = m_highlightingDataDir + "/Identifiers.qml"; + const auto fileItem = fileObject(filePath); + QTest::addRow("js-property") << fileItem + << Token(QQmlJS::SourceLocation(222, 3, 10, 13), + int(SemanticTokenTypes::Variable), 0); + QTest::addRow("property-id") + << fileItem + << Token(QQmlJS::SourceLocation(302, 4, 12, 19), int(SemanticTokenTypes::Property), + (1 << int(SemanticTokenModifiers::Readonly))); + QTest::addRow("property-changed") << fileItem + << Token(QQmlJS::SourceLocation(451, 11, 18, 9), + int(SemanticTokenTypes::Method), 0); + QTest::addRow("signal") << fileItem + << Token(QQmlJS::SourceLocation(474, 7, 19, 9), + int(SemanticTokenTypes::Method), 0); + + QTest::addRow("attached-id") + << fileItem + << Token(QQmlJS::SourceLocation(512, 4, 23, 5), int(SemanticTokenTypes::Type), 0); + QTest::addRow("attached-signalhandler") << fileItem + << Token(QQmlJS::SourceLocation(517, 9, 23, 10), + int(SemanticTokenTypes::Method), 0); + QTest::addRow("propchanged-handler") << fileItem + << Token(QQmlJS::SourceLocation(572, 13, 27, 5), + int(SemanticTokenTypes::Method), 0); + QTest::addRow("method-id") + << fileItem + << Token(QQmlJS::SourceLocation(597, 1, 28, 9), int(SemanticTokenTypes::Method), 0); + QTest::addRow("signal-handler") + << fileItem + << Token(QQmlJS::SourceLocation(656, 9, 32, 5), int(SemanticTokenTypes::Method), 0); + } + { // script expressions + const auto filePath = m_highlightingDataDir + "/scriptExpressions.qml"; + const auto fileItem = fileObject(filePath); + + QTest::addRow("var-keyword") << fileItem + << Token(QQmlJS::SourceLocation(192, 3, 11, 9), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("const-keyword") << fileItem + << Token(QQmlJS::SourceLocation(217, 5, 12, 9), + int(SemanticTokenTypes::Keyword), 0); + const auto modifier = (1 << int(SemanticTokenModifiers::Readonly)); + QTest::addRow("const-name") << fileItem + << Token(QQmlJS::SourceLocation(223, 10, 12, 15), + int(SemanticTokenTypes::Variable), modifier); + QTest::addRow("do-keyword") << fileItem + << Token(QQmlJS::SourceLocation(248, 2, 13, 9), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("if-keyword") << fileItem + << Token(QQmlJS::SourceLocation(287, 2, 15, 13), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("continue-keyword") << fileItem + << Token(QQmlJS::SourceLocation(319, 8, 16, 17), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("else-keyword") << fileItem + << Token(QQmlJS::SourceLocation(341, 4, 17, 13), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("while-keyword") << fileItem + << Token(QQmlJS::SourceLocation(382, 5, 19, 11), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("switch-keyword") << fileItem + << Token(QQmlJS::SourceLocation(418, 6, 20, 9), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("case-keyword") << fileItem + << Token(QQmlJS::SourceLocation(444, 4, 21, 9), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("return-keyword") << fileItem + << Token(QQmlJS::SourceLocation(464, 6, 22, 13), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("default-keyword") << fileItem + << Token(QQmlJS::SourceLocation(483, 7, 23, 9), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("break-keyword") << fileItem + << Token(QQmlJS::SourceLocation(504, 5, 24, 13), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("try-keyword") << fileItem + << Token(QQmlJS::SourceLocation(529, 3, 26, 9), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("catch-keyword") << fileItem + << Token(QQmlJS::SourceLocation(560, 5, 28, 11), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("finally-keyword") << fileItem + << Token(QQmlJS::SourceLocation(601, 7, 30, 11), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("for-keyword") << fileItem + << Token(QQmlJS::SourceLocation(620, 3, 31, 9), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("throw-keyword") << fileItem + << Token(QQmlJS::SourceLocation(661, 5, 32, 13), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("for-declaration") << fileItem + << Token(QQmlJS::SourceLocation(625, 5, 31, 14), + int(SemanticTokenTypes::Keyword), 0); + QTest::addRow("destructuring") + << fileItem + << Token(QQmlJS::SourceLocation(1511, 2, 73, 16), int(SemanticTokenTypes::Variable), + (1 << int(SemanticTokenModifiers::Readonly))); + QTest::addRow("obj-destructuring") + << fileItem + << Token(QQmlJS::SourceLocation(1589, 2, 76, 17), int(SemanticTokenTypes::Variable), + (1 << int(SemanticTokenModifiers::Readonly))); + } +} + +void tst_qmlls_highlighting::highlights() +{ + using namespace QQmlJS::Dom; + QFETCH(DomItem, fileItem); + QFETCH(Token, expectedHighlightedToken); + + Highlights h; + HighlightingVisitor hv(h, std::nullopt); + + fileItem.visitTree(QQmlJS::Dom::Path(), hv, VisitOption::Default, emptyChildrenVisitor, + emptyChildrenVisitor); + + const auto highlights = h.highlights(); + QVERIFY(highlights.contains(expectedHighlightedToken.offset)); + QCOMPARE(highlights.value(expectedHighlightedToken.offset), expectedHighlightedToken); +} + +void tst_qmlls_highlighting::rangeOverlapsWithSourceLocation_data() +{ + QTest::addColumn<QQmlJS::SourceLocation>("sourceLocation"); + QTest::addColumn<HighlightsRange>("range"); + QTest::addColumn<bool>("overlaps"); + + QTest::addRow("sl-inside-range") + << QQmlJS::SourceLocation(5, 1, 1, 1) << HighlightsRange{ 0, 100 } << true; + QTest::addRow("sl-exceeds-rightBoundRange") + << QQmlJS::SourceLocation(5, 1000, 1, 1) << HighlightsRange{ 0, 100 } << true; + QTest::addRow("sl-exceeds-leftRightBoundRange") + << QQmlJS::SourceLocation(5, 1000, 1, 1) << HighlightsRange{ 8, 100 } << true; + QTest::addRow("sl-exceeds-leftBoundRange") + << QQmlJS::SourceLocation(5, 100, 1, 1) << HighlightsRange{ 8, 1000 } << true; + QTest::addRow("no-overlaps") << QQmlJS::SourceLocation(5, 100, 1, 1) + << HighlightsRange{ 8000, 100000 } << false; +} + +void tst_qmlls_highlighting::rangeOverlapsWithSourceLocation() +{ + QFETCH(QQmlJS::SourceLocation, sourceLocation); + QFETCH(HighlightsRange, range); + QFETCH(bool, overlaps); + QVERIFY(overlaps == HighlightingUtils::rangeOverlapsWithSourceLocation(sourceLocation, range)); +} + +void tst_qmlls_highlighting::updateResultID_data() +{ + QTest::addColumn<QByteArray>("currentId"); + QTest::addColumn<QByteArray>("expectedNextId"); + + QTest::addRow("zero-to-one") << QByteArray("0") << QByteArray("1"); + QTest::addRow("nine-to-ten") << QByteArray("9") << QByteArray("10"); + QTest::addRow("nineteen-to-twenty") << QByteArray("19") << QByteArray("20"); + QTest::addRow("twodigit-to-threedigit") << QByteArray("99") << QByteArray("100"); +} + +void tst_qmlls_highlighting::updateResultID() +{ + QFETCH(QByteArray, currentId); + QFETCH(QByteArray, expectedNextId); + + HighlightingUtils::updateResultID(currentId); + QCOMPARE(currentId, expectedNextId); +} + +void tst_qmlls_highlighting::computeDiff_data() +{ + QTest::addColumn<QList<int>>("oldData"); + QTest::addColumn<QList<int>>("newData"); + QTest::addColumn<QList<SemanticTokensEdit>>("expected"); + + { + QList<int> oldData { 2,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0}; + QList<int> newData { 3,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0}; + SemanticTokensEdit expected; + expected.start = 0; + expected.deleteCount = 1; + expected.data = QList{3}; + QTest::addRow("simple") << oldData << newData << QList{expected}; + } + { + QList<int> oldData { 0, 0, 5, 5, 0}; + QList<int> newData { 3, 3, 3, 3, 3, 0, 0, 5, 5, 0}; + SemanticTokensEdit expected; + expected.start = 0; + expected.deleteCount = 0; + expected.data = QList{3, 3, 3, 3, 3}; + QTest::addRow("prepend") << oldData << newData << QList{expected}; + } + { + QList<int> oldData { 3, 3, 3, 3, 3, 0, 0, 5, 5, 0}; + QList<int> newData { 0, 0, 5, 5, 0}; + SemanticTokensEdit expected; + expected.start = 0; + expected.deleteCount = 5; + expected.data = {}; + QTest::addRow("remove-front") << oldData << newData << QList{expected}; + } + { + QList<int> oldData { 0, 0, 5, 5, 0}; + QList<int> newData { 0, 0, 5, 5, 0, 1, 0, 23, 5, 0}; + SemanticTokensEdit expected; + expected.start = 5; + expected.deleteCount = 0; + expected.data = QList{1, 0, 23, 5, 0}; + QTest::addRow("append") << oldData << newData << QList{expected}; + } + { + QList<int> oldData { 0, 0, 5, 5, 0, 1, 0, 23, 5, 0}; + QList<int> newData { 0, 0, 5, 5, 0}; + SemanticTokensEdit expected; + expected.start = 5; + expected.deleteCount = 5; + expected.data = {}; + QTest::addRow("remove-back") << oldData << newData << QList{expected}; + } + { + QList<int> oldData { 0, 0, 5, 5, 0, 1, 0, 23, 5, 0}; + QList<int> newData { 0, 0, 5, 5, 0, 3, 3, 3, 3, 3, 1, 0, 23, 5, 0}; + SemanticTokensEdit expected; + expected.start = 5; + expected.deleteCount = 0; + expected.data = QList{3, 3, 3, 3, 3}; + QTest::addRow("insert-middle") << oldData << newData << QList{expected}; + } + { + QList<int> oldData { 0, 0, 5, 5, 0, 3, 3, 3, 3, 3, 1, 0, 23, 5, 0}; + QList<int> newData { 0, 0, 5, 5, 0, 1, 0, 23, 5, 0}; + SemanticTokensEdit expected; + expected.start = 5; + expected.deleteCount = 5; + expected.data = {}; + QTest::addRow("remove-middle") << oldData << newData << QList{expected}; + } +} + +void tst_qmlls_highlighting::computeDiff() +{ + QFETCH(QList<int>, oldData); + QFETCH(QList<int>, newData); + QFETCH(QList<SemanticTokensEdit>, expected); + + const auto edits = HighlightingUtils::computeDiff(oldData, newData); + QCOMPARE(edits.size(), expected.size()); + + qsizetype i = 0; + for (const auto &edit : edits) { + QCOMPARE(edit.start, expected.at(i).start); + QCOMPARE(edit.deleteCount, expected.at(i).deleteCount); + QCOMPARE(edit.data, expected.at(i).data); + ++i; + } +} + + +QTEST_MAIN(tst_qmlls_highlighting) diff --git a/tests/auto/qmlls/utils/tst_qmlls_highlighting.h b/tests/auto/qmlls/utils/tst_qmlls_highlighting.h new file mode 100644 index 0000000000..a1d0e3c9b1 --- /dev/null +++ b/tests/auto/qmlls/utils/tst_qmlls_highlighting.h @@ -0,0 +1,37 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef TST_QMLLS_HIGHLIGHTING_H +#define TST_QMLLS_HIGHLIGHTING_H + +#include <QtQuickTestUtils/private/qmlutils_p.h> +#include <QtCore/qobject.h> +#include <QtTest/qtest.h> + +class tst_qmlls_highlighting : public QQmlDataTest +{ + Q_OBJECT +public: + tst_qmlls_highlighting(); +private slots: + void encodeSemanticTokens_data(); + void encodeSemanticTokens(); + void sourceLocationsFromMultiLineToken_data(); + void sourceLocationsFromMultiLineToken(); + + void highlights_data(); + void highlights(); + + void rangeOverlapsWithSourceLocation_data(); + void rangeOverlapsWithSourceLocation(); + + void updateResultID_data(); + void updateResultID(); + + void computeDiff_data(); + void computeDiff(); +private: + QString m_highlightingDataDir; +}; + +#endif // TST_QMLLS_HIGHLIGHTING_H diff --git a/tests/auto/qmlls/utils/tst_qmlls_utils.cpp b/tests/auto/qmlls/utils/tst_qmlls_utils.cpp index c8808e2d7c..332dc13590 100644 --- a/tests/auto/qmlls/utils/tst_qmlls_utils.cpp +++ b/tests/auto/qmlls/utils/tst_qmlls_utils.cpp @@ -39,7 +39,7 @@ tst_qmlls_utils::createEnvironmentAndLoadFile(const QString &filePath) { CacheKey cacheKey = QDir::cleanPath(filePath + u"/.."_s); if (auto entry = cache.find(cacheKey); entry != cache.end()) { - DomItem env{ *entry }; + QQmlJS::Dom::DomItem env{ *entry }; return { env, env.field(QQmlJS::Dom::Fields::qmlFileWithPath).key(filePath) }; }; @@ -663,7 +663,7 @@ void tst_qmlls_utils::findBaseObject() struct UsageData { QString testFileName; - QList<QQmlLSUtilsLocation> expectedUsages; + QQmlLSUtils::Usages expectedUsages; }; void tst_qmlls_utils::findUsages_data() @@ -679,33 +679,41 @@ void tst_qmlls_utils::findUsages_data() return QString{}; }; - const auto makeUsages = [](const QString &fileName, QList<QQmlLSUtilsLocation> &locations) { + const auto makeUsages = [](const QString &fileName, QList<QQmlLSUtils::Location> &locations) { UsageData data; std::sort(locations.begin(), locations.end()); - data.expectedUsages = locations; + data.expectedUsages = { locations, {} }; data.testFileName = fileName; return data; }; { - QList<QQmlLSUtilsLocation> expectedUsages; + QList<QQmlLSUtils::Location> expectedUsages; const auto testFileName = testFile("findUsages/jsIdentifier/jsIdentifier.qml"); const auto testFileContent = readFileContent(testFileName); { - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 8, 13, strlen("sum")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 10, 13, strlen("sum")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 10, 19, strlen("sum")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 13, + strlen("sum")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 10, 13, + strlen("sum")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 10, 19, + strlen("sum")); const auto sumUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("findSumFromDeclaration") << 8 << 13 << sumUsages; QTest::addRow("findSumFromUsage") << 10 << 20 << sumUsages; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 9, 17, strlen("i")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 9, 24, strlen("i")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 9, 32, strlen("i")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 9, 36, strlen("i")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 10, 25, strlen("i")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 17, + strlen("i")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 24, + strlen("i")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 32, + strlen("i")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 36, + strlen("i")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 10, 25, + strlen("i")); const auto iUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("findIFromDeclaration") << 9 << 17 << iUsages; QTest::addRow("findIFromUsage") << 9 << 24 << iUsages; @@ -718,40 +726,54 @@ void tst_qmlls_utils::findUsages_data() const auto testFileContent = readFileContent(testFileName); const auto otherFileContent = readFileContent(otherFile); { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 8, 18, strlen("helloProperty")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 13, 13, strlen("helloProperty")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 13, 29, strlen("helloProperty")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 20, 9, strlen("helloProperty")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 21, 9, strlen("helloPropertyChanged")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 23, 5, strlen("onHelloPropertyChanged")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 18, + strlen("helloProperty")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 13, 13, + strlen("helloProperty")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 13, 29, + strlen("helloProperty")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 20, 9, + strlen("helloProperty")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 21, 9, + strlen("helloPropertyChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 23, 5, + strlen("onHelloPropertyChanged")); const auto helloPropertyUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("findPropertyFromDeclaration") << 8 << 18 << helloPropertyUsages; QTest::addRow("findPropertyFromUsage") << 13 << 13 << helloPropertyUsages; QTest::addRow("findPropertyFromUsage2") << 13 << 29 << helloPropertyUsages; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 36, 20, strlen("helloProperty")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 38, 25, strlen("helloProperty")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 36, 20, + strlen("helloProperty")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 38, 25, + strlen("helloProperty")); const auto subItemHelloPropertyUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("findPropertyFromDeclarationInSubItem") << 38 << 25 << subItemHelloPropertyUsages; QTest::addRow("findPropertyFromUsageInSubItem") << 36 << 20 << subItemHelloPropertyUsages; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 27, 22, strlen("helloProperty")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 29, 20, strlen("helloProperty")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 27, 22, + strlen("helloProperty")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 29, 20, + strlen("helloProperty")); const auto ICHelloPropertyUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("findPropertyFromDeclarationInIC") << 27 << 22 << ICHelloPropertyUsages; QTest::addRow("findPropertyFromUsageInIC") << 29 << 20 << ICHelloPropertyUsages; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(otherFile, otherFileContent, 4, 18, strlen("helloProperty")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 42, 9, strlen("helloProperty")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 44, 20, strlen("helloProperty")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 46, 9, strlen("OnHelloPropertyChanged")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(otherFile, otherFileContent, 4, 18, + strlen("helloProperty")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 42, 9, + strlen("helloProperty")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 44, 20, + strlen("helloProperty")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 46, 9, + strlen("OnHelloPropertyChanged")); const auto helloPropertyUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("findPropertyFromOtherFile") << 42 << 13 << helloPropertyUsages; @@ -770,35 +792,52 @@ void tst_qmlls_utils::findUsages_data() const auto componentFileContent3 = readFileContent(componentFileName); { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 7, 18, strlen("p2")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 33, 31, strlen("p2")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 34, 37, strlen("p2")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 35, 43, strlen("p2")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 36, 49, strlen("p2")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 42, 26, strlen("p2")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 7, 18, + strlen("p2")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 33, 31, + strlen("p2")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 34, 37, + strlen("p2")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 35, 43, + strlen("p2")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 36, 49, + strlen("p2")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 42, 26, + strlen("p2")); const auto p2Usages = makeUsages(testFileName, expectedUsages); QTest::addRow("findPropertyFromDeclaration2") << 7 << 18 << p2Usages; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 29, 13, strlen("myNested")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 32, 17, strlen("myNested")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 33, 17, strlen("myNested")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 34, 17, strlen("myNested")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 35, 17, strlen("myNested")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 36, 17, strlen("myNested")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 29, 13, + strlen("myNested")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 32, 17, + strlen("myNested")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 33, 17, + strlen("myNested")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 34, 17, + strlen("myNested")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 35, 17, + strlen("myNested")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 36, 17, + strlen("myNested")); const auto nestedUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("findIdFromUsage") << 36 << 20 << nestedUsages; QTest::addRow("findIdFromDefinition") << 29 << 17 << nestedUsages; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 14, 35, strlen("inner")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 32, 32, strlen("inner")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 35, 32, strlen("inner")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 36, 32, strlen("inner")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 16, 9, strlen("inner")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 14, 35, + strlen("inner")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 32, 32, + strlen("inner")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 35, 32, + strlen("inner")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 36, 32, + strlen("inner")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 16, 9, + strlen("inner")); const auto nestedComponent3Usages = makeUsages(testFileName, expectedUsages); QTest::addRow("findPropertyFromUsageInFieldMemberExpression") << 36 << 34 << nestedComponent3Usages; @@ -807,12 +846,17 @@ void tst_qmlls_utils::findUsages_data() << 14 << 38 << nestedComponent3Usages; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(componentFileName, componentFileContent, 4, 37, strlen("inner")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 50, 32, strlen("inner")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 52, 32, strlen("inner")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 53, 32, strlen("inner")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 54, 32, strlen("inner")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(componentFileName, componentFileContent, + 4, 37, strlen("inner")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 50, 32, + strlen("inner")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 52, 32, + strlen("inner")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 53, 32, + strlen("inner")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 54, 32, + strlen("inner")); const auto nestedComponent3Usages = makeUsages(testFileName, expectedUsages); const auto nestedComponent3UsagesFromOtherFile = makeUsages(componentFileName, expectedUsages); @@ -823,19 +867,21 @@ void tst_qmlls_utils::findUsages_data() << 4 << 38 << nestedComponent3UsagesFromOtherFile; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 35, 38, strlen("p2")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 20, 22, strlen("p2")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 35, 38, + strlen("p2")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 20, 22, + strlen("p2")); const auto nestedComponent3P2Usages = makeUsages(testFileName, expectedUsages); QTest::addRow("findProperty2FromUsageInFieldMemberExpression") << 35 << 39 << nestedComponent3P2Usages; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(componentFileName3, componentFileContent3, - 5, 18, strlen("p2")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 53, 44, - strlen("p2")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(componentFileName3, componentFileContent3, + 5, 18, strlen("p2")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 53, 44, + strlen("p2")); const auto nestedComponent3P2Usages = makeUsages(testFileName, expectedUsages); const auto nestedComponent3P2UsagesFromOtherFile = makeUsages(componentFileName3, expectedUsages); QTest::addRow("findProperty2FromUsageInFieldMemberExpressionInOtherFile") @@ -845,28 +891,40 @@ void tst_qmlls_utils::findUsages_data() } } { - QList<QQmlLSUtilsLocation> expectedUsages; + QList<QQmlLSUtils::Location> expectedUsages; const auto testFileName = testFile("findUsages/idUsages/idUsages.qml"); const auto testFileContent = readFileContent(testFileName); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 7, 9, strlen("rootId")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 11, 17, strlen("rootId")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 12, 20, strlen("rootId")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 17, 9, strlen("rootId")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 7, 9, + strlen("rootId")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 11, 17, + strlen("rootId")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 12, 20, + strlen("rootId")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 17, 9, + strlen("rootId")); const auto rootIdUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("findIdFromUsageInChild") << 12 << 20 << rootIdUsages; } { - QList<QQmlLSUtilsLocation> expectedUsages; + QList<QQmlLSUtils::Location> expectedUsages; const auto testFileName = testFile("findUsages/recursive/recursive.qml"); const auto testFileContent = readFileContent(testFileName); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 8, 14, strlen("recursive")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 10, 24, strlen("recursive")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 10, 34, strlen("recursive")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 10, 51, strlen("recursive")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 10, 68, strlen("recursive")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 12, 20, strlen("recursive")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 15, 34, strlen("recursive")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 19, 27, strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 14, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 10, 24, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 10, 34, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 10, 51, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 10, 68, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 12, 20, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 15, 34, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 19, 27, + strlen("recursive")); const auto recursiveUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("findFunctionUsage") << 10 << 30 << recursiveUsages; QTest::addRow("findFunctionUsage2") << 12 << 24 << recursiveUsages; @@ -874,26 +932,26 @@ void tst_qmlls_utils::findUsages_data() QTest::addRow("findFunctionUsageFromDefinition") << 8 << 17 << recursiveUsages; } { - QList<QQmlLSUtilsLocation> expectedUsages; + QList<QQmlLSUtils::Location> expectedUsages; const auto testFileName = testFile("findUsages/recursive/recursive.qml"); const auto testFileContent = readFileContent(testFileName); const auto otherFileName = testFile("findUsages/recursive/RecursiveInOtherFile.qml"); const auto otherFileContent = readFileContent(otherFileName); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 27, 61, - strlen("recursive")); - expectedUsages << QQmlLSUtilsLocation::from(otherFileName, otherFileContent, 4, 14, - strlen("recursive")); - expectedUsages << QQmlLSUtilsLocation::from(otherFileName, otherFileContent, 6, 24, - strlen("recursive")); - expectedUsages << QQmlLSUtilsLocation::from(otherFileName, otherFileContent, 6, 34, - strlen("recursive")); - expectedUsages << QQmlLSUtilsLocation::from(otherFileName, otherFileContent, 6, 51, - strlen("recursive")); - expectedUsages << QQmlLSUtilsLocation::from(otherFileName, otherFileContent, 6, 68, - strlen("recursive")); - expectedUsages << QQmlLSUtilsLocation::from(otherFileName, otherFileContent, 8, 20, - strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 27, 61, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(otherFileName, otherFileContent, 4, 14, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(otherFileName, otherFileContent, 6, 24, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(otherFileName, otherFileContent, 6, 34, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(otherFileName, otherFileContent, 6, 51, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(otherFileName, otherFileContent, 6, 68, + strlen("recursive")); + expectedUsages << QQmlLSUtils::Location::from(otherFileName, otherFileContent, 8, 20, + strlen("recursive")); const auto recursiveUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("findFunctionUsageFromOtherFile") << 27 << 64 << recursiveUsages; @@ -909,26 +967,38 @@ void tst_qmlls_utils::findUsages_data() const auto otherFileName = testFile("findUsages/signalsAndHandlers/widthChangedInAnotherFile.qml"); const auto otherFileContent = readFileContent(otherFileName); { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 8, 12, strlen("helloSignal")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 11, 9, strlen("helloSignal")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 13, 13, strlen("helloSignal")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 17, 17, strlen("helloSignal")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 21, 9, strlen("helloSignal")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 39, 5, strlen("onHelloSignal")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 12, + strlen("helloSignal")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 11, 9, + strlen("helloSignal")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 13, 13, + strlen("helloSignal")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 17, 17, + strlen("helloSignal")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 21, 9, + strlen("helloSignal")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 39, 5, + strlen("onHelloSignal")); const auto helloSignalUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("findQmlSignalUsageFromDefinition") << 8 << 17 << helloSignalUsages; QTest::addRow("findQmlSignalUsageFromUsage") << 13 << 17 << helloSignalUsages; QTest::addRow("findQmlSignalUsageFromHandler") << 39 << 11 << helloSignalUsages; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(otherFileName, otherFileContent, 5, 5, strlen("onWidthChanged")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 23, 13, strlen("widthChanged")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 27, 17, strlen("widthChanged")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 28, 20, strlen("widthChanged")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 34, 20, strlen("widthChanged")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 33, 13, strlen("widthChanged")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(otherFileName, otherFileContent, 5, 5, + strlen("onWidthChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 23, 13, + strlen("widthChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 27, 17, + strlen("widthChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 28, 20, + strlen("widthChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 34, 20, + strlen("widthChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 33, 13, + strlen("widthChanged")); const auto widthChangedUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("findCppSignalUsageFromUsage") << 27 << 23 << widthChangedUsages; QTest::addRow("findCppSignalUsageFromQualifiedUsage") << 28 << 23 << widthChangedUsages; @@ -938,11 +1008,11 @@ void tst_qmlls_utils::findUsages_data() { const auto testFileName = testFile("findUsages/binding/binding.qml"); const auto testFileContent = readFileContent(testFileName); - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 9, 18, - strlen("helloPropertyBinding")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 10, 5, - strlen("helloPropertyBinding")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 18, + strlen("helloPropertyBinding")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 10, 5, + strlen("helloPropertyBinding")); const auto helloPropertyBindingUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("findBindingUsagesFromDefinition") << 9 << 21 << helloPropertyBindingUsages; QTest::addRow("findBindingUsagesFromBinding") << 10 << 19 << helloPropertyBindingUsages; @@ -951,80 +1021,107 @@ void tst_qmlls_utils::findUsages_data() const auto testFileName = testFile("findUsages/signalsAndHandlers/signalAndHandlers2.qml"); const auto testFileContent = readFileContent(testFileName); { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 7, 14, strlen("myHelloHandler")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 8, 20, strlen("myHelloHandler")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 14, 29, strlen("myHelloHandler")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 15, 24, strlen("myHelloHandler")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 23, 17, strlen("myHelloHandler")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 24, 24, strlen("myHelloHandler")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 25, 21, strlen("myHelloHandler")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 33, 19, strlen("myHelloHandler")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 42, 29, strlen("myHelloHandler")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 7, 14, + strlen("myHelloHandler")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 20, + strlen("myHelloHandler")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 14, 29, + strlen("myHelloHandler")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 15, 24, + strlen("myHelloHandler")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 23, 17, + strlen("myHelloHandler")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 24, 24, + strlen("myHelloHandler")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 25, 21, + strlen("myHelloHandler")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 33, 19, + strlen("myHelloHandler")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 42, 29, + strlen("myHelloHandler")); const auto myHelloHandlerUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("findJSMethodFromUsageInBinding") << 8 << 27 << myHelloHandlerUsages; QTest::addRow("findJSMethodFromDefinition") << 7 << 22 << myHelloHandlerUsages; QTest::addRow("findJSMethodFromDefinition2") << 7 << 9 << myHelloHandlerUsages; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 13, 18, strlen("checkHandlers")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 14, 5, - strlen("onCheckHandlersChanged")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 17, 9, - strlen("checkHandlersChanged")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 13, 18, + strlen("checkHandlers")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 14, 5, + strlen("onCheckHandlersChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 17, 9, + strlen("checkHandlersChanged")); const auto checkHandlersUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("findQmlPropertyHandlerFromDefinition") << 13 << 18 << checkHandlersUsages; QTest::addRow("findQmlPropertyHandlerFromHandler") << 14 << 5 << checkHandlersUsages; QTest::addRow("findQmlPropertyHandlerFromSignalCall") << 17 << 9 << checkHandlersUsages; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 15, 5, - strlen("onChildrenChanged")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 18, 9, strlen("childrenChanged")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 15, 5, + strlen("onChildrenChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 18, 9, + strlen("childrenChanged")); const auto checkCppHandlersUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("findCppPropertyHandlerFromHandler") << 15 << 5 << checkCppHandlersUsages; QTest::addRow("findCppPropertyHandlerFromSignalCall") << 18 << 9 << checkCppHandlersUsages; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 20, 18, strlen("_")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 23, 5, strlen("on_Changed")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 27, 9, strlen("_Changed")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 20, 18, + strlen("_")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 23, 5, + strlen("on_Changed")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 27, 9, + strlen("_Changed")); const auto checkHandlersUsages2 = makeUsages(testFileName, expectedUsages); QTest::addRow("findQmlPropertyHandler2FromDefinition") << 20 << 18 << checkHandlersUsages2; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 21, 18, strlen("______42")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 24, 5, - strlen("on______42Changed")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 28, 9, strlen("______42Changed")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 21, 18, + strlen("______42")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 24, 5, + strlen("on______42Changed")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 28, 9, + strlen("______42Changed")); const auto checkHandlersUsages3 = makeUsages(testFileName, expectedUsages); QTest::addRow("findQmlPropertyHandler3FromDefinition") << 21 << 18 << checkHandlersUsages3; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 22, 18, strlen("_123a")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 25, 5, strlen("on_123AChanged")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 29, 9, strlen("_123aChanged")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 22, 18, + strlen("_123a")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 25, 5, + strlen("on_123AChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 29, 9, + strlen("_123aChanged")); const auto checkHandlersUsages4 = makeUsages(testFileName, expectedUsages); QTest::addRow("findQmlPropertyHandler4FromDefinition") << 22 << 18 << checkHandlersUsages4; } } { - QList<QQmlLSUtilsLocation> expectedUsages; + QList<QQmlLSUtils::Location> expectedUsages; const auto testFileName = testFile("findUsages/connections/connections.qml"); const auto testFileContent = readFileContent(testFileName); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 9, 9, strlen("onClicked")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 17, 23, strlen("clicked")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 33, 15, strlen("clicked")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 16, 22, strlen("onClicked")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 34, 15, strlen("clicked")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 18, 23, strlen("clicked")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 28, 9, strlen("onClicked")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 35, 15, strlen("clicked")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 9, + strlen("onClicked")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 17, 23, + strlen("clicked")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 33, 15, + strlen("clicked")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 16, 22, + strlen("onClicked")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 34, 15, + strlen("clicked")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 18, 23, + strlen("clicked")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 28, 9, + strlen("onClicked")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 35, 15, + strlen("clicked")); const auto signalInConnection = makeUsages(testFileName, expectedUsages); QTest::addRow("findSignalsInConnectionFromSignal") << 33 << 15 << signalInConnection; QTest::addRow("findSignalsInConnectionFromHandler") << 9 << 9 << signalInConnection; @@ -1035,41 +1132,51 @@ void tst_qmlls_utils::findUsages_data() testFile("findUsages/parametersAndDeconstruction/parametersAndDeconstruction.qml"); const auto testFileContent = readFileContent(testFileName); { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 8, 30, strlen("a")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 9, 16, strlen("a")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 30, + strlen("a")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 16, + strlen("a")); const auto aParamUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("findMethodParameterA") << 9 << 16 << aParamUsages; QTest::addRow("findMethodParameterAFromUsage") << 8 << 30 << aParamUsages; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 8, 50, strlen("x")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 9, 28, strlen("x")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 50, + strlen("x")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 28, + strlen("x")); const auto xParamUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("findMethodParameterXDeconstructed") << 8 << 50 << xParamUsages; QTest::addRow("findMethodParameterXDeconstructedFromUsage") << 9 << 28 << xParamUsages; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 8, 53, strlen("y")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 9, 32, strlen("y")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 53, + strlen("y")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 32, + strlen("y")); const auto yParamUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("findMethodParameterYDeconstructed") << 8 << 53 << yParamUsages; QTest::addRow("findMethodParameterYDeconstructedFromUsage") << 9 << 32 << yParamUsages; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 8, 59, strlen("z")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 9, 36, strlen("z")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 59, + strlen("z")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 36, + strlen("z")); const auto zParamUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("findMethodParameterZDeconstructed") << 8 << 59 << zParamUsages; QTest::addRow("findMethodParameterZDeconstructedFromUsage") << 9 << 36 << zParamUsages; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 13, 14, strlen("a")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 14, 17, strlen("a")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 13, 14, + strlen("a")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 14, 17, + strlen("a")); const auto deconstructedAUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("deconstructed") << 14 << 17 << deconstructedAUsages; QTest::addRow("deconstructedFromDefinition") << 13 << 14 << deconstructedAUsages; @@ -1081,12 +1188,17 @@ void tst_qmlls_utils::findUsages_data() const auto otherFileName = testFile("findUsages/groupPropertyUsage/fontFamilyUsage.qml"); const auto otherFileContent = readFileContent(otherFileName); { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(otherFileName, otherFileContent, 5, 34, strlen("family")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 14, 17, strlen("family")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 23, 35, strlen("family")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 23, 10, strlen("family")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 33, 48, strlen("family")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(otherFileName, otherFileContent, 5, 34, + strlen("family")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 14, 17, + strlen("family")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 23, 35, + strlen("family")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 23, 10, + strlen("family")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 33, 48, + strlen("family")); const auto groupPropertyUsages1 = makeUsages(testFileName, expectedUsages); QTest::addRow("groupPropertyUsages1") << 14 << 17 << groupPropertyUsages1; const auto groupPropertyUsages1FromOtherFile = @@ -1095,13 +1207,19 @@ void tst_qmlls_utils::findUsages_data() << 5 << 37 << groupPropertyUsages1FromOtherFile; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 23, 5, strlen("font")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 24, 5, strlen("font")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 12, 13, strlen("font")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 23, 30, strlen("font")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 32, 41, strlen("font")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 33, 43, strlen("font")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 23, 5, + strlen("font")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 24, 5, + strlen("font")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 12, 13, + strlen("font")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 23, 30, + strlen("font")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 32, 41, + strlen("font")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 33, 43, + strlen("font")); const auto groupPropertyUsages2 = makeUsages(testFileName, expectedUsages); QTest::addRow("groupPropertyUsages2") << 23 << 5 << groupPropertyUsages2; } @@ -1110,20 +1228,26 @@ void tst_qmlls_utils::findUsages_data() const auto testFileName = testFile("findUsages/attachedPropertyUsage/attachedPropertyUsage.qml"); const auto testFileContent = readFileContent(testFileName); - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 9, 5, strlen("Keys")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 12, 25, strlen("Keys")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 5, + strlen("Keys")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 12, 25, + strlen("Keys")); const auto attachedPropertyUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("attachedPropertyUsages") << 12 << 25 << attachedPropertyUsages; } { const auto testFileName = testFile("findUsages/inlineComponents/inlineComponents.qml"); const auto testFileContent = readFileContent(testFileName); - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 9, 22, strlen("foo")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 10, 44, strlen("foo")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 14, 27, strlen("foo")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 20, 20, strlen("foo")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 22, + strlen("foo")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 10, 44, + strlen("foo")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 14, 27, + strlen("foo")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 20, 20, + strlen("foo")); const auto inlineUsages = makeUsages(testFileName, expectedUsages); QTest::addRow("inlineUsagesFromProperty") << 9 << 22 << inlineUsages; QTest::addRow("inlineUsagesFromUsageOfBaseProperty") << 14 << 27 << inlineUsages; @@ -1132,26 +1256,38 @@ void tst_qmlls_utils::findUsages_data() { const auto testFileName = testFile("findUsages/propertyChanges/propertyChanges.qml"); const auto testFileContent = readFileContent(testFileName); - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 8, 9, strlen("onClicked")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 16, 21, strlen("onClicked")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 19, 25, strlen("onClicked")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 25, 17, strlen("onClicked")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 9, + strlen("onClicked")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 16, 21, + strlen("onClicked")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 19, 25, + strlen("onClicked")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 25, 17, + strlen("onClicked")); const auto propertyChanges = makeUsages(testFileName, expectedUsages); QTest::addRow("propertyChanges1") << 16 << 21 << propertyChanges; } { const auto testFileName = testFile("findUsages/bindings/bindings.qml"); const auto testFileContent = readFileContent(testFileName); - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 11, 23, strlen("patronChanged")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 14, 27, strlen("patronChanged")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 21, 27, strlen("patronChanged")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 27, 19, strlen("patronChanged")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 27, 41, strlen("patronChanged")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 34, 17, strlen("patronChanged")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 13, 20, strlen("patronChanged")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 20, 23, strlen("\"patronChanged\"")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 11, 23, + strlen("patronChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 14, 27, + strlen("patronChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 21, 27, + strlen("patronChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 27, 19, + strlen("patronChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 27, 41, + strlen("patronChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 34, 17, + strlen("patronChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 13, 20, + strlen("patronChanged")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 20, 23, + strlen("\"patronChanged\"")); const auto bindings = makeUsages(testFileName, expectedUsages); QTest::addRow("propertyInBindingsFromDecl") << 11 << 23 << bindings; QTest::addRow("generalizedGroupPropertyBindings") << 27 << 19 << bindings; @@ -1162,28 +1298,33 @@ void tst_qmlls_utils::findUsages_data() const auto otherFileName = testFile("findUsages/enums/EnumsFromAnotherFile.qml"); const auto otherFileContent = readFileContent(otherFileName); { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 9, 9, strlen("Patron")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 22, 35, strlen("Patron")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 23, 34, strlen("Patron")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 9, 9, + strlen("Patron")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 22, 35, + strlen("Patron")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 23, 34, + strlen("Patron")); const auto enums = makeUsages(testFileName, expectedUsages); QTest::addRow("enumValuesFromDeclaration") << 9 << 9 << enums; QTest::addRow("enumValuesFromUsage") << 22 << 35 << enums; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 8, 10, strlen("Cats")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 22, 30, strlen("Cats")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 8, 10, + strlen("Cats")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 22, 30, + strlen("Cats")); const auto enums = makeUsages(testFileName, expectedUsages); QTest::addRow("enumNameFromDeclaration") << 8 << 10 << enums; QTest::addRow("enumNameFromUsage") << 22 << 30 << enums; } { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 26, 46, - strlen("FromAnotherUniverse")); - expectedUsages << QQmlLSUtilsLocation::from(otherFileName, otherFileContent, 4, 68, - strlen("FromAnotherUniverse")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 26, 46, + strlen("FromAnotherUniverse")); + expectedUsages << QQmlLSUtils::Location::from(otherFileName, otherFileContent, 4, 68, + strlen("FromAnotherUniverse")); const auto enums = makeUsages(testFileName, expectedUsages); QTest::addRow("enumNameFromDeclarationInOtherFile") << 26 << 50 << enums; const auto enumsFromOtherFile = makeUsages(otherFileName, expectedUsages); @@ -1194,13 +1335,19 @@ void tst_qmlls_utils::findUsages_data() const auto testFileName = testFile("findUsages/inlineComponents/inlineComponents2.qml"); const auto testFileContent = readFileContent(testFileName); { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 4, 15, strlen("MyIC")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 5, 5, strlen("MyIC")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 5, 12, strlen("MyIC")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 5, 19, strlen("MyIC")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 6, 19, strlen("MyIC")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 6, 26, strlen("MyIC")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 4, 15, + strlen("MyIC")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 5, 5, + strlen("MyIC")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 5, 12, + strlen("MyIC")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 5, 19, + strlen("MyIC")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 6, 19, + strlen("MyIC")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 6, 26, + strlen("MyIC")); const auto inlineComponents = makeUsages(testFileName, expectedUsages); QTest::addRow("findICUsagesFromDefinition") << 4 << 16 << inlineComponents; QTest::addRow("findICUsagesFromDefinition2") << 4 << 9 << inlineComponents; @@ -1215,20 +1362,20 @@ void tst_qmlls_utils::findUsages_data() testFile("findUsages/inlineComponents/InlineComponentProvider.qml"); const auto providerFileContent = readFileContent(providerFileName); { - QList<QQmlLSUtilsLocation> expectedUsages; - expectedUsages << QQmlLSUtilsLocation::from(providerFileName, providerFileContent, 4, - 15, strlen("IC1")); - expectedUsages << QQmlLSUtilsLocation::from(providerFileName, providerFileContent, 5, - 36, strlen("IC1")); - expectedUsages << QQmlLSUtilsLocation::from(providerFileName, providerFileContent, 7, 5, - strlen("IC1")); - expectedUsages << QQmlLSUtilsLocation::from(providerFileName, providerFileContent, 17, - 13, strlen("IC1")); - - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 25, 38, - strlen("IC1")); - expectedUsages << QQmlLSUtilsLocation::from(testFileName, testFileContent, 25, 84, - strlen("IC1")); + QList<QQmlLSUtils::Location> expectedUsages; + expectedUsages << QQmlLSUtils::Location::from(providerFileName, providerFileContent, 4, + 15, strlen("IC1")); + expectedUsages << QQmlLSUtils::Location::from(providerFileName, providerFileContent, 5, + 36, strlen("IC1")); + expectedUsages << QQmlLSUtils::Location::from(providerFileName, providerFileContent, 7, + 5, strlen("IC1")); + expectedUsages << QQmlLSUtils::Location::from(providerFileName, providerFileContent, 17, + 13, strlen("IC1")); + + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 25, 38, + strlen("IC1")); + expectedUsages << QQmlLSUtils::Location::from(testFileName, testFileContent, 25, 84, + strlen("IC1")); { const auto usagesForTestFile = makeUsages(testFileName, expectedUsages); @@ -1255,7 +1402,13 @@ void tst_qmlls_utils::findUsages() QFETCH(int, line); QFETCH(int, character); QFETCH(UsageData, data); - QVERIFY(std::is_sorted(data.expectedUsages.begin(), data.expectedUsages.end())); + + { + auto usagesInFilename = data.expectedUsages.usagesInFilename(); + QVERIFY(std::is_sorted(usagesInFilename.begin(), usagesInFilename.end())); + auto usagesInFile = data.expectedUsages.usagesInFile(); + QVERIFY(std::is_sorted(usagesInFile.begin(), usagesInFile.end())); + } auto [env, file] = createEnvironmentAndLoadFile(data.testFileName); @@ -1275,17 +1428,19 @@ void tst_qmlls_utils::findUsages() if constexpr (enable_debug_output) { if (usages != data.expectedUsages) { qDebug() << "Got:\n"; - for (auto &x : usages) { + for (auto &x : usages.usagesInFile()) { qDebug() << x.filename << "(" << x.sourceLocation.startLine << ", " << x.sourceLocation.startColumn << "), " << x.sourceLocation.offset << "+" << x.sourceLocation.length; } + qDebug() << "with usages in filenames:" << usages.usagesInFilename(); qDebug() << "But expected: \n"; - for (auto &x : data.expectedUsages) { + for (auto &x : data.expectedUsages.usagesInFile()) { qDebug() << x.filename << "(" << x.sourceLocation.startLine << ", " << x.sourceLocation.startColumn << "), " << x.sourceLocation.offset << "+" << x.sourceLocation.length; } + qDebug() << "with usages in filenames:" << data.expectedUsages.usagesInFilename(); } } @@ -1299,7 +1454,7 @@ void tst_qmlls_utils::renameUsages_data() QTest::addColumn<int>("line"); QTest::addColumn<int>("character"); QTest::addColumn<QString>("newName"); - QTest::addColumn<QList<QQmlLSUtilsEdit>>("expectedRenames"); + QTest::addColumn<QQmlLSUtils::RenameUsages>("expectedRenames"); QTest::addColumn<QString>("expectedError"); const QString testFileName = testFile(u"JSUsages.qml"_s); @@ -1308,97 +1463,140 @@ void tst_qmlls_utils::renameUsages_data() const QString testFileFromAnotherFileContent = readFileContent(testFileNameFromAnotherFile); const QString noError; - const QList<QQmlLSUtilsEdit> noRenames; - - QList<QQmlLSUtilsEdit> methodFRename{ - QQmlLSUtilsEdit::from(testFileName, testFileContent, 72, 14, strlen("recursive"), - u"newNameNewMe"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 74, 24, strlen("recursive"), - u"newNameNewMe"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 74, 34, strlen("recursive"), - u"newNameNewMe"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 74, 51, strlen("recursive"), - u"newNameNewMe"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 74, 68, strlen("recursive"), - u"newNameNewMe"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 76, 20, strlen("recursive"), - u"newNameNewMe"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 79, 34, strlen("recursive"), - u"newNameNewMe"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 84, 27, strlen("recursive"), - u"newNameNewMe"_s), + const QQmlLSUtils::RenameUsages noRenames; + + QQmlLSUtils::RenameUsages methodFRename{ + { + QQmlLSUtils::Edit::from(testFileName, testFileContent, 72, 14, strlen("recursive"), + u"newNameNewMe"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 74, 24, strlen("recursive"), + u"newNameNewMe"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 74, 34, strlen("recursive"), + u"newNameNewMe"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 74, 51, strlen("recursive"), + u"newNameNewMe"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 74, 68, strlen("recursive"), + u"newNameNewMe"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 76, 20, strlen("recursive"), + u"newNameNewMe"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 79, 34, strlen("recursive"), + u"newNameNewMe"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 84, 27, strlen("recursive"), + u"newNameNewMe"_s), + }, + {} }; - QList<QQmlLSUtilsEdit> JSIdentifierSumRename{ - QQmlLSUtilsEdit::from(testFileName, testFileContent, 8, 13, strlen("sum"), - u"sumsumsum123"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 10, 13, strlen("sum"), - u"sumsumsum123"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 10, 19, strlen("sum"), - u"sumsumsum123"_s), + QQmlLSUtils::RenameUsages JSIdentifierSumRename{ + { + QQmlLSUtils::Edit::from(testFileName, testFileContent, 8, 13, strlen("sum"), + u"sumsumsum123"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 10, 13, strlen("sum"), + u"sumsumsum123"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 10, 19, strlen("sum"), + u"sumsumsum123"_s), + }, + {} }; - QList<QQmlLSUtilsEdit> qmlSignalRename{ - QQmlLSUtilsEdit::from(testFileName, testFileContent, 88, 12, strlen("helloSignal"), - u"finalSignal"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 91, 9, strlen("helloSignal"), - u"finalSignal"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 93, 13, strlen("helloSignal"), - u"finalSignal"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 97, 17, strlen("helloSignal"), - u"finalSignal"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 101, 9, strlen("helloSignal"), - u"finalSignal"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 119, 5, strlen("onHelloSignal"), - u"onFinalSignal"_s), + QQmlLSUtils::RenameUsages qmlSignalRename{ + { + QQmlLSUtils::Edit::from(testFileName, testFileContent, 88, 12, + strlen("helloSignal"), u"finalSignal"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 91, 9, strlen("helloSignal"), + u"finalSignal"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 93, 13, + strlen("helloSignal"), u"finalSignal"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 97, 17, + strlen("helloSignal"), u"finalSignal"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 101, 9, + strlen("helloSignal"), u"finalSignal"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 119, 5, + strlen("onHelloSignal"), u"onFinalSignal"_s), + }, + {} }; - QList<QQmlLSUtilsEdit> helloPropertyRename{ - QQmlLSUtilsEdit::from(testFileName, testFileContent, 17, 18, strlen("helloProperty"), - u"freshPropertyName"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 24, 13, strlen("helloProperty"), - u"freshPropertyName"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 24, 29, strlen("helloProperty"), - u"freshPropertyName"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 65, 60, strlen("helloProperty"), - u"freshPropertyName"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 151, 9, strlen("helloPropertyChanged"), - u"freshPropertyNameChanged"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 153, 5, - strlen("onHelloPropertyChanged"), u"onFreshPropertyNameChanged"_s), - QQmlLSUtilsEdit::from(testFileNameFromAnotherFile, testFileFromAnotherFileContent, 12, 16, - strlen("helloProperty"), u"freshPropertyName"_s), + QQmlLSUtils::RenameUsages helloPropertyRename{ + { + QQmlLSUtils::Edit::from(testFileName, testFileContent, 17, 18, + strlen("helloProperty"), u"freshPropertyName"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 24, 13, + strlen("helloProperty"), u"freshPropertyName"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 24, 29, + strlen("helloProperty"), u"freshPropertyName"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 65, 60, + strlen("helloProperty"), u"freshPropertyName"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 151, 9, + strlen("helloPropertyChanged"), + u"freshPropertyNameChanged"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 153, 5, + strlen("onHelloPropertyChanged"), + u"onFreshPropertyNameChanged"_s), + QQmlLSUtils::Edit::from(testFileNameFromAnotherFile, testFileFromAnotherFileContent, + 12, 16, strlen("helloProperty"), u"freshPropertyName"_s), + }, + {} }; - QList<QQmlLSUtilsEdit> nestedComponentRename{ - QQmlLSUtilsEdit::from(testFileName, testFileContent, 42, 15, strlen("NestedComponent"), - u"SuperInlineComponent"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 61, 5, strlen("NestedComponent"), - u"SuperInlineComponent"_s), + QQmlLSUtils::RenameUsages nestedComponentRename{ + { + QQmlLSUtils::Edit::from(testFileName, testFileContent, 42, 15, + strlen("NestedComponent"), u"SuperInlineComponent"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 61, 5, + strlen("NestedComponent"), u"SuperInlineComponent"_s), + }, + {} }; - QList<QQmlLSUtilsEdit> myNestedIdRename{ - QQmlLSUtilsEdit::from(testFileName, testFileContent, 62, 13, strlen("myNested"), - u"freshNewIdForMyNested"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 65, 17, strlen("myNested"), - u"freshNewIdForMyNested"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 66, 17, strlen("myNested"), - u"freshNewIdForMyNested"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 67, 17, strlen("myNested"), - u"freshNewIdForMyNested"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 68, 17, strlen("myNested"), - u"freshNewIdForMyNested"_s), - QQmlLSUtilsEdit::from(testFileName, testFileContent, 69, 17, strlen("myNested"), - u"freshNewIdForMyNested"_s), + QQmlLSUtils::RenameUsages myNestedIdRename{ + { + QQmlLSUtils::Edit::from(testFileName, testFileContent, 62, 13, strlen("myNested"), + u"freshNewIdForMyNested"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 65, 17, strlen("myNested"), + u"freshNewIdForMyNested"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 66, 17, strlen("myNested"), + u"freshNewIdForMyNested"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 67, 17, strlen("myNested"), + u"freshNewIdForMyNested"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 68, 17, strlen("myNested"), + u"freshNewIdForMyNested"_s), + QQmlLSUtils::Edit::from(testFileName, testFileContent, 69, 17, strlen("myNested"), + u"freshNewIdForMyNested"_s), + }, + {} }; - std::sort(methodFRename.begin(), methodFRename.end()); - std::sort(JSIdentifierSumRename.begin(), JSIdentifierSumRename.end()); - std::sort(qmlSignalRename.begin(), qmlSignalRename.end()); - std::sort(helloPropertyRename.begin(), helloPropertyRename.end()); - std::sort(helloPropertyRename.begin(), helloPropertyRename.end()); - std::sort(nestedComponentRename.begin(), nestedComponentRename.end()); - std::sort(myNestedIdRename.begin(), myNestedIdRename.end()); + const QString renameFileQml = testFile("renaming/main.qml"); + const QString renameFileQmlContent = readFileContent(renameFileQml); + const QQmlLSUtils::RenameUsages renameComponent1{ + { + QQmlLSUtils::Edit::from(renameFileQml, renameFileQmlContent, 4, 5, + strlen("RenameMe"), u"FreshNewComponentName"_s), + }, + { + { testFile("renaming/RenameMe.qml"), + testFile(u"renaming/FreshNewComponentName.qml"_s) }, + } + }; + const QQmlLSUtils::RenameUsages renameComponent2{ + { + QQmlLSUtils::Edit::from(renameFileQml, renameFileQmlContent, 5, 5, + strlen("RenameMe2"), u"AnotherOneThankYou"_s), + }, + { + { testFile("renaming/RenameMe2.ui.qml"), + testFile(u"renaming/AnotherOneThankYou.ui.qml"_s) }, + } + }; + const QQmlLSUtils::RenameUsages renameComponentNamedByQmldir{ + { + QQmlLSUtils::Edit::from(renameFileQml, renameFileQmlContent, 6, 5, + strlen("HelloWorld"), u"AnotherOneThankYou"_s), + }, + // make sure that the file itself does not get renamed + {} + }; const QString parserError = u"Invalid EcmaScript identifier!"_s; @@ -1461,6 +1659,16 @@ void tst_qmlls_utils::renameUsages_data() QTest::addRow("JSIdentifierStartsWithNumber") << testFileName << 67 << 13 << u"123"_s << noRenames << parserError; + + QTest::addRow("renameQmlFile") << testFile(u"renaming/main.qml"_s) << 4 << 9 + << u"FreshNewComponentName"_s << renameComponent1 << noError; + + QTest::addRow("renameUiQmlFile") << testFile(u"renaming/main.qml"_s) << 5 << 9 + << u"AnotherOneThankYou"_s << renameComponent2 << noError; + + QTest::addRow("renameQmlFileRenamedByQmldir") + << testFile(u"renaming/main.qml"_s) << 6 << 8 << u"AnotherOneThankYou"_s + << renameComponentNamedByQmldir << noError; } void tst_qmlls_utils::renameUsages() @@ -1471,10 +1679,15 @@ void tst_qmlls_utils::renameUsages() QFETCH(int, line); QFETCH(int, character); QFETCH(QString, newName); - QFETCH(QList<QQmlLSUtilsEdit>, expectedRenames); + QFETCH(QQmlLSUtils::RenameUsages, expectedRenames); QFETCH(QString, expectedError); - QVERIFY(std::is_sorted(expectedRenames.begin(), expectedRenames.end())); + { + const auto renameInFile = expectedRenames.renameInFile(); + QVERIFY(std::is_sorted(renameInFile.constBegin(), renameInFile.constEnd())); + const auto renameInFilename = expectedRenames.renameInFilename(); + QVERIFY(std::is_sorted(renameInFilename.begin(), renameInFilename.end())); + } auto [env, file] = createEnvironmentAndLoadFile(filePath); @@ -1505,21 +1718,29 @@ void tst_qmlls_utils::renameUsages() if constexpr (enable_debug_output) { if (edits != expectedRenames) { qDebug() << "Got:\n"; - for (auto &x : edits) { + for (auto &x : edits.renameInFile()) { qDebug() << x.replacement << x.location.filename << "(" << x.location.sourceLocation.startLine << ", " << x.location.sourceLocation.startColumn << "), " << x.location.sourceLocation.offset << "+" << x.location.sourceLocation.length; } + qDebug() << "with renames in filenames:"; + for (auto &x : edits.renameInFilename()) { + qDebug() << x.oldFilename << "->" << x.newFilename; + } qDebug() << "But expected: \n"; - for (auto &x : expectedRenames) { + for (auto &x : expectedRenames.renameInFile()) { qDebug() << x.replacement << x.location.filename << "(" << x.location.sourceLocation.startLine << ", " << x.location.sourceLocation.startColumn << "), " << x.location.sourceLocation.offset << "+" << x.location.sourceLocation.length; } + qDebug() << "with renames in filenames:"; + for (auto &x : expectedRenames.renameInFilename()) { + qDebug() << x.oldFilename << "->" << x.newFilename; + } } } QCOMPARE(edits, expectedRenames); @@ -1676,11 +1897,13 @@ void tst_qmlls_utils::resolveExpressionType_data() // keep in mind that line and character are starting at 1! QTest::addColumn<int>("line"); QTest::addColumn<int>("character"); - QTest::addColumn<QQmlLSUtilsResolveOptions>("resolveOption"); + QTest::addColumn<QQmlLSUtils::ResolveOptions>("resolveOption"); QTest::addColumn<QString>("expectedFile"); // startline of the owners definition QTest::addColumn<int>("expectedLine"); - QTest::addColumn<QQmlLSUtilsIdentifierType>("expectedType"); + QTest::addColumn<QQmlLSUtils::IdentifierType>("expectedType"); + + using namespace QQmlLSUtils; const int noLine = -1; const QString noFile; @@ -1828,10 +2051,10 @@ void tst_qmlls_utils::resolveExpressionType() QFETCH(QString, filePath); QFETCH(int, line); QFETCH(int, character); - QFETCH(QQmlLSUtilsResolveOptions, resolveOption); + QFETCH(QQmlLSUtils::ResolveOptions, resolveOption); QFETCH(QString, expectedFile); QFETCH(int, expectedLine); - QFETCH(QQmlLSUtilsIdentifierType, expectedType); + QFETCH(QQmlLSUtils::IdentifierType, expectedType); // they all start at 1. Q_ASSERT(line > 0); @@ -3930,97 +4153,4 @@ void tst_qmlls_utils::cmakeBuildCommand() QCOMPARE(QQmlLSUtils::cmakeBuildCommand(path), expected); } -void tst_qmlls_utils::qdochtmlparser_data() -{ - QTest::addColumn<QString>("filePath"); - QTest::addColumn<QDocHtmlExtractor::Element>("element"); - QTest::addColumn<QDocHtmlExtractor::ExtractionMode>("extractionMode"); - QTest::addColumn<QString>("expectedDocumentation"); - - QTest::addRow("qml-object-type-extended-plaintext") - << testFile("qdochtmlparser/qml-qtqml-qtobject.html") - << QDocHtmlExtractor::Element{"QtObject", QDocHtmlExtractor::ElementType::QmlType} - << QDocHtmlExtractor::ExtractionMode::Extended - << R"(The QtObject type is a non-visual element which contains only the objectName property. -It can be useful to create a QtObject if you need an extremely lightweight type to enclose a set of custom properties: - - import QtQuick - - Item { - QtObject { - id: attributes - property string name - property int size - property variant attributes - } - - Text { text: attributes.name } - } - -It can also be useful for C++ integration, as it is just a plain QObject. See the QObject documentation for further details.)"; - - QTest::addRow("qml-object-type-simplified-plaintext") - << testFile("qdochtmlparser/qml-qtqml-qtobject.html") - << QDocHtmlExtractor::Element{"QtObject", QDocHtmlExtractor::ElementType::QmlType} - << QDocHtmlExtractor::ExtractionMode::Simplified - << R"(A basic QML type.)"; - - QTest::addRow("qml-property-simplified-plaintext") - << testFile("qdochtmlparser/qml-qtqml-qtobject.html") - << QDocHtmlExtractor::Element{"objectName",QDocHtmlExtractor::ElementType::QmlProperty} - << QDocHtmlExtractor::ExtractionMode::Simplified - << R"(This property holds the QObject::objectName for this specific object instance.)"; - - QTest::addRow("qml-property-simplified-plaintext-from-Qt5") - << testFile("qdochtmlparser/qml-qtqml-qtobject-qt-5.html") << QDocHtmlExtractor::Element{"objectName", QDocHtmlExtractor::ElementType::QmlProperty} - << QDocHtmlExtractor::ExtractionMode::Simplified - << R"(This property holds the QObject::objectName for this specific object instance.)"; - - QTest::addRow("qml-property-simplified-plaintext") - << testFile("qdochtmlparser/qml-qtquick-item.html") << QDocHtmlExtractor::Element{"width", QDocHtmlExtractor::ElementType::QmlProperty} - << QDocHtmlExtractor::ExtractionMode::Simplified - << R"(Defines the item's position and size. The default value is 0.)"; - - QTest::addRow("qml-group-property-simplified-plaintext") - << testFile("qdochtmlparser/qml-qtquick-item.html") << QDocHtmlExtractor::Element{"anchors.fill", QDocHtmlExtractor::ElementType::QmlProperty} - << QDocHtmlExtractor::ExtractionMode::Simplified - << R"(Anchors provide a way to position an item by specifying its relationship with other items.)"; - QTest::addRow("qml-functions") - << testFile("qdochtmlparser/qml-qtquick-item.html") << QDocHtmlExtractor::Element{"mapFromGlobal", QDocHtmlExtractor::ElementType::QmlMethod} - << QDocHtmlExtractor::ExtractionMode::Simplified - << "Maps the point (x, y), which is in the global coordinate system, to the item's coordinate system," - " and returns a point matching the mapped coordinate."; - - QTest::addRow("qml-functions-list") - << testFile("qdochtmlparser/qml-qtquick-item.html") << QDocHtmlExtractor::Element{"mapFromItem", QDocHtmlExtractor::ElementType::QmlMethod} - << QDocHtmlExtractor::ExtractionMode::Simplified - << "Maps the point (x, y) or rect (x, y, width, height), which is in item's coordinate system," - " to this item's coordinate system, and returns a point or rect matching the mapped coordinate."; - QTest::addRow("qml-signal") - << testFile("qdochtmlparser/qml-qtquick-mousearea.html") << QDocHtmlExtractor::Element{"pressAndHold", QDocHtmlExtractor::ElementType::QmlSignal} - << QDocHtmlExtractor::ExtractionMode::Simplified - << "This signal is emitted when there is a long press (currently 800ms). The mouse parameter provides information about the press, " - "including the x and y position of the press, and which button is pressed."; -} - -void tst_qmlls_utils::qdochtmlparser() -{ - QFETCH(QString, filePath); - QFETCH(QDocHtmlExtractor::Element, element); - QFETCH(QDocHtmlExtractor::ExtractionMode, extractionMode); - QFETCH(QString, expectedDocumentation); - - const auto htmlCode = [](const QString &testFileName) { - QFile file(testFileName); - if (file.open(QIODeviceBase::ReadOnly | QIODevice::Text)) - return QString::fromUtf8(file.readAll()); - return QString{}; - }(filePath); - - - QDocHtmlExtractor extractor(htmlCode); - const auto actual = extractor.extract(element, extractionMode); - QCOMPARE(actual, expectedDocumentation); -} - QTEST_MAIN(tst_qmlls_utils) diff --git a/tests/auto/qmlls/utils/tst_qmlls_utils.h b/tests/auto/qmlls/utils/tst_qmlls_utils.h index 51fa74dd0a..2f1ea19a2c 100644 --- a/tests/auto/qmlls/utils/tst_qmlls_utils.h +++ b/tests/auto/qmlls/utils/tst_qmlls_utils.h @@ -80,9 +80,6 @@ private slots: void cmakeBuildCommand(); - void qdochtmlparser_data(); - void qdochtmlparser(); - private: using EnvironmentAndFile = std::tuple<QQmlJS::Dom::DomItem, QQmlJS::Dom::DomItem>; diff --git a/tests/auto/quick/CMakeLists.txt b/tests/auto/quick/CMakeLists.txt index 3ecf89f351..32a893defb 100644 --- a/tests/auto/quick/CMakeLists.txt +++ b/tests/auto/quick/CMakeLists.txt @@ -95,6 +95,7 @@ if(QT_FEATURE_private_tests) add_subdirectory(qquickspritesequence) add_subdirectory(qquickrhiitem) add_subdirectory(rendernode) + add_subdirectory(platform) if(QT_FEATURE_opengl) add_subdirectory(qquickframebufferobject) diff --git a/tests/auto/quick/platform/CMakeLists.txt b/tests/auto/quick/platform/CMakeLists.txt new file mode 100644 index 0000000000..6a5f5760fc --- /dev/null +++ b/tests/auto/quick/platform/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(ANDROID) + add_subdirectory(android) +endif() diff --git a/tests/auto/quick/platform/android/CMakeLists.txt b/tests/auto/quick/platform/android/CMakeLists.txt new file mode 100644 index 0000000000..d902be5aac --- /dev/null +++ b/tests/auto/quick/platform/android/CMakeLists.txt @@ -0,0 +1,4 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +add_subdirectory(qtandroiditemmodel) diff --git a/tests/auto/quick/platform/android/qtandroiditemmodel/CMakeLists.txt b/tests/auto/quick/platform/android/qtandroiditemmodel/CMakeLists.txt new file mode 100644 index 0000000000..d8cdb763c0 --- /dev/null +++ b/tests/auto/quick/platform/android/qtandroiditemmodel/CMakeLists.txt @@ -0,0 +1,25 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qtandroiditemmodel Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qtandroiditemmodel LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qtandroiditemmodel + SOURCES + tst_qtandroiditemmodel.cpp + LIBRARIES + Qt::Gui + Qt::Quick + Qt::QuickPrivate +) + +set_property(TARGET tst_qtandroiditemmodel APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR + ${CMAKE_CURRENT_SOURCE_DIR}/testdata +) diff --git a/tests/auto/quick/platform/android/qtandroiditemmodel/testdata/src/org/qtproject/qt/android/tests/TestModel.java b/tests/auto/quick/platform/android/qtandroiditemmodel/testdata/src/org/qtproject/qt/android/tests/TestModel.java new file mode 100644 index 0000000000..6bfb1dbc2f --- /dev/null +++ b/tests/auto/quick/platform/android/qtandroiditemmodel/testdata/src/org/qtproject/qt/android/tests/TestModel.java @@ -0,0 +1,120 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +package org.qtproject.qt.android.tests; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.qtproject.qt.android.QtAbstractItemModel; +import org.qtproject.qt.android.QtModelIndex; + +public class TestModel extends QtAbstractItemModel { + int m_rows = 0; + int m_cols = 0; + + @Override + public int columnCount(QtModelIndex parent) { + return parent.isValid() ? 0 : m_cols; + } + + @Override + public Object data(QtModelIndex index, int role) { + int r = index.row(); + int c = index.column(); + if (r < 0 || c < 0 || c > m_cols || r > m_rows) + return null; + + switch (role) { + case 0: + return String.format("r%d/c%d", r, c); + case 1: + return new Boolean(((r + c) % 2) == 0); + case 2: + return new Integer((c << 8) + r); + case 3: + return new Double((r + 1.0) / (c + 1.0)); + case 4: + return new Long((c << 8) * (r << 8)); + default: + return null; + } + } + + @Override + public QtModelIndex index(int row, int column, QtModelIndex parent) { + return hasIndex(row, column, parent) ? createIndex(row, column, 0) : new QtModelIndex(); + } + + @Override + public QtModelIndex parent(QtModelIndex qtModelIndex) { + return new QtModelIndex(); + } + + @Override + public int rowCount(QtModelIndex parent) { + return parent.isValid() ? 0 : m_rows; + } + + @Override + public HashMap<Integer,String> roleNames(){ + final HashMap<Integer,String> roles = new HashMap<Integer,String>(); + roles.put(0, "stringRole"); + roles.put(1, "booleanRole"); + roles.put(2, "integerRole"); + roles.put(3, "doubleRole"); + roles.put(4, "longRole"); + return roles; + } + + @Override public boolean canFetchMore(QtModelIndex parent) + { + return !parent.isValid() && (m_rows < 30); + } + + @Override public void fetchMore(QtModelIndex parent) + { + if (!canFetchMore(parent)) + return; + int toAdd = Math.min(10, 30 - rowCount(parent)); + beginInsertRows(new QtModelIndex(), m_rows, m_rows + toAdd - 1); + m_rows += toAdd; + endInsertRows(); + } + + public void addRow() { + beginInsertRows(new QtModelIndex(), m_rows, m_rows); + m_rows++; + endInsertRows(); + } + + public void removeRow() { + if (m_rows == 0) + return; + beginRemoveRows(new QtModelIndex(), 0, 0); + m_rows--; + endRemoveRows(); + } + + public void addCol() { + beginInsertColumns(new QtModelIndex(), m_cols, m_cols); + m_cols++; + endInsertColumns(); + } + + public void removeCol() { + if (m_cols == 0) + return; + beginRemoveColumns(new QtModelIndex(), 0, 0); + m_cols--; + endRemoveColumns(); + } + + public void reset() { + beginResetModel(); + m_rows = 0; + m_cols = 0; + endResetModel(); + } +} diff --git a/tests/auto/quick/platform/android/qtandroiditemmodel/tst_qtandroiditemmodel.cpp b/tests/auto/quick/platform/android/qtandroiditemmodel/tst_qtandroiditemmodel.cpp new file mode 100644 index 0000000000..ae1971df01 --- /dev/null +++ b/tests/auto/quick/platform/android/qtandroiditemmodel/tst_qtandroiditemmodel.cpp @@ -0,0 +1,182 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtTest/QTest> + +#include <QtQuick/private/qandroiditemmodelproxy_p.h> +#include <QtQuick/private/qandroidmodelindexproxy_p.h> +#include <QtQuick/private/qandroidtypes_p.h> + +#include <QGuiApplication> +#include <QtCore/qabstractitemmodel.h> +#include <QtCore/qjniobject.h> +#include <QtCore/qjnitypes.h> +#include <QtCore/qstring.h> + +using namespace Qt::Literals; + +Q_DECLARE_JNI_CLASS(JTestModel, "org/qtproject/qt/android/tests/TestModel") + +class tst_QtAndroidItemModel : public QObject +{ + Q_OBJECT + JTestModel jModel; + QAbstractItemModel *qProxy; + void resetModel(); + +private slots: + void initTestCase(); + void cleanup(); + void addRow(); + void addColumn(); + void removeRow(); + void removeColumn(); + void roleNames(); + void fetchMore(); + void hasIndex(); + void data(); +}; + +void tst_QtAndroidItemModel::initTestCase() +{ + QVERIFY(jModel.isValid()); + qProxy = QAndroidItemModelProxy::createNativeProxy(jModel); + QVERIFY(qProxy); +} + +void tst_QtAndroidItemModel::cleanup() +{ + resetModel(); +} + +void tst_QtAndroidItemModel::addRow() +{ + const int rowsBefore = qProxy->rowCount(); + jModel.callMethod<void>("addRow"); + QCOMPARE_EQ(qProxy->rowCount(), rowsBefore + 1); +} + +void tst_QtAndroidItemModel::addColumn() +{ + const int columnsBefore = qProxy->columnCount(); + jModel.callMethod<void>("addCol"); + QCOMPARE_EQ(qProxy->columnCount(), columnsBefore + 1); +} + +void tst_QtAndroidItemModel::removeRow() +{ + jModel.callMethod<void>("addRow"); + jModel.callMethod<void>("addRow"); + QCOMPARE_EQ(qProxy->rowCount(), 2); + jModel.callMethod<void>("removeRow"); + QCOMPARE_EQ(qProxy->rowCount(), 1); + jModel.callMethod<void>("removeRow"); + QCOMPARE_EQ(qProxy->rowCount(), 0); +} + +void tst_QtAndroidItemModel::removeColumn() +{ + jModel.callMethod<void>("addCol"); + jModel.callMethod<void>("addCol"); + QCOMPARE_EQ(qProxy->columnCount(), 2); + jModel.callMethod<void>("removeCol"); + QCOMPARE_EQ(qProxy->columnCount(), 1); + jModel.callMethod<void>("removeCol"); + QCOMPARE_EQ(qProxy->columnCount(), 0); +} + +void tst_QtAndroidItemModel::roleNames() +{ + const static QHash<int, QByteArray> expectedRoles = { { 0, "stringRole" }, + { 1, "booleanRole" }, + { 2, "integerRole" }, + { 3, "doubleRole" }, + { 4, "longRole" } }; + QCOMPARE(qProxy->roleNames(), expectedRoles); +} + +void tst_QtAndroidItemModel::fetchMore() +{ + // In the Java TestModel : + // canFetchMore() returns true when row count is less than 30 + // fetchMore() adds 10 rows at most, or the remaining until row count is 30 + QVERIFY(qProxy->canFetchMore(QModelIndex())); + qProxy->fetchMore(QModelIndex()); + QCOMPARE_EQ(qProxy->rowCount(), 10); + QVERIFY(qProxy->canFetchMore(QModelIndex())); + qProxy->fetchMore(QModelIndex()); + QCOMPARE_EQ(qProxy->rowCount(), 20); + jModel.callMethod<void>("addRow"); + QVERIFY(qProxy->canFetchMore(QModelIndex())); + qProxy->fetchMore(QModelIndex()); + QCOMPARE_EQ(qProxy->rowCount(), 30); + QVERIFY(!qProxy->canFetchMore(QModelIndex())); +} + +void tst_QtAndroidItemModel::hasIndex() +{ + // fetchMore() adds 10 rows + qProxy->fetchMore(QModelIndex()); + jModel.callMethod<void>("addCol"); + jModel.callMethod<void>("addCol"); + + for (int r = 0; r < 10; ++r) { + for (int c = 0; c < 2; ++c) { + QVERIFY(qProxy->hasIndex(r, c)); + } + } +} + +void tst_QtAndroidItemModel::data() +{ + const static QHash<int, QMetaType::Type> roleToType = { { 0, QMetaType::QString }, + { 1, QMetaType::Bool }, + { 2, QMetaType::Int }, + { 3, QMetaType::Double }, + { 4, QMetaType::Long } }; + QVERIFY(qProxy->canFetchMore(QModelIndex())); + qProxy->fetchMore(QModelIndex()); + QCOMPARE_EQ(qProxy->rowCount(), 10); + jModel.callMethod<void>("addCol"); + jModel.callMethod<void>("addCol"); + jModel.callMethod<void>("addCol"); + + for (int r = 0; r < 10; ++r) { + for (int c = 0; c < 3; ++c) { + QModelIndex index = qProxy->index(r, c); + for (int role : roleToType.keys()) { + const QVariant data = qProxy->data(index, role); + QCOMPARE_EQ(data.typeId(), roleToType[role]); + switch (role) { + case 0: + QCOMPARE(data.toString(), + "r%1/c%2"_L1.arg(QString::number(r), QString::number(c))); + break; + case 1: + QCOMPARE(data.toBool(), ((r + c) % 2) == 0); + break; + case 2: + QCOMPARE(data.toInt(), (c << 8) + r); + break; + case 3: + QVERIFY(qFuzzyCompare(data.toDouble(), (1.0 + r) / (1.0 + c))); + break; + case 4: + QCOMPARE(data.toULongLong(), ((c << 8) * (r << 8))); + break; + } + } + } + } +} + +void tst_QtAndroidItemModel::resetModel() +{ + jModel.callMethod<void>("reset"); + QCOMPARE_EQ(qProxy->rowCount(), 0); + QCOMPARE_EQ(qProxy->columnCount(), 0); +} + +#include "tst_qtandroiditemmodel.moc" + +QTEST_MAIN(tst_QtAndroidItemModel) diff --git a/tests/auto/quick/qquickaccessible/tst_qquickaccessible.cpp b/tests/auto/quick/qquickaccessible/tst_qquickaccessible.cpp index e164d89217..d2a9ea9997 100644 --- a/tests/auto/quick/qquickaccessible/tst_qquickaccessible.cpp +++ b/tests/auto/quick/qquickaccessible/tst_qquickaccessible.cpp @@ -63,6 +63,7 @@ private slots: void checkableTest(); void ignoredTest(); void passwordTest(); + void announceTest(); }; tst_QQuickAccessible::tst_QQuickAccessible() @@ -161,7 +162,7 @@ void tst_QQuickAccessible::quickAttachedProperties() // Attaching to non-item { QObject parent; - QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML QtObject: Accessible must be attached to an Item"); + QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML QtObject: Accessible must be attached to an Item or an Action"); QQuickAccessibleAttached *attachedObj = new QQuickAccessibleAttached(&parent); QCOMPARE(attachedObj->ignored(), false); @@ -699,6 +700,25 @@ void tst_QQuickAccessible::passwordTest() QTestAccessibility::clearEvents(); } +void tst_QQuickAccessible::announceTest() +{ + QQmlEngine engine; + QQmlComponent component(&engine); + component.setData("import QtQuick\nItem {\n" + "Component.onCompleted: Accessible.announce('I am complete!')" + "}", + QUrl()); + auto object = std::unique_ptr<QObject>(component.create()); + QVERIFY(object != nullptr); + + QAccessibleEvent createdEvent(object.get(), QAccessible::ObjectCreated); + QVERIFY_EVENT(&createdEvent); + QAccessibleAnnouncementEvent event(object.get(), QStringLiteral("I am complete!")); + QVERIFY_EVENT(&event); + + QTestAccessibility::clearEvents(); +} + QTEST_MAIN(tst_QQuickAccessible) #include "tst_qquickaccessible.moc" diff --git a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp index 25c8559ed3..693bdc1a55 100644 --- a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp +++ b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp @@ -1,28 +1,31 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only -#include <QtTest/QtTest> + +#include <private/qanimationgroupjob_p.h> +#include <private/qmlutils_p.h> + +#include <private/qqmllistmodel_p.h> +#include <private/qqmltimer_p.h> + +#include <private/qquickanimation_p_p.h> +#include <private/qquickanimatorjob_p.h> +#include <private/qquickflickable_p.h> +#include <private/qquickframeanimation_p.h> +#include <private/qquickitem_p.h> +#include <private/qquickitemanimation_p.h> +#include <private/qquickpathinterpolator_p.h> +#include <private/qquickrectangle_p.h> +#include <private/qquicktransition_p.h> + +#include <QtQuick/qquickview.h> + +#include <QtTest/qtest.h> +#include <QtTest/qsignalspy.h> + #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcomponent.h> -#include <QtQuick/qquickview.h> -#include <QtQml/private/qqmltimer_p.h> -#include <QtQmlModels/private/qqmllistmodel_p.h> -#include <QtQml/private/qanimationgroupjob_p.h> -#include <QtQuick/private/qquickrectangle_p.h> -#include <QtQuick/private/qquickitemanimation_p.h> -#include <QtQuick/private/qquickitemanimation_p_p.h> -#include <QtQuick/private/qquicktransition_p.h> -#include <QtQuick/private/qquickanimation_p.h> -#include <QtQuick/private/qquickanimatorjob_p.h> -#include <QtQuick/private/qquickpathinterpolator_p.h> -#include <QtQuick/private/qquickitem_p.h> -#include <QtQuick/private/qquicklistview_p.h> -#include <QtQuick/private/qquickframeanimation_p.h> -#include <QEasingCurve> - -#include <limits.h> -#include <math.h> - -#include <QtQuickTestUtils/private/qmlutils_p.h> + +#include <QtCore/qeasingcurve.h> class tst_qquickanimations : public QQmlDataTest { diff --git a/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp b/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp index b003511356..e13a818cf8 100644 --- a/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp +++ b/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp @@ -1693,7 +1693,7 @@ void tst_qquickflickable::flickVelocity() QTRY_VERIFY(flickable->verticalVelocity() < 0.0); QTRY_COMPARE(flickable->verticalVelocity(), 0.0); -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS QSKIP("boost doesn't work on OS X"); return; #endif diff --git a/tests/auto/quick/qquickimage/data/multiframeAsyncRetain.qml b/tests/auto/quick/qquickimage/data/multiframeAsyncRetain.qml new file mode 100644 index 0000000000..fc0f135528 --- /dev/null +++ b/tests/auto/quick/qquickimage/data/multiframeAsyncRetain.qml @@ -0,0 +1,7 @@ +import QtQuick + +Image { + source: "multi.ico" + asynchronous: true + retainWhileLoading: true +} diff --git a/tests/auto/quick/qquickimage/tst_qquickimage.cpp b/tests/auto/quick/qquickimage/tst_qquickimage.cpp index 427a45977f..d19c99de82 100644 --- a/tests/auto/quick/qquickimage/tst_qquickimage.cpp +++ b/tests/auto/quick/qquickimage/tst_qquickimage.cpp @@ -135,41 +135,49 @@ void tst_qquickimage::imageSource_data() QTest::addColumn<bool>("async"); QTest::addColumn<bool>("cache"); QTest::addColumn<QString>("error"); + QTest::addColumn<bool>("retainWhileLoading"); - QTest::newRow("local") << testFileUrl("colors.png").toString() << 120.0 << 120.0 << false << false << true << ""; - QTest::newRow("local no cache") << testFileUrl("colors.png").toString() << 120.0 << 120.0 << false << false << false << ""; - QTest::newRow("local async") << testFileUrl("colors1.png").toString() << 120.0 << 120.0 << false << true << true << ""; + QTest::newRow("local") << testFileUrl("colors.png").toString() << 120.0 << 120.0 << false << false << true << "" << false; + QTest::newRow("local no cache") << testFileUrl("colors.png").toString() << 120.0 << 120.0 << false << false << false << "" << false; + QTest::newRow("local async") << testFileUrl("colors1.png").toString() << 120.0 << 120.0 << false << true << true << "" << false; + QTest::newRow("local async retain") << testFileUrl("colors1.png").toString() << 120.0 << 120.0 << false << true << true << "" << true; QTest::newRow("local not found") << testFileUrl("no-such-file.png").toString() << 0.0 << 0.0 << false - << false << true << "<Unknown File>:2:1: QML Image: Cannot open: " + testFileUrl("no-such-file.png").toString(); + << false << true << "<Unknown File>:2:1: QML Image: Cannot open: " + testFileUrl("no-such-file.png").toString() << false; QTest::newRow("local async not found") << testFileUrl("no-such-file-1.png").toString() << 0.0 << 0.0 << false - << true << true << "<Unknown File>:2:1: QML Image: Cannot open: " + testFileUrl("no-such-file-1.png").toString(); - QTest::newRow("remote") << "/colors.png" << 120.0 << 120.0 << true << false << true << ""; - QTest::newRow("remote redirected") << "/oldcolors.png" << 120.0 << 120.0 << true << false << false << ""; + << true << true << "<Unknown File>:2:1: QML Image: Cannot open: " + testFileUrl("no-such-file-1.png").toString() << false; + QTest::newRow("local async retain not found") << testFileUrl("no-such-file-1.png").toString() << 0.0 << 0.0 << false + << true << true << "<Unknown File>:2:1: QML Image: Cannot open: " + testFileUrl("no-such-file-1.png").toString() << true; + QTest::newRow("remote") << "/colors.png" << 120.0 << 120.0 << true << false << true << "" << false; + QTest::newRow("remote retain") << "/colors.png" << 120.0 << 120.0 << true << false << true << "" << true; + QTest::newRow("remote redirected") << "/oldcolors.png" << 120.0 << 120.0 << true << false << false << "" << false; if (QImageReader::supportedImageFormats().contains("svg")) - QTest::newRow("remote svg") << "/heart.svg" << 595.0 << 841.0 << true << false << false << ""; + QTest::newRow("remote svg") << "/heart.svg" << 595.0 << 841.0 << true << false << false << "" << false; if (QImageReader::supportedImageFormats().contains("svgz")) - QTest::newRow("remote svgz") << "/heart.svgz" << 595.0 << 841.0 << true << false << false << ""; + QTest::newRow("remote svgz") << "/heart.svgz" << 595.0 << 841.0 << true << false << false << "" << false; if (graphicsApi != QSGRendererInterface::Software) { - QTest::newRow("texturefile pkm format") << testFileUrl("logo.pkm").toString() << 256.0 << 256.0 << false << false << true << ""; - QTest::newRow("texturefile ktx format") << testFileUrl("car.ktx").toString() << 146.0 << 80.0 << false << false << true << ""; - QTest::newRow("texturefile async") << testFileUrl("logo.pkm").toString() << 256.0 << 256.0 << false << true << true << ""; - QTest::newRow("texturefile remote") << "/logo.pkm" << 256.0 << 256.0 << true << false << true << ""; + QTest::newRow("texturefile pkm format") << testFileUrl("logo.pkm").toString() << 256.0 << 256.0 << false << false << true << "" << false; + QTest::newRow("texturefile ktx format") << testFileUrl("car.ktx").toString() << 146.0 << 80.0 << false << false << true << "" << false; + QTest::newRow("texturefile async") << testFileUrl("logo.pkm").toString() << 256.0 << 256.0 << false << true << true << "" << false; + QTest::newRow("texturefile async retain") << testFileUrl("logo.pkm").toString() << 256.0 << 256.0 << false << true << true << "" << true; + QTest::newRow("texturefile remote") << "/logo.pkm" << 256.0 << 256.0 << true << false << true << "" << false; + QTest::newRow("texturefile remote retain") << "/logo.pkm" << 256.0 << 256.0 << true << false << true << "" << true; } QTest::newRow("remote not found") << "/no-such-file.png" << 0.0 << 0.0 << true - << false << true << "<Unknown File>:2:1: QML Image: Error transferring {{ServerBaseUrl}}/no-such-file.png - server replied: Not found"; - QTest::newRow("extless") << testFileUrl("colors").toString() << 120.0 << 120.0 << false << false << true << ""; - QTest::newRow("extless no cache") << testFileUrl("colors").toString() << 120.0 << 120.0 << false << false << false << ""; - QTest::newRow("extless async") << testFileUrl("colors1").toString() << 120.0 << 120.0 << false << true << true << ""; + << false << true << "<Unknown File>:2:1: QML Image: Error transferring {{ServerBaseUrl}}/no-such-file.png - server replied: Not found" << false; + QTest::newRow("extless") << testFileUrl("colors").toString() << 120.0 << 120.0 << false << false << true << "" << false; + QTest::newRow("extless no cache") << testFileUrl("colors").toString() << 120.0 << 120.0 << false << false << false << "" << false; + QTest::newRow("extless async") << testFileUrl("colors1").toString() << 120.0 << 120.0 << false << true << true << "" << false; + QTest::newRow("extless async retain") << testFileUrl("colors1").toString() << 120.0 << 120.0 << false << true << true << "" << true; QTest::newRow("extless not found") << testFileUrl("no-such-file").toString() << 0.0 << 0.0 << false - << false << true << "<Unknown File>:2:1: QML Image: Cannot open: " + testFileUrl("no-such-file").toString(); + << false << true << "<Unknown File>:2:1: QML Image: Cannot open: " + testFileUrl("no-such-file").toString() << false; // Test that texture file is preferred over image file, when supported. // Since pattern.pkm has different size than pattern.png, these tests verify that the right file has been loaded if (graphicsApi != QSGRendererInterface::Software) { - QTest::newRow("extless prefer-tex") << testFileUrl("pattern").toString() << 64.0 << 64.0 << false << false << true << ""; - QTest::newRow("extless prefer-tex async") << testFileUrl("pattern").toString() << 64.0 << 64.0 << false << true << true << ""; + QTest::newRow("extless prefer-tex") << testFileUrl("pattern").toString() << 64.0 << 64.0 << false << false << true << "" << false; + QTest::newRow("extless prefer-tex async") << testFileUrl("pattern").toString() << 64.0 << 64.0 << false << true << true << "" << false; } else { - QTest::newRow("extless ignore-tex") << testFileUrl("pattern").toString() << 200.0 << 200.0 << false << false << true << ""; - QTest::newRow("extless ignore-tex async") << testFileUrl("pattern").toString() << 200.0 << 200.0 << false << true << true << ""; + QTest::newRow("extless ignore-tex") << testFileUrl("pattern").toString() << 200.0 << 200.0 << false << false << true << "" << false; + QTest::newRow("extless ignore-tex async") << testFileUrl("pattern").toString() << 200.0 << 200.0 << false << true << true << "" << false; } } @@ -183,6 +191,7 @@ void tst_qquickimage::imageSource() QFETCH(bool, async); QFETCH(bool, cache); QFETCH(QString, error); + QFETCH(bool, retainWhileLoading); TestHTTPServer server; if (remote) { @@ -196,9 +205,10 @@ void tst_qquickimage::imageSource() if (!error.isEmpty()) QTest::ignoreMessage(QtWarningMsg, error.toUtf8()); - QString componentStr = "import QtQuick 2.0\nImage { source: \"" + source + "\"; asynchronous: " + QString componentStr = "import QtQuick\nImage { source: \"" + source + "\"; asynchronous: " + (async ? QLatin1String("true") : QLatin1String("false")) + "; cache: " - + (cache ? QLatin1String("true") : QLatin1String("false")) + " }"; + + (cache ? QLatin1String("true") : QLatin1String("false")) + "; retainWhileLoading: " + + (retainWhileLoading ? QLatin1String("true") : QLatin1String("false")) + " }"; QQmlComponent component(&engine); component.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickImage *obj = qobject_cast<QQuickImage*>(component.create()); @@ -209,6 +219,8 @@ void tst_qquickimage::imageSource() else QVERIFY(!obj->asynchronous()); + QCOMPARE(obj->retainWhileLoading(), retainWhileLoading); + if (cache) QVERIFY(obj->cache()); else @@ -1208,6 +1220,7 @@ void tst_qquickimage::multiFrame_data() QTest::addRow("default") << "multiframe.qml" << false; QTest::addRow("async") << "multiframeAsync.qml" << true; + QTest::addRow("async retain") << "multiframeAsyncRetain.qml" << true; } void tst_qquickimage::multiFrame() diff --git a/tests/auto/quick/qquickitem2/CMakeLists.txt b/tests/auto/quick/qquickitem2/CMakeLists.txt index 7034acc184..6b115efd2e 100644 --- a/tests/auto/quick/qquickitem2/CMakeLists.txt +++ b/tests/auto/quick/qquickitem2/CMakeLists.txt @@ -37,6 +37,11 @@ qt_internal_add_test(tst_qquickitem2 ## Scopes: ##################################################################### +qt_internal_extend_target(tst_qquickitem2 CONDITION TARGET Qt::Widgets + LIBRARIES + Qt::Widgets +) + qt_internal_extend_target(tst_qquickitem2 CONDITION ANDROID OR IOS DEFINES QT_QMLTEST_DATADIR=":/data" diff --git a/tests/auto/quick/qquickitem2/data/embedded.qml b/tests/auto/quick/qquickitem2/data/embedded.qml new file mode 100644 index 0000000000..a9cf115699 --- /dev/null +++ b/tests/auto/quick/qquickitem2/data/embedded.qml @@ -0,0 +1,30 @@ +import QtQuick + +Rectangle { + width: 300 + height: 300 + + Column { + anchors.fill: parent + anchors.rightMargin: 2 + anchors.leftMargin: 2 + anchors.topMargin: 10 + spacing: 20 + Rectangle { + objectName: "rect1" + width: parent.width + height: 30 + border.width: 1 + border.color: activeFocus ? "blue" : "black" + focusPolicy: Qt.TabFocus + } + Rectangle { + objectName: "rect2" + width: parent.width + height: 30 + border.width: 1 + border.color: activeFocus ? "blue" : "black" + focusPolicy: Qt.TabFocus + } + } +} diff --git a/tests/auto/quick/qquickitem2/data/embedded_FocusScope.qml b/tests/auto/quick/qquickitem2/data/embedded_FocusScope.qml new file mode 100644 index 0000000000..0d154f76e5 --- /dev/null +++ b/tests/auto/quick/qquickitem2/data/embedded_FocusScope.qml @@ -0,0 +1,37 @@ +import QtQuick + +Rectangle { + width: 300 + height: 300 + + FocusScope { + width: parent.width + height: parent.height + focus: true + + Column { + anchors.fill: parent + anchors.rightMargin: 2 + anchors.leftMargin: 2 + anchors.topMargin: 10 + spacing: 20 + Rectangle { + objectName: "rect1" + width: parent.width + height: 30 + border.width: 1 + border.color: activeFocus ? "blue" : "black" + focusPolicy: Qt.TabFocus + } + Rectangle { + objectName: "rect2" + width: parent.width + height: 30 + border.width: 1 + border.color: activeFocus ? "blue" : "black" + focusPolicy: Qt.TabFocus + focus: true + } + } + } +} diff --git a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp index 57deb0a46a..56271ec3f2 100644 --- a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp +++ b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp @@ -23,6 +23,11 @@ #include <QtQuickTestUtils/private/viewtestutils_p.h> #include <QtQuickTestUtils/private/platforminputcontext_p.h> #include <QtTest/private/qpropertytesthelper_p.h> +#ifdef QT_WIDGETS_LIB +#include <QtWidgets/qwidget.h> +#include <QtWidgets/qboxlayout.h> +#include <QtWidgets/qlineedit.h> +#endif using namespace QQuickVisualTestUtils; @@ -134,6 +139,11 @@ private slots: void lastFocusChangeReason(); void focusInScopeChanges(); +#ifdef QT_WIDGETS_LIB + void embeddedInWidgetsFocus_data(); + void embeddedInWidgetsFocus(); +#endif + private: QQmlEngine engine; bool qt_tab_all_widgets() { @@ -4431,6 +4441,78 @@ void tst_QQuickItem::focusInScopeChanges() QVERIFY(textInput->hasActiveFocus()); } +#ifdef QT_WIDGETS_LIB +void tst_QQuickItem::embeddedInWidgetsFocus_data() +{ + QTest::addColumn<QUrl>("source"); + QTest::newRow("Embedded") << testFileUrl("embedded.qml"); + QTest::newRow("Embedded Focus Scope") << testFileUrl("embedded_FocusScope.qml"); +} + +void tst_QQuickItem::embeddedInWidgetsFocus() +{ + QFETCH(QUrl, source); + QWidget root; + QVBoxLayout *layout = new QVBoxLayout(&root); + + QLineEdit *lineEdit1 = new QLineEdit(&root); + lineEdit1->setFocusPolicy(Qt::FocusPolicy::TabFocus); + + QQuickView *quickView = new QQuickView; + quickView->setSource(source); + QWidget *container = QWidget::createWindowContainer(quickView, &root); + container->setMinimumSize(quickView->size()); + container->setFocusPolicy(Qt::TabFocus); + + QLineEdit *lineEdit2 = new QLineEdit(&root); + lineEdit2->setFocusPolicy(Qt::FocusPolicy::TabFocus); + + layout->addWidget(lineEdit1); + layout->addWidget(container); + layout->addWidget(lineEdit2); + + QQuickItem *rect1 = findItem<QQuickItem>(quickView->rootObject(), "rect1"); + QQuickItem *rect2 = findItem<QQuickItem>(quickView->rootObject(), "rect2"); + QVERIFY(rect1); + QVERIFY(rect2); + + root.show(); + QTRY_VERIFY(root.isVisible()); + QVERIFY(QTest::qWaitForWindowExposed(&root)); + QVERIFY(QTest::qWaitForWindowFocused(root.windowHandle())); + + lineEdit1->setFocus(); + QTRY_VERIFY(lineEdit1->hasFocus()); + + // Tab forward + QTest::keyClick(QGuiApplication::focusWindow(), Qt::Key_Tab); + QTRY_VERIFY(container->hasFocus()); + QVERIFY(QTest::qWaitForWindowFocused(quickView)); + QVERIFY(rect1->hasActiveFocus()); + + QTest::keyClick(QGuiApplication::focusWindow(), Qt::Key_Tab); + QTRY_VERIFY(rect2->hasActiveFocus()); + + QTest::keyClick(QGuiApplication::focusWindow(), Qt::Key_Tab); + QVERIFY(QTest::qWaitForWindowFocused(root.windowHandle())); + QVERIFY(lineEdit2->hasFocus()); + QVERIFY(!rect2->hasActiveFocus()); + + // Tab backwards + QTest::keyClick(QGuiApplication::focusWindow(), Qt::Key_Tab, Qt::ShiftModifier); + QTRY_VERIFY(container->hasFocus()); + QVERIFY(QTest::qWaitForWindowFocused(quickView)); + QVERIFY(rect2->hasActiveFocus()); + + QTest::keyClick(QGuiApplication::focusWindow(), Qt::Key_Tab, Qt::ShiftModifier); + QVERIFY(rect1->hasActiveFocus()); + + QTest::keyClick(QGuiApplication::focusWindow(), Qt::Key_Tab, Qt::ShiftModifier); + QVERIFY(QTest::qWaitForWindowFocused(root.windowHandle())); + QVERIFY(lineEdit1->hasFocus()); +} +#endif + QTEST_MAIN(tst_QQuickItem) #include "tst_qquickitem.moc" diff --git a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp index 48171266de..3def877f00 100644 --- a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp +++ b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp @@ -7979,7 +7979,7 @@ void tst_QQuickListView::flickBeyondBounds() // Flick view up beyond bounds flick(window.data(), QPoint(10, 10), QPoint(10, -2000), 180); -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS QSKIP("Disabled due to flaky behavior on CI system (QTBUG-44493)"); QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper").count(), 0); #endif diff --git a/tests/auto/quick/qquickpath/tst_qquickpath.cpp b/tests/auto/quick/qquickpath/tst_qquickpath.cpp index 9fd4d9ec8a..3e02f63ea5 100644 --- a/tests/auto/quick/qquickpath/tst_qquickpath.cpp +++ b/tests/auto/quick/qquickpath/tst_qquickpath.cpp @@ -5,6 +5,7 @@ #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcomponent.h> #include <QtQuick/private/qquickpath_p.h> +#include <QtQuick/private/qquickrectangle_p.h> #include <QtQuickTestUtils/private/qmlutils_p.h> @@ -21,6 +22,9 @@ private slots: void closedCatmullRomCurve(); void svg(); void line(); + void rectangle_data(); + void rectangle(); + void rectangleRadii(); private: void arc(QSizeF scale); @@ -29,6 +33,7 @@ private: void closedCatmullRomCurve(QSizeF scale, const QVector<QPointF> &points); void svg(QSizeF scale); void line(QSizeF scale); + void rectangle(const QQuickPath *path, const QRectF &rect); }; static void compare(const QPointF &point, const QSizeF &scale, int line, double x, double y) @@ -318,6 +323,104 @@ void tst_QuickPath::line() line(QSizeF(7.23,7.23)); } +void tst_QuickPath::rectangle_data() +{ + QTest::addColumn<QByteArray>("pathqml"); + QTest::addColumn<QRectF>("rect"); + + QTest::newRow("basic") << QByteArray("PathRectangle { width: 100; height: 100 }\n") + << QRectF(0, 0, 100, 100); + + QTest::newRow("relative") << QByteArray("startX: -50; startY: -100\nPathRectangle {" + "relativeX: 100.2; relativeY: 200.3;" + "width: 10.5; height: 10.5 }\n") + << QRectF(50.2, 100.3, 10.5, 10.5); + + QTest::newRow("stroke") << QByteArray("PathRectangle { x: 5; y: 10; width: 100; height: 100;" + "strokeAdjustment: 20 }\n") + << QRectF(5, 10, 100, 100).adjusted(10, 10, -10, -10); +} + +void tst_QuickPath::rectangle(const QQuickPath *path, const QRectF &rect) +{ + QCOMPARE(path->pointAtPercent(0), rect.topLeft()); + QCOMPARE(path->pointAtPercent(1), rect.topLeft()); + QCOMPARE(path->pointAtPercent(1.0 / 8), QPointF(rect.center().x(), rect.top())); + QCOMPARE(path->pointAtPercent(3.0 / 8), QPointF(rect.right(), rect.center().y())); + QCOMPARE(path->pointAtPercent(5.0 / 8), QPointF(rect.center().x(), rect.bottom())); + QCOMPARE(path->pointAtPercent(7.0 / 8), QPointF(rect.left(), rect.center().y())); +} + +void tst_QuickPath::rectangle() +{ + QFETCH(QByteArray, pathqml); + QFETCH(QRectF, rect); + + QQmlEngine engine; + QQmlComponent c1(&engine); + c1.setData("import QtQuick\nPath {\n" + pathqml + "}", QUrl()); + QScopedPointer<QObject> o1(c1.create()); + QQuickPath *path = qobject_cast<QQuickPath *>(o1.data()); + QVERIFY(path); + QCOMPARE(path->pointAtPercent(0), rect.topLeft()); + QCOMPARE(path->pointAtPercent(1), rect.topLeft()); + QCOMPARE(path->pointAtPercent(1.0 / 8), QPointF(rect.center().x(), rect.top())); + QCOMPARE(path->pointAtPercent(3.0 / 8), QPointF(rect.right(), rect.center().y())); + QCOMPARE(path->pointAtPercent(5.0 / 8), QPointF(rect.center().x(), rect.bottom())); + QCOMPARE(path->pointAtPercent(7.0 / 8), QPointF(rect.left(), rect.center().y())); +} + +#define COMPARE_RADII(P, Q) \ + QCOMPARE(P.radius(), Q->radius()); \ + QCOMPARE(P.topLeftRadius(), Q->topLeftRadius()); \ + QCOMPARE(P.topRightRadius(), Q->topRightRadius()); \ + QCOMPARE(P.bottomLeftRadius(), Q->bottomLeftRadius()); \ + QCOMPARE(P.bottomRightRadius(), Q->bottomRightRadius()); + +void tst_QuickPath::rectangleRadii() +{ + // Test that the radius logic of PathRectangle is the same as Rectangle's + QQmlEngine engine; + QQmlComponent c1(&engine); + c1.setData("import QtQuick\n" + "Rectangle { x: 10; y: 20; width: 30; height: 40\n" + "}", + QUrl()); + QScopedPointer<QObject> o1(c1.create()); + QQuickRectangle *quickRectangle = qobject_cast<QQuickRectangle *>(o1.data()); + QVERIFY(quickRectangle); + QQuickPathRectangle pathRectangle; + pathRectangle.setX(quickRectangle->x()); + pathRectangle.setY(quickRectangle->y()); + pathRectangle.setWidth(quickRectangle->width()); + pathRectangle.setHeight(quickRectangle->height()); + COMPARE_RADII(pathRectangle, quickRectangle); + pathRectangle.setRadius(5); + quickRectangle->setRadius(5); + COMPARE_RADII(pathRectangle, quickRectangle); + pathRectangle.setBottomLeftRadius(15); + quickRectangle->setBottomLeftRadius(15); + COMPARE_RADII(pathRectangle, quickRectangle); + pathRectangle.setRadius(-5); + quickRectangle->setRadius(-5); + COMPARE_RADII(pathRectangle, quickRectangle); + pathRectangle.setRadius(0); + quickRectangle->setRadius(0); + COMPARE_RADII(pathRectangle, quickRectangle); + pathRectangle.setTopLeftRadius(-7); + quickRectangle->setTopLeftRadius(-7); + COMPARE_RADII(pathRectangle, quickRectangle); + pathRectangle.setRadius(4); + quickRectangle->setRadius(4); + pathRectangle.resetBottomLeftRadius(); + quickRectangle->resetBottomLeftRadius(); + pathRectangle.setTopRightRadius(0); + quickRectangle->setTopRightRadius(0); + pathRectangle.setTopLeftRadius(200); + quickRectangle->setTopLeftRadius(200); + COMPARE_RADII(pathRectangle, quickRectangle); +} + QTEST_MAIN(tst_QuickPath) #include "tst_qquickpath.moc" diff --git a/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp b/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp index 1b06e0047b..1a471d39af 100644 --- a/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp +++ b/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp @@ -1379,7 +1379,7 @@ void tst_QQuickPathView::package() QQuickPathView *pathView = window->rootObject()->findChild<QQuickPathView*>("photoPathView"); QVERIFY(pathView); -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS QSKIP("QTBUG-27170 view does not reliably receive polish without a running animation"); #endif diff --git a/tests/auto/quick/qquickpixmapcache/data/slowLoading.qml b/tests/auto/quick/qquickpixmapcache/data/slowLoading.qml new file mode 100644 index 0000000000..7ac98ae87b --- /dev/null +++ b/tests/auto/quick/qquickpixmapcache/data/slowLoading.qml @@ -0,0 +1,13 @@ +import QtQuick +import PixmapCacheTest + +DeviceLoadingImage { + id: root + width: 240 + height: 240 + sourceSize.width: width + sourceSize.height: height + source: "image://slow/200" + asynchronous: true + retainWhileLoading: true +} diff --git a/tests/auto/quick/qquickpixmapcache/deviceloadingimage.cpp b/tests/auto/quick/qquickpixmapcache/deviceloadingimage.cpp index 72a956d1d2..bc7a3b4246 100644 --- a/tests/auto/quick/qquickpixmapcache/deviceloadingimage.cpp +++ b/tests/auto/quick/qquickpixmapcache/deviceloadingimage.cpp @@ -19,25 +19,24 @@ void DeviceLoadingImage::load() Q_ASSERT(context); QUrl resolved = context->resolvedUrl(d->url); device = std::make_unique<QFile>(resolved.toLocalFile()); - d->pix.loadImageFromDevice(qmlEngine(this), device.get(), context->resolvedUrl(d->url), + const bool statusChange = (d->status != Loading); + if (statusChange) + d->status = Loading; + d->pendingPix->loadImageFromDevice(qmlEngine(this), device.get(), context->resolvedUrl(d->url), d->sourceClipRect.toRect(), d->sourcesize * d->devicePixelRatio, QQuickImageProviderOptions(), d->currentFrame, d->frameCount); + connectSuccess &= d->pendingPix->connectFinished(this, thisRequestFinished); + connectSuccess &= d->pendingPix->connectFinished(this, SLOT(onRequestFinished())); + qCDebug(lcTests) << "loading page" << d->currentFrame << "of" << d->frameCount + << "statuses" << d->currentPix->status() << d->pendingPix->status() + << "waiting?" << connectSuccess; + if (statusChange) + emit statusChanged(d->status); +} - qCDebug(lcTests) << "loading page" << d->currentFrame << "of" << d->frameCount << "status" << d->pix.status(); - - switch (d->pix.status()) { - case QQuickPixmap::Ready: - pixmapChange(); - break; - case QQuickPixmap::Loading: - d->pix.connectFinished(this, thisRequestFinished); - if (d->status != Loading) { - d->status = Loading; - emit statusChanged(d->status); - } - break; - default: - qCWarning(lcTests) << "unexpected status" << d->pix.status(); - break; - } +void DeviceLoadingImage::onRequestFinished() +{ + auto *d = static_cast<QQuickImagePrivate *>(QQuickImagePrivate::get(this)); + qCDebug(lcTests) << "statuses" << d->currentPix->status() << d->pendingPix->status(); + ++requestsFinished; } diff --git a/tests/auto/quick/qquickpixmapcache/deviceloadingimage.h b/tests/auto/quick/qquickpixmapcache/deviceloadingimage.h index 062f51d16f..c00b456b94 100644 --- a/tests/auto/quick/qquickpixmapcache/deviceloadingimage.h +++ b/tests/auto/quick/qquickpixmapcache/deviceloadingimage.h @@ -15,5 +15,11 @@ public: protected: void load() override; +protected slots: + void onRequestFinished(); + +public: std::unique_ptr<QFile> device; + bool connectSuccess = true; + int requestsFinished = 0; }; diff --git a/tests/auto/quick/qquickpixmapcache/tst_qquickpixmapcache.cpp b/tests/auto/quick/qquickpixmapcache/tst_qquickpixmapcache.cpp index fce9bba0c6..effcff75ef 100644 --- a/tests/auto/quick/qquickpixmapcache/tst_qquickpixmapcache.cpp +++ b/tests/auto/quick/qquickpixmapcache/tst_qquickpixmapcache.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <qtest.h> #include <QtTest/QtTest> +#include <QtQuick/private/qquickimage_p_p.h> #include <QtQuick/private/qquickpixmapcache_p.h> #include <QtQml/qqmlengine.h> #include <QtQuick/qquickimageprovider.h> @@ -18,18 +19,20 @@ #include <qfuture.h> #endif +#include "deviceloadingimage.h" + Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests") class SlowProvider : public QQuickImageProvider { public: - SlowProvider() : QQuickImageProvider(Pixmap) {} + SlowProvider() : QQuickImageProvider(Image) {} - QPixmap requestPixmap(const QString &id, QSize *size, const QSize& requestedSize) override + QImage requestImage(const QString &id, QSize *size, const QSize& requestedSize) override { const int row = id.toInt(); qCDebug(lcTests) << requestCount << QThread::currentThread() << "row" << row << requestedSize; - QPixmap image(requestedSize); + QImage image(requestedSize, QImage::Format_RGB888); QPainter painter(&image); const QColor c(128, row % 8 * 32, 64); painter.fillRect(0, 0, requestedSize.width(), requestedSize.height(), c); @@ -73,6 +76,7 @@ private slots: void dataLeak(); #endif void slowDevice(); + void slowDeviceInterrupted(); private: QQmlEngine engine; TestHTTPServer server; @@ -565,6 +569,44 @@ void tst_qquickpixmapcache::slowDevice() #endif } +void tst_qquickpixmapcache::slowDeviceInterrupted() +{ +#ifdef QT_BUILD_INTERNAL + auto *provider = new SlowProvider; + engine.addImageProvider("slow", provider); // takes ownership + + const QColor secondExpectedColor(128, 50 % 8 * 32, 64); + + { + QQuickView window(&engine, nullptr); + QVERIFY(QQuickTest::showView(window, testFileUrl("slowLoading.qml"))); + DeviceLoadingImage *dlimg = qobject_cast<DeviceLoadingImage *>(window.rootObject()); + QVERIFY(dlimg); + // the declared source: "image://slow/200" should take 200 ms to load + QTRY_COMPARE(dlimg->status(), QQuickImageBase::Loading); + QVERIFY(dlimg->connectSuccess); + dlimg->setSource(QUrl("image://slow/50")); + QTRY_COMPARE(dlimg->requestsFinished, 2); + QCOMPARE(provider->requestCount, 2); + QCOMPARE(dlimg->status(), QQuickImageBase::Ready); + auto *img_d = static_cast<QQuickImagePrivate *>(QQuickImagePrivate::get(dlimg)); + QCOMPARE(img_d->currentPix->image().pixelColor({1, 1}), secondExpectedColor); + QCOMPARE(QQuickPixmapCache::instance()->m_cache.size(), 2); + // Unless CI paused at the wrong time for > 200 ms, we cancelled loading + // the first image and switched to the second, so QQuickImageBase::requestFinished() + // should have only called swap() once. But if this check ends up being flaky in CI, + // it can be be removed. + QCOMPARE(img_d->currentPix, &img_d->pix2); + } // window goes out of scope: all QQuickPixmapData instances should be eventually unreferenced + + QTRY_COMPARE(QQuickPixmapCache::instance()->referencedCost(), 0); + const int leakedPixmaps = QQuickPixmapCache::instance()->destroyCache(); + QCOMPARE_LE(leakedPixmaps, 0); // -1 if the cache is already destroyed +#else + QSKIP("This test relies on private APIs that are only exported in developer-builds"); +#endif +} + QT_END_NAMESPACE QTEST_MAIN(tst_qquickpixmapcache) diff --git a/tests/auto/quick/qquickshape/data/filltransform.qml b/tests/auto/quick/qquickshape/data/filltransform.qml new file mode 100644 index 0000000000..4f83c04c43 --- /dev/null +++ b/tests/auto/quick/qquickshape/data/filltransform.qml @@ -0,0 +1,58 @@ +import QtQuick +import QtQuick.Shapes +import tst_qquickpathitem + +Rectangle { + width: 440 + height: 220 + color: "white" + + Shape { + objectName: "shape1" + ShapePath { + id: path1 + objectName: "path1" + fillGradient: RadialGradient { + centerX: path1.startX + 100 + centerY: path1.startY + 100 + centerRadius: 100 + focalX: centerX + focalY: centerY + GradientStop { position: 0.0; color: "blue" } + GradientStop { position: 0.5; color: "cyan" } + GradientStop { position: 1.0; color: "blue" } + } + + fillTransform: PlanarTransform.fromScale(2, 1); + + startX: 10 + startY: 10 + PathLine { relativeX: 200; relativeY: 0 } + PathLine { relativeX: 0; relativeY: 200 } + PathLine { relativeX: -200; relativeY: 0 } + PathLine { relativeX: 0; relativeY: -200 } + } + + ShapePath { + id: path2 + objectName: "path2" + fillGradient: RadialGradient { + centerX: path2.startX + 100 + centerY: path2.startY + 100 + centerRadius: 100 + focalX: centerX + focalY: centerY + GradientStop { position: 0.0; color: "blue" } + GradientStop { position: 0.5; color: "cyan" } + GradientStop { position: 1.0; color: "blue" } + } + + startX: 220 + 10 + startY: 10 + PathLine { relativeX: 200; relativeY: 0 } + PathLine { relativeX: 0; relativeY: 200 } + PathLine { relativeX: -200; relativeY: 0 } + PathLine { relativeX: 0; relativeY: -200 } + } + } +} diff --git a/tests/auto/quick/qquickshape/tst_qquickshape.cpp b/tests/auto/quick/qquickshape/tst_qquickshape.cpp index f846cc4e4f..707e0037f5 100644 --- a/tests/auto/quick/qquickshape/tst_qquickshape.cpp +++ b/tests/auto/quick/qquickshape/tst_qquickshape.cpp @@ -62,6 +62,7 @@ private slots: void multilineDataTypes_data(); void multilineDataTypes(); void multilineStronglyTyped(); + void fillTransform(); private: QVector<QPolygonF> m_lowPolyLogo; @@ -247,6 +248,8 @@ void tst_QQuickShape::changeSignals() QCOMPARE(vpChangeSpy.size(), 15); qobject_cast<QQuickGradientStop *>(stopList.at(1))->setColor(Qt::black); QCOMPARE(vpChangeSpy.size(), 16); + vp->setFillTransform(QMatrix4x4(QTransform::fromScale(3, 0.14))); + QCOMPARE(vpChangeSpy.size(), 17); } void tst_QQuickShape::render() @@ -674,6 +677,40 @@ void tst_QQuickShape::multilineStronglyTyped() } } +void tst_QQuickShape::fillTransform() +{ + QScopedPointer<QQuickView> window(createView()); + + window->setSource(testFileUrl("filltransform.qml")); + qApp->processEvents(); + + QQuickShape *obj = findItem<QQuickShape>(window->rootObject(), "shape1"); + QVERIFY(obj != nullptr); + QQmlListReference list(obj, "data"); + QCOMPARE(list.count(), 2); + + QQuickShapePath *p1 = qobject_cast<QQuickShapePath *>(list.at(0)); + QVERIFY(p1 != nullptr); + QVERIFY(p1->objectName() == "path1"); + QVERIFY(p1->fillTransform() == QMatrix4x4(2,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1)); + + QQuickShapePath *p2 = qobject_cast<QQuickShapePath *>(list.at(1)); + QVERIFY(p2 != nullptr); + QVERIFY(p2->objectName() == "path2"); + QVERIFY(p2->fillTransform().isIdentity()); + + QMatrix4x4 xf(QTransform::fromTranslate(-36, 0).shear(0.35, 0)); + p1->setFillTransform(xf); + QVERIFY(p1->fillTransform() == xf); + + QVERIFY(p2->fillTransform().isIdentity()); + p2->setFillTransform(xf); + QVERIFY(p2->fillTransform() == xf); + + p1->setFillTransform(QMatrix4x4{}); + QVERIFY(p1->fillTransform().isIdentity()); +} + QTEST_MAIN(tst_QQuickShape) #include "tst_qquickshape.moc" diff --git a/tests/auto/quick/qquicktableview/data/reordertableview.qml b/tests/auto/quick/qquicktableview/data/reordertableview.qml new file mode 100644 index 0000000000..704126180f --- /dev/null +++ b/tests/auto/quick/qquicktableview/data/reordertableview.qml @@ -0,0 +1,52 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + width: 640 + height: 450 + + property alias tableView: tableView + property real delegateWidth: 100 + property real delegateHeight: 50 + property Component delegate: tableViewDelegate + property bool delegateParentSetBeforeCompleted: false + + TableView { + id: tableView + width: 600 + height: 400 + anchors.margins: 1 + clip: true + delegate: tableViewDelegate + columnSpacing: 1 + rowSpacing: 1 + animate: false + } + + Component { + id: tableViewDelegate + Rectangle { + required property int column + required property int row + objectName: "tableViewDelegate" + column + row + implicitWidth: delegateWidth + implicitHeight: delegateHeight + color: "lightgray" + border.width: 1 + + property string modelDataBinding: modelData + + Text { + anchors.centerIn: parent + text: modelData + } + + Component.onCompleted: { + delegateParentSetBeforeCompleted = parent != null; + } + } + } + +} diff --git a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp index bb425b5a6f..56dee4b585 100644 --- a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp +++ b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp @@ -279,6 +279,13 @@ private slots: void checkScroll_data(); void checkScroll(); void checkRebuildJsModel(); + + // Row and column reordering + void checkVisualRowColumnAfterReorder(); + void checkColumnRowSizeAfterReorder(); + void checkCellModelIdxAfterReorder(); + void checkEditAfterReorder(); + void checkSelectionAfterReorder(); }; tst_QQuickTableView::tst_QQuickTableView() @@ -4868,23 +4875,23 @@ void tst_QQuickTableView::testSelectableScrollTowardsPos() const QPointF bottomLeft(-100, tableView->height() + 100); const QPointF bottomRight(tableView->width() + 100, tableView->height() + 100); - tableViewPrivate->scrollTowardsSelectionPoint(topRight, step); + tableViewPrivate->scrollTowardsPoint(topRight, step); QCOMPARE(tableView->contentX(), step.width()); QCOMPARE(tableView->contentY(), 0); - tableViewPrivate->scrollTowardsSelectionPoint(bottomRight, step); + tableViewPrivate->scrollTowardsPoint(bottomRight, step); QCOMPARE(tableView->contentX(), step.width() * 2); QCOMPARE(tableView->contentY(), step.height()); - tableViewPrivate->scrollTowardsSelectionPoint(bottomLeft, step); + tableViewPrivate->scrollTowardsPoint(bottomLeft, step); QCOMPARE(tableView->contentX(), step.width()); QCOMPARE(tableView->contentY(), step.height() * 2); - tableViewPrivate->scrollTowardsSelectionPoint(topLeft, step); + tableViewPrivate->scrollTowardsPoint(topLeft, step); QCOMPARE(tableView->contentX(), 0); QCOMPARE(tableView->contentY(), step.height()); - tableViewPrivate->scrollTowardsSelectionPoint(topLeft, step); + tableViewPrivate->scrollTowardsPoint(topLeft, step); QCOMPARE(tableView->contentX(), 0); QCOMPARE(tableView->contentY(), 0); } @@ -7548,6 +7555,236 @@ void tst_QQuickTableView::checkRebuildJsModel() QCOMPARE(tableView->property(modelUpdated).toInt(), 1); } +void tst_QQuickTableView::checkVisualRowColumnAfterReorder() +{ + LOAD_TABLEVIEW("reordertableview.qml"); // gives us 'tableView' variable + auto model = TestModelAsVariant(3, 3); + tableView->setModel(model); + + WAIT_UNTIL_POLISHED; + + QSignalSpy columnMovedSpy(tableView, &QQuickTableView::columnMoved); + QSignalSpy rowMovedSpy(tableView, &QQuickTableView::rowMoved); + + // Move row and column + tableView->moveColumn(0, 2); + WAIT_UNTIL_POLISHED; + QCOMPARE(columnMovedSpy.size(), 3); + + tableView->moveRow(1, 0); + WAIT_UNTIL_POLISHED; + QCOMPARE(rowMovedSpy.size(), 2); + + QVariantList firstColumnVar = columnMovedSpy.takeFirst(); + QCOMPARE(firstColumnVar.at(0), 0); // Logical index + QCOMPARE(firstColumnVar.at(1), 0); // Old visual index + QCOMPARE(firstColumnVar.at(2), 2); // New visual index + + QVariantList firstRowVar = rowMovedSpy.takeFirst(); + QCOMPARE(firstRowVar.at(0), 0); // Logical index + QCOMPARE(firstRowVar.at(1), 0); // Old visual index + QCOMPARE(firstRowVar.at(2), 1); // New visual index +} + +void tst_QQuickTableView::checkColumnRowSizeAfterReorder() +{ + LOAD_TABLEVIEW("reordertableview.qml"); // gives us 'tableView' variable + auto model = TestModelAsVariant(3, 3); + tableView->setModel(model); + + WAIT_UNTIL_POLISHED; + + const QSignalSpy columMovedSpy(tableView, &QQuickTableView::columnMoved); + const QSignalSpy rowMovedSpy(tableView, &QQuickTableView::rowMoved); + + for (int index = 0, minSize = 10; index < tableView->columns(); index++, minSize+=10) { + tableView->setColumnWidth(index, minSize); + tableView->setRowHeight(index, minSize); + } + WAIT_UNTIL_POLISHED; + + // Move row and column + tableView->moveColumn(0, 2); + WAIT_UNTIL_POLISHED; + QCOMPARE(columMovedSpy.size(), 3); + + tableView->moveRow(0, 2); + WAIT_UNTIL_POLISHED; + QCOMPARE(rowMovedSpy.size(), 3); + + QCOMPARE(tableView->columnWidth(0), 20); + QCOMPARE(tableView->columnWidth(1), 30); + QCOMPARE(tableView->columnWidth(2), 10); + + QCOMPARE(tableView->rowHeight(0), 20); + QCOMPARE(tableView->rowHeight(1), 30); + QCOMPARE(tableView->rowHeight(2), 10); +} + +void tst_QQuickTableView::checkCellModelIdxAfterReorder() +{ + LOAD_TABLEVIEW("reordertableview.qml"); // gives us 'tableView' variable + auto model = TestModelAsVariant(3, 3); + tableView->setModel(model); + + WAIT_UNTIL_POLISHED; + + const QSignalSpy columnMovedSpy(tableView, &QQuickTableView::columnMoved); + const QSignalSpy rowMovedSpy(tableView, &QQuickTableView::rowMoved); + + const QSharedPointer<TestModel> testModel = model.value<QSharedPointer<TestModel>>(); + const QString objNameItem21(tableView->itemAtIndex(testModel->index(2, 1))->objectName()); + const QString objNameItem00(tableView->itemAtIndex(testModel->index(0 ,0))->objectName()); + const QString objNameItem11(tableView->itemAtIndex(testModel->index(1 ,1))->objectName()); + + // Move row and column + tableView->moveColumn(0, 2); + WAIT_UNTIL_POLISHED; + QCOMPARE(columnMovedSpy.size(), 3); + + tableView->moveRow(1, 0); + WAIT_UNTIL_POLISHED; + QCOMPARE(rowMovedSpy.size(), 2); + + // Check model index - index() + QModelIndex modelIndex = tableView->index(0, 0); + QCOMPARE(modelIndex.column(), 1); + QCOMPARE(modelIndex.row(), 1); + + modelIndex = tableView->index(1, 1); + QCOMPARE(modelIndex.column(), 2); + QCOMPARE(modelIndex.row(), 0); + + modelIndex = tableView->index(2, 2); + QCOMPARE(modelIndex.column(), 0); + QCOMPARE(modelIndex.row(), 2); + + // Check cell index - cellAtIndex() + { + QPoint cell = tableView->cellAtIndex(testModel->index(0, 0)); + QCOMPARE(cell.x(), 2); + QCOMPARE(cell.y(), 1); + } + + // Check column and row index - columnAtIndex(), rowAtIndex() + { + int columnIndex = tableView->columnAtIndex(testModel->index(0, 0)); + int rowIndex = tableView->rowAtIndex(testModel->index(0, 0)); + QCOMPARE(columnIndex, 2); + QCOMPARE(rowIndex, 1); + } + + // Check item - itemAtIndex() + // Item at index provides the item that is mapped to that model index + // and it shouldn't be confused with cell index + { + QQuickItem *item = tableView->itemAtIndex(testModel->index(0 ,0)); + QCOMPARE(objNameItem00, item->objectName()); + } + + // Check item at cell localtion 0, 0 - itemAtCell() + { + QQuickItem *item = tableView->itemAtCell(QPoint(0, 0)); + QCOMPARE(objNameItem11, item->objectName()); + } +} + +void tst_QQuickTableView::checkEditAfterReorder() +{ + LOAD_TABLEVIEW("editdelegate.qml"); // gives us 'tableView' variable + auto model = TestModelAsVariant(3, 3); + tableView->setModel(model); + + WAIT_UNTIL_POLISHED; + + const QSignalSpy columnMovedSpy(tableView, &QQuickTableView::columnMoved); + const QSignalSpy rowMovedSpy(tableView, &QQuickTableView::rowMoved); + + // Move row and column + tableView->moveColumn(0, 1); + WAIT_UNTIL_POLISHED; + QCOMPARE(columnMovedSpy.size(), 2); + + tableView->moveRow(0, 1); + WAIT_UNTIL_POLISHED; + QCOMPARE(rowMovedSpy.size(), 2); + + // Edit model index (0, 0) + const QSharedPointer<TestModel> testModel = model.value<QSharedPointer<TestModel>>(); + const auto cellItem1 = tableView->itemAtCell(QPoint(0, 0)); + QCOMPARE(cellItem1->property("editing").toBool(), false); + + tableView->edit(testModel->index(1, 1)); + QCOMPARE(cellItem1->property("editing").toBool(), true); + + // Close the editor + tableView->closeEditor(); + QCOMPARE(cellItem1->property("editing").toBool(), false); +} + +void tst_QQuickTableView::checkSelectionAfterReorder() +{ + LOAD_TABLEVIEW("tableviewwithselected1.qml"); + + TestModel model(10, 10); + QItemSelectionModel selectionModel(&model); + + tableView->setModel(QVariant::fromValue(&model)); + tableView->setSelectionModel(&selectionModel); + + WAIT_UNTIL_POLISHED; + + QCOMPARE(selectionModel.hasSelection(), false); + QCOMPARE(tableView->selectionBehavior(), QQuickTableView::SelectCells); + + const QSignalSpy columnMovedSpy(tableView, &QQuickTableView::columnMoved); + tableView->moveColumn(0, 2); + WAIT_UNTIL_POLISHED; + QCOMPARE(columnMovedSpy.size(), 3); + + const QPoint endCellDist(1, 1); + const QPoint startCell(0, 0); + const QPoint endCell = startCell + endCellDist; + + const QQuickItem *startItem = tableView->itemAtCell(startCell); + const QQuickItem *endItem = tableView->itemAtCell(endCell); + QVERIFY(startItem); + QVERIFY(endItem); + + const QPointF startPos(startItem->x(), startItem->y()); + const QPointF endPos(endItem->x(), endItem->y()); + + QVERIFY(tableViewPrivate->startSelection(startPos, Qt::NoModifier)); + tableViewPrivate->setSelectionStartPos(startPos); + tableViewPrivate->setSelectionEndPos(endPos); + + QCOMPARE(selectionModel.hasSelection(), true); + + const int x1 = qMin(startCell.x(), endCell.x()); + const int x2 = qMax(startCell.x(), endCell.x()); + const int y1 = qMin(startCell.y(), endCell.y()); + const int y2 = qMax(startCell.y(), endCell.y()); + + for (int x = x1; x <= x2; ++x) { + for (int y = y1; y <= y2; ++y) { + const auto index = tableView->index(y, x); + QVERIFY(selectionModel.isSelected(index)); + } + } + + const int expectedCount = (x2 - x1 + 1) * (y2 - y1 + 1); + const int actualCount = selectionModel.selectedIndexes().size(); + QCOMPARE(actualCount, expectedCount); + + // The column which has been moved shouldn't have the selected + // bit enabled + for (int index = 0; index < model.rowCount(); index++) + QCOMPARE(selectionModel.isSelected(model.index(index, 0)), false); + + tableViewPrivate->clearSelection(); + QCOMPARE(selectionModel.hasSelection(), false); +} + QTEST_MAIN(tst_QQuickTableView) #include "tst_qquicktableview.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/tst_qquicktextedit.cpp b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp index cc994fe783..c8377aa2d3 100644 --- a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp +++ b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp @@ -245,7 +245,6 @@ private: void simulateKey(QWindow *, int key, Qt::KeyboardModifiers modifiers = {}); bool isMainFontFixed(); - static bool hasWindowActivation(); QStringList standard; QStringList richText; @@ -986,8 +985,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. @@ -1014,7 +1013,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); @@ -1024,7 +1023,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); @@ -1036,36 +1035,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); } } @@ -3332,11 +3331,6 @@ bool tst_qquicktextedit::isMainFontFixed() return ret; } -bool tst_qquicktextedit::hasWindowActivation() -{ - return (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)); -} - void tst_qquicktextedit::textInput() { QQuickView window; @@ -6521,8 +6515,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 b7e689e147..8f8442544f 100644 --- a/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp +++ b/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp @@ -6,6 +6,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> @@ -213,7 +214,6 @@ private: #if QT_CONFIG(shortcut) void simulateKeys(QWindow *window, const QKeySequence &sequence); #endif - static bool hasWindowActivation(); QQmlEngine engine; QStringList standard; @@ -239,11 +239,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) @@ -7201,8 +7196,8 @@ void tst_qquicktextinput::touchscreenDoesNotSelect() 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/quickcontrols/accessibility/data/actionAccessibility/button.qml b/tests/auto/quickcontrols/accessibility/data/actionAccessibility/button.qml new file mode 100644 index 0000000000..7e392e9cc3 --- /dev/null +++ b/tests/auto/quickcontrols/accessibility/data/actionAccessibility/button.qml @@ -0,0 +1,12 @@ +import QtQuick +import QtQuick.Controls + +Button { + action: Action { + id: anAction + text: "Peaches" + Accessible.name: "Peach" + Accessible.description: "Show peaches some love" + } + text: Accessible.description +} diff --git a/tests/auto/quickcontrols/accessibility/tst_accessibility.cpp b/tests/auto/quickcontrols/accessibility/tst_accessibility.cpp index 9774bf4e07..8bdd9453c8 100644 --- a/tests/auto/quickcontrols/accessibility/tst_accessibility.cpp +++ b/tests/auto/quickcontrols/accessibility/tst_accessibility.cpp @@ -31,6 +31,8 @@ private slots: void override(); void ordering(); + + void actionAccessibility(); private: QQmlEngine engine; }; @@ -274,6 +276,26 @@ void tst_accessibility::ordering() #endif } +void tst_accessibility::actionAccessibility() +{ +#if QT_CONFIG(accessibility) + QQmlComponent component(&engine); + component.loadUrl(testFileUrl("actionAccessibility/button.qml")); + + QScopedPointer<QObject> object(component.create()); + QVERIFY2(!object.isNull(), qPrintable(component.errorString())); + + QQuickItem *item = qobject_cast<QQuickItem *>(object.data()); + QVERIFY(item); + const QString description = "Show peaches some love"; + QCOMPARE(item->property("text"), description); + QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(item); + QVERIFY(iface); + QCOMPARE(iface->text(QAccessible::Name), "Peach"); + QCOMPARE(iface->text(QAccessible::Description), description); +#endif +} + QTEST_MAIN(tst_accessibility) #include "tst_accessibility.moc" diff --git a/tests/auto/quickcontrols/controls/CMakeLists.txt b/tests/auto/quickcontrols/controls/CMakeLists.txt index 6984315b5a..593d87fb75 100644 --- a/tests/auto/quickcontrols/controls/CMakeLists.txt +++ b/tests/auto/quickcontrols/controls/CMakeLists.txt @@ -8,6 +8,7 @@ add_subdirectory(fusion) add_subdirectory(imagine) add_subdirectory(material) add_subdirectory(universal) +add_subdirectory(fluentwinui3) if(MACOS) add_subdirectory(macos) add_subdirectory(ios) diff --git a/tests/auto/quickcontrols/controls/basic/tst_basic.cpp b/tests/auto/quickcontrols/controls/basic/tst_basic.cpp index 33417cca55..7e73bd2231 100644 --- a/tests/auto/quickcontrols/controls/basic/tst_basic.cpp +++ b/tests/auto/quickcontrols/controls/basic/tst_basic.cpp @@ -8,6 +8,9 @@ int main(int argc, char *argv[]) { QTEST_SET_MAIN_SOURCE_PATH qputenv("QML_NO_TOUCH_COMPRESSION", "1"); + // The tests were originally written before native menus existed, + // and some of them try to open menus, which we can't test natively. + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); QQuickStyle::setStyle("Basic"); return quick_test_main(argc, argv, "tst_controls::Basic", TST_CONTROLS_DATA); } 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/combobox/shader.frag.qsb b/tests/auto/quickcontrols/controls/data/combobox/shader.frag.qsb Binary files differnew file mode 100644 index 0000000000..b86ce9a76e --- /dev/null +++ b/tests/auto/quickcontrols/controls/data/combobox/shader.frag.qsb diff --git a/tests/auto/quickcontrols/controls/data/tst_abstractbutton.qml b/tests/auto/quickcontrols/controls/data/tst_abstractbutton.qml index 822c703a42..bce13b37f2 100644 --- a/tests/auto/quickcontrols/controls/data/tst_abstractbutton.qml +++ b/tests/auto/quickcontrols/controls/data/tst_abstractbutton.qml @@ -42,6 +42,45 @@ TestCase { SignalSpy { } } + property var expectedPressSignals: [ + ["activeFocusChanged", { "activeFocus": true }], + ["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed" + ] + + property var expectedReleaseSignals: [ + ["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }], + "released", + "clicked" + ] + + property var expectedClickSignals + + property var expectedCheckableClickSignals: [ + ["activeFocusChanged", { "activeFocus": true }], + ["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed", + ["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }], + ["checkedChanged", { "checked": true }], + "toggled", + "released", + "clicked" + ] + + function initTestCase() { + // AbstractButton has TabFocus on macOS, not StrongFocus. + if (Qt.platform.os === "osx") { + expectedPressSignals.splice(0, 1) + expectedCheckableClickSignals.splice(0, 1) + } + + expectedClickSignals = [...expectedPressSignals, ...expectedReleaseSignals] + } + function init() { failOnWarning(/.?/) } @@ -1004,4 +1043,137 @@ TestCase { compare(releasedSpy.count, 0) compare(clickedSpy.count, 0) } + + Component { + id: signalSequenceSpy + SignalSequenceSpy { + // List all signals, even ones we might not be interested in for a particular test, + // so that it can catch unwanted ones and fail the test. + signals: ["pressed", "released", "canceled", "clicked", "toggled", "doubleClicked", + "pressedChanged", "downChanged", "checkedChanged", "activeFocusChanged"] + } + } + + function test_click() { + let control = createTemporaryObject(button, testCase) + verify(control) + + let sequenceSpy = signalSequenceSpy.createObject(control, { target: control }) + sequenceSpy.expectedSequence = testCase.expectedClickSignals + control.click() + verify(sequenceSpy.success) + } + + function test_clickCheckableButton() { + let control = createTemporaryObject(button, testCase, { checkable: true }) + verify(control) + + let sequenceSpy = signalSequenceSpy.createObject(control, { target: control }) + sequenceSpy.expectedSequence = testCase.expectedCheckableClickSignals + control.click() + verify(sequenceSpy.success) + } + + function test_animateClick() { + let control = createTemporaryObject(button, testCase) + verify(control) + + let sequenceSpy = signalSequenceSpy.createObject(control, { target: control }) + sequenceSpy.expectedSequence = testCase.expectedClickSignals + control.animateClick() + tryVerify(() => { return sequenceSpy.success }, 1000) + } + + function test_animateClickCheckableButton() { + let control = createTemporaryObject(button, testCase, { checkable: true }) + verify(control) + + let sequenceSpy = signalSequenceSpy.createObject(control, { target: control }) + sequenceSpy.expectedSequence = testCase.expectedCheckableClickSignals + control.animateClick() + tryVerify(() => { return sequenceSpy.success }, 1000) + } + + function test_animateClickTwice() { + let control = createTemporaryObject(button, testCase) + verify(control) + + let sequenceSpy = signalSequenceSpy.createObject(control, { target: control }) + sequenceSpy.expectedSequence = testCase.expectedPressSignals + // Check that calling it again before it finishes works as expected. + control.animateClick() + verify(sequenceSpy.success) + // Let the timer progress a bit. + wait(0) + sequenceSpy.expectedSequence = testCase.expectedReleaseSignals + control.animateClick() + tryVerify(() => { return sequenceSpy.success }, 1000) + } + + function test_clickOnDisabledButton() { + let control = createTemporaryObject(button, testCase, { enabled: false }) + verify(control) + + let sequenceSpy = signalSequenceSpy.createObject(control, { target: control }) + sequenceSpy.expectedSequence = [] + control.click() + verify(sequenceSpy.success) + } + + function test_animateClickOnDisabledButton() { + let control = createTemporaryObject(button, testCase, { enabled: false }) + verify(control) + + let sequenceSpy = signalSequenceSpy.createObject(control, { target: control }) + sequenceSpy.expectedSequence = [] + control.animateClick() + verify(sequenceSpy.success) + } + + Component { + id: destroyOnPressButtonComponent + + AbstractButton { + width: 100 + height: 50 + + onPressed: destroy(this) + } + } + + function test_clickDestroyOnPress() { + let control = createTemporaryObject(destroyOnPressButtonComponent, testCase) + verify(control) + + // Parent it to the testCase, otherwise it will be destroyed when the control is. + let destructionSpy = createTemporaryObject(signalSpy, testCase, + { target: control.Component, signalName: "destruction" }) + verify(destructionSpy.valid) + + let sequenceSpy = signalSequenceSpy.createObject(control, { target: control }) + sequenceSpy.expectedSequence = testCase.expectedClickSignals + // Shouldn't crash, etc. Note that destroy() isn't synchronous, and so + // the destruction will happen after the release. + control.click() + verify(sequenceSpy.success) + tryCompare(destructionSpy, "count", 1) + } + + function test_animateClickDestroyOnPress() { + let control = createTemporaryObject(destroyOnPressButtonComponent, testCase) + verify(control) + + // Parent it to the testCase, otherwise it will be destroyed when the control is. + let destructionSpy = createTemporaryObject(signalSpy, testCase, + { target: control.Component, signalName: "destruction" }) + verify(destructionSpy.valid) + + let sequenceSpy = signalSequenceSpy.createObject(control, { target: control }) + sequenceSpy.expectedSequence = testCase.expectedPressSignals + // Shouldn't crash, etc. Note that destroy() isn't synchronous, but it is processed + // on the next frame, so should always come before the release's 100 ms delay. + control.animateClick() + verify(sequenceSpy.success) + tryCompare(destructionSpy, "count", 1) + } } diff --git a/tests/auto/quickcontrols/controls/data/tst_combobox.qml b/tests/auto/quickcontrols/controls/data/tst_combobox.qml index 9f852e29e4..4dfe53fcb0 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: "combobox/shader.frag.qsb" } } } @@ -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_splitview.qml b/tests/auto/quickcontrols/controls/data/tst_splitview.qml index 3bcf9a53c6..aed303689a 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 @@ -2678,4 +2677,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/fluentwinui3/BLACKLIST b/tests/auto/quickcontrols/controls/fluentwinui3/BLACKLIST new file mode 100644 index 0000000000..3895bc7d94 --- /dev/null +++ b/tests/auto/quickcontrols/controls/fluentwinui3/BLACKLIST @@ -0,0 +1,10 @@ +# See qtbase/src/testlib/qtestblacklist.cpp for format + +# until adding FluentWinUI3's own BusyIndicator implementation +[BusyIndicator::test_visibility] +* + +# QTBUG-95750 +[RangeSlider::test_overlappingHandles] +b2qt +qnx diff --git a/tests/auto/quickcontrols/controls/fluentwinui3/CMakeLists.txt b/tests/auto/quickcontrols/controls/fluentwinui3/CMakeLists.txt new file mode 100644 index 0000000000..384b8c47f4 --- /dev/null +++ b/tests/auto/quickcontrols/controls/fluentwinui3/CMakeLists.txt @@ -0,0 +1,38 @@ +# 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_fluentwinui3 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} + ${CMAKE_CURRENT_SOURCE_DIR}/../data/tst_*) +list(APPEND test_data ${test_data_glob}) + +qt_internal_add_test(tst_fluentwinui3 + GUI + QMLTEST + SOURCES + tst_fluentwinui3.cpp + DEFINES + TST_CONTROLS_DATA="${CMAKE_CURRENT_SOURCE_DIR}/../data" + LIBRARIES + Qt::Gui + Qt::QuickControls2 + TESTDATA ${test_data} +) + +# Make the QML files available to Creator's locator. +target_sources(tst_fluentwinui3 + PRIVATE + ${test_data} +) + +set_source_files_properties(${test_data} + PROPERTIES + HEADER_FILE_ONLY ON +) diff --git a/tests/auto/quickcontrols/controls/fluentwinui3/dependencies.qml b/tests/auto/quickcontrols/controls/fluentwinui3/dependencies.qml new file mode 100644 index 0000000000..2b442ac527 --- /dev/null +++ b/tests/auto/quickcontrols/controls/fluentwinui3/dependencies.qml @@ -0,0 +1,6 @@ +import QtTest +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.FluentWinUI3 + +TestCase { } diff --git a/tests/auto/quickcontrols/controls/fluentwinui3/dummy_imports.qml b/tests/auto/quickcontrols/controls/fluentwinui3/dummy_imports.qml new file mode 100644 index 0000000000..66e184f138 --- /dev/null +++ b/tests/auto/quickcontrols/controls/fluentwinui3/dummy_imports.qml @@ -0,0 +1,12 @@ +// This file exists for the sole purpose for qmlimportscanner to find +// which modules it needs to extract for deployment. +// Otherwise, it fails to find the imports that are expressed in C++. + +import QtQml +import QtCore +import QtQuick +import QtQuick.NativeStyle +import QtQuick.Layouts +import Qt.labs.qmlmodels + +QtObject { } diff --git a/tests/auto/quickcontrols/controls/fluentwinui3/tst_fluentwinui3.cpp b/tests/auto/quickcontrols/controls/fluentwinui3/tst_fluentwinui3.cpp new file mode 100644 index 0000000000..9f67b3dc9e --- /dev/null +++ b/tests/auto/quickcontrols/controls/fluentwinui3/tst_fluentwinui3.cpp @@ -0,0 +1,13 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtQuickTest/quicktest.h> +#include <QtQuickControls2/qquickstyle.h> + +int main(int argc, char *argv[]) +{ + QTEST_SET_MAIN_SOURCE_PATH + qputenv("QML_NO_TOUCH_COMPRESSION", "1"); + QQuickStyle::setStyle("FluentWinUI3"); + return quick_test_main(argc, argv, "tst_controls::FluentWinUI3", TST_CONTROLS_DATA); +} diff --git a/tests/auto/quickcontrols/controls/fusion/tst_fusion.cpp b/tests/auto/quickcontrols/controls/fusion/tst_fusion.cpp index 4485ca0c70..3c1f255d6e 100644 --- a/tests/auto/quickcontrols/controls/fusion/tst_fusion.cpp +++ b/tests/auto/quickcontrols/controls/fusion/tst_fusion.cpp @@ -8,6 +8,9 @@ int main(int argc, char *argv[]) { QTEST_SET_MAIN_SOURCE_PATH qputenv("QML_NO_TOUCH_COMPRESSION", "1"); + // The tests were originally written before native menus existed, + // and some of them try to open menus, which we can't test natively. + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); QQuickStyle::setStyle("Fusion"); return quick_test_main(argc, argv, "tst_controls::Fusion", TST_CONTROLS_DATA); } diff --git a/tests/auto/quickcontrols/controls/imagine/tst_imagine.cpp b/tests/auto/quickcontrols/controls/imagine/tst_imagine.cpp index 54c363797b..ca9ff3fddd 100644 --- a/tests/auto/quickcontrols/controls/imagine/tst_imagine.cpp +++ b/tests/auto/quickcontrols/controls/imagine/tst_imagine.cpp @@ -8,6 +8,9 @@ int main(int argc, char *argv[]) { QTEST_SET_MAIN_SOURCE_PATH qputenv("QML_NO_TOUCH_COMPRESSION", "1"); + // The tests were originally written before native menus existed, + // and some of them try to open menus, which we can't test natively. + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); QQuickStyle::setStyle("Imagine"); return quick_test_main(argc, argv, "tst_controls::Imagine", TST_CONTROLS_DATA); } diff --git a/tests/auto/quickcontrols/controls/ios/tst_ios.cpp b/tests/auto/quickcontrols/controls/ios/tst_ios.cpp index 11c6f35b0b..b34a580e3c 100644 --- a/tests/auto/quickcontrols/controls/ios/tst_ios.cpp +++ b/tests/auto/quickcontrols/controls/ios/tst_ios.cpp @@ -8,6 +8,9 @@ int main(int argc, char *argv[]) { QTEST_SET_MAIN_SOURCE_PATH qputenv("QML_NO_TOUCH_COMPRESSION", "1"); + // The tests were originally written before native menus existed, + // and some of them try to open menus, which we can't test natively. + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); QQuickStyle::setStyle("iOS"); return quick_test_main(argc, argv, "tst_controls::iOS", TST_CONTROLS_DATA); } diff --git a/tests/auto/quickcontrols/controls/macos/tst_macos.cpp b/tests/auto/quickcontrols/controls/macos/tst_macos.cpp index 1ba0ebf587..91ce22cc0b 100644 --- a/tests/auto/quickcontrols/controls/macos/tst_macos.cpp +++ b/tests/auto/quickcontrols/controls/macos/tst_macos.cpp @@ -10,6 +10,9 @@ int main(int argc, char *argv[]) qputenv("QML_NO_TOUCH_COMPRESSION", "1"); // See comment in tst_windows.cpp. qputenv("QT_QUICK_CONTROLS_IGNORE_CUSTOMIZATION_WARNINGS", "1"); + // The tests were originally written before native menus existed, + // and some of them try to open menus, which we can't test natively. + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); QQuickStyle::setStyle("macOS"); return quick_test_main(argc, argv, "tst_controls::macOS", TST_CONTROLS_DATA); } diff --git a/tests/auto/quickcontrols/controls/material/tst_material.cpp b/tests/auto/quickcontrols/controls/material/tst_material.cpp index 782397a592..9a76046d23 100644 --- a/tests/auto/quickcontrols/controls/material/tst_material.cpp +++ b/tests/auto/quickcontrols/controls/material/tst_material.cpp @@ -8,6 +8,9 @@ int main(int argc, char *argv[]) { QTEST_SET_MAIN_SOURCE_PATH qputenv("QML_NO_TOUCH_COMPRESSION", "1"); + // The tests were originally written before native menus existed, + // and some of them try to open menus, which we can't test natively. + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); QQuickStyle::setStyle("Material"); return quick_test_main(argc, argv, "tst_controls::Material", TST_CONTROLS_DATA); } diff --git a/tests/auto/quickcontrols/controls/universal/tst_universal.cpp b/tests/auto/quickcontrols/controls/universal/tst_universal.cpp index 2d9e687bea..3cd41be836 100644 --- a/tests/auto/quickcontrols/controls/universal/tst_universal.cpp +++ b/tests/auto/quickcontrols/controls/universal/tst_universal.cpp @@ -8,6 +8,9 @@ int main(int argc, char *argv[]) { QTEST_SET_MAIN_SOURCE_PATH qputenv("QML_NO_TOUCH_COMPRESSION", "1"); + // The tests were originally written before native menus existed, + // and some of them try to open menus, which we can't test natively. + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); QQuickStyle::setStyle("Universal"); return quick_test_main(argc, argv, "tst_controls::Universal", TST_CONTROLS_DATA); } diff --git a/tests/auto/quickcontrols/controls/windows/tst_windows.cpp b/tests/auto/quickcontrols/controls/windows/tst_windows.cpp index 221ff116dd..bcd8fa35d8 100644 --- a/tests/auto/quickcontrols/controls/windows/tst_windows.cpp +++ b/tests/auto/quickcontrols/controls/windows/tst_windows.cpp @@ -17,6 +17,9 @@ int main(int argc, char *argv[]) // issued when default-constructing controls. For that we have // tst_customization::noCustomizationWarningsForDefaultControls. qputenv("QT_QUICK_CONTROLS_IGNORE_CUSTOMIZATION_WARNINGS", "1"); + // The tests were originally written before native menus existed, + // and some of them try to open menus, which we can't test natively. + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); QQuickStyle::setStyle("Windows"); return quick_test_main(argc, argv, "tst_controls::Windows", TST_CONTROLS_DATA); } diff --git a/tests/auto/quickcontrols/focus/tst_focus.cpp b/tests/auto/quickcontrols/focus/tst_focus.cpp index 5d745813dc..dde4621060 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,15 @@ tst_focus::tst_focus() { } +void tst_focus::init() +{ + QTest::failOnWarning(QRegularExpression(".?")); +} + void tst_focus::initTestCase() { + SKIP_IF_NO_WINDOW_ACTIVATION + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); QQuickStyle::setStyle("Basic"); QQmlDataTest::initTestCase(); } diff --git a/tests/auto/quickcontrols/font/tst_font.cpp b/tests/auto/quickcontrols/font/tst_font.cpp index bdb73bbf4b..ec8e927693 100644 --- a/tests/auto/quickcontrols/font/tst_font.cpp +++ b/tests/auto/quickcontrols/font/tst_font.cpp @@ -113,7 +113,7 @@ void tst_font::font() QFETCH(QString, testFile); QFETCH(QFont, expectedFont); - if (QSysInfo::productType().compare(QLatin1String("osx"), Qt::CaseInsensitive) == 0 + if (QSysInfo::productType().compare(QLatin1String("macos"), Qt::CaseInsensitive) == 0 && qgetenv("QTEST_ENVIRONMENT").split(' ').contains("CI")) { QSKIP("This test crashes on macOS: QTBUG-70063"); } 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/tst_palette.cpp b/tests/auto/quickcontrols/palette/tst_palette.cpp index 5109ad0f8f..d434589bc6 100644 --- a/tests/auto/quickcontrols/palette/tst_palette.cpp +++ b/tests/auto/quickcontrols/palette/tst_palette.cpp @@ -7,9 +7,11 @@ #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> @@ -19,6 +21,7 @@ #include <QtQuickControls2/qquickstyle.h> #include <QSignalSpy> +using namespace QQuickVisualTestUtils; using namespace QQuickControlsTestUtils; class tst_palette : public QQmlDataTest @@ -52,6 +55,11 @@ private slots: void resetColor(); void updateBindingPalette(); + + void comboBoxPopup_data(); + void comboBoxPopup(); + void comboBoxPopupWithThemeDefault_data(); + void comboBoxPopupWithThemeDefault(); }; tst_palette::tst_palette() @@ -551,6 +559,92 @@ 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); +} + QTEST_MAIN(tst_palette) #include "tst_palette.moc" diff --git a/tests/auto/quickcontrols/qquickapplicationwindow/tst_qquickapplicationwindow.cpp b/tests/auto/quickcontrols/qquickapplicationwindow/tst_qquickapplicationwindow.cpp index ff0b4418d2..420baa234d 100644 --- a/tests/auto/quickcontrols/qquickapplicationwindow/tst_qquickapplicationwindow.cpp +++ b/tests/auto/quickcontrols/qquickapplicationwindow/tst_qquickapplicationwindow.cpp @@ -58,6 +58,8 @@ private slots: tst_QQuickApplicationWindow::tst_QQuickApplicationWindow() : QQmlDataTest(QT_QMLTEST_DATADIR) { + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); QQuickStyle::setStyle("Basic"); } @@ -278,6 +280,10 @@ void tst_QQuickApplicationWindow::implicitFill() void tst_QQuickApplicationWindow::attachedProperties() { + if (QGuiApplication::platformName().startsWith(QLatin1String("eglfs"), Qt::CaseInsensitive)) + { + QSKIP("This test uses multiple windows and it crashes on EGLFS because of that"); + } QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("attachedProperties.qml")); diff --git a/tests/auto/quickcontrols/qquickdrawer/tst_qquickdrawer.cpp b/tests/auto/quickcontrols/qquickdrawer/tst_qquickdrawer.cpp index 5eb3895849..d16bc7790f 100644 --- a/tests/auto/quickcontrols/qquickdrawer/tst_qquickdrawer.cpp +++ b/tests/auto/quickcontrols/qquickdrawer/tst_qquickdrawer.cpp @@ -1055,8 +1055,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/qquickiconlabel/tst_qquickiconlabel.cpp b/tests/auto/quickcontrols/qquickiconlabel/tst_qquickiconlabel.cpp index 9f0feb5139..c263fbe1bd 100644 --- a/tests/auto/quickcontrols/qquickiconlabel/tst_qquickiconlabel.cpp +++ b/tests/auto/quickcontrols/qquickiconlabel/tst_qquickiconlabel.cpp @@ -336,7 +336,7 @@ void tst_qquickiconlabel::iconSourceContext() QVERIFY(image); QQuickImagePrivate *imagePrivate = static_cast<QQuickImagePrivate *>(QQuickItemPrivate::get(image)); - QCOMPARE(imagePrivate->pix.url(), testFileUrl("a.png")); + QCOMPARE(imagePrivate->pix1.url(), testFileUrl("a.png")); } #endif } diff --git a/tests/auto/quickcontrols/qquickmaterialstyle/tst_qquickmaterialstyle.cpp b/tests/auto/quickcontrols/qquickmaterialstyle/tst_qquickmaterialstyle.cpp index 783c5499c8..48a3e2138a 100644 --- a/tests/auto/quickcontrols/qquickmaterialstyle/tst_qquickmaterialstyle.cpp +++ b/tests/auto/quickcontrols/qquickmaterialstyle/tst_qquickmaterialstyle.cpp @@ -2,4 +2,18 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtQuickTest/quicktest.h> -QUICK_TEST_MAIN(tst_qquickmaterialstyle) + +class Setup : public QObject +{ + Q_OBJECT + +public slots: + void applicationAvailable() + { + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); + } +}; + +QUICK_TEST_MAIN_WITH_SETUP(tst_qquickmaterialstyle, Setup) + +#include "tst_qquickmaterialstyle.moc" diff --git a/tests/auto/quickcontrols/qquickmenu/data/nativeDynamicSubmenus.qml b/tests/auto/quickcontrols/qquickmenu/data/nativeDynamicSubmenus.qml new file mode 100644 index 0000000000..951cb7cb6c --- /dev/null +++ b/tests/auto/quickcontrols/qquickmenu/data/nativeDynamicSubmenus.qml @@ -0,0 +1,53 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Templates as T +import QtQuick.Controls + +ApplicationWindow { + width: 400 + height: 400 + + property alias contextMenu: contextMenu + + function addSubMenu(title: string) { + contextMenu.addMenu(subMenuComponent.createObject(null, { title: title })) + } + + function addAction(menu: T.Menu, text: string) { + menu.addAction(actionComponent.createObject(null, { text: text })) + } + + function insertAction(menu: T.Menu, index: int, text: string) { + menu.insertAction(index, actionComponent.createObject(null, { text: text })) + } + + Component { + id: actionComponent + + Action { + objectName: text + } + } + + Component { + id: subMenuComponent + + Menu { + id: subMenu + objectName: title + popupType: Popup.Native + + Action { + text: subMenu.objectName + "Action1" + } + } + } + + Menu { + id: contextMenu + objectName: "menu" + popupType: Popup.Native + } +} diff --git a/tests/auto/quickcontrols/qquickmenu/data/nativeEmptyMenu.qml b/tests/auto/quickcontrols/qquickmenu/data/nativeEmptyMenu.qml new file mode 100644 index 0000000000..0ae2c5dc66 --- /dev/null +++ b/tests/auto/quickcontrols/qquickmenu/data/nativeEmptyMenu.qml @@ -0,0 +1,51 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Templates as T +import QtQuick.Controls + +ApplicationWindow { + width: 400 + height: 400 + + property alias contextMenu: contextMenu + + function addAction(menu: T.Menu, text: string) { + menu.addAction(actionComponent.createObject(null, { text: text })) + } + + function insertAction(menu: T.Menu, index: int, text: string) { + menu.insertAction(index, actionComponent.createObject(null, { text: text })) + } + + function removeAction(menu: T.Menu, index: int) { + menu.removeAction(menu.actionAt(index)) + } + + function addMenu(menu: T.Menu, title: string) { + menu.addMenu(menuComponent.createObject(null, { title: title })) + } + + Component { + id: actionComponent + + Action { + objectName: text + } + } + + Component { + id: menuComponent + + Menu { + objectName: title + } + } + + Menu { + id: contextMenu + objectName: "menu" + popupType: Popup.Native + } +} diff --git a/tests/auto/quickcontrols/qquickmenu/data/nativeMenuSeparator.qml b/tests/auto/quickcontrols/qquickmenu/data/nativeMenuSeparator.qml new file mode 100644 index 0000000000..54195af349 --- /dev/null +++ b/tests/auto/quickcontrols/qquickmenu/data/nativeMenuSeparator.qml @@ -0,0 +1,43 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + width: 400 + height: 400 + + property alias contextMenu: contextMenu + + Menu { + id: contextMenu + objectName: "menu" + popupType: Popup.Native + + Action { + objectName: text + text: "action1" + } + + MenuSeparator {} + + Menu { + id: subMenu + objectName: "subMenu" + popupType: Popup.Native + + Action { + objectName: text + text: "subAction1" + } + + MenuSeparator {} + + Action { + objectName: text + text: "subAction2" + } + } + } +} diff --git a/tests/auto/quickcontrols/qquickmenu/data/nativeMixedItems.qml b/tests/auto/quickcontrols/qquickmenu/data/nativeMixedItems.qml new file mode 100644 index 0000000000..119d8debec --- /dev/null +++ b/tests/auto/quickcontrols/qquickmenu/data/nativeMixedItems.qml @@ -0,0 +1,69 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Templates as T +import QtQuick.Controls + +ApplicationWindow { + width: 400 + height: 400 + + property alias contextMenu: contextMenu + + function insertRectangle(menu: T.Menu, index: int, color: color) { + menu.insertItem(index, rectangleComponent.createObject(null, { color: color })) + } + + Component { + id: rectangleComponent + + Rectangle { + objectName: "rectangle" + width: 32 + height: 32 + } + } + + Component { + id: menuComponent + + Menu { + objectName: title + popupType: contextMenu.popupType + } + } + + Menu { + id: contextMenu + objectName: "menu" + popupType: contextMenu.popupType + + Action { + objectName: text + text: "action" + } + + MenuItem { + text: "menuItem" + objectName: text + } + + Menu { + id: subMenu + title: "subMenu" + objectName: title + popupType: contextMenu.popupType + + Action { + objectName: text + text: "subAction1" + } + + Action { + objectName: text + text: "subAction2" + } + } + } +} diff --git a/tests/auto/quickcontrols/qquickmenu/data/nativeStatic.qml b/tests/auto/quickcontrols/qquickmenu/data/nativeStatic.qml new file mode 100644 index 0000000000..32ba1f1829 --- /dev/null +++ b/tests/auto/quickcontrols/qquickmenu/data/nativeStatic.qml @@ -0,0 +1,53 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Templates as T +import QtQuick.Controls + +ApplicationWindow { + width: 400 + height: 400 + + property alias contextMenu: contextMenu + + Menu { + id: contextMenu + objectName: "menu" + popupType: Popup.Native + + Action { + objectName: text + text: "action1" + shortcut: "A" + } + + MenuItem { + objectName: text + action: Action { + text: "menuItemAction" + objectName: text + shortcut: "B" + } + } + + Menu { + id: subMenu + title: "subMenu" + objectName: title + popupType: Popup.Native + // TODO: remove me when the defaults are true + + Action { + objectName: text + text: "subAction1" + shortcut: "1" + } + } + } + + TapHandler { + acceptedButtons: Qt.RightButton + onTapped: contextMenu.popup() + } +} diff --git a/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp b/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp index a80aec5ca1..e652168002 100644 --- a/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp +++ b/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp @@ -9,12 +9,14 @@ #endif #include <QtGui/qstylehints.h> #include <QtGui/qpa/qplatformintegration.h> +#include <QtGui/qpa/qplatformtheme.h> #include <QtGui/private/qguiapplication_p.h> #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcomponent.h> #include <QtQml/qqmlcontext.h> #include <QtQuick/qquickview.h> #include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/private/qquickrectangle_p.h> #include <QtQuickTestUtils/private/qmlutils_p.h> #include <QtQuickTestUtils/private/visualtestutils_p.h> #include <QtQuickControlsTestUtils/private/controlstestutils_p.h> @@ -26,12 +28,16 @@ #include <QtQuickTemplates2/private/qquickbutton_p.h> #include <QtQuickTemplates2/private/qquickicon_p.h> #include <QtQuickTemplates2/private/qquickmenu_p.h> +#include <QtQuickTemplates2/private/qquickmenu_p_p.h> #include <QtQuickTemplates2/private/qquickmenuitem_p.h> #include <QtQuickTemplates2/private/qquickmenuseparator_p.h> +#include <QtQuickTemplates2/private/qquicknativemenuitem_p.h> using namespace QQuickVisualTestUtils; using namespace QQuickControlsTestUtils; +// Native menu tests are in "nativemenus". + class tst_QQuickMenu : public QQmlDataTest { Q_OBJECT @@ -40,6 +46,8 @@ public: tst_QQuickMenu(); private slots: + void init(); + void defaults(); void count(); void mouse(); @@ -55,6 +63,7 @@ private slots: #if QT_CONFIG(cursor) void popup(); #endif + void openParentlessMenu(); void actions(); #if QT_CONFIG(shortcut) void actionShortcuts(); @@ -87,19 +96,42 @@ private slots: void customMenuCullItems(); void customMenuUseRepeaterAsTheContentItem(); void invalidUrlInImgTag(); + void nativeStatic(); + void nativeDynamicActions(); + void nativeDynamicSubmenus(); + void nativeMenuSeparator(); + void dontUseNativeMenuWindowsChanges(); + void nativeMixedItems(); + void textPadding(); private: - static bool hasWindowActivation(); + bool nativeMenuSupported = false; }; +// This allows us to use QQuickMenuItem's more descriptive operator<< output +// for the QCOMPARE failure message. It doesn't seem possible to use toString +// overloads or template specialization when types declared in QML are involved, +// as is the case for the MenuItems created from Menu's delegate. +#define COMPARE_MENUITEMS(actualMenuItem, expectedMenuItem) \ +QVERIFY2(actualMenuItem == expectedMenuItem, \ + qPrintable(QString::fromLatin1("\n Actual: %1\n Expected: %2") \ + .arg(QDebug::toString(actualMenuItem), QDebug::toString(expectedMenuItem)))); + tst_QQuickMenu::tst_QQuickMenu() : QQmlDataTest(QT_QMLTEST_DATADIR) { + std::unique_ptr<QPlatformMenu> platformMenu(QGuiApplicationPrivate::platformTheme()->createPlatformMenu()); + nativeMenuSupported = platformMenu != nullptr; } -bool tst_QQuickMenu::hasWindowActivation() +void tst_QQuickMenu::init() { - return (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)); + QQmlDataTest::init(); + + // By default we don't want to use native menus, as the majority of the tests + // were written before they were a thing. We instead explicitly set it where necessary. + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); } void tst_QQuickMenu::defaults() @@ -146,8 +178,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"))) @@ -160,6 +191,7 @@ void tst_QQuickMenu::mouse() centerOnScreen(window); moveMouseAway(window); window->show(); + window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); QQuickMenu *menu = window->property("menu").value<QQuickMenu*>(); @@ -278,8 +310,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 +499,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 +565,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"); @@ -593,8 +622,7 @@ void tst_QQuickMenu::mnemonics() 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 +676,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()); @@ -657,6 +684,7 @@ void tst_QQuickMenu::menuSeparator() centerOnScreen(window); moveMouseAway(window); window->show(); + window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); QQuickMenu *menu = window->property("menu").value<QQuickMenu*>(); @@ -965,6 +993,29 @@ void tst_QQuickMenu::popup() } #endif // QT_CONFIG(cursor) +void tst_QQuickMenu::openParentlessMenu() +{ + // Check that we don't get a crash if the application sets a menu's parentItem + // to null. This will also result in the menu not showing at all, since it's + // no longer a part of the scene. Even if this limitiation is technically only + // relevant for non-native menus, we enforce it also for native menus to ensure + // that an application works the same on all platforms. + QQuickControlsApplicationHelper helper(this, QLatin1String("popup.qml")); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickApplicationWindow *window = helper.appWindow; + centerOnScreen(window); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + QTest::ignoreMessage(QtWarningMsg, QRegularExpression("cannot show menu: parent is null")); + + QQuickMenu *menu = window->property("menu").value<QQuickMenu *>(); + QVERIFY(menu); + menu->setParentItem(nullptr); + menu->popup(); + QVERIFY(!menu->isVisible()); +} + void tst_QQuickMenu::actions() { QQuickControlsApplicationHelper helper(this, QLatin1String("actions.qml")); @@ -1037,13 +1088,13 @@ 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()); QQuickWindow *window = helper.window; window->show(); + window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); // Try the menu's shortcut. @@ -1332,8 +1383,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); @@ -1344,6 +1394,7 @@ void tst_QQuickMenu::subMenuKeyboard() centerOnScreen(window); moveMouseAway(window); window->show(); + window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); if (mirrored) { @@ -1461,8 +1512,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); @@ -1473,6 +1523,7 @@ void tst_QQuickMenu::subMenuDisabledKeyboard() centerOnScreen(window); moveMouseAway(window); window->show(); + window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); if (mirrored) { @@ -2050,13 +2101,13 @@ 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()); QQuickApplicationWindow *window = helper.appWindow; window->show(); + window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); // Press enter on the button to open the menu. @@ -2138,6 +2189,501 @@ void tst_QQuickMenu::invalidUrlInImgTag() QVERIFY(menuItemFirst); } +void tst_QQuickMenu::nativeStatic() +{ + QQuickControlsApplicationHelper helper(this, QLatin1String("nativeStatic.qml")); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickApplicationWindow *window = helper.appWindow; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + QQuickMenu *contextMenu = window->property("contextMenu").value<QQuickMenu*>(); + QVERIFY(contextMenu); + auto *contextMenuPrivate = QQuickMenuPrivate::get(contextMenu); + QVERIFY(contextMenuPrivate->useNativeMenu()); + + // Check that the actions of the parent menu can be accessed + // and are in the appropriate places in contentModel and contentData. + auto *action1 = contextMenu->actionAt(0); + QVERIFY(action1); + auto *action1MenuItem = qobject_cast<QQuickMenuItem *>(contextMenu->itemAt(0)); + QVERIFY(action1MenuItem); + QCOMPARE(action1MenuItem->action(), action1); + COMPARE_MENUITEMS(qobject_cast<QQuickMenuItem *>(contextMenuPrivate->contentData.at(0)), + action1MenuItem); + + auto *menuItem = qobject_cast<QQuickMenuItem *>(contextMenu->itemAt(1)); + QVERIFY(menuItem); + QVERIFY(menuItem->action()); + QCOMPARE(menuItem->action()->text(), "menuItemAction"); + COMPARE_MENUITEMS(qobject_cast<QQuickMenuItem *>(contextMenuPrivate->contentData.at(1)), menuItem); + + // Check that the sub-menu can be accessed and is in the + // appropriate place in contentData. + auto *subMenu = contextMenu->menuAt(2); + QVERIFY(subMenu); + auto *subMenuPrivate = QQuickMenuPrivate::get(subMenu); + auto *subMenuAction1 = subMenu->actionAt(0); + QVERIFY(subMenuAction1); + auto *subMenuAction1MenuItem = qobject_cast<QQuickMenuItem *>(subMenu->itemAt(0)); + QVERIFY(subMenuAction1MenuItem); + QCOMPARE(subMenuAction1MenuItem->action(), subMenuAction1); + COMPARE_MENUITEMS(qobject_cast<QQuickMenuItem *>(subMenuPrivate->contentData.at(0)), + subMenuAction1MenuItem); +} + +void tst_QQuickMenu::nativeDynamicActions() +{ + QQuickControlsApplicationHelper helper(this, QLatin1String("nativeEmptyMenu.qml")); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickApplicationWindow *window = helper.appWindow; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + QQuickMenu *contextMenu = window->property("contextMenu").value<QQuickMenu*>(); + QVERIFY(contextMenu); + auto *contextMenuPrivate = QQuickMenuPrivate::get(contextMenu); + + // Check that items can be appended to an empty menu. + QCOMPARE(contextMenu->actionAt(0), nullptr); + QVERIFY(QMetaObject::invokeMethod(window, "addAction", + Q_ARG(QQuickMenu *, contextMenu), Q_ARG(QString, "action1"))); + { + auto action1 = contextMenu->actionAt(0); + QVERIFY(action1); + QCOMPARE(action1->text(), "action1"); + auto *action1MenuItem = qobject_cast<QQuickMenuItem *>(contextMenu->itemAt(0)); + QVERIFY(action1MenuItem); + QCOMPARE(action1MenuItem->action(), action1); + COMPARE_MENUITEMS(qobject_cast<QQuickMenuItem *>(contextMenuPrivate->contentData.at(0)), + action1MenuItem); + } + + // Check that actions can be appended after existing items in the parent menu. + QCOMPARE(contextMenu->actionAt(1), nullptr); + QVERIFY(QMetaObject::invokeMethod(window, "addAction", + Q_ARG(QQuickMenu *, contextMenu), Q_ARG(QString, "action2"))); + { + auto action2 = contextMenu->actionAt(1); + QVERIFY(action2); + QCOMPARE(action2->text(), "action2"); + auto *action2MenuItem = qobject_cast<QQuickMenuItem *>(contextMenu->itemAt(1)); + QVERIFY(action2MenuItem); + QCOMPARE(action2MenuItem->action(), action2); + COMPARE_MENUITEMS(qobject_cast<QQuickMenuItem *>(contextMenuPrivate->contentData.at(1)), + action2MenuItem); + } + + // Check that actions can be inserted before existing items in the parent menu. + QVERIFY(QMetaObject::invokeMethod(window, "insertAction", + Q_ARG(QQuickMenu *, contextMenu), Q_ARG(int, 0), Q_ARG(QString, "action0"))); + { + auto action0 = contextMenu->actionAt(0); + QVERIFY(action0); + QCOMPARE(action0->text(), "action0"); + auto *action0MenuItem = qobject_cast<QQuickMenuItem *>(contextMenu->itemAt(0)); + QVERIFY(action0MenuItem); + QCOMPARE(action0MenuItem->action(), action0); + // New items are always appended to contentData, regardless of the actual insertion index + // in contentModel. + COMPARE_MENUITEMS(qobject_cast<QQuickMenuItem *>(contextMenuPrivate->contentData.at(2)), + action0MenuItem); + } +} + +void tst_QQuickMenu::nativeDynamicSubmenus() +{ + QQuickControlsApplicationHelper helper(this, QLatin1String("nativeDynamicSubmenus.qml")); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickApplicationWindow *window = helper.appWindow; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + QQuickMenu *contextMenu = window->property("contextMenu").value<QQuickMenu*>(); + QVERIFY(contextMenu); + auto *contextMenuPrivate = QQuickMenuPrivate::get(contextMenu); + + // We construct the sub-menu first in QML. At least on Windows, menu items + // added to an empty sub-menu won't show up (tested with Widgets): QTBUG-120494. + // So, this adds an already-populated menu as a sub-menu. + QVERIFY(QMetaObject::invokeMethod(window, "addSubMenu", Q_ARG(QString, "subMenu1"))); + auto subMenu1 = contextMenu->menuAt(0); + QVERIFY(subMenu1); + QCOMPARE(subMenu1->title(), "subMenu1"); + auto *subMenu1Private = QQuickMenuPrivate::get(subMenu1); + if (nativeMenuSupported) { + QVERIFY(subMenu1Private->handle); + QCOMPARE(subMenu1Private->nativeItems.size(), 1); + } + auto *subMenu1MenuItem = qobject_cast<QQuickMenuItem *>(contextMenu->itemAt(0)); + QVERIFY(subMenu1MenuItem); + COMPARE_MENUITEMS(qobject_cast<QQuickMenuItem *>(contextMenuPrivate->contentData.at(0)), + subMenu1MenuItem); + QCOMPARE(contextMenuPrivate->contentData.size(), 1); + { + auto subMenuAction1 = subMenu1->actionAt(0); + QVERIFY(subMenuAction1); + QCOMPARE(subMenuAction1->text(), "subMenu1Action1"); + auto *subMenuAction1MenuItem = qobject_cast<QQuickMenuItem *>(subMenu1->itemAt(0)); + QVERIFY(subMenuAction1MenuItem); + QCOMPARE(subMenuAction1MenuItem->action(), subMenuAction1); + COMPARE_MENUITEMS(qobject_cast<QQuickMenuItem *>(subMenu1Private->contentData.at(0)), + subMenuAction1MenuItem); + if (nativeMenuSupported) + QCOMPARE(subMenu1Private->nativeItems.size(), 1); + } + + // Check that actions can be appended after existing items in the sub-menu. + QCOMPARE(subMenu1->actionAt(1), nullptr); + QVERIFY(QMetaObject::invokeMethod(window, "addAction", + Q_ARG(QQuickMenu *, subMenu1), Q_ARG(QString, "subMenu1Action2"))); + { + auto subMenu1Action2 = subMenu1->actionAt(1); + QVERIFY(subMenu1Action2); + QCOMPARE(subMenu1Action2->text(), "subMenu1Action2"); + auto *subMenu1Action2MenuItem = qobject_cast<QQuickMenuItem *>(subMenu1->itemAt(1)); + QVERIFY(subMenu1Action2MenuItem); + QCOMPARE(subMenu1Action2MenuItem->action(), subMenu1Action2); + COMPARE_MENUITEMS(qobject_cast<QQuickMenuItem *>(subMenu1Private->contentData.at(1)), + subMenu1Action2MenuItem); + QCOMPARE(subMenu1Private->contentData.size(), 2); + } + + // Check that actions can be inserted before existing items in the sub-menu. + QVERIFY(QMetaObject::invokeMethod(window, "insertAction", + Q_ARG(QQuickMenu *, subMenu1), Q_ARG(int, 0), Q_ARG(QString, "subMenu1Action0"))); + { + auto subMenu1Action0 = subMenu1->actionAt(0); + QVERIFY(subMenu1Action0); + QCOMPARE(subMenu1Action0->text(), "subMenu1Action0"); + auto *subMenu1Action0MenuItem = qobject_cast<QQuickMenuItem *>(subMenu1->itemAt(0)); + QVERIFY(subMenu1Action0MenuItem); + QCOMPARE(subMenu1Action0MenuItem->action(), subMenu1Action0); + // New items are always appended to contentData, regardless of the actual insertion index + // in contentModel. + COMPARE_MENUITEMS(qobject_cast<QQuickMenuItem *>(subMenu1Private->contentData.at(2)), + subMenu1Action0MenuItem); + QCOMPARE(subMenu1Private->contentData.size(), 3); + } + + { + // Check that takeMenu works. + auto *takenSubMenu = contextMenu->takeMenu(0); + QCOMPARE(takenSubMenu, subMenu1); + QCOMPARE(contextMenuPrivate->contentData.size(), 0); + if (nativeMenuSupported) { + QVERIFY(!subMenu1Private->handle); + QCOMPARE(subMenu1Private->nativeItems.size(), 0); + } + + // Check that the sub-menu can be added back in to the menu. + contextMenu->addMenu(takenSubMenu); + QCOMPARE(contextMenuPrivate->contentData.size(), 1); + auto *subMenu1MenuItem = qobject_cast<QQuickMenuItem *>(contextMenu->itemAt(0)); + QVERIFY(subMenu1MenuItem); + QCOMPARE(subMenu1MenuItem->text(), "subMenu1"); + if (nativeMenuSupported) { + QVERIFY(subMenu1Private->handle); + QCOMPARE(subMenu1Private->nativeItems.size(), 3); + } + QCOMPARE(subMenu1Private->contentData.size(), 3); + + auto *subMenu1Action0MenuItem = qobject_cast<QQuickMenuItem *>(subMenu1->itemAt(0)); + QVERIFY(subMenu1Action0MenuItem); + } + + // Check that removeMenu works. + QVERIFY(contextMenu->menuAt(0)); + contextMenu->removeMenu(contextMenu->menuAt(0)); + QCOMPARE(contextMenuPrivate->contentData.size(), 0); +} + +void tst_QQuickMenu::nativeMenuSeparator() +{ + QQuickControlsApplicationHelper helper(this, QLatin1String("nativeMenuSeparator.qml")); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickApplicationWindow *window = helper.appWindow; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + // Check that separators in menus are where we expect them to be. + QQuickMenu *contextMenu = window->property("contextMenu").value<QQuickMenu*>(); + QVERIFY(contextMenu); + auto *contextMenuSeparatorAsItem = contextMenu->itemAt(1); + QVERIFY(contextMenuSeparatorAsItem); + auto *contextMenuSeparator = qobject_cast<QQuickMenuSeparator *>(contextMenuSeparatorAsItem); + QVERIFY(contextMenuSeparator); + if (nativeMenuSupported) { + auto *contextMenuPrivate = QQuickMenuPrivate::get(contextMenu); + QCOMPARE(contextMenuPrivate->nativeItems.size(), 3); + auto *contextMenuSeparatorNativeItem = contextMenuPrivate->nativeItems.at(1); + QVERIFY(contextMenuSeparatorNativeItem); + QVERIFY(contextMenuSeparatorNativeItem->separator()); + } + + // Check that separators in sub-menus are where we expect them to be. + QQuickMenu *subMenu = window->property("contextMenu").value<QQuickMenu*>(); + QVERIFY(subMenu); + auto *subMenuSeparatorAsItem = subMenu->itemAt(1); + QVERIFY(subMenuSeparatorAsItem); + auto *subMenuSeparator = qobject_cast<QQuickMenuSeparator *>(subMenuSeparatorAsItem); + QVERIFY(subMenuSeparator); + if (nativeMenuSupported) { + auto *subMenuPrivate = QQuickMenuPrivate::get(subMenu); + QCOMPARE(subMenuPrivate->nativeItems.size(), 3); + auto *subMenuSeparatorNativeItem = subMenuPrivate->nativeItems.at(1); + QVERIFY(subMenuSeparatorNativeItem); + QVERIFY(subMenuSeparatorNativeItem->separator()); + } +} + +void tst_QQuickMenu::dontUseNativeMenuWindowsChanges() +{ + QSKIP("QTBUG-125967 This test will need to be fixed, by using popupType: Popup.Native instead of AA_DontUseNativeMenuWindows."); + + if (QSysInfo::productType() == QLatin1String("b2qt")) + QSKIP("b2qt doesn't support native menus"); + + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows, false); + QQuickControlsApplicationHelper helper(this, QLatin1String("nativeStatic.qml")); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickApplicationWindow *window = helper.appWindow; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + QQuickMenu *contextMenu = window->property("contextMenu").value<QQuickMenu*>(); + QVERIFY(contextMenu); + QCOMPARE(contextMenu->count(), 3); + // Sub-menus should respect the native-ness of their parents. + auto *subMenu = contextMenu->menuAt(2); + auto *subMenuPrivate = QQuickMenuPrivate::get(subMenu); + QVERIFY(subMenuPrivate->useNativeMenu()); + if (nativeMenuSupported) + QVERIFY(subMenuPrivate->handle); + else + QVERIFY(!subMenuPrivate->handle); + + // Ensure that the menu and its sub-menu have enough room to open. + if (window->width() / 2 <= contextMenu->width()) + window->setWidth(contextMenu->width() * 2 + 1); + if (window->height() <= contextMenu->height()) + window->setHeight(contextMenu->height() + 1); + QTRY_COMPARE(window->contentItem()->size(), window->size()); + + // We can't test that aboutToShow/aboutToHide is emitted for native menus + // because when they are shown, the event loop is blocked until they are closed. + // So we just check that a native menu is actually in use before going on to test + // non-native menus. + auto *contextMenuPrivate = QQuickMenuPrivate::get(contextMenu); + if (nativeMenuSupported) + QVERIFY(contextMenuPrivate->handle); + else + QVERIFY(!contextMenuPrivate->handle); + + // We need to wait until the menu is opened before it picks up the changes, + // which is why we don't check the native handle here yet. + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); + QVERIFY(!contextMenuPrivate->useNativeMenu()); + QVERIFY(!subMenuPrivate->useNativeMenu()); + + // Check that we can open the menu by right-clicking (or just open it manually + // if the platform doesn't support (moving) QCursor). + QSignalSpy aboutToShowSpy(contextMenu, &QQuickMenu::aboutToShow); + QVERIFY(aboutToShowSpy.isValid()); + bool couldMoveCursorPos = false; + const QPoint cursorPos(1, 1); +#if QT_CONFIG(cursor) + // Try moving the cursor from the current position to test if the platform + // supports moving the cursor. + const QPoint point = QCursor::pos() + QPoint(1, 1); + QCursor::setPos(point); + if (QTest::qWaitFor([point]{ return QCursor::pos() == point; })) { + couldMoveCursorPos = true; + const QPoint globalCursorPos = window->mapToGlobal(cursorPos); + QCursor::setPos(globalCursorPos); + QTest::mouseClick(window, Qt::RightButton, Qt::NoModifier, cursorPos); + } +#endif + if (!couldMoveCursorPos) { + contextMenu->setX(cursorPos.x()); + contextMenu->setY(cursorPos.y()); + contextMenu->open(); + } + QVERIFY(contextMenu->isVisible()); + QTRY_VERIFY(contextMenu->isOpened()); + QCOMPARE(aboutToShowSpy.size(), 1); + // Now that it's open and has picked up the changes to Qt::AA_DontUseNativeMenuWindows, we can check it. + QVERIFY(!contextMenuPrivate->handle); + QVERIFY(!subMenuPrivate->handle); + // Check that it opened at the mouse cursor and actually has menu items. + QCOMPARE(contextMenu->x(), cursorPos.x()); + QCOMPARE(contextMenu->y(), cursorPos.y()); + auto *action1MenuItem = qobject_cast<QQuickMenuItem *>(contextMenu->itemAt(0)); + QVERIFY(action1MenuItem); + QCOMPARE(action1MenuItem->text(), "action1"); + + // Test setting Qt::AA_DontUseNativeMenuWindows while visible has no effect + // (until it's re-opened, which we can't test because we can't test opening native menus). + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows, false); + QVERIFY(contextMenuPrivate->useNativeMenu()); + QVERIFY(!contextMenuPrivate->handle); + QVERIFY(!subMenuPrivate->handle); + + // Also check the submenu. + auto *subAction1MenuItem = qobject_cast<QQuickMenuItem *>(subMenu->itemAt(0)); + QVERIFY(subAction1MenuItem); + QCOMPARE(subAction1MenuItem->text(), "subAction1"); + + // Test closing the non-native menu by clicking on an item. + QSignalSpy aboutToHideSpy(contextMenu, &QQuickMenu::aboutToHide); + QVERIFY(aboutToHideSpy.isValid()); + QVERIFY(clickButton(action1MenuItem)); + QVERIFY(!contextMenu->isOpened()); + QTRY_VERIFY(!contextMenu->isVisible()); + QCOMPARE(aboutToShowSpy.size(), 1); + + // Although we can't open the native menu, we can at least check that + // attempting (the changes won't come into effect until it's re-opened) + // to make the menu native again doesn't e.g. crash. + QVERIFY(contextMenuPrivate->useNativeMenu()); + QVERIFY(subMenuPrivate->useNativeMenu()); + QVERIFY(!contextMenuPrivate->handle); + QVERIFY(!subMenuPrivate->handle); +} + +// Check that non-menu items (e.g. Rectangles) can be inserted between menu items without issues. +void tst_QQuickMenu::nativeMixedItems() +{ + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows, false); + QQuickControlsApplicationHelper helper(this, QLatin1String("nativeMixedItems.qml")); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickApplicationWindow *window = helper.appWindow; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + QQuickMenu *contextMenu = window->property("contextMenu").value<QQuickMenu*>(); + QVERIFY(contextMenu); + + // Insert a Rectangle between the Action and MenuItem in the top-level menu. + QVERIFY(QMetaObject::invokeMethod(window, "insertRectangle", + Q_ARG(QQuickMenu *, contextMenu), Q_ARG(int, 1), Q_ARG(QColor, QColorConstants::Red))); + { + auto *action = contextMenu->actionAt(0); + QVERIFY(action); + QCOMPARE(action->text(), "action"); + auto *rectangle = qobject_cast<QQuickRectangle *>(contextMenu->itemAt(1)); + QVERIFY(rectangle); + QCOMPARE(rectangle->color(), QColorConstants::Red); + auto *menuItem = qobject_cast<QQuickMenuItem *>(contextMenu->itemAt(2)); + QVERIFY(menuItem); + QCOMPARE(menuItem->text(), "menuItem"); + auto *subMenu = contextMenu->menuAt(3); + QVERIFY(subMenu); + QCOMPARE(subMenu->title(), "subMenu"); + } + + // Insert a Rectangle at the end of all of the items (which were: {Action, Rectangle, MenuItem, Menu}). + QVERIFY(QMetaObject::invokeMethod(window, "insertRectangle", + Q_ARG(QQuickMenu *, contextMenu), Q_ARG(int, 4), Q_ARG(QColor, QColorConstants::Blue))); + { + auto *action = contextMenu->actionAt(0); + QVERIFY(action); + QCOMPARE(action->text(), "action"); + auto *rectangle1 = qobject_cast<QQuickRectangle *>(contextMenu->itemAt(1)); + QVERIFY(rectangle1); + QCOMPARE(rectangle1->color(), QColorConstants::Red); + auto *menuItem = qobject_cast<QQuickMenuItem *>(contextMenu->itemAt(2)); + QVERIFY(menuItem); + QCOMPARE(menuItem->text(), "menuItem"); + auto *subMenu = contextMenu->menuAt(3); + QVERIFY(subMenu); + QCOMPARE(subMenu->title(), "subMenu"); + auto *rectangle2 = qobject_cast<QQuickRectangle *>(contextMenu->itemAt(4)); + QVERIFY(rectangle2); + QCOMPARE(rectangle2->color(), QColorConstants::Blue); + } + + // Check that the sub-menu can be accessed and is in the + // appropriate place in contentData. + auto *subMenu = contextMenu->menuAt(3); + QVERIFY(subMenu); + // Insert a Rectangle between the Action and MenuItem in the top-level menu. + QVERIFY(QMetaObject::invokeMethod(window, "insertRectangle", + Q_ARG(QQuickMenu *, subMenu), Q_ARG(int, 1), Q_ARG(QColor, QColorConstants::Green))); + { + auto *action1 = subMenu->actionAt(0); + QVERIFY(action1); + QCOMPARE(action1->text(), "subAction1"); + auto *rectangle = qobject_cast<QQuickRectangle *>(subMenu->itemAt(1)); + QVERIFY(rectangle); + QCOMPARE(rectangle->color(), QColorConstants::Green); + auto *action2 = subMenu->actionAt(2); + QVERIFY(action2); + QCOMPARE(action2->text(), "subAction2"); + } +} + +void tst_QQuickMenu::textPadding() +{ + // Check that you can set implicitTextPadding on each MenuItem, and that + // textPadding will end up as the maximum implicitTextPadding among all the + // MenuItems in the same Menu. + + QQuickControlsApplicationHelper helper(this, QLatin1String("nativeMixedItems.qml")); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickApplicationWindow *window = helper.appWindow; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + QQuickMenu *contextMenu = window->property("contextMenu").value<QQuickMenu*>(); + QVERIFY(contextMenu); + contextMenu->setPopupType(QQuickPopup::Item); + + contextMenu->setVisible(true); + + // Go through all MenuItems, and give them an implicitTextPadding of 0 + for (int i = 0; i < contextMenu->count(); ++i) { + auto menuItem = qobject_cast<QQuickMenuItem *>(contextMenu->itemAt(i)); + QVERIFY(menuItem); + menuItem->setImplicitTextPadding(0); + QCOMPARE(menuItem->implicitTextPadding(), 0); + } + + // Check that all MenuItems now has a textPadding of 0 + for (int i = 0; i < contextMenu->count(); ++i) { + auto menuItem = qobject_cast<QQuickMenuItem *>(contextMenu->itemAt(i)); + QCOMPARE(menuItem->textPadding(), 0); + } + + // Let the first MenuItem get a implicitTextPadding of 100. This will + // make all MenuItems get a textPadding of 100. + auto firstItem = qobject_cast<QQuickMenuItem *>(contextMenu->itemAt(0)); + firstItem->setImplicitTextPadding(100); + QCOMPARE(firstItem->implicitTextPadding(), 100); + QCOMPARE(firstItem->textPadding(), 100); + for (int i = 1; i < contextMenu->count(); ++i) { + auto menuItem = qobject_cast<QQuickMenuItem *>(contextMenu->itemAt(i)); + QCOMPARE(menuItem->implicitTextPadding(), 0); + QCOMPARE(menuItem->textPadding(), 100); + } + + // Hide the MenuItem with implicitTextPadding set to 100. This + // should make all the MenuItems get a textPadding of 0 again. + firstItem->setVisible(false); + QCOMPARE(firstItem->implicitTextPadding(), 100); + for (int i = 0; i < contextMenu->count(); ++i) { + auto menuItem = qobject_cast<QQuickMenuItem *>(contextMenu->itemAt(i)); + QCOMPARE(menuItem->textPadding(), 0); + } + + // Show it again + firstItem->setVisible(true); + for (int i = 0; i < contextMenu->count(); ++i) { + auto menuItem = qobject_cast<QQuickMenuItem *>(contextMenu->itemAt(i)); + QCOMPARE(menuItem->textPadding(), 100); + } +} + QTEST_QUICKCONTROLS_MAIN(tst_QQuickMenu) #include "tst_qquickmenu.moc" diff --git a/tests/auto/quickcontrols/qquickmenubar/data/invaliddelegate.qml b/tests/auto/quickcontrols/qquickmenubar/data/invaliddelegate.qml new file mode 100644 index 0000000000..4a6272bc47 --- /dev/null +++ b/tests/auto/quickcontrols/qquickmenubar/data/invaliddelegate.qml @@ -0,0 +1,40 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + id: root + + width: 400 + height: 400 + visible: true + + menuBar: MenuBar { + delegate: Item { /* unsupported since it's not a MenuBarItem */ } + Menu { + id: fileMenu + title: "&File" + MenuItem { text: "&Open..." } + MenuItem { text: "&Save" } + MenuItem { text: "Save &As..." } + MenuSeparator { } + MenuItem { text: "&Quit" } + } + + Menu { + title: "&Edit" + MenuItem { text: "&Cut" } + MenuItem { text: "&Copy" } + MenuItem { text: "&Paste" } + } + + MenuBarItem { + menu: Menu { + title: "&Help" + MenuItem { text: "&About" } + } + } + } +} diff --git a/tests/auto/quickcontrols/qquickmenubar/data/menubarAsHeader.qml b/tests/auto/quickcontrols/qquickmenubar/data/menubarAsHeader.qml new file mode 100644 index 0000000000..3261ca4b59 --- /dev/null +++ b/tests/auto/quickcontrols/qquickmenubar/data/menubarAsHeader.qml @@ -0,0 +1,64 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + id: root + property bool menuBarVisible: true + property alias fileMenu: fileMenu + property alias contents: contents + + width: 400 + height: 400 + visible: true + + header: MenuBar { + visible: root.menuBarVisible + Menu { + id: fileMenu + title: "&File" + MenuItem { text: "&Open..." } + MenuItem { text: "&Save" } + MenuItem { text: "Save &As..." } + MenuSeparator { } + MenuItem { text: "&Quit" } + } + Menu { + title: "&Edit" + MenuItem { text: "&Cut" } + MenuItem { text: "&Copy" } + MenuItem { text: "&Paste" } + } + Menu { + title: "&View" + Menu { + title: "&Alignment" + Menu { + title: "&Horizontal" + MenuItem { text: "&Left" } + MenuItem { text: "&Center" } + MenuItem { text: "&Right" } + } + Menu { + title: "&Vertical" + MenuItem { text: "&Top" } + MenuItem { text: "&Center" } + MenuItem { text: "&Bottom" } + } + } + } + + Menu { + title: "&Help" + MenuItem { text: "&About" } + } + } + + Rectangle { + id: contents + anchors.fill: parent + color: "green" + } +} diff --git a/tests/auto/quickcontrols/qquickmenubar/data/menubar.qml b/tests/auto/quickcontrols/qquickmenubar/data/menubaritems.qml index cf8958e4c4..d7b628afea 100644 --- a/tests/auto/quickcontrols/qquickmenubar/data/menubar.qml +++ b/tests/auto/quickcontrols/qquickmenubar/data/menubaritems.qml @@ -5,16 +5,20 @@ import QtQuick import QtQuick.Controls ApplicationWindow { + id: root readonly property Button oopsButton: oopsButton + property alias fileMenu: fileMenu width: 400 height: 400 visible: true - header: MenuBar { + menuBar: MenuBar { MenuBarItem { menu: Menu { + id: fileMenu title: "&File" + objectName: title MenuItem { text: "&Open..." } MenuItem { text: "&Save" } MenuItem { text: "Save &As..." } @@ -25,6 +29,7 @@ ApplicationWindow { MenuBarItem { menu: Menu { title: "&Edit" + objectName: title MenuItem { text: "&Cut" } MenuItem { text: "&Copy" } MenuItem { text: "&Paste" } @@ -35,14 +40,17 @@ ApplicationWindow { title: "&View" Menu { title: "&Alignment" + objectName: title Menu { title: "&Horizontal" + objectName: title MenuItem { text: "&Left" } MenuItem { text: "&Center" } MenuItem { text: "&Right" } } Menu { title: "&Vertical" + objectName: title MenuItem { text: "&Top" } MenuItem { text: "&Center" } MenuItem { text: "&Bottom" } @@ -54,6 +62,7 @@ ApplicationWindow { MenuBarItem { menu: Menu { title: "&Help" + objectName: title MenuItem { text: "&About" } } } diff --git a/tests/auto/quickcontrols/qquickmenubar/data/menus.qml b/tests/auto/quickcontrols/qquickmenubar/data/menus.qml new file mode 100644 index 0000000000..947beb50fb --- /dev/null +++ b/tests/auto/quickcontrols/qquickmenubar/data/menus.qml @@ -0,0 +1,85 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + id: root + property bool menuBarVisible: true + property alias fileMenu: fileMenu + property alias contents: contents + + width: 400 + height: 400 + visible: true + + menuBar: MenuBar { + visible: root.menuBarVisible + Menu { + id: fileMenu + title: "&File" + MenuItem { text: "&Open..." } + MenuItem { text: "&Save" } + MenuItem { text: "Save &As..." } + MenuSeparator { } + MenuItem { text: "&Quit" } + } + Menu { + title: "&Edit" + MenuItem { text: "&Cut" } + MenuItem { text: "&Copy" } + MenuItem { text: "&Paste" } + } + Menu { + title: "&View" + Menu { + title: "&Alignment" + Menu { + title: "&Horizontal" + MenuItem { text: "&Left" } + MenuItem { text: "&Center" } + MenuItem { text: "&Right" } + } + Menu { + title: "&Vertical" + MenuItem { text: "&Top" } + MenuItem { text: "&Center" } + MenuItem { text: "&Bottom" } + } + } + } + + Menu { + title: "&Help" + MenuItem { text: "&About" } + } + } + + Rectangle { + id: contents + anchors.fill: parent + color: "green" + } + + Text { + // dummy binding to test that fileMenu will be kept alive + // after a call to menuBar.removeMenu(fileMenu) followed + // by running the garbage collector. + text: fileMenu.title + } + + Component { + id: menuComp + Menu { + objectName: "Extra" + title: "extra" + } + } + + function addTestMenu() + { + let menu = menuComp.createObject(null) + menuBar.addMenu(menu) + } +} diff --git a/tests/auto/quickcontrols/qquickmenubar/data/mixed.qml b/tests/auto/quickcontrols/qquickmenubar/data/mixed.qml new file mode 100644 index 0000000000..25dbf01e15 --- /dev/null +++ b/tests/auto/quickcontrols/qquickmenubar/data/mixed.qml @@ -0,0 +1,55 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + id: root + property bool menuBarVisible: true + property alias fileMenu: fileMenu + property alias contents: contents + + width: 400 + height: 400 + visible: true + + menuBar: MenuBar { + visible: root.menuBarVisible + Menu { + id: fileMenu + title: "&File" + MenuItem { text: "&Open..." } + MenuItem { text: "&Save" } + MenuItem { text: "Save &As..." } + MenuSeparator { } + MenuItem { text: "&Quit" } + } + + Rectangle { + color: "red" + width: 100 + height: 20 + } + + Menu { + title: "&Edit" + MenuItem { text: "&Cut" } + MenuItem { text: "&Copy" } + MenuItem { text: "&Paste" } + } + + MenuBarItem { + menu: Menu { + title: "&Help" + MenuItem { text: "&About" } + } + } + } + + Rectangle { + id: contents + anchors.fill: parent + color: "green" + } +} diff --git a/tests/auto/quickcontrols/qquickmenubar/data/nodelegate.qml b/tests/auto/quickcontrols/qquickmenubar/data/nodelegate.qml new file mode 100644 index 0000000000..552aea8400 --- /dev/null +++ b/tests/auto/quickcontrols/qquickmenubar/data/nodelegate.qml @@ -0,0 +1,40 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + id: root + + width: 400 + height: 400 + visible: true + + menuBar: MenuBar { + delegate: null + Menu { + id: fileMenu + title: "&File" + MenuItem { text: "&Open..." } + MenuItem { text: "&Save" } + MenuItem { text: "Save &As..." } + MenuSeparator { } + MenuItem { text: "&Quit" } + } + + Menu { + title: "&Edit" + MenuItem { text: "&Cut" } + MenuItem { text: "&Copy" } + MenuItem { text: "&Paste" } + } + + MenuBarItem { + menu: Menu { + title: "&Help" + MenuItem { text: "&About" } + } + } + } +} diff --git a/tests/auto/quickcontrols/qquickmenubar/data/showandhide.qml b/tests/auto/quickcontrols/qquickmenubar/data/showandhide.qml new file mode 100644 index 0000000000..fe887c3f99 --- /dev/null +++ b/tests/auto/quickcontrols/qquickmenubar/data/showandhide.qml @@ -0,0 +1,40 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + id: root + + width: 400 + height: 400 + visible: true + + menuBar: MenuBar { + Menu { + title: "Menu1" + Action { text: qsTr("Action") } + } + + Menu { + title: "Menu2" + Action { text: qsTr("Action") } + } + + MenuBarItem { + menu: Menu { + title: "Menu3" + Action { text: qsTr("Action") } + } + } + + MenuBarItem { + visible: false + menu: Menu { + title: "Menu4" + Action { text: qsTr("Action") } + } + } + } +} diff --git a/tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp b/tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp index 63dec4dc6a..17960ccde5 100644 --- a/tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp +++ b/tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp @@ -11,7 +11,9 @@ #include <QtQuickTemplates2/private/qquickapplicationwindow_p.h> #include <QtQuickTemplates2/private/qquickbutton_p.h> #include <QtQuickTemplates2/private/qquickmenu_p.h> +#include <QtQuickTemplates2/private/qquickmenu_p_p.h> #include <QtQuickTemplates2/private/qquickmenubar_p.h> +#include <QtQuickTemplates2/private/qquickmenubar_p_p.h> #include <QtQuickTemplates2/private/qquickmenubaritem_p.h> #include <QtQuickTemplates2/private/qquickmenuitem_p.h> #include <QtQuickControlsTestUtils/private/controlstestutils_p.h> @@ -28,19 +30,52 @@ public: tst_qquickmenubar(); private slots: + void init() override; void delegate(); + void mouse_data(); void mouse(); void touch(); + void keys_data(); void keys(); void mnemonics(); void altNavigation(); + void addRemove_data(); void addRemove(); + void addRemoveInlineMenus_data(); + void addRemoveInlineMenus(); + void addRemoveMenuFromQml_data(); + void addRemoveMenuFromQml(); + void insert_data(); + void insert(); + void showAndHideMenuBarItems_data(); + void showAndHideMenuBarItems(); + void removeMenuThatIsOpen(); + void addRemoveExistingMenus_data(); + void addRemoveExistingMenus(); + void checkHighlightWhenMenuDismissed_data(); void checkHighlightWhenMenuDismissed(); + void hoverAfterClosingWithEscape_data(); void hoverAfterClosingWithEscape(); + void AA_DontUseNativeMenuBar(); + void containerItems_data(); + void containerItems(); + void mixedContainerItems_data(); + void mixedContainerItems(); + void applicationWindow_data(); + void applicationWindow(); + void menubarAsHeader_data(); + void menubarAsHeader(); + void menuPosition(); + void changeDelegate_data(); + void changeDelegate(); + void invalidDelegate_data(); + void invalidDelegate(); + void panMenuBar_data(); + void panMenuBar(); private: - static bool hasWindowActivation(); - + bool nativeMenuBarSupported = false; + bool popupWindowsSupported = false; QScopedPointer<QPointingDevice> touchScreen = QScopedPointer<QPointingDevice>(QTest::createTouchDevice()); }; @@ -53,11 +88,17 @@ tst_qquickmenubar::tst_qquickmenubar() : QQmlDataTest(QT_QMLTEST_DATADIR) { qputenv("QML_NO_TOUCH_COMPRESSION", "1"); + QQuickMenuBar mb; + nativeMenuBarSupported = QQuickMenuBarPrivate::get(&mb)->useNativeMenuBar(); + popupWindowsSupported = QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::Capability::MultipleWindows); } -bool tst_qquickmenubar::hasWindowActivation() +void tst_qquickmenubar::init() { - return (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)); + // Enable non-native menubars by default. + // Note that some tests will set this property to 'true', which + // is why we need to set it back to 'false' here. + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, false); } void tst_qquickmenubar::delegate() @@ -73,16 +114,29 @@ void tst_qquickmenubar::delegate() QVERIFY(item); } +void tst_qquickmenubar::mouse_data() +{ + QTest::addColumn<bool>("usePopupWindow"); + QTest::newRow("in-scene popup") << false; + // Uncomment when popup windows work 100% + // if (popupWindowsSupported) + // QTest::newRow("popup window") << true; +} + void tst_qquickmenubar::mouse() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + QFETCH(bool, usePopupWindow); + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); + QCoreApplication::setAttribute(Qt::AA_DontUsePopupWindows, !usePopupWindow); + + SKIP_IF_NO_WINDOW_ACTIVATION if ((QGuiApplication::platformName() == QLatin1String("offscreen")) || (QGuiApplication::platformName() == QLatin1String("minimal"))) QSKIP("Mouse highlight not functional on offscreen/minimal platforms"); - QQmlApplicationEngine engine(testFileUrl("menubar.qml")); + QQmlApplicationEngine engine(testFileUrl("menubaritems.qml")); QScopedPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0))); QVERIFY(window); @@ -91,7 +145,7 @@ void tst_qquickmenubar::mouse() moveMouseAway(window.data()); QVERIFY(QTest::qWaitForWindowActive(window.data())); - QQuickMenuBar *menuBar = window->property("header").value<QQuickMenuBar *>(); + QQuickMenuBar *menuBar = window->property("menuBar").value<QQuickMenuBar *>(); QVERIFY(menuBar); QQuickMenu *fileMenuBarMenu = menuBar->menuAt(0); @@ -260,6 +314,8 @@ void tst_qquickmenubar::mouse() // - It's what happens with e.g. overflow menus on Android. void tst_qquickmenubar::touch() { + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); QQuickControlsApplicationHelper helper(this, QLatin1String("touch.qml")); QVERIFY2(helper.ready, helper.failureMessage()); centerOnScreen(helper.window); @@ -285,12 +341,25 @@ void tst_qquickmenubar::touch() QTRY_VERIFY(fileMenuBarMenu->isOpened()); } +void tst_qquickmenubar::keys_data() +{ + QTest::addColumn<bool>("usePopupWindow"); + QTest::newRow("in-scene popup") << false; + // Uncomment when popup windows work 100% + // if (popupWindowsSupported) + // QTest::newRow("popup window") << true; +} + void tst_qquickmenubar::keys() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + QFETCH(bool, usePopupWindow); + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); + QCoreApplication::setAttribute(Qt::AA_DontUsePopupWindows, !usePopupWindow); - QQmlApplicationEngine engine(testFileUrl("menubar.qml")); + SKIP_IF_NO_WINDOW_ACTIVATION + + QQmlApplicationEngine engine(testFileUrl("menubaritems.qml")); QScopedPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0))); QVERIFY(window); @@ -299,7 +368,7 @@ void tst_qquickmenubar::keys() moveMouseAway(window.data()); QVERIFY(QTest::qWaitForWindowActive(window.data())); - QQuickMenuBar *menuBar = window->property("header").value<QQuickMenuBar *>(); + QQuickMenuBar *menuBar = window->property("menuBar").value<QQuickMenuBar *>(); QVERIFY(menuBar); QQuickMenu *fileMenuBarMenu = menuBar->menuAt(0); @@ -479,14 +548,16 @@ void tst_qquickmenubar::keys() void tst_qquickmenubar::mnemonics() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); + + SKIP_IF_NO_WINDOW_ACTIVATION #if defined(Q_OS_MACOS) or defined(Q_OS_WEBOS) QSKIP("Mnemonics are not used on this platform"); #endif - QQmlApplicationEngine engine(testFileUrl("menubar.qml")); + QQmlApplicationEngine engine(testFileUrl("menubaritems.qml")); QScopedPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0))); QVERIFY(window); @@ -497,7 +568,7 @@ void tst_qquickmenubar::mnemonics() MnemonicKeySimulator keySim(window.data()); - QQuickMenuBar *menuBar = window->property("header").value<QQuickMenuBar *>(); + QQuickMenuBar *menuBar = window->property("menuBar").value<QQuickMenuBar *>(); QVERIFY(menuBar); QQuickMenu *fileMenuBarMenu = menuBar->menuAt(0); @@ -632,10 +703,12 @@ void tst_qquickmenubar::mnemonics() void tst_qquickmenubar::altNavigation() { + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); if (!QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::MenuBarFocusOnAltPressRelease).toBool()) QSKIP("Menu doesn't get focus via Alt press&release on this platform"); - QQmlApplicationEngine engine(testFileUrl("menubar.qml")); + QQmlApplicationEngine engine(testFileUrl("menubaritems.qml")); QScopedPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0))); QVERIFY(window); @@ -644,7 +717,7 @@ void tst_qquickmenubar::altNavigation() moveMouseAway(window.data()); QVERIFY(QTest::qWaitForWindowActive(window.data())); - QQuickMenuBar *menuBar = window->property("header").value<QQuickMenuBar *>(); + QQuickMenuBar *menuBar = window->property("menuBar").value<QQuickMenuBar *>(); QVERIFY(menuBar); QQuickMenu *fileMenuBarMenu = menuBar->menuAt(0); @@ -669,12 +742,30 @@ void tst_qquickmenubar::altNavigation() QVERIFY(editMenuBarMenu->hasActiveFocus()); } +void tst_qquickmenubar::addRemove_data() +{ + QTest::addColumn<QString>("testUrl"); + QTest::addColumn<bool>("native"); + QTest::newRow("menuitems, not native") << QStringLiteral("empty.qml") << false; + if (nativeMenuBarSupported) + QTest::newRow("menuitems, native") << QStringLiteral("empty.qml") << true; +} + void tst_qquickmenubar::addRemove() { - QQmlApplicationEngine engine(testFileUrl("empty.qml")); + QFETCH(QString, testUrl); + QFETCH(bool, native); - QScopedPointer<QQuickMenuBar> menuBar(qobject_cast<QQuickMenuBar *>(engine.rootObjects().value(0))); + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, !native); + QQmlApplicationEngine engine; + engine.load(testFileUrl(testUrl)); + + QQuickMenuBar *menuBar = qobject_cast<QQuickMenuBar *>(engine.rootObjects().value(0)); QVERIFY(menuBar); + QQuickMenuBarPrivate *menuBarPrivate = QQuickMenuBarPrivate::get(menuBar); + QCOMPARE(menuBarPrivate->useNativeMenuBar(), native); + if (native) + QVERIFY(menuBarPrivate->nativeHandle()); QQmlComponent component(&engine); component.setData("import QtQuick.Controls; Menu { }", QUrl()); @@ -690,7 +781,7 @@ void tst_qquickmenubar::addRemove() QCOMPARE(menuBarItem1->menu(), menu1.data()); QCOMPARE(menuBar->itemAt(0), menuBarItem1.data()); - QScopedPointer<QQuickMenu> menu2(qobject_cast<QQuickMenu *>(component.create())); + QPointer<QQuickMenu> menu2(qobject_cast<QQuickMenu *>(component.create())); QVERIFY(!menu2.isNull()); menuBar->insertMenu(0, menu2.data()); QCOMPARE(menuBar->count(), 2); @@ -703,15 +794,26 @@ void tst_qquickmenubar::addRemove() QCOMPARE(menuBar->itemAt(0), menuBarItem2.data()); QCOMPARE(menuBar->itemAt(1), menuBarItem1.data()); - // takeMenu(int) does not destroy the menu, but does destroy the respective item in the menubar + // takeMenu(int) does not explicitly destroy the menu, but leave + // this to the garbage collector. The MenuBarItem, OTOH, is currently + // being destroyed from c++, but this might change in the future. QCOMPARE(menuBar->takeMenu(1), menu1.data()); QCOMPARE(menuBar->count(), 1); QVERIFY(!menuBar->menuAt(1)); QVERIFY(!menuBar->itemAt(1)); - QCoreApplication::sendPostedEvents(menu1.data(), QEvent::DeferredDelete); + QTRY_VERIFY(menuBarItem1.isNull()); + QVERIFY(!menu1.isNull()); + gc(engine); QVERIFY(!menu1.isNull()); - QCoreApplication::sendPostedEvents(menuBarItem1, QEvent::DeferredDelete); - QVERIFY(menuBarItem1.isNull()); + + // check that it's safe to call takeMenu(int) with + // an index that is out of range. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*out of range")); + QCOMPARE(menuBar->takeMenu(-1), nullptr); + QCOMPARE(menuBar->count(), 1); + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*out of range")); + QCOMPARE(menuBar->takeMenu(10), nullptr); + QCOMPARE(menuBar->count(), 1); // addMenu(Menu) re-creates the respective item in the menubar menuBar->addMenu(menu1.data()); @@ -719,18 +821,299 @@ void tst_qquickmenubar::addRemove() menuBarItem1 = qobject_cast<QQuickMenuBarItem *>(menuBar->itemAt(1)); QVERIFY(!menuBarItem1.isNull()); - // removeMenu(Menu) destroys both the menu and the respective item in the menubar + // removeMenu(menu) does not explicitly destroy the menu, but leave + // this to the garbage collector. The MenuBarItem, OTOH, is currently + // being destroyed from c++, but this might change in the future. menuBar->removeMenu(menu1.data()); QCOMPARE(menuBar->count(), 1); QVERIFY(!menuBar->itemAt(1)); - QCoreApplication::sendPostedEvents(menu1.data(), QEvent::DeferredDelete); - QVERIFY(menu1.isNull()); - QCoreApplication::sendPostedEvents(menuBarItem1, QEvent::DeferredDelete); - QVERIFY(menuBarItem1.isNull()); + QTRY_VERIFY(menuBarItem1.isNull()); + QVERIFY(!menu1.isNull()); + gc(engine); + QVERIFY(!menu1.isNull()); +} + +void tst_qquickmenubar::addRemoveInlineMenus_data() +{ + QTest::addColumn<bool>("native"); + QTest::newRow("not native") << false; + if (nativeMenuBarSupported) + QTest::newRow("native") << true; +} + +void tst_qquickmenubar::addRemoveInlineMenus() +{ + // Check that it's safe to remove a menu from the menubar, that + // is an inline child from QML (fileMenu). Since it's owned by + // JavaScript, it should be deleted by the gc when appropriate, and + // not upon a call to removeMenu. + QFETCH(bool, native); + + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, !native); + QQmlApplicationEngine engine; + engine.load(testFileUrl("menus.qml")); + + auto window = qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0)); + QVERIFY(window); + auto menuBar = window->property("menuBar").value<QQuickMenuBar *>(); + QVERIFY(menuBar); + + QPointer<QQuickMenu> fileMenu = window->property("fileMenu").value<QQuickMenu *>(); + QVERIFY(fileMenu); + QCOMPARE(menuBar->menuAt(0), fileMenu); + + QPointer<QQuickItem> menuBarItem = menuBar->itemAt(0); + QVERIFY(menuBarItem); + + menuBar->removeMenu(fileMenu); + QVERIFY(menuBar->menuAt(0) != fileMenu); + QTRY_VERIFY(!menuBarItem); + QVERIFY(fileMenu); + gc(engine); + QVERIFY(fileMenu); + + // Add it back again, but to the end. This should also be fine, even + // if it no longer matches the initial order in the QML file. + menuBar->addMenu(fileMenu); + QVERIFY(fileMenu); + QCOMPARE(menuBar->menuAt(menuBar->count() - 1), fileMenu); +} + +void tst_qquickmenubar::addRemoveMenuFromQml_data() +{ + QTest::addColumn<bool>("native"); + QTest::newRow("not native") << false; + if (nativeMenuBarSupported) + QTest::newRow("native") << true; +} + +void tst_qquickmenubar::addRemoveMenuFromQml() +{ + // Create a menu dynamically from QML, and add it to + // the menubar. Remove it again. Check that the + // garbage collector will then destruct it. + QFETCH(bool, native); + + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, !native); + QQmlApplicationEngine engine; + engine.load(testFileUrl("menus.qml")); + + auto window = qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0)); + QVERIFY(window); + auto menuBar = window->property("menuBar").value<QQuickMenuBar *>(); + QVERIFY(menuBar); + + const int initialMenuCount = menuBar->count(); + QVERIFY(initialMenuCount > 0); + + QMetaObject::invokeMethod(window, "addTestMenu"); + + QCOMPARE(menuBar->count(), initialMenuCount + 1); + + // The "extra" menu should have been added to + // the end of the menu bar. Verify this. + QQuickItem *item = menuBar->itemAt(menuBar->count() - 1); + QPointer<QQuickMenuBarItem> menuBarItem = qobject_cast<QQuickMenuBarItem *>(item); + QVERIFY(menuBarItem); + QPointer<QQuickMenu> menu = menuBar->menuAt(menuBar->count() - 1); + QVERIFY(menu); + QCOMPARE(menu->title(), "extra"); + QCOMPARE(menuBarItem->menu(), menu); + + // Remove the menu again. Since we have no other references to + // it from QML, it should be collected by the gc. + menuBar->removeMenu(menu); + QCOMPARE(menuBar->count(), initialMenuCount); + QTRY_VERIFY(!menuBarItem); + QVERIFY(menu); + gc(engine); + QVERIFY(!menu); +} + +void tst_qquickmenubar::insert_data() +{ + QTest::addColumn<bool>("native"); + QTest::newRow("not native") << false; + if (nativeMenuBarSupported) + QTest::newRow("native") << true; +} + +void tst_qquickmenubar::insert() +{ + QFETCH(bool, native); + + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, !native); + QQmlApplicationEngine engine; + engine.load(testFileUrl("menus.qml")); + + QScopedPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0))); + QVERIFY(window); + QQuickMenuBar *menuBar = window->property("menuBar").value<QQuickMenuBar *>(); + QVERIFY(menuBar); + + const int initialMenuCount = menuBar->count(); + QVERIFY(initialMenuCount > 0); + + QQmlComponent component(&engine); + component.setData("import QtQuick.Controls; Menu { }", QUrl()); + + QPointer<QQuickMenu> menu1(qobject_cast<QQuickMenu *>(component.create())); + QVERIFY(!menu1.isNull()); + menuBar->insertMenu(0, menu1.data()); + QCOMPARE(menuBar->count(), initialMenuCount + 1); + QCOMPARE(menuBar->menuAt(0), menu1.data()); + + QPointer<QQuickMenu> menu2(qobject_cast<QQuickMenu *>(component.create())); + QVERIFY(!menu2.isNull()); + menuBar->insertMenu(2, menu2.data()); + QCOMPARE(menuBar->count(), initialMenuCount + 2); + QCOMPARE(menuBar->menuAt(2), menu2.data()); +} + +void tst_qquickmenubar::showAndHideMenuBarItems_data() +{ + QTest::addColumn<bool>("native"); + QTest::newRow("not native") << false; + if (nativeMenuBarSupported) + QTest::newRow("native") << true; +} + +void tst_qquickmenubar::showAndHideMenuBarItems() +{ + // Check that you can toggle MenuBarItem.visible to show and hide menus in the + // menu bar. Note that this is not the same as setting Menu.visible, which will + // instead open or close the menus. + QFETCH(bool, native); + + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, !native); + QQmlApplicationEngine engine; + engine.load(testFileUrl("showandhide.qml")); + + QScopedPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0))); + QVERIFY(window); + QQuickMenuBar *menuBar = window->property("menuBar").value<QQuickMenuBar *>(); + QVERIFY(menuBar); + QCOMPARE(menuBar->count(), 4); + + auto menuBarItem0 = qobject_cast<QQuickMenuBarItem *>(menuBar->itemAt(0)); + auto menuBarItem1 = qobject_cast<QQuickMenuBarItem *>(menuBar->itemAt(1)); + auto menuBarItem2 = qobject_cast<QQuickMenuBarItem *>(menuBar->itemAt(2)); + auto menuBarItem3 = qobject_cast<QQuickMenuBarItem *>(menuBar->itemAt(3)); + + // Initially, the three first MenuBarItems are visible, but the 4th is hidden + QVERIFY(menuBarItem0->isVisible()); + QVERIFY(menuBarItem1->isVisible()); + QVERIFY(menuBarItem2->isVisible()); + QVERIFY(!menuBarItem3->isVisible()); + + // Native and visible QQuickMenus should be backed by + // QPlatformMenus. Otherwise the handle should be nullptr. + QCOMPARE(bool(QQuickMenuPrivate::get(menuBarItem0->menu())->maybeNativeHandle()), native); + QCOMPARE(bool(QQuickMenuPrivate::get(menuBarItem1->menu())->maybeNativeHandle()), native); + QCOMPARE(bool(QQuickMenuPrivate::get(menuBarItem2->menu())->maybeNativeHandle()), native); + QVERIFY(!QQuickMenuPrivate::get(menuBarItem3->menu())->maybeNativeHandle()); + + // Make the hidden MenuBarItem visible + menuBarItem3->setVisible(true); + QCOMPARE(bool(QQuickMenuPrivate::get(menuBarItem3->menu())->maybeNativeHandle()), native); + QCOMPARE(menuBar->count(), 4); + // Hide it again + menuBarItem3->setVisible(false); + QVERIFY(!QQuickMenuPrivate::get(menuBarItem3->menu())->maybeNativeHandle()); + QCOMPARE(menuBar->count(), 4); + + // Toggle the visibility of a MenuBarItem created from the + // delegate, which is also initially visible. + menuBarItem0->setVisible(false); + QVERIFY(!QQuickMenuPrivate::get(menuBarItem0->menu())->maybeNativeHandle()); + QCOMPARE(menuBar->count(), 4); + // Hide it again + menuBarItem0->setVisible(true); + QCOMPARE(bool(QQuickMenuPrivate::get(menuBarItem0->menu())->maybeNativeHandle()), native); + QCOMPARE(menuBar->count(), 4); +} + +void tst_qquickmenubar::removeMenuThatIsOpen() +{ + // Check that if we remove a menu that is open, it ends + // up being hidden / closed. This is mostly important for + // non-native menubars. + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); + QQmlApplicationEngine engine; + engine.load(testFileUrl("menus.qml")); + + QScopedPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0))); + QVERIFY(window); + QQuickMenuBar *menuBar = window->property("menuBar").value<QQuickMenuBar *>(); + QVERIFY(menuBar); + + QQuickMenu *fileMenu = window->property("fileMenu").value<QQuickMenu *>(); + QVERIFY(fileMenu); + fileMenu->open(); + QVERIFY(fileMenu->isVisible()); + menuBar->removeMenu(fileMenu); + QVERIFY(fileMenu); + QTRY_VERIFY(!fileMenu->isVisible()); +} + +void tst_qquickmenubar::addRemoveExistingMenus_data() +{ + QTest::addColumn<bool>("native"); + QTest::addColumn<bool>("usePopupWindow"); + QTest::newRow("non-native, in-scene") << false << false; + if (nativeMenuBarSupported) + QTest::newRow("native, native") << true << true; + // Uncomment when popup windows work 100% + // if (popupWindowsSupported) + // QTest::newRow("non-native, popup window") << false << true; +} + +void tst_qquickmenubar::addRemoveExistingMenus() +{ + // Check that you get warnings if trying to add menus that + // are already in the menubar, or remove menus that are not. + QFETCH(bool, native); + QFETCH(bool, usePopupWindow); + + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, !native); + QCoreApplication::setAttribute(Qt::AA_DontUsePopupWindows, !usePopupWindow); + QQmlApplicationEngine engine; + engine.load(testFileUrl("menus.qml")); + + auto window = qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0)); + QVERIFY(window); + auto menuBar = window->property("menuBar").value<QQuickMenuBar *>(); + QVERIFY(menuBar); + + QPointer<QQuickMenu> fileMenu = window->property("fileMenu").value<QQuickMenu *>(); + QVERIFY(fileMenu); + QCOMPARE(menuBar->menuAt(0), fileMenu); + + QTest::ignoreMessage(QtWarningMsg, QRegularExpression("cannot add menu.*")); + menuBar->addMenu(fileMenu); + QTest::ignoreMessage(QtWarningMsg, QRegularExpression("cannot insert menu.*")); + menuBar->insertMenu(0, fileMenu); + menuBar->removeMenu(fileMenu); + QTest::ignoreMessage(QtWarningMsg, QRegularExpression("cannot remove menu.*")); + menuBar->removeMenu(fileMenu); +} + +void tst_qquickmenubar::checkHighlightWhenMenuDismissed_data() +{ + QTest::addColumn<bool>("usePopupWindow"); + QTest::newRow("in-scene popup") << false; + // Uncomment when popup windows work 100% + // if (popupWindowsSupported) + // QTest::newRow("popup window") << true; } void tst_qquickmenubar::checkHighlightWhenMenuDismissed() { + QFETCH(bool, usePopupWindow); + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); + QCoreApplication::setAttribute(Qt::AA_DontUsePopupWindows, !usePopupWindow); if ((QGuiApplication::platformName() == QLatin1String("offscreen")) || (QGuiApplication::platformName() == QLatin1String("minimal"))) QSKIP("Mouse highlight not functional on offscreen/minimal platforms"); @@ -787,8 +1170,21 @@ void tst_qquickmenubar::checkHighlightWhenMenuDismissed() QVERIFY(!dynamicMenuBarItem->isHighlighted()); } +void tst_qquickmenubar::hoverAfterClosingWithEscape_data() +{ + QTest::addColumn<bool>("usePopupWindow"); + QTest::newRow("in-scene popup") << false; + // Uncomment when popup windows work 100% + // if (popupWindowsSupported) + // QTest::newRow("popup window") << true; +} + void tst_qquickmenubar::hoverAfterClosingWithEscape() { + QFETCH(bool, usePopupWindow); + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); + QCoreApplication::setAttribute(Qt::AA_DontUsePopupWindows, !usePopupWindow); if ((QGuiApplication::platformName() == QLatin1String("offscreen")) || (QGuiApplication::platformName() == QLatin1String("minimal"))) QSKIP("Mouse highlight not functional on offscreen/minimal platforms"); @@ -821,6 +1217,530 @@ void tst_qquickmenubar::hoverAfterClosingWithEscape() QVERIFY(!secondMenu->isVisible()); } +void tst_qquickmenubar::AA_DontUseNativeMenuBar() +{ + // Check that we end up with a non-native menu bar when AA_DontUseNativeMenuBar is set. + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); + QQmlApplicationEngine engine; + engine.load(testFileUrl("menus.qml")); + + QScopedPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0))); + QVERIFY(window); + QQuickMenuBar *menuBar = window->property("menuBar").value<QQuickMenuBar *>(); + QVERIFY(menuBar); + auto menuBarPrivate = QQuickMenuBarPrivate::get(menuBar); + QQuickItem *contents = window->property("contents").value<QQuickItem *>(); + QVERIFY(contents); + + QVERIFY(!menuBarPrivate->nativeHandle()); + QVERIFY(menuBar->isVisible()); + QVERIFY(menuBar->count() > 0); + QVERIFY(menuBar->height() > 0); + QCOMPARE(contents->height(), window->height() - menuBar->height()); + + // If the menu bar is not native, the menus should not be native either. + // The main reason for this limitation is that a native menu typically + // run in separate native event loop which will not forward mouse events + // to Qt. And this is needed for a non-native menu bar to work (e.g to + // support hovering over the menu bar items to open and close menus). + const auto firstMenu = menuBar->menuAt(0); + QVERIFY(firstMenu); + QVERIFY(!QQuickMenuPrivate::get(firstMenu)->maybeNativeHandle()); +} + +void tst_qquickmenubar::containerItems_data() +{ + QTest::addColumn<QString>("testUrl"); + QTest::addColumn<bool>("native"); + QTest::newRow("menuitems, not native") << QStringLiteral("menubaritems.qml") << false; + QTest::newRow("menus, not native") << QStringLiteral("menus.qml") << false; + if (nativeMenuBarSupported) { + QTest::newRow("menuitems, native") << QStringLiteral("menubaritems.qml") << true; + QTest::newRow("menus, native") << QStringLiteral("menus.qml") << true; + } +} + +void tst_qquickmenubar::containerItems() +{ + // Check that the MenuBar ends up containing a MenuBarItem + // for each Menu added. This should be the case regardless of + // if the MenuBar is native or not. There are several ways + // of accessing those MenuBarItems and menus in the MenuBar + // API, so check that all end up in sync. + QFETCH(QString, testUrl); + QFETCH(bool, native); + + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, !native); + QQmlApplicationEngine engine; + engine.load(testFileUrl(testUrl)); + + QScopedPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0))); + QVERIFY(window); + QQuickMenuBar *menuBar = window->property("menuBar").value<QQuickMenuBar *>(); + QVERIFY(menuBar); + auto *menuBarPrivate = QQuickMenuBarPrivate::get(menuBar); + QCOMPARE(menuBarPrivate->useNativeMenuBar(), native); + + QCOMPARE(menuBar->count(), 4); + for (int i = 0; i < menuBar->count(); ++i) { + QQuickMenu *menu = menuBar->menuAt(i); + QVERIFY(menu); + + // Test the itemAt() API + QQuickItem *item = menuBar->itemAt(i); + QVERIFY(item); + auto menuBarItem = qobject_cast<QQuickMenuBarItem *>(item); + QVERIFY(menuBarItem); + QCOMPARE(menuBarItem->menu(), menu); + + // Test the "contentData" list property API + auto cd = menuBarPrivate->contentData(); + QCOMPARE(cd.count(&cd), menuBar->count()); + auto cdItem = static_cast<QQuickItem *>(cd.at(&cd, i)); + QVERIFY(cdItem); + auto cdMenuBarItem = qobject_cast<QQuickMenuBarItem *>(cdItem); + QVERIFY(cdMenuBarItem); + QCOMPARE(cdMenuBarItem->menu(), menu); + + // Test the "menus" list property API + auto menus = QQuickMenuBarPrivate::get(menuBar)->menus(); + QCOMPARE(menus.count(&menus), menuBar->count()); + auto menusMenu = menus.at(&menus, i); + QVERIFY(menusMenu); + QCOMPARE(menusMenu, menu); + } +} + +void tst_qquickmenubar::mixedContainerItems_data() +{ + QTest::addColumn<bool>("native"); + QTest::newRow("not native") << false; + if (nativeMenuBarSupported) + QTest::newRow("native") << true; +} + +void tst_qquickmenubar::mixedContainerItems() +{ + // The application is allowed to add items other + // than MenuBarItems and Menus as children. But those + // should just be ignored by the MenuBar (and the Container). + QFETCH(bool, native); + + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, !native); + QQmlApplicationEngine engine; + engine.load(testFileUrl("mixed.qml")); + + QScopedPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0))); + QVERIFY(window); + QQuickMenuBar *menuBar = window->property("menuBar").value<QQuickMenuBar *>(); + QVERIFY(menuBar); + + // The menubar has four children, but only three of them are + // Menus and MenuBarItems. So we should therefore only end up + // with three menus in the MenuBar, and three items in the Container. + QCOMPARE(menuBar->count(), 3); + for (int i = 0; i < 3; ++i) { + auto item = menuBar->itemAt(i); + QVERIFY(item); + auto menuBarItem = qobject_cast<QQuickMenuBarItem *>(item); + QVERIFY(menuBarItem); + QCOMPARE(menuBarItem->menu(), menuBar->menuAt(i)); + } + + // Try to add an unsupported item dynamically. It should + // have no impact on the MenuBar/Container API. + QQmlComponent component(&engine); + component.setData("import QtQuick; Item { }", QUrl()); + QPointer<QQuickItem> plainItem(qobject_cast<QQuickItem *>(component.create())); + QVERIFY(plainItem); + + menuBar->addItem(plainItem); + QCOMPARE(menuBar->count(), 3); + for (int i = 0; i < 3; ++i) { + auto item = menuBar->itemAt(i); + QVERIFY(item); + auto menuBarItem = qobject_cast<QQuickMenuBarItem *>(item); + QVERIFY(menuBarItem); + QCOMPARE(menuBarItem->menu(), menuBar->menuAt(i)); + } + + // Remove it again. It should have no impact on + // the MenuBar/Container API. + menuBar->removeItem(plainItem); + QCOMPARE(menuBar->count(), 3); + for (int i = 0; i < 3; ++i) { + auto item = menuBar->itemAt(i); + QVERIFY(item); + auto menuBarItem = qobject_cast<QQuickMenuBarItem *>(item); + QVERIFY(menuBarItem); + QCOMPARE(menuBarItem->menu(), menuBar->menuAt(i)); + } +} + +void tst_qquickmenubar::applicationWindow_data() +{ + QTest::addColumn<bool>("initiallyNative"); + QTest::addColumn<bool>("initiallyVisible"); + QTest::newRow("initially not native, visible") << false << true; + QTest::newRow("initially not native, hidden") << false << false; + if (nativeMenuBarSupported) { + QTest::newRow("initially native, visible") << true << true; + QTest::newRow("initially native, hidden") << true << false; + } +} + +void tst_qquickmenubar::applicationWindow() +{ + // Check that ApplicationWindow adds or removes the non-native + // menubar in response to toggling Qt::AA_DontUseNativeMenuBar and + // MenuBar.visible. + QFETCH(bool, initiallyNative); + QFETCH(bool, initiallyVisible); + + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, !initiallyNative); + QQmlApplicationEngine engine; + engine.setInitialProperties({{ "visible", initiallyVisible }}); + engine.load(testFileUrl("menus.qml")); + + QPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0))); + QVERIFY(window); + QQuickMenuBar *menuBar = window->property("menuBar").value<QQuickMenuBar *>(); + QVERIFY(menuBar); + auto menuBarPrivate = QQuickMenuBarPrivate::get(menuBar); + QQuickItem *contents = window->property("contents").value<QQuickItem *>(); + QVERIFY(contents); + + for (const bool visible : {initiallyVisible, !initiallyVisible, initiallyVisible}) { + menuBar->setVisible(visible); + + const bool nativeMenuBarVisible = bool(menuBarPrivate->nativeHandle()); + QCOMPARE(nativeMenuBarVisible, initiallyNative && visible); + + if (!visible) { + QVERIFY(!menuBar->isVisible()); + QVERIFY(!nativeMenuBarVisible); + QCOMPARE(contents->height(), window->height()); + } else if (nativeMenuBarVisible) { + QVERIFY(menuBar->isVisible()); + QCOMPARE(contents->height(), window->height()); + } else { + QVERIFY(menuBar->isVisible()); + QVERIFY(menuBar->height() > 0); + QCOMPARE(contents->height(), window->height() - menuBar->height()); + } + } +} + +void tst_qquickmenubar::menubarAsHeader_data() +{ + QTest::addColumn<bool>("native"); + QTest::newRow("not native") << false; + if (nativeMenuBarSupported) + QTest::newRow("native") << true; +} + +void tst_qquickmenubar::menubarAsHeader() +{ + // ApplicationWindow.menuBar was added in Qt 5.10. Before that + // the menuBar was supposed to be assigned to ApplicationWindow.header. + // For backwards compatibility, check that you can still do that. + QFETCH(bool, native); + + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, !native); + QQmlApplicationEngine engine; + engine.load(testFileUrl("menubarAsHeader.qml")); + + QScopedPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0))); + QVERIFY(window); + QQuickMenuBar *menuBar = window->property("header").value<QQuickMenuBar *>(); + QVERIFY(menuBar); + auto menuBarPrivate = QQuickMenuBarPrivate::get(menuBar); + QQuickItem *contents = window->property("contents").value<QQuickItem *>(); + QVERIFY(contents); + QVERIFY(menuBar->count() > 0); + QCOMPARE(menuBarPrivate->nativeHandle() != nullptr, native); + + if (menuBarPrivate->nativeHandle()) { + // Using native menubar + QCOMPARE(contents->height(), window->height()); + } else { + // Not using native menubar + QCOMPARE(contents->height(), window->height() - menuBar->height()); + } +} + +void tst_qquickmenubar::menuPosition() +{ + // A Menu.qml will typically have a background with a drop-shadow. And to make + // room for this shadow, the Menu itself is made bigger by using Control.insets. + // This will make room for both the background and its shadow. + // To make sure that the corner of the background (rather than the shadow) ends up + // at the requested menu position, the effective position of the menu will be + // shifted a bit up and left. This test will therefore check that the corner of the + // background ends up that the requested position. + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, true); + // Use in-scene popups for this test, since we have no guarantee where a window + // manager might end up placing a menu. + QCoreApplication::setAttribute(Qt::AA_DontUsePopupWindows, true); + QQmlApplicationEngine engine; + engine.load(testFileUrl("menus.qml")); + + QScopedPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0))); + QVERIFY(window); + QQuickMenuBar *menuBar = window->property("menuBar").value<QQuickMenuBar *>(); + QVERIFY(menuBar); + + QPointF requestedPos{50, 50}; + + QQuickMenu *editMenu = menuBar->menuAt(1); + QVERIFY(editMenu); + editMenu->setX(requestedPos.x()); + editMenu->setY(requestedPos.y()); + editMenu->setVisible(true); + QTRY_VERIFY(editMenu->isOpened()); + QCOMPARE(editMenu->x(), requestedPos.x()); + QCOMPARE(editMenu->y(), requestedPos.y()); + + QQuickItem *background = editMenu->background(); + QVERIFY(background); + + QPointF bgPos = background->mapToItem(editMenu->parentItem(), {0, 0}); + QCOMPARE(bgPos, requestedPos); +} + +void tst_qquickmenubar::changeDelegate_data() +{ + QTest::addColumn<bool>("native"); + QTest::newRow("not native") << false; + if (nativeMenuBarSupported) + QTest::newRow("native") << true; +} + +void tst_qquickmenubar::changeDelegate() +{ + // Check that you can change the delegate, and that this + // will produce new delegate items, except for the MenuBarItem + // that is created inline in the QML code, and hence doesn't use the delegate. + QFETCH(bool, native); + + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, !native); + QQmlApplicationEngine engine; + engine.load(testFileUrl("nodelegate.qml")); + + QScopedPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0))); + QVERIFY(window); + QQuickMenuBar *menuBar = window->property("menuBar").value<QQuickMenuBar *>(); + QVERIFY(menuBar); + QCOMPARE(menuBar->count(), 3); + + QQmlComponent delegate1(&engine); + delegate1.setData("import QtQuick.Controls; MenuBarItem {}", QUrl()); + menuBar->setDelegate(&delegate1); + + auto menuBarItem0_v1 = qobject_cast<QQuickMenuBarItem *>(menuBar->itemAt(0)); + auto menuBarItem1_v1 = qobject_cast<QQuickMenuBarItem *>(menuBar->itemAt(1)); + auto menuBarItem2_v1 = qobject_cast<QQuickMenuBarItem *>(menuBar->itemAt(2)); + QVERIFY(menuBarItem0_v1); + QVERIFY(menuBarItem1_v1); + QVERIFY(menuBarItem2_v1); + QVERIFY(menuBarItem0_v1->isVisible()); + QVERIFY(menuBarItem1_v1->isVisible()); + QVERIFY(menuBarItem2_v1->isVisible()); + QVERIFY(menuBarItem0_v1->menu()); + QVERIFY(menuBarItem1_v1->menu()); + QVERIFY(menuBarItem2_v1->menu()); + QCOMPARE(menuBar->menuAt(0), menuBarItem0_v1->menu()); + QCOMPARE(menuBar->menuAt(1), menuBarItem1_v1->menu()); + QCOMPARE(menuBar->menuAt(2), menuBarItem2_v1->menu()); + + // Change the delegate + QQmlComponent delegate2(&engine); + delegate2.setData("import QtQuick.Controls; MenuBarItem {}", QUrl()); + menuBar->setDelegate(&delegate2); + + auto menuBarItem0_v2 = qobject_cast<QQuickMenuBarItem *>(menuBar->itemAt(0)); + auto menuBarItem1_v2 = qobject_cast<QQuickMenuBarItem *>(menuBar->itemAt(1)); + auto menuBarItem2_v2 = qobject_cast<QQuickMenuBarItem *>(menuBar->itemAt(2)); + QVERIFY(menuBarItem0_v2); + QVERIFY(menuBarItem1_v2); + QVERIFY(menuBarItem2_v2); + + // The delegate items should now have changed, except for + // menuBarItem2, which is not created from the delegate. + QVERIFY(menuBarItem0_v2 != menuBarItem0_v1); + QVERIFY(menuBarItem1_v2 != menuBarItem1_v1); + QCOMPARE(menuBarItem2_v2, menuBarItem2_v1); + + QVERIFY(menuBarItem0_v2->isVisible()); + QVERIFY(menuBarItem1_v2->isVisible()); + QVERIFY(menuBarItem2_v2->isVisible()); + QVERIFY(menuBarItem0_v2->menu()); + QVERIFY(menuBarItem1_v2->menu()); + QVERIFY(menuBarItem2_v2->menu()); + QCOMPARE(menuBar->menuAt(0), menuBarItem0_v2->menu()); + QCOMPARE(menuBar->menuAt(1), menuBarItem1_v2->menu()); + QCOMPARE(menuBar->menuAt(2), menuBarItem2_v2->menu()); +} + +void tst_qquickmenubar::invalidDelegate_data() +{ + QTest::addColumn<bool>("native"); + QTest::addColumn<bool>("useInvalidDelegate"); + QTest::newRow("not native, no delegate") << false << false; + QTest::newRow("not native, invalid delegate") << false << true; + if (nativeMenuBarSupported) { + QTest::newRow("native, no delegate") << true << false; + QTest::newRow("native, invalid delegate") << true << true; + } +} + +void tst_qquickmenubar::invalidDelegate() +{ + // Check that QQuickMenuBar can handle a delegate that is either null, or not a + // MenuBarItem. The former won't produce any warnings, but the latter should. + // In either case, this will not produce visible menus in the menu bar, except + // for the menus that are wrapped inside inline MenuBarItems, and therefore + // not using the delegate. + // To ensure that we still bookkeep the menus for the failing delegates, in case + // the delegate changes later, and that functions such as menuAt(index) continues + // to work, hidden placeholder MenuBarItems will be used instead. + QFETCH(bool, native); + QFETCH(bool, useInvalidDelegate); + + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, !native); + QQmlApplicationEngine engine; + + if (useInvalidDelegate) { + QTest::ignoreMessage(QtWarningMsg, QRegularExpression("cannot insert menu.*")); + QTest::ignoreMessage(QtWarningMsg, QRegularExpression("cannot insert menu.*")); + } + + if (useInvalidDelegate) + engine.load(testFileUrl("invaliddelegate.qml")); + else + engine.load(testFileUrl("nodelegate.qml")); + + QScopedPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0))); + QVERIFY(window); + QQuickMenuBar *menuBar = window->property("menuBar").value<QQuickMenuBar *>(); + QVERIFY(menuBar); + QCOMPARE(menuBar->count(), 3); + + // Menu 2 is an inline MenuBarItem, and is unaffected by the delegate + auto inlineMenuBarItem = qobject_cast<QQuickMenuBarItem *>(menuBar->itemAt(2)); + + for (int i = 0; i <= 2; ++i) { + auto menuBarItem = qobject_cast<QQuickMenuBarItem *>(menuBar->itemAt(i)); + QVERIFY(menuBarItem); + auto menu = menuBarItem->menu(); + QVERIFY(menu); + QCOMPARE(menu, menuBar->menuAt(i)); + if (menuBarItem == inlineMenuBarItem) { + QVERIFY(menuBarItem->isVisible()); + QCOMPARE(bool(QQuickMenuPrivate::get(menu)->maybeNativeHandle()), native); + } else { + // Menus created from the invalid delegate should be hidden. They should also + // not have a native handle, since they should not be in a native menu bar. + QVERIFY(!menuBarItem->isVisible()); + QVERIFY(!bool(QQuickMenuPrivate::get(menu)->maybeNativeHandle())); + } + } + + // Add a new menu. This one should also be inserted into a placeholder MenuBarItem + if (useInvalidDelegate) + QTest::ignoreMessage(QtWarningMsg, QRegularExpression("cannot insert menu.*")); + + QQmlComponent component(&engine); + component.setData("import QtQuick.Controls; Menu { }", QUrl()); + auto menu = qobject_cast<QQuickMenu *>(component.create()); + QVERIFY(menu); + + menuBar->addMenu(menu); + QCOMPARE(menuBar->count(), 4); + auto menuBarItem3 = qobject_cast<QQuickMenuBarItem *>(menuBar->itemAt(3)); + QVERIFY(menuBarItem3); + QVERIFY(!menuBarItem3->isVisible()); + QCOMPARE(menuBar->menuAt(3), menu); + QCOMPARE(menuBar->menuAt(3), menuBarItem3->menu()); + QVERIFY(!QQuickMenuPrivate::get(menu)->maybeNativeHandle()); + + // Finally, set a valid delegate. This will make all MenuBarItems visible. + QQmlComponent delegate(&engine); + delegate.setData("import QtQuick.Controls; MenuBarItem { }", QUrl()); + menuBar->setDelegate(&delegate); + + for (int i = 0; i <= 3; ++i) { + auto menuBarItem = qobject_cast<QQuickMenuBarItem *>(menuBar->itemAt(i)); + QVERIFY(menuBarItem); + QVERIFY(menuBarItem->isVisible()); + auto menu = menuBarItem->menu(); + QVERIFY(menu); + QCOMPARE(menu, menuBar->menuAt(i)); + QCOMPARE(bool(QQuickMenuPrivate::get(menu)->maybeNativeHandle()), native); + } + + // inlineMenuBarItem was not created from a delegate, and shouldn't change + QCOMPARE(qobject_cast<QQuickMenuBarItem *>(menuBar->itemAt(2)), inlineMenuBarItem); +} + +void tst_qquickmenubar::panMenuBar_data() +{ + QTest::addColumn<bool>("usePopupWindow"); + QTest::newRow("in-scene popup") << false; + // Uncomment when popup windows work 100% + // if (popupWindowsSupported) + // QTest::newRow("popup window") << true; +} + +void tst_qquickmenubar::panMenuBar() +{ + // Check that a MenuBarItem's menu opens when you click it. And then check that + // if you hover the next MenuBarItem in the MenuBar, that the first one will + // close, and the second one will open. + QFETCH(bool, usePopupWindow); + +#if !defined(Q_OS_MACOS) || !defined(Q_OS_WINDOWS) + QSKIP("This test doesn't pass on e.g QNX. It needs more investigation before it can be enabled"); +#endif + +#ifdef Q_OS_ANDROID + // Android theme does not use hover effects, so moving the mouse would not + // highlight an item + QSKIP("Panning of MenuBar not supported"); +#endif + + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, true); + QCoreApplication::setAttribute(Qt::AA_DontUsePopupWindows, !usePopupWindow); + QQmlApplicationEngine engine; + engine.load(testFileUrl("menus.qml")); + + QScopedPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0))); + QVERIFY(window); + QQuickMenuBar *menuBar = window->property("menuBar").value<QQuickMenuBar *>(); + QVERIFY(menuBar); + QQuickMenuBarPrivate *menuBar_d = QQuickMenuBarPrivate::get(menuBar); + + auto menuBarItem0 = qobject_cast<QQuickMenuBarItem *>(menuBar->itemAt(0)); + auto menuBarItem1 = qobject_cast<QQuickMenuBarItem *>(menuBar->itemAt(1)); + QVERIFY(menuBarItem0); + QVERIFY(menuBarItem1); + + QTest::mouseClick(window.data(), Qt::LeftButton, Qt::NoModifier, itemSceneCenter(menuBarItem0)); + QVERIFY(menuBarItem0->isHighlighted()); + QVERIFY(!menuBarItem1->isHighlighted()); + QCOMPARE(menuBar_d->currentItem, menuBarItem0); + QVERIFY(menuBar_d->currentMenuOpen); + QTRY_VERIFY(menuBarItem0->menu()->isOpened()); + + QTest::mouseMove(window.data(), itemSceneCenter(menuBarItem1)); + QVERIFY(!menuBarItem0->isHighlighted()); + QVERIFY(menuBarItem1->isHighlighted()); + QCOMPARE(menuBar_d->currentItem, menuBarItem1); + QVERIFY(menuBar_d->currentMenuOpen); + QTRY_VERIFY(menuBarItem1->menu()->isOpened()); + QTRY_VERIFY(!menuBarItem0->menu()->isOpened()); +} + QTEST_QUICKCONTROLS_MAIN(tst_qquickmenubar) #include "tst_qquickmenubar.moc" diff --git a/tests/auto/quickcontrols/qquickpopup/BLACKLIST b/tests/auto/quickcontrols/qquickpopup/BLACKLIST index bd2185328f..9a05aad150 100644 --- a/tests/auto/quickcontrols/qquickpopup/BLACKLIST +++ b/tests/auto/quickcontrols/qquickpopup/BLACKLIST @@ -23,3 +23,6 @@ opensuse-leap [cursorShape] opensuse-leap + +[popupWindowFocus] +* # QTBUG-121363 diff --git a/tests/auto/quickcontrols/qquickpopup/data/popupCenterIn.qml b/tests/auto/quickcontrols/qquickpopup/data/popupCenterIn.qml new file mode 100644 index 0000000000..6a67af30fc --- /dev/null +++ b/tests/auto/quickcontrols/qquickpopup/data/popupCenterIn.qml @@ -0,0 +1,22 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +Window { + width: 1080 + height: 720 + + property alias popup: simplepopup + + Popup { + id: simplepopup + anchors.centerIn: parent + popupType: Popup.Window + + Text { + text: "I am a centered popup" + } + } +} diff --git a/tests/auto/quickcontrols/qquickpopup/data/popupWindowFocusHandling.qml b/tests/auto/quickcontrols/qquickpopup/data/popupWindowFocusHandling.qml new file mode 100644 index 0000000000..1477db047e --- /dev/null +++ b/tests/auto/quickcontrols/qquickpopup/data/popupWindowFocusHandling.qml @@ -0,0 +1,28 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +Window { + width: 400 + height: 400 + + property alias popup: simplepopup + property alias textField1: outerTextField + property alias textField2: innerTextField + + TextField { + id: outerTextField + focus: true + } + + Popup { + id: simplepopup + popupType: Popup.Window + TextField { + id: innerTextField + focus: true + } + } +} diff --git a/tests/auto/quickcontrols/qquickpopup/data/popupWithButtonInBackground.qml b/tests/auto/quickcontrols/qquickpopup/data/popupWithButtonInBackground.qml new file mode 100644 index 0000000000..b265a80df7 --- /dev/null +++ b/tests/auto/quickcontrols/qquickpopup/data/popupWithButtonInBackground.qml @@ -0,0 +1,28 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +Window { + width: 1080 + height: 720 + + property alias popup: simplepopup + + Button { + text: "Button" + } + + Popup { + id: simplepopup + popupType: Popup.Window + + x: 50 + y: 50 + + Text { + text: "I am a very interesting popup" + } + } +} diff --git a/tests/auto/quickcontrols/qquickpopup/data/reparentingPopup.qml b/tests/auto/quickcontrols/qquickpopup/data/reparentingPopup.qml new file mode 100644 index 0000000000..e747704e4b --- /dev/null +++ b/tests/auto/quickcontrols/qquickpopup/data/reparentingPopup.qml @@ -0,0 +1,49 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +Window { + width: 400 + height: 400 + + property alias popup: simplepopup + property alias rectangle1: item1 + property alias rectangle2: item2 + property alias rectangle3: item3 + + Popup { + id: simplepopup + visible: true + popupType: Popup.Window + x: 10 + y: 10 + width: 200 + height: 200 + } + + Rectangle { + id: item1 + color: "red" + width: 200 + height: 200 + } + + Rectangle { + id: item2 + color: "green" + x: 0 + y: 200 + width: parent.width + height: 200 + Rectangle { + id: item3 + color: "blue" + x: 200 + y: 0 + width: 200 + height: item2.height + } + } +} diff --git a/tests/auto/quickcontrols/qquickpopup/data/simplepopup.qml b/tests/auto/quickcontrols/qquickpopup/data/simplepopup.qml new file mode 100644 index 0000000000..60371d20d4 --- /dev/null +++ b/tests/auto/quickcontrols/qquickpopup/data/simplepopup.qml @@ -0,0 +1,23 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +Window { + width: 1080 + height: 720 + + property alias popup: simplepopup + + Popup { + id: simplepopup + popupType: Popup.Window + x: 50 + y: 50 + + Text { + text: "I am a very interesting popup" + } + } +} diff --git a/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp b/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp index f894387672..a10c0aa53a 100644 --- a/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp +++ b/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp @@ -16,18 +16,21 @@ #include <QtQuickTestUtils/private/viewtestutils_p.h> #include <QtQuickTestUtils/private/visualtestutils_p.h> #include <QtQuickTemplates2/private/qquickapplicationwindow_p.h> +#include <QtQuickTemplates2/private/qquickbutton_p.h> #include <QtQuickTemplates2/private/qquickcombobox_p.h> #include <QtQuickTemplates2/private/qquickdialog_p.h> +#include <QtQuickTemplates2/private/qquickdrawer_p.h> #include <QtQuickTemplates2/private/qquickoverlay_p.h> #include <QtQuickTemplates2/private/qquickoverlay_p_p.h> #include <QtQuickTemplates2/private/qquickpopup_p.h> +#include <QtQuickTemplates2/private/qquickpopup_p_p.h> +#include <QtQuickTemplates2/private/qquickpopupanchors_p.h> #include <QtQuickTemplates2/private/qquickpopupitem_p_p.h> -#include <QtQuickTemplates2/private/qquickbutton_p.h> +#include <QtQuickTemplates2/private/qquickpopupwindow_p_p.h> #include <QtQuickTemplates2/private/qquickslider_p.h> #include <QtQuickTemplates2/private/qquickstackview_p.h> -#include <QtQuickTemplates2/private/qquickpopup_p_p.h> #include <QtQuickTemplates2/private/qquicktooltip_p.h> -#include <QtQuickTemplates2/private/qquickdrawer_p.h> +#include <QtQuick/private/qquicktextinput_p.h> #include <QtQuick/private/qquicklistview_p.h> #include <QtQuick/private/qquicktextedit_p.h> #include <QtQuick/private/qquickdroparea_p.h> @@ -111,8 +114,16 @@ private slots: void fadeDimmer(); void noDimmer(); + void popupWindowPositioning(); + void popupWindowAnchorsCenterIn_data(); + void popupWindowAnchorsCenterIn(); + void popupWindowModality(); + void popupWindowClosesOnParentWindowClosing(); + void popupWindowChangingParent(); + void popupWindowFocus(); + void popupWindowChangeFromWindowToInScene(); + private: - static bool hasWindowActivation(); QScopedPointer<QPointingDevice> touchScreen = QScopedPointer<QPointingDevice>(QTest::createTouchDevice()); }; @@ -126,6 +137,7 @@ tst_QQuickPopup::tst_QQuickPopup() void tst_QQuickPopup::initTestCase() { QQmlDataTest::initTestCase(); + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); qputenv("QML_NO_TOUCH_COMPRESSION", "1"); } @@ -136,11 +148,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); @@ -519,8 +526,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); @@ -662,8 +668,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. @@ -708,8 +713,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 @@ -750,8 +754,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. @@ -786,8 +789,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")); @@ -838,8 +840,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")); @@ -890,8 +891,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. @@ -919,8 +919,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()); @@ -961,8 +960,7 @@ void tst_QQuickPopup::activeFocusDespiteLowerStackingOrder() void tst_QQuickPopup::activeFocusItemAfterWindowInactive() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QQuickControlsApplicationHelper helper(this, QStringLiteral("activeFocusAfterWindowInactive.qml")); QVERIFY2(helper.ready, helper.failureMessage()); @@ -1448,8 +1446,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. @@ -1518,8 +1515,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()); @@ -1629,8 +1625,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()); @@ -1669,8 +1664,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()); @@ -1778,8 +1772,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"); @@ -1891,8 +1884,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()); @@ -2387,6 +2379,334 @@ void tst_QQuickPopup::noDimmer() QTRY_VERIFY(!drawer->isModal()); } +#define VERIFY_LOCAL_POS(POPUP, EXPECTED) \ + QTRY_COMPARE_LE(qAbs(POPUP->x() - qreal(EXPECTED.x())), 1); \ + QCOMPARE_LE(qAbs(POPUP->position().x() - qreal(EXPECTED.x())), 1); \ + QCOMPARE_LE(qAbs(POPUP->y() - qreal(EXPECTED.y())), 1); \ + QCOMPARE_LE(qAbs(POPUP->position().y() - qreal(EXPECTED.y())), 1) + +#define VERIFY_GLOBAL_POS(FROM, POPUPWINDOW, EXPECTED) \ + do { \ + const auto expectedGlobalPos = FROM->mapToGlobal(EXPECTED.x(), EXPECTED.y()); \ + const auto actualGlobalPos = POPUPWINDOW->position(); \ + QTRY_COMPARE_LE(qAbs(actualGlobalPos.x() - qFloor(expectedGlobalPos.x())), 1); \ + QCOMPARE_LE(qAbs(actualGlobalPos.y() - qFloor(expectedGlobalPos.y())), 1); \ + } while (false) + +void tst_QQuickPopup::popupWindowPositioning() +{ + QQuickApplicationHelper helper(this, "simplepopup.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + auto *popup = window->contentItem()->findChild<QQuickPopup *>(); + QVERIFY(popup); + auto *popupPrivate = QQuickPopupPrivate::get(popup); + QVERIFY(popupPrivate); + + if (!popupPrivate->usePopupWindow()) + QSKIP("The platform doesn't support native popup windows. Skipping test."); + + popup->open(); + QTRY_VERIFY(popup->isVisible()); + + QSignalSpy xSpy(popup, SIGNAL(xChanged())); + QSignalSpy ySpy(popup, SIGNAL(yChanged())); + + auto *popupWindow = popupPrivate->popupWindow; + QVERIFY(popupWindow); + + // x and y properties should be 50 initially + const QPoint initialPos(50, 50); + + VERIFY_GLOBAL_POS(popup->parentItem(), popupWindow, initialPos); + VERIFY_LOCAL_POS(popup, initialPos); + + // Move popup via QQuickPopup API + const QPoint secondPosition(100, 100); + popup->setPosition(secondPosition.toPointF()); + + QTRY_COMPARE(xSpy.count(), 1); + QCOMPARE(ySpy.count(), 1); + + VERIFY_GLOBAL_POS(popup->parentItem(), popupWindow, secondPosition); + VERIFY_LOCAL_POS(popup, secondPosition); + + // Move popup via QWindow API (which uses global coordinates) + const QPoint thirdPosition(150, 150); + popupWindow->setPosition(popup->parentItem()->mapToGlobal(thirdPosition.x(), thirdPosition.y()).toPoint()); + + QTRY_COMPARE(xSpy.count(), 2); + QCOMPARE(ySpy.count(), 2); + + VERIFY_GLOBAL_POS(popup->parentItem(), popupWindow, thirdPosition); + VERIFY_LOCAL_POS(popup, thirdPosition); + + // Moving parent window should change the popups position (because it's stationary, but x and y are relative coordinates) + const QPoint movement(30, 30); + const QPoint oldPos = window->position(); + window->setPosition(oldPos + movement); + + // TODO: Figure out these signals are emitted twice + // QTRY_COMPARE(xSpy.count(), 3); + // QCOMPARE(ySpy.count(), 3); + + VERIFY_GLOBAL_POS(popup->parentItem(), popupWindow, (thirdPosition - movement)); +} + +void tst_QQuickPopup::popupWindowAnchorsCenterIn_data() +{ + QTest::addColumn<bool>("centerInParent"); + QTest::newRow("parent") << true; + QTest::newRow("overlay") << false; +} + +void tst_QQuickPopup::popupWindowAnchorsCenterIn() +{ + QFETCH(bool, centerInParent); + + QQuickApplicationHelper helper(this, "popupCenterIn.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + auto *popup = window->contentItem()->findChild<QQuickPopup *>(); + QVERIFY(popup); + auto *popupPrivate = QQuickPopupPrivate::get(popup); + QVERIFY(popupPrivate); + + if (!popupPrivate->usePopupWindow()) + QSKIP("The platform doesn't support native popup windows. Skipping test."); + + popupPrivate->getAnchors()->setCenterIn(centerInParent ? window->contentItem() : QQuickOverlay::overlay(window)); + + popup->open(); + QTRY_VERIFY(popup->isVisible()); + + auto *popupWindow = popupPrivate->popupWindow; + QVERIFY(popupWindow); + + const QPoint centeredPosition(qFloor(window->width() / 2 - popupWindow->width() / 2), qFloor(window->height() / 2 - popupWindow->height() / 2)); + + VERIFY_GLOBAL_POS(popup->parentItem(), popupWindow, centeredPosition); + VERIFY_LOCAL_POS(popup, centeredPosition); +} + +void tst_QQuickPopup::popupWindowModality() +{ + QSKIP("The behavior isn't correctly implemented yet. Waiting for patch in qtbase"); + + QQuickApplicationHelper helper(this, "popupWithButtonInBackground.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + auto *popup = window->contentItem()->findChild<QQuickPopup *>(); + QVERIFY(popup); + + auto *popupPrivate = QQuickPopupPrivate::get(popup); + QVERIFY(popupPrivate); + + if (!popupPrivate->usePopupWindow()) + QSKIP("The platform doesn't support native popup windows. Skipping test."); + + auto *button = window->findChild<QQuickButton *>(); + QVERIFY(button); + + QSignalSpy buttonSpy(button, SIGNAL(clicked())); + + popup->open(); + QTRY_VERIFY(popup->isVisible()); + + auto *popupWindow = popupPrivate->popupWindow; + QVERIFY(popupWindow); + QVERIFY(popupWindow->isVisible()); + // NonModal by default + QCOMPARE(popupWindow->modality(), Qt::NonModal); + + // Non modal popups should close on press outside + QTest::mouseClick(helper.window, Qt::LeftButton, Qt::NoModifier, button->mapToScene(QPointF(button->width() / 2, button->height() / 2)).toPoint()); + QTRY_COMPARE(buttonSpy.count(), 1); + QVERIFY(!popupWindow->isVisible()); + + popup->setModal(true); + popup->open(); + QTRY_VERIFY(popup->isVisible()); + QVERIFY(popupWindow->isVisible()); + QCOMPARE(popupWindow->modality(), Qt::ApplicationModal); + + // Pressing outside the popup shouldn't cause the button to get the event, because of modality. + QTest::mouseClick(helper.window, Qt::LeftButton, Qt::NoModifier, button->mapToScene(QPointF(button->width() / 2, button->height() / 2)).toPoint()); + QCoreApplication::processEvents(); + QCOMPARE(buttonSpy.count(), 1); + QVERIFY(popupWindow->isVisible()); + + popup->close(); + QTRY_VERIFY(!popup->isVisible()); +} + +void tst_QQuickPopup::popupWindowClosesOnParentWindowClosing() +{ + QSKIP("The behavior isn't correctly implemented yet. Waiting for patch in qtbase"); + QQuickApplicationHelper helper(this, "simplepopup.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + auto *popup = window->contentItem()->findChild<QQuickPopup *>(); + QVERIFY(popup); + auto *popupPrivate = QQuickPopupPrivate::get(popup); + QVERIFY(popupPrivate); + + if (!popupPrivate->usePopupWindow()) + QSKIP("The platform doesn't support native popup windows. Skipping test."); + + popup->open(); + QTRY_VERIFY(popup->isVisible()); + + auto *popupWindow = popupPrivate->popupWindow; + QVERIFY(popupWindow); + QVERIFY(popupWindow->isVisible()); + + // Closing parent window, should close child window; + window->close(); + + QTRY_VERIFY(!window->isVisible()); + QTRY_VERIFY(!popupWindow->isVisible()); +} + +void tst_QQuickPopup::popupWindowChangingParent() +{ + QQuickApplicationHelper helper(this, "reparentingPopup.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + auto *popup = window->contentItem()->findChild<QQuickPopup *>(); + QVERIFY(popup); + auto *popupPrivate = QQuickPopupPrivate::get(popup); + QVERIFY(popupPrivate); + + QQuickItem *item1 = window->property("rectangle1").value<QQuickItem *>(); + QVERIFY(item1); + + QQuickItem *item2 = window->property("rectangle2").value<QQuickItem *>(); + QVERIFY(item2); + + QQuickItem *item3 = window->property("rectangle3").value<QQuickItem *>(); + QVERIFY(item3); + + if (!popupPrivate->usePopupWindow()) + QSKIP("The platform doesn't support native popup windows. Skipping test."); + + popup->open(); + QTRY_VERIFY(popup->isVisible()); + + auto *popupWindow = popupPrivate->popupWindow; + QVERIFY(popupWindow); + QVERIFY(popupWindow->isVisible()); + + const QPoint initialPos(10, 10); + + VERIFY_GLOBAL_POS(item1, popupWindow, initialPos); + VERIFY_LOCAL_POS(popup, initialPos); + + popup->setParentItem(item1); + + VERIFY_GLOBAL_POS(item1, popupWindow, initialPos); + VERIFY_LOCAL_POS(popup, initialPos); + + popup->setParentItem(item2); + + VERIFY_GLOBAL_POS(item2, popupWindow, initialPos); + VERIFY_LOCAL_POS(popup, initialPos); + + popup->setParentItem(item3); + + VERIFY_GLOBAL_POS(item3, popupWindow, initialPos); + VERIFY_LOCAL_POS(popup, initialPos); +} + +void tst_QQuickPopup::popupWindowFocus() +{ + QQuickApplicationHelper helper(this, "popupWindowFocusHandling.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickWindow *window = helper.window; + QVERIFY(window); + auto *popup = window->contentItem()->findChild<QQuickPopup *>(); + QVERIFY(popup); + auto *popupPrivate = QQuickPopupPrivate::get(popup); + QVERIFY(popupPrivate); + QQuickTextInput *textField1 = window->property("textField1").value<QQuickTextInput *>(); + QVERIFY(textField1); + QQuickTextInput *textField2 = window->property("textField2").value<QQuickTextInput *>(); + QVERIFY(textField2); + if (!popupPrivate->usePopupWindow()) + QSKIP("The platform doesn't support native popup windows. Skipping test."); + + window->show(); + QVERIFY(QTest::qWaitForWindowFocused(window)); + QVERIFY(QGuiApplication::focusObject() == textField1); + QTest::keyClick(helper.window, Qt::Key_Q); + QTRY_COMPARE(textField1->text(), "q"); + popup->open(); + QTRY_VERIFY(popup->isVisible()); + auto *popupWindow = popupPrivate->popupWindow; + QVERIFY(popupWindow); + QVERIFY(popupWindow->isVisible()); + // The focusWindow should still be the main window, + // the popup window should get its event forwarded via the delivery agent + QVERIFY(QGuiApplication::focusWindow() == helper.window); + QVERIFY(popupWindow->focusObject() == textField2); + QTest::keyClick(popupWindow, Qt::Key_T); + QTRY_COMPARE(textField2->text(), "t"); + popup->close(); + QTRY_VERIFY(!popup->isVisible()); + QVERIFY(QGuiApplication::focusObject() == textField1); +} + +void tst_QQuickPopup::popupWindowChangeFromWindowToInScene() +{ + QQuickApplicationHelper helper(this, "simplepopup.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + auto *popup = window->contentItem()->findChild<QQuickPopup *>(); + QVERIFY(popup); + auto *popupPrivate = QQuickPopupPrivate::get(popup); + QVERIFY(popupPrivate); + if (!popupPrivate->usePopupWindow()) + QSKIP("The platform doesn't support native popup windows. Skipping test."); + + popup->open(); + QTRY_VERIFY(popup->isVisible()); + const QWindow *popupWindow = popupPrivate->popupWindow; + QVERIFY(popupWindow); + QVERIFY(popupWindow->isVisible()); + popup->close(); + QTRY_VERIFY(!popupWindow->isVisible()); + QVERIFY(!popup->isVisible()); + popup->setPopupType(QQuickPopup::Item); + popup->open(); + QTRY_VERIFY(popup->isVisible()); + QQuickOverlay *overlay = QQuickOverlay::overlay(window); + QVERIFY(overlay); + QVERIFY(overlay->childItems().contains(popup->popupItem())); + popup->close(); +} + 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 a576bb7941..75e2550d7a 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/quickcontrols/qquickuniversalstyle/tst_qquickuniversalstyle.cpp b/tests/auto/quickcontrols/qquickuniversalstyle/tst_qquickuniversalstyle.cpp index f703c549f8..538bac0203 100644 --- a/tests/auto/quickcontrols/qquickuniversalstyle/tst_qquickuniversalstyle.cpp +++ b/tests/auto/quickcontrols/qquickuniversalstyle/tst_qquickuniversalstyle.cpp @@ -2,4 +2,18 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtQuickTest/quicktest.h> -QUICK_TEST_MAIN(tst_qquickuniversalstyle) + +class Setup : public QObject +{ + Q_OBJECT + +public slots: + void applicationAvailable() + { + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); + } +}; + +QUICK_TEST_MAIN_WITH_SETUP(tst_qquickuniversalstyle, Setup) + +#include "tst_qquickuniversalstyle.moc" diff --git a/tests/auto/quickcontrols/snippets/tst_snippets.cpp b/tests/auto/quickcontrols/snippets/tst_snippets.cpp index f0d1da48d7..68e6ef5dfa 100644 --- a/tests/auto/quickcontrols/snippets/tst_snippets.cpp +++ b/tests/auto/quickcontrols/snippets/tst_snippets.cpp @@ -39,6 +39,9 @@ static QMap<QString, QStringPair> findSnippets(const QDir &inputDir, const QDir void tst_Snippets::initTestCase() { + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); + qInfo() << "Snippets are taken from" << QQC2_SNIPPETS_PATH; QDir snippetsDir(QQC2_SNIPPETS_PATH); diff --git a/tests/auto/quickdialogs/qquickfolderdialogimpl/tst_qquickfolderdialogimpl.cpp b/tests/auto/quickdialogs/qquickfolderdialogimpl/tst_qquickfolderdialogimpl.cpp index 2afcd81a44..f4e35a37bf 100644 --- a/tests/auto/quickdialogs/qquickfolderdialogimpl/tst_qquickfolderdialogimpl.cpp +++ b/tests/auto/quickdialogs/qquickfolderdialogimpl/tst_qquickfolderdialogimpl.cpp @@ -814,7 +814,6 @@ void tst_QQuickFolderDialogImpl::itemsDisabledWhenNecessary() QVERIFY(breadcrumbBar->textField()->isVisible()); QCOMPARE(openButton->isEnabled(), false); #endif - // Hide it with the escape key. The Open button should now be enabled. QTest::keyClick(dialogHelper.window(), Qt::Key_Escape); QVERIFY(!breadcrumbBar->textField()->isVisible()); diff --git a/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp b/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp index 0067c716e0..3d5cb6e3b0 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> @@ -991,8 +992,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"); diff --git a/tests/baseline/controls/BLACKLIST b/tests/baseline/controls/BLACKLIST new file mode 100644 index 0000000000..834b16d765 --- /dev/null +++ b/tests/baseline/controls/BLACKLIST @@ -0,0 +1,15 @@ +# animated controls, can't take stable snapshot +[native:busyIndicator/busy_indicator] +* +[basic:busyIndicator/busy_indicator] +* +[universal:busyIndicator/busy_indicator] +* +[imagine:busyIndicator/busy_indicator] +* +[fusion:busyIndicator/busy_indicator] +* +[material:busyIndicator/busy_indicator] +* +[ios:busyIndicator/busy_indicator] +* diff --git a/tests/baseline/controls/data/textarea/textarea.qml b/tests/baseline/controls/data/textarea/textarea.qml index e34709c6cf..57b57606b4 100644 --- a/tests/baseline/controls/data/textarea/textarea.qml +++ b/tests/baseline/controls/data/textarea/textarea.qml @@ -8,11 +8,13 @@ ColumnLayout { TextArea { text: "TextArea\n...\n...\n...\n..." + cursorVisible: false } TextArea { placeholderText: "TextArea\n...\n...\n..." enabled: false + cursorVisible: false } TextArea { @@ -24,6 +26,7 @@ ColumnLayout { TextArea { text: "TextArea\n...\n...\n...\n..." LayoutMirroring.enabled: true + cursorVisible: false } } diff --git a/tests/baseline/controls/data/textfield/textfield.qml b/tests/baseline/controls/data/textfield/textfield.qml index bc0b1f215f..00b00913f8 100644 --- a/tests/baseline/controls/data/textfield/textfield.qml +++ b/tests/baseline/controls/data/textfield/textfield.qml @@ -9,21 +9,25 @@ ColumnLayout { TextField { placeholderText: qsTr("Enter text") enabled: false + cursorVisible: false } TextField { placeholderText: qsTr("Enter text") placeholderTextColor: "red" + cursorVisible: false } TextField { placeholderText: qsTr("Enter text") focus: true + cursorVisible: false } TextField { placeholderText: qsTr("Enter text") LayoutMirroring.enabled: true + cursorVisible: false } TextField { @@ -31,5 +35,6 @@ ColumnLayout { + "sed do eiusmod tempor incididunt utlabore et dolore magna" + "aliqua.Ut enim ad minim veniam, quis nostrud exercitation" + "ullamco laboris nisi ut aliquip ex ea commodo consequat.") + cursorVisible: false } } diff --git a/tests/baseline/scenegraph/data/shape/shape_fillItem.qml b/tests/baseline/scenegraph/data/shape/shape_fillItem.qml new file mode 100644 index 0000000000..0862e364b5 --- /dev/null +++ b/tests/baseline/scenegraph/data/shape/shape_fillItem.qml @@ -0,0 +1,177 @@ +import QtQuick +import QtQuick.Shapes + +Item { + width: 640 + height: 840 + + ListModel { + id: renderers + ListElement { renderer: Shape.GeometryRenderer; rotationAmount: 0 } + ListElement { renderer: Shape.GeometryRenderer; rotationAmount: 30 } + ListElement { renderer: Shape.CurveRenderer; rotationAmount: 0 } + ListElement { renderer: Shape.CurveRenderer; rotationAmount: 30 } + } + + Image { + id: image + visible: false + source: "../shared/col320x480.jpg" + } + + Image { + id: tiledImage + visible: false + source: "../shared/col320x480.jpg" + layer.enabled: true + layer.smooth: true + layer.wrapMode: ShaderEffectSource.Repeat + } + + Image { + id: asynchronousImage + visible: false + source: "../shared/col320x480.jpg" + layer.enabled: true + layer.smooth: true + layer.wrapMode: ShaderEffectSource.Repeat + asynchronous: true + } + + Rectangle { + id: item + visible: false + layer.enabled: true + layer.smooth: true + layer.wrapMode: ShaderEffectSource.Repeat + color: "cyan" + width: 20 + height: 20 + Text { + anchors.centerIn: parent + text: "😊" + } + } + + Rectangle { + id: sourceItem + color: "cyan" + width: 20 + height: 20 + Text { + anchors.centerIn: parent + text: "😁" + } + } + + ShaderEffectSource { + id: shaderEffectSource + sourceItem: sourceItem + width: 20 + height: 20 + wrapMode: ShaderEffectSource.Repeat + visible: false + hideSource: true + smooth: true + } + + Row { + anchors.fill: parent + Repeater { + model: renderers + Column { + Shape { + id: shape + preferredRendererType: renderer + width: 160 + height: 700 + property real rotate: rotationAmount + + ShapePath { + strokeColor: "transparent" + fillItem: image + fillTransform: PlanarTransform.fromRotate(shape.rotate) + + PathRectangle { + x: 10; y: 10 + width: 140 + height: 100 + } + + // startX: 10; startY: 10 + // PathLine { relativeX: 140; relativeY: 0 } + // PathLine { relativeX: 0; relativeY: 100 } + // PathLine { relativeX: -140; relativeY: 0 } + // PathLine { relativeX: 0; relativeY: -100 } + } + + ShapePath { + strokeColor: "transparent" + fillItem: tiledImage + + PathRectangle { + x: 10; y: 10 + 1 * 140 + width: 140 + height: 100 + } + fillTransform: PlanarTransform.fromRotate(shape.rotate) + } + + ShapePath { + strokeColor: "transparent" + fillItem: item + fillTransform: PlanarTransform.fromRotate(shape.rotate) + + PathRectangle { + x: 10; y: 10 + 2 * 140 + width: 140 + height: 100 + } + } + + ShapePath { + strokeColor: "transparent" + fillItem: asynchronousImage + fillTransform: PlanarTransform.fromRotate(shape.rotate) + + PathRectangle { + x: 10; y: 10 + 3 * 140 + width: 140 + height: 100 + } + } + + ShapePath { + strokeColor: "transparent" + fillItem: shaderEffectSource + fillTransform: PlanarTransform.fromRotate(shape.rotate) + + PathRectangle { + x: 10; y: 10 + 4 * 140 + width: 140 + height: 100 + } + } + } + + Shape { + preferredRendererType: renderer + width: 160 + height: 200 + x: 10 + + ShapePath { + strokeColor: "transparent" + fillItem: image + fillTransform: PlanarTransform.fromRotate(shape.rotate) + + PathRectangle { + width: 140 + height: 100 + } + } + } + } + } + } +} diff --git a/tests/baseline/scenegraph/data/shape/shape_gradient_xf.qml b/tests/baseline/scenegraph/data/shape/shape_gradient_xf.qml new file mode 100644 index 0000000000..a0a33d9f2c --- /dev/null +++ b/tests/baseline/scenegraph/data/shape/shape_gradient_xf.qml @@ -0,0 +1,81 @@ +import QtQuick +import QtQuick.Shapes + +Item { + width: 320 + height: 480 + + ListModel { + id: renderers + ListElement { renderer: Shape.GeometryRenderer } + ListElement { renderer: Shape.CurveRenderer } + } + + Row { + Repeater { + model: renderers + Column { + Shape { + preferredRendererType: renderer + width: 160 + height: 150 + + ShapePath { + strokeColor: "transparent" + fillGradient: LinearGradient { + y1: 50; y2: 80 + GradientStop { position: 0; color: "black" } + GradientStop { position: 1; color: "cyan" } + } + fillTransform: PlanarTransform.fromAffineMatrix(0.8, 0.2, 0.3, 1.5, 20, -50 + startY) + + startX: 10; startY: 10 + PathLine { relativeX: 140; relativeY: 0 } + PathLine { relativeX: 0; relativeY: 100 } + PathLine { relativeX: -140; relativeY: 0 } + PathLine { relativeX: 0; relativeY: -100 } + } + + ShapePath { + strokeColor: "transparent" + fillGradient: RadialGradient { + centerX: 80 + centerY: 75 + centerRadius: centerY + focalX: centerX + focalY: centerY + GradientStop { position: 0; color: "black" } + GradientStop { position: .5; color: "cyan" } + GradientStop { position: 1; color: "black" } + } + fillTransform: PlanarTransform.fromAffineMatrix(0.8, 0.2, 0.3, 1.5, 20, -50 + startY) + + startX: 10; startY: 10 + 1 * 140 + PathLine { relativeX: 140; relativeY: 0 } + PathLine { relativeX: 0; relativeY: 100 } + PathLine { relativeX: -140; relativeY: 0 } + PathLine { relativeX: 0; relativeY: -100 } + } + + ShapePath { + strokeColor: "transparent" + fillGradient: ConicalGradient { + centerX: 80 + centerY: 75 + GradientStop { position: 0; color: "black" } + GradientStop { position: .5; color: "cyan" } + GradientStop { position: 1; color: "black" } + } + fillTransform: PlanarTransform.fromAffineMatrix(0.8, 0.2, 0.3, 1.5, 20, -50 + startY) + + startX: 10; startY: 10 + 2 * 140 + PathLine { relativeX: 140; relativeY: 0 } + PathLine { relativeX: 0; relativeY: 100 } + PathLine { relativeX: -140; relativeY: 0 } + PathLine { relativeX: 0; relativeY: -100 } + } + } + } + } + } +} diff --git a/tests/baseline/scenegraph/data/shape/shape_intersecting8.qml b/tests/baseline/scenegraph/data/shape/shape_intersecting8.qml new file mode 100644 index 0000000000..fa6e062e17 --- /dev/null +++ b/tests/baseline/scenegraph/data/shape/shape_intersecting8.qml @@ -0,0 +1,64 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Shapes + +Item { + width: 600 + height: 600 + + ListModel { + id: fillRules + ListElement { fillrule: ShapePath.WindingFill } + ListElement { fillrule: ShapePath.OddEvenFill } + } + + ListModel { + id: renderers + ListElement { renderer: Shape.GeometryRenderer } + ListElement { renderer: Shape.CurveRenderer } + } + + ListModel { + id: svgstrings + ListElement { scaleToFit: 0.6; offsetX: 20; offsetY: 20; pathString: "M 10 100 Q 10 89.6447 17.3223 82.3223 Q 24.6447 75 35 75 Q 45.3553 75 52.6777 82.3223 Q 60 89.6447 60 100 Q 60 85.5025 67.3223 75.2513 Q 74.6447 65 85 65 Q 95.3553 65 102.678 75.2513 Q 110 85.5025 110 100 Q 110 75.1472 117.322 57.5736 Q 124.645 40 135 40 Q 145.355 40 152.678 57.5736 Q 160 75.1472 160 100 Q 171.603 83.923 185 83.923 Q 198.397 83.923 210 100 L 10 100" } + } + Column { + Repeater { + model: renderers + Column { + Repeater { + model: fillRules + Row { + Repeater { + model: svgstrings + Rectangle { + width: 150 + height: 150 + border.color: "black" + + Shape { + preferredRendererType: renderer + ShapePath { + fillColor: renderer == Shape.CurveRenderer ? "#99483d8b" : "#99dc143c" + fillRule: fillrule + strokeWidth: 0 + PathSvg { path: pathString } + } + + transform: Matrix4x4 { + matrix: Qt.matrix4x4(scaleToFit, 0, 0, offsetX, + 0, scaleToFit, 0, offsetY, + 0, 0, 1, 0, + 0, 0, 0, 1) + } + } + } + } + } + } + } + } + } +} diff --git a/tests/baseline/scenegraph/data/shape/shape_rectangle.qml b/tests/baseline/scenegraph/data/shape/shape_rectangle.qml new file mode 100644 index 0000000000..50e2895f85 --- /dev/null +++ b/tests/baseline/scenegraph/data/shape/shape_rectangle.qml @@ -0,0 +1,151 @@ +import QtQuick +import QtQuick.Shapes + +Rectangle { + width: 320 + height: 480 + color: "lightgray" + + ListModel { + id: renderers + ListElement { renderer: Shape.GeometryRenderer } + ListElement { renderer: Shape.CurveRenderer } + } + + Row { + padding: 10 + Repeater { + model: renderers + Column { + spacing: 10 + Shape { + width: 160 + preferredRendererType: renderer + + ShapePath { + fillColor: "transparent" + strokeColor: "blue" + strokeWidth: 1 + + PathRectangle { + x: 20; y: 0 + width: 100; height: 20 + } + + PathRectangle { + x: 20.5; y: 30.5 + width: 100; height: 20 + } + } + } + + Shape { + width: 160 + preferredRendererType: renderer + + ShapePath { + fillColor: "yellow" + strokeColor: "transparent" + + PathRectangle { + x: 20; y: 0 + width: 100; height: 20 + } + + PathRectangle { + x: 20.5; y: 30.5 + width: 100; height: 20 + } + } + } + + Shape { + width: 160 + preferredRendererType: renderer + + ShapePath { + fillColor: "yellow" + strokeColor: "green" + strokeWidth: 5 + joinStyle: ShapePath.RoundJoin + + PathRectangle { + x: 20; y: 00 + width: 100; height: 20 + } + + PathRectangle { + x: 20; y: 30 + width: 100; height: 20 + radius: 5 + } + } + + ShapePath { + fillColor: "yellow" + strokeColor: "green" + strokeWidth: 5 + joinStyle: ShapePath.MiterJoin + + PathRectangle { + x: 20; y: 60 + width: 100; height: 20 + } + + PathRectangle { + x: 20; y: 90 + width: 100; height: 20 + radius: 5 + } + + PathRectangle { + x: 20; y: 120 + width: 100; height: 20 + radius: 50 + } + + PathRectangle { + x: 20; y: 150 + width: 100; height: 30 + radius: 10 + topLeftRadius: 50 + bottomRightRadius: 5 + bottomLeftRadius: 0 + } + } + } + + Rectangle { + id: rect + width: 120 + height: 60 + color: "white" + border.width: 20 + border.color: "blue" + topRightRadius: 30 + } + + Shape { + width: 160 + preferredRendererType: renderer + + ShapePath { + id: myPath + fillColor: rect.color + strokeColor: rect.border.color + strokeWidth: rect.border.width + joinStyle: ShapePath.MiterJoin + + PathRectangle { + width: rect.width + height: rect.height + topRightRadius: rect.topRightRadius + strokeAdjustment: myPath.strokeWidth + } + } + } + } + } + } +} + diff --git a/tests/baseline/scenegraph/data/shape/shape_spread_xf.qml b/tests/baseline/scenegraph/data/shape/shape_spread_xf.qml new file mode 100644 index 0000000000..c9f67e472d --- /dev/null +++ b/tests/baseline/scenegraph/data/shape/shape_spread_xf.qml @@ -0,0 +1,47 @@ +import QtQuick +import QtQuick.Shapes + +Item { + width: 320 + height: 480 + + ListModel { + id: renderers + ListElement { renderer: Shape.GeometryRenderer } + ListElement { renderer: Shape.CurveRenderer } + } + + Row { + Repeater { + model: renderers + Column { + Repeater { + model: 3 + Shape { + preferredRendererType: renderer + width: 160 + height: 150 + ShapePath { + strokeColor: "transparent" + + fillGradient: LinearGradient { + id: grad + y1: 50; y2: 80 + spread: model.index === 0 ? ShapeGradient.PadSpread : (model.index === 1 ? ShapeGradient.RepeatSpread : ShapeGradient.ReflectSpread) + GradientStop { position: 0; color: "black" } + GradientStop { position: 1; color: "red" } + } + fillTransform: PlanarTransform.fromShear(0, 0.2) + + startX: 10; startY: 10 + PathLine { relativeX: 140; relativeY: 0 } + PathLine { relativeX: 0; relativeY: 100 } + PathLine { relativeX: -140; relativeY: 0 } + PathLine { relativeX: 0; relativeY: -100 } + } + } + } + } + } + } +} diff --git a/tests/baseline/scenegraph/data/shape/shape_updatecolor.qml b/tests/baseline/scenegraph/data/shape/shape_updatecolor.qml new file mode 100644 index 0000000000..32cc73ad45 --- /dev/null +++ b/tests/baseline/scenegraph/data/shape/shape_updatecolor.qml @@ -0,0 +1,77 @@ +import QtQuick +import QtQuick.Shapes + +Item { + width: 320 + height: 800 + + ListModel { + id: renderers + ListElement { renderer: Shape.GeometryRenderer } + ListElement { renderer: Shape.CurveRenderer } + } + + Row { + Repeater { + model: renderers + Column { + Shape { + preferredRendererType: renderer + width: 160 + height: 150 + + ShapePath { + strokeColor: "transparent" + fillColor: "red" + + startX: 10; startY: 10 + PathLine { relativeX: 140; relativeY: 0 } + PathLine { relativeX: 0; relativeY: 100 } + PathLine { relativeX: -140; relativeY: 0 } + PathLine { relativeX: 0; relativeY: -100 } + } + } + + Shape { + preferredRendererType: renderer + width: 160 + height: 150 + + ShapePath { + strokeColor: "transparent" + fillColor: "red" + + startX: 10; startY: 10 + 1 * 140 + PathLine { relativeX: 140; relativeY: 0 } + PathLine { relativeX: 0; relativeY: 100 } + PathLine { relativeX: -140; relativeY: 0 } + PathLine { relativeX: 0; relativeY: -100 } + } + } + + Shape { + preferredRendererType: renderer + width: 160 + height: 150 + Timer { + interval: 100 + running: true + onTriggered: s.fillColor = Qt.rgba(0, 1, 0, 1) + } + + ShapePath { + id: s + strokeColor: "transparent" + fillColor: "red" + + startX: 10; startY: 10 + 2 * 140 + PathLine { relativeX: 140; relativeY: 0 } + PathLine { relativeX: 0; relativeY: 100 } + PathLine { relativeX: -140; relativeY: 0 } + PathLine { relativeX: 0; relativeY: -100 } + } + } + } + } + } +} diff --git a/tests/baseline/scenegraph/data/shape/shape_updatefill.qml b/tests/baseline/scenegraph/data/shape/shape_updatefill.qml new file mode 100644 index 0000000000..8e034c7676 --- /dev/null +++ b/tests/baseline/scenegraph/data/shape/shape_updatefill.qml @@ -0,0 +1,214 @@ +import QtQuick +import QtQuick.Shapes + +Rectangle { + width: 320 + height: 480 + color: "lightgray" + + ListModel { + id: renderers + ListElement { renderer: Shape.GeometryRenderer } + ListElement { renderer: Shape.CurveRenderer } + } + + LinearGradient { + id: grad1 + x2: 60; y2: 60 + GradientStop { position: 0; color: "black" } + GradientStop { position: 1; color: "red" } + } + + ConicalGradient { + id: grad2 + centerX: 15; centerY: 15 + GradientStop { position: 0; color: "yellow" } + GradientStop { position: .5; color: "black" } + GradientStop { position: 1; color: "yellow" } + } + + Image { + id: img1 + source: "../shared/world.png" + visible: false + } + + Image { + id: img2 + source: "../shared/sample_1.png" + visible: false + } + + Row { + padding: 10 + spacing: 20 + Repeater { + model: renderers + Shape { + width: 140 + preferredRendererType: renderer + + ShapePath { + id: c1 + strokeColor: "black" + fillColor: "cyan" + fillTransform: PlanarTransform.fromTranslate(rc1.x, rc1.y) + + PathRectangle { + id: rc1 + width: 60; height: 60 + } + } + + ShapePath { + id: c2 + strokeColor: "black" + fillColor: "cyan" + fillTransform: PlanarTransform.fromTranslate(rc2.x, rc2.y) + + PathRectangle { + id: rc2 + x: 80 + width: 60; height: 60 + } + } + + ShapePath { + id: g1 + strokeColor: "black" + fillColor: "cyan" + fillTransform: PlanarTransform.fromTranslate(rg1.x, rg1.y) + fillGradient: grad1 + + PathRectangle { + id: rg1 + y: 80 + width: 60; height: 60 + } + } + + ShapePath { + id: t1 + strokeColor: "black" + fillColor: "cyan" + fillItem: img1 + fillTransform: PlanarTransform.fromTranslate(rt1.x, rt1.y) + + PathRectangle { + id: rt1 + x: 80; y: 80 + width: 60; height: 60 + } + } + + ShapePath { + id: g2 + strokeColor: "black" + fillColor: "cyan" + fillTransform: PlanarTransform.fromTranslate(rg2.x, rg2.y) + fillGradient: grad1 + + PathRectangle { + id: rg2 + y: 2 * 80 + width: 60; height: 60 + } + } + + ShapePath { + id: t2 + strokeColor: "black" + fillColor: "cyan" + fillTransform: PlanarTransform.fromTranslate(rt2.x, rt2.y) + fillItem: img1 + + PathRectangle { + id: rt2 + x: 80; y: 2 * 80 + width: 60; height: 60 + } + } + + ShapePath { + id: g3 + strokeColor: "black" + fillColor: "cyan" + fillTransform: PlanarTransform.fromTranslate(rg3.x, rg3.y) + fillGradient: grad1 + + PathRectangle { + id: rg3 + y: 3 * 80 + width: 60; height: 60 + } + } + + ShapePath { + id: t3 + strokeColor: "black" + fillColor: "cyan" + fillTransform: PlanarTransform.fromTranslate(rt3.x, rt3.y) + fillItem: img1 + + PathRectangle { + id: rt3 + x: 80; y: 3 * 80 + width: 60; height: 60 + } + } + + ShapePath { + id: g4 + strokeColor: "black" + fillColor: "cyan" + fillTransform: PlanarTransform.fromTranslate(rg4.x, rg4.y) + fillGradient: grad2 + + PathRectangle { + id: rg4 + y: 4 * 80 + width: 60; height: 60 + } + } + + ShapePath { + id: t4 + strokeColor: "black" + fillColor: "cyan" + fillTransform: PlanarTransform.fromTranslate(rt4.x, rt4.y) + fillItem: img2 + + PathRectangle { + id: rt4 + x: 80; y: 4 * 80 + width: 60; height: 60 + } + } + + Timer { + running: true + interval: 150 // <200ms needed for scenegrabber; disable for manual testing + onTriggered: { + // Test all changes A->B, where A,B in {fillColor, fillGradient, fillItem} + // plus change of fillTransform + + c1.fillGradient = grad1 + g1.fillGradient = null + g2.fillGradient = null + g2.fillItem = img1 + g3.fillGradient = grad2 + + c2.fillItem = img1 + t1.fillItem = null + t2.fillGradient = grad1 + t3.fillItem = img2 + + g4.fillTransform = g4.fillTransform.times(PlanarTransform.fromRotate(45, 30, 30)) + t4.fillTransform = t4.fillTransform.times(PlanarTransform.fromRotate(45, 30, 30)) + } + } + } + } + } +} + diff --git a/tests/baseline/scenegraph/data/shape/shape_updategradient.qml b/tests/baseline/scenegraph/data/shape/shape_updategradient.qml new file mode 100644 index 0000000000..f1fa0f0f3d --- /dev/null +++ b/tests/baseline/scenegraph/data/shape/shape_updategradient.qml @@ -0,0 +1,105 @@ +import QtQuick +import QtQuick.Shapes + +Item { + width: 320 + height: 800 + + ListModel { + id: renderers + ListElement { renderer: Shape.GeometryRenderer } + ListElement { renderer: Shape.CurveRenderer } + } + + RadialGradient { + id: radialGradient + centerX: 80 + centerY: 75 + centerRadius: centerY + focalX: centerX + focalY: centerY + GradientStop { position: 0; color: "black" } + GradientStop { position: .5; color: "cyan" } + GradientStop { position: 1; color: "black" } + } + + Row { + Repeater { + model: renderers + Column { + Shape { + preferredRendererType: renderer + width: 160 + height: 150 + + ShapePath { + strokeColor: "transparent" + fillGradient: LinearGradient { + y1: 50; y2: 80 + GradientStop { position: 0; color: "black" } + GradientStop { position: 1; color: "cyan" } + } + fillTransform: PlanarTransform.fromAffineMatrix(0.8, 0.2, 0.3, 1.5, 20, -50 + startY) + + startX: 10; startY: 10 + PathLine { relativeX: 140; relativeY: 0 } + PathLine { relativeX: 0; relativeY: 100 } + PathLine { relativeX: -140; relativeY: 0 } + PathLine { relativeX: 0; relativeY: -100 } + } + } + + Shape { + preferredRendererType: renderer + width: 160 + height: 150 + + ShapePath { + strokeColor: "transparent" + fillGradient: LinearGradient { + y1: 50; y2: 80 + GradientStop { position: 0; color: "black" } + GradientStop { position: 1; color: "cyan" } + } + fillTransform: PlanarTransform.fromAffineMatrix(0.8, 0.2, 0.3, 1.5, 20, -50 + startY) + + startX: 10; startY: 10 + 1 * 140 + PathLine { relativeX: 140; relativeY: 0 } + PathLine { relativeX: 0; relativeY: 100 } + PathLine { relativeX: -140; relativeY: 0 } + PathLine { relativeX: 0; relativeY: -100 } + } + } + + Shape { + preferredRendererType: renderer + width: 160 + height: 150 + Timer { + interval: 100 + running: true + onTriggered: s.fillGradient = radialGradient + } + + ShapePath { + id: s + strokeColor: "transparent" + fillGradient: LinearGradient { + y1: 50; y2: 80 + GradientStop { position: 0; color: "black" } + GradientStop { position: 1; color: "cyan" } + } + fillTransform: PlanarTransform.fromAffineMatrix(0.8, 0.2, 0.3, 1.5, 20, -50 + startY) + + + startX: 10; startY: 10 + 2 * 140 + PathLine { relativeX: 140; relativeY: 0 } + PathLine { relativeX: 0; relativeY: 100 } + PathLine { relativeX: -140; relativeY: 0 } + PathLine { relativeX: 0; relativeY: -100 } + } + } + } + } + } +} diff --git a/tests/baseline/scenegraph/data/shared/qt_logo.svg b/tests/baseline/scenegraph/data/shared/qt_logo.svg new file mode 100644 index 0000000000..062daff3e9 --- /dev/null +++ b/tests/baseline/scenegraph/data/shared/qt_logo.svg @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns="http://www.w3.org/2000/svg" + width="462pt" + height="339pt" + viewBox="0 0 462 339" + version="1.1" + id="svg2" +> + <path + fill="#41cd52" + d=" M 63.50 0.00 L 462.00 0.00 L 462.00 274.79 C 440.60 296.26 419.13 317.66 397.61 339.00 L 0.00 339.00 L 0.00 63.39 C 21.08 42.18 42.34 21.13 63.50 0.00 Z" + id="path6"/> + <path + d=" M 122.37 71.33 C 137.50 61.32 156.21 58.79 174.00 58.95 C 190.94 59.16 208.72 62.13 222.76 72.24 C 232.96 79.41 239.59 90.48 244.01 101.93 C 251.16 120.73 253.26 141.03 253.50 161.01 C 253.53 181.13 252.62 201.69 245.96 220.86 C 241.50 233.90 233.01 245.48 221.81 253.52 C 229.87 266.58 238.09 279.54 246.15 292.60 C 236.02 297.27 225.92 301.97 215.78 306.62 C 207.15 292.38 198.56 278.11 189.90 263.89 C 178.19 265.81 166.21 265.66 154.44 264.36 C 140.34 262.67 125.97 258.37 115.09 248.88 C 106.73 241.64 101.48 231.51 97.89 221.21 C 92.01 203.79 90.43 185.25 90.16 166.97 C 90.02 147.21 91.28 127.14 97.24 108.18 C 101.85 93.92 109.48 79.69 122.37 71.33 Z" + id="path8" + fill="#ffffff"/> + <path + d=" M 294.13 70.69 C 304.73 70.68 315.33 70.68 325.93 70.69 C 325.96 84.71 325.92 98.72 325.95 112.74 C 339.50 112.76 353.05 112.74 366.60 112.75 C 366.37 121.85 366.12 130.95 365.86 140.05 C 352.32 140.08 338.79 140.04 325.25 140.07 C 325.28 163.05 325.18 186.03 325.30 209.01 C 325.56 215.30 325.42 221.94 328.19 227.75 C 330.21 232.23 335.65 233.38 340.08 233.53 C 348.43 233.50 356.77 233.01 365.12 232.86 C 365.63 241.22 366.12 249.59 366.60 257.95 C 349.99 260.74 332.56 264.08 316.06 258.86 C 309.11 256.80 302.63 252.19 299.81 245.32 C 294.76 233.63 294.35 220.62 294.13 208.07 C 294.11 185.40 294.13 162.74 294.12 140.07 C 286.73 140.05 279.34 140.08 271.95 140.05 C 271.93 130.96 271.93 121.86 271.95 112.76 C 279.34 112.73 286.72 112.77 294.11 112.74 C 294.14 98.72 294.10 84.71 294.13 70.69 Z" + id="path10" + fill="#ffffff"/> + <path + fill="#41cd52" + d=" M 160.51 87.70 C 170.80 86.36 181.60 86.72 191.34 90.61 C 199.23 93.73 205.93 99.84 209.47 107.58 C 214.90 119.31 216.98 132.26 218.03 145.05 C 219.17 162.07 219.01 179.25 216.66 196.17 C 215.01 206.24 212.66 216.85 205.84 224.79 C 198.92 232.76 188.25 236.18 178.01 236.98 C 167.21 237.77 155.82 236.98 146.07 231.87 C 140.38 228.84 135.55 224.09 132.73 218.27 C 129.31 211.30 127.43 203.69 126.11 196.07 C 122.13 171.91 121.17 146.91 126.61 122.89 C 128.85 113.83 132.11 104.53 138.73 97.70 C 144.49 91.85 152.51 88.83 160.51 87.70 Z" + id="path12"/> +</svg> diff --git a/tests/baseline/scenegraph/data/text/text_context_font_merging.qml b/tests/baseline/scenegraph/data/text/text_context_font_merging.qml new file mode 100644 index 0000000000..3f0adfe2d8 --- /dev/null +++ b/tests/baseline/scenegraph/data/text/text_context_font_merging.qml @@ -0,0 +1,18 @@ +import QtQuick 2.0 + +Item { + width: 320 + height: 100 + Column { + anchors.fill: parent + Text { + text: "說文閩音通說文閩音通說文閩音通" + font.pixelSize: 20 + } + Text { + text: "說文閩音通說文閩音通說文閩音通" + font.contextFontMerging: true + font.pixelSize: 20 + } + } +} diff --git a/tests/baseline/scenegraph/data/text/text_prefertypolinemetrics.qml b/tests/baseline/scenegraph/data/text/text_prefertypolinemetrics.qml new file mode 100644 index 0000000000..3bb9452021 --- /dev/null +++ b/tests/baseline/scenegraph/data/text/text_prefertypolinemetrics.qml @@ -0,0 +1,24 @@ +import QtQuick 2.0 + +Item { + width: 180 + height: 60 + + Row { + anchors.fill: parent + Text { + font.family: "Arial" + font.pixelSize: 16 + textFormat: Qt.RichText + text: "First line<br />Second line<br />Third line" + } + Text { + font.family: "Arial" + font.pixelSize: 16 + textFormat: Qt.RichText + text: "First line<br />Second line<br />Third line" + font.preferTypoLineMetrics: true + } + + } +} diff --git a/tests/baseline/scenegraph/data/vectorimages/fillMode.qml b/tests/baseline/scenegraph/data/vectorimages/fillMode.qml new file mode 100644 index 0000000000..03e575daf4 --- /dev/null +++ b/tests/baseline/scenegraph/data/vectorimages/fillMode.qml @@ -0,0 +1,59 @@ +import QtQuick +import QtQuick.VectorImage + +Rectangle{ + id: topLevelItem + width: 200 + height: 880 + + Column { + anchors.fill: parent + Repeater { + model: ListModel { + ListElement { + name: "Stretch" + mode: VectorImage.Stretch + } + ListElement { + name: "NoResize" + mode: VectorImage.NoResize + } + ListElement { + name: "PreserveAspectCrop" + mode: VectorImage.PreserveAspectCrop + } + ListElement { + name: "PreserveAspectFit" + mode: VectorImage.PreserveAspectFit + } + + } + + Column { + width: 200 + height: 200 + t.height + Rectangle { + color: "white" + border.width: 1 + border.color: "black" + width: 152 + height: 202 + VectorImage { + x: 1 + y: 1 + width: 150 + height: 200 + source: "../shared/qt_logo.svg" + fillMode: mode + clip: true + z: 100 + } + } + Text { + id: t + text: name + } + } + } + } +} diff --git a/tests/manual/painterpathquickshape/ControlPanel.qml b/tests/manual/painterpathquickshape/ControlPanel.qml index 87eb9ae3e7..ea3168d124 100644 --- a/tests/manual/painterpathquickshape/ControlPanel.qml +++ b/tests/manual/painterpathquickshape/ControlPanel.qml @@ -28,6 +28,7 @@ Item { property alias painterComparisonAlpha: painterComparisonColorAlpha.value property alias outlineEnabled: enableOutline.checked property alias gradientType: gradientType.currentIndex + property alias fillScaleX: fillTransformSlider.value property alias rendererName: rendererLabel.text property alias preferCurve: rendererLabel.preferCurve @@ -254,6 +255,19 @@ Item { } } Label { + text: "Fill transform (scale x: " + fillTransformSlider.value.toFixed(2) + "):" + color: "white" + visible: gradientType.currentIndex != 0 + } + Slider { + id: fillTransformSlider + Layout.fillWidth: true + from: 0.2 + to: 5.0 + value: 1.0 + visible: gradientType.currentIndex != 0 + } + Label { text: "Fill alpha(" + Math.round(alphaSlider.value*100)/100 + "):" color: "white" } diff --git a/tests/manual/painterpathquickshape/ControlledShape.qml b/tests/manual/painterpathquickshape/ControlledShape.qml index e690f59ccc..26a57163cd 100644 --- a/tests/manual/painterpathquickshape/ControlledShape.qml +++ b/tests/manual/painterpathquickshape/ControlledShape.qml @@ -89,6 +89,7 @@ Item { strokeStyle: controlPanel.outlineStyle joinStyle: controlPanel.joinStyle capStyle: controlPanel.capStyle + fillTransform: Qt.matrix4x4(controlPanel.fillScaleX,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1) } Repeater { diff --git a/tests/manual/platforms/android/qml_in_android_service/.gitignore b/tests/manual/platforms/android/qml_in_android_service/.gitignore new file mode 100644 index 0000000000..2300a2df35 --- /dev/null +++ b/tests/manual/platforms/android/qml_in_android_service/.gitignore @@ -0,0 +1,2 @@ +build/* +CMakeLists.txt.user diff --git a/tests/manual/platforms/android/qml_in_android_service/CMakeLists.txt b/tests/manual/platforms/android/qml_in_android_service/CMakeLists.txt new file mode 100644 index 0000000000..47afd4c0c3 --- /dev/null +++ b/tests/manual/platforms/android/qml_in_android_service/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +cmake_minimum_required(VERSION 3.16) + +project(qml_in_android_service VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 6.7 REQUIRED COMPONENTS Quick) + +qt_standard_project_setup(REQUIRES 6.6) + + +qt_add_executable(qml_in_android_service + main.cpp +) + +qt_add_qml_module(qml_in_android_service + URI qml_in_android_service + VERSION 1.0 + QML_FILES Main.qml +) + +target_link_libraries(qml_in_android_service + PRIVATE Qt6::Quick +) + +install(TARGETS qml_in_android_service + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + diff --git a/tests/manual/platforms/android/qml_in_android_service/Main.qml b/tests/manual/platforms/android/qml_in_android_service/Main.qml new file mode 100644 index 0000000000..6b8684e525 --- /dev/null +++ b/tests/manual/platforms/android/qml_in_android_service/Main.qml @@ -0,0 +1,100 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +import QtQuick +import QtQuick.Controls + +Rectangle { + id: mainRectangle + + property string colorStringFormat: "#1CB669" + + signal onClicked() + + color: colorStringFormat + + Text { + id: helloText + + text: "QML" + color: "white" + font.pixelSize: 72 + font.bold: true + fontSizeMode: Text.VerticalFit + horizontalAlignment: Text.AlignHCenter + + // Height is calculated based on display orientation + // from Screen height, dividing numbers are based on what what seem + // to look good on most displays + height: Screen.width > Screen.height ? Screen.height / 8 : (Screen.height / 2) / 8 + + anchors { + horizontalCenter: parent.horizontalCenter + top: parent.top + topMargin: 5 + } + } + + + Text { + id: changeColorText + + text: "Tap button to change Java view background color" + color: "white" + font.pixelSize: 58 + fontSizeMode: Text.Fit + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + + // Height and width are calculated based on display orientation + // from Screen height and width, dividing numbers are based on what seem to + // look good on most displays + height: Screen.width > Screen.height ? Screen.height / 8 : (Screen.height / 2) / 8 + width: Screen.width > Screen.height ? (Screen.width / 2) / 2 : Screen.width / 2 + + anchors { + horizontalCenter: parent.horizontalCenter + top: helloText.bottom + topMargin: Screen.height / 10 + } + } + + Button { + id: button + // Width is calculated from changeColorText which is calculated from Screen size + // dividing numbers are base on what seems to look good on most displays + width: changeColorText.width / 1.6 + height: changeColorText.height * 1.2 + + anchors { + + horizontalCenter: parent.horizontalCenter + top: changeColorText.bottom + topMargin: height / 5 + } + + onClicked: mainRectangle.onClicked() + + background: Rectangle { + id: buttonBackground + + radius: 14 + color: "#6200EE" + opacity: button.down ? 0.6 : 1 + scale: button.down ? 0.9 : 1 + } + + contentItem: Text { + id: buttonText + + text: "CHANGE COLOR" + color: "white" + font.pixelSize: 58 + minimumPixelSize: 10 + fontSizeMode: Text.Fit + font.bold: true + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } +} diff --git a/tests/manual/platforms/android/qml_in_android_service/main.cpp b/tests/manual/platforms/android/qml_in_android_service/main.cpp new file mode 100644 index 0000000000..3293373061 --- /dev/null +++ b/tests/manual/platforms/android/qml_in_android_service/main.cpp @@ -0,0 +1,10 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +#include <QGuiApplication> + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + return app.exec(); +} diff --git a/tests/manual/platforms/android/qml_in_java_based_android_project/.gitignore b/tests/manual/platforms/android/qml_in_java_based_android_project/.gitignore new file mode 100644 index 0000000000..347e252ef1 --- /dev/null +++ b/tests/manual/platforms/android/qml_in_java_based_android_project/.gitignore @@ -0,0 +1,33 @@ +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Log/OS Files +*.log + +# Android Studio generated files and folders +captures/ +.externalNativeBuild/ +.cxx/ +*.apk +output.json + +# IntelliJ +*.iml +.idea/ +misc.xml +deploymentTargetDropDown.xml +render.experimental.xml + +# Keystore files +*.jks +*.keystore + +# Google Services (e.g. APIs or Firebase) +google-services.json + +# Android Profiling +*.hprof diff --git a/tests/manual/platforms/android/qml_in_java_based_android_project/README.md b/tests/manual/platforms/android/qml_in_java_based_android_project/README.md new file mode 100644 index 0000000000..5199430bc8 --- /dev/null +++ b/tests/manual/platforms/android/qml_in_java_based_android_project/README.md @@ -0,0 +1,35 @@ +# What is this? + +This project is for manual testing of embedding QML into Android Services. It +loads a QML view and a regular Android view side by side, both hosted by a +Service, and wires them together. + +This application is meant to be built using Android Studio, with the Qt Gradle +plugin. There is no need to manually build the Qt project or edit it, only this +Android project. + +# How to sign the application +In order to sign the application, you must have a keystore file and list it in +a 'keystore.properties' file in the project root. + +1) Create 'keystore.properties' file in the same folder as this README +2) Add the following information to the file: + ``` + storePassword=somePassword + keyPassword=someOtherPassword + keyAlias=someKeyAlias + storeFile=/full/path/to/your/keystore.keystore + ``` + +After this, the app build.gradle will read that file and extract the required +information from it, and use that to sign the app before it is deployed. + +# How to configure QtBuild Gradle plugin +The app-level build.gradle already includes and configures the plugin, but it requires some information about the environment it's running in: The Qt installation directory, and the Qt for Android kit directory. + +1) Create 'qtbuild.properties' file in the same folder as this README +2) Add the following information to the file: + ``` + qtKitDir=/path/to/your/android/kit/ + qtPath=/path/to/your/Qt/installation // e.g. /etc/Qt/ + ``` diff --git a/tests/manual/platforms/android/qml_in_java_based_android_project/app/build.gradle b/tests/manual/platforms/android/qml_in_java_based_android_project/app/build.gradle new file mode 100644 index 0000000000..3aa396c87a --- /dev/null +++ b/tests/manual/platforms/android/qml_in_java_based_android_project/app/build.gradle @@ -0,0 +1,83 @@ +plugins { + id 'com.android.application' + id 'org.qtproject.qt.gradleplugin' version '0.1-SNAPSHOT+' +} + +def qtBuildPropertiesFile = rootProject.file('qtbuild.properties'); +def qtBuildProperties = new Properties(); +qtBuildProperties.load(new FileInputStream(qtBuildPropertiesFile)); +QtBuild { + projectPath file('../../qml_in_android_service') + qtKitDir file(qtBuildProperties['qtKitDir']) + qtPath file(qtBuildProperties['qtPath']) +} + +def keystorePropertiesFile = rootProject.file('keystore.properties'); +def keystoreProperties = new Properties(); +keystoreProperties.load(new FileInputStream(keystorePropertiesFile)); + +android { + signingConfigs { + debug { + storeFile file(keystoreProperties['storeFile']) + storePassword keystoreProperties['storePassword'] + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + } + } + namespace 'com.example.qml_in_java_based_android_project' + compileSdk 34 + + defaultConfig { + applicationId "com.example.qml_in_java_based_android_project" + minSdk 28 + targetSdk 34 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + signingConfig signingConfigs.debug + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.debug + } + debug { + signingConfig signingConfigs.debug + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + packagingOptions { + jniLibs { + useLegacyPackaging true + } + } + sourceSets { + main { + assets { + srcDirs 'assets' + } + jniLibs { + srcDirs 'libs' + } + } + } +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.9.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation "org.qtproject.qt.gradleplugin:QtGradlePlugin:0.1-SNAPSHOT" + implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' +} + diff --git a/tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/AndroidManifest.xml b/tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..896b975a0b --- /dev/null +++ b/tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/AndroidManifest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools"> + + <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> + + <application + android:allowBackup="true" + android:dataExtractionRules="@xml/data_extraction_rules" + android:fullBackupContent="@xml/backup_rules" + android:label="@string/app_name" + android:supportsRtl="true" + tools:targetApi="34" + android:theme="@style/Theme.AppCompat"> + <service + android:name=".QmlService" + android:enabled="true" + android:exported="true"/> + + <activity + android:name=".MainActivity" + android:configChanges="orientation|screenLayout|screenSize" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + +</manifest> diff --git a/tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/java/com/example/qml_in_java_based_android_project/MainActivity.java b/tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/java/com/example/qml_in_java_based_android_project/MainActivity.java new file mode 100644 index 0000000000..c619dce985 --- /dev/null +++ b/tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/java/com/example/qml_in_java_based_android_project/MainActivity.java @@ -0,0 +1,20 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +package com.example.qml_in_java_based_android_project; + +import androidx.appcompat.app.AppCompatActivity; +import android.content.Intent; +import android.os.Bundle; + +public class MainActivity extends AppCompatActivity +{ + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.view_main); + startService(new Intent(this, QmlService.class)); + finish(); + } +} diff --git a/tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/java/com/example/qml_in_java_based_android_project/QmlService.java b/tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/java/com/example/qml_in_java_based_android_project/QmlService.java new file mode 100644 index 0000000000..0c9de067e6 --- /dev/null +++ b/tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/java/com/example/qml_in_java_based_android_project/QmlService.java @@ -0,0 +1,204 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +package com.example.qml_in_java_based_android_project; + +import android.annotation.SuppressLint; +import android.app.Service; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.os.IBinder; +import android.util.Size; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.Gravity; + +import android.widget.Button; +import android.widget.Switch; +import android.widget.TextView; + +import java.util.Random; +import java.util.function.Consumer; + +import org.qtproject.qt.android.QtQuickView; +import org.qtproject.qt.android.QtQmlStatus; +import org.qtproject.qt.android.QtQmlStatusChangeListener; +import org.qtproject.example.qml_in_android_service.Qml_in_android_service.Main; + +@SuppressLint("UseSwitchCompatOrMaterialCode") +public class QmlService extends Service implements QtQmlStatusChangeListener +{ + private static final String TAG = "QmlService"; + private WindowManager m_windowManager; + private QtQuickView m_serviceView; + private final Main m_serviceViewComponent = new Main(); + private View m_mainView; + + private TextView m_qmlBackgroundColorTextView; + private TextView m_qmlStatusTextView; + private View m_colorBox; + private Switch m_connectionSwitch; + private int m_qmlSignalListenerId; + + @Override + public IBinder onBind(Intent intent) + { + throw new UnsupportedOperationException("Not yet implemented"); + } + + @Override + public void onCreate() + { + m_windowManager = getSystemService(WindowManager.class); + + getScreenSize((size) -> { + // Get the available geometry, and split it between the Android and QML UIs + m_serviceView = addQuickView(new Size(size.getWidth() / 2, size.getHeight())); + m_serviceViewComponent.setStatusChangeListener(this); + m_serviceView.loadComponent(m_serviceViewComponent); + + m_mainView = addMainView(new Size(size.getWidth() / 2, size.getHeight())); + connectToNativeControls(m_mainView); + }); + } + + /* + Draw the "main" view on the left side of the screen, with the native controls + */ + private View addMainView(final Size size) + { + final LayoutInflater inflater = getSystemService(LayoutInflater.class); + + final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( + size.getWidth(), size.getHeight(), + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + & ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, + PixelFormat.TRANSLUCENT); + layoutParams.gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL; + + View mainView = inflater.inflate(R.layout.view_main, null); + m_windowManager.addView(mainView, layoutParams); + return mainView; + } + + /* + Take size, and draw QtQuickView of that size on the right side of the screen + */ + private QtQuickView addQuickView(final Size size) + { + WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( + size.getWidth(), size.getHeight(), + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + & ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, + PixelFormat.TRANSLUCENT); + layoutParams.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL; + + QtQuickView serviceView = new QtQuickView(this); + m_windowManager.addView(serviceView, layoutParams); + return serviceView; + } + + /* + Draw empty View that fills the parent (screen in this case) to discover the available size, + report to consumer + */ + private void getScreenSize(final Consumer<Size> screenSizeConsumer) + { + final WindowManager.LayoutParams params = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + & ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, + PixelFormat.TRANSLUCENT); + final View view = new View(this) { + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) + { + m_windowManager.removeView(this); + screenSizeConsumer.accept(new Size(right - left, bottom - top)); + } + }; + m_windowManager.addView(view, params); + } + + @Override + public void onDestroy() + { + if (m_windowManager != null) { + if (m_serviceView != null) { + m_windowManager.removeView(m_serviceView); + m_serviceView = null; + } + if (m_mainView != null) { + m_windowManager.removeView(m_mainView); + m_mainView = null; + } + } + } + + /* + Connect listeners to the native controls + */ + private void connectToNativeControls(final View mainView) + { + m_qmlBackgroundColorTextView = mainView.findViewById(R.id.qmlBackgroundColorText); + m_qmlStatusTextView = mainView.findViewById(R.id.qmlStatus); + m_colorBox = mainView.findViewById(R.id.box); + + m_connectionSwitch = mainView.findViewById(R.id.switch1); + m_connectionSwitch.setOnCheckedChangeListener( + (buttonView, isChecked) -> connectSwitchListener(isChecked)); + + final Button changeColorButton = mainView.findViewById(R.id.button); + changeColorButton.setOnClickListener(this::onChangeColorButtonListener); + } + + public void onChangeColorButtonListener(View view) + { + m_serviceViewComponent.setColorStringFormat(getRandomColorString()); + + final String qmlColor = m_serviceView.getProperty("colorStringFormat"); + m_qmlBackgroundColorTextView.setText(qmlColor); + m_colorBox.setBackgroundColor(Color.parseColor(qmlColor)); + } + + @Override + public void onStatusChanged(QtQmlStatus status) + { + m_qmlStatusTextView.setText( + String.format("%s %s", getResources().getString(R.string.qml_view_status), status)); + // Once QML is loaded and the signal listener switch is not checked, + // connect to onClicked() signal in main.qml + if (status == QtQmlStatus.READY && m_connectionSwitch.isChecked()) + connectSwitchListener(m_connectionSwitch.isChecked()); + } + + private void connectSwitchListener(boolean checked) + { + if (checked) { + m_qmlSignalListenerId = m_serviceView.connectSignalListener( + "onClicked", Object.class, this::onQmlChangeColorButtonClicked); + } else { + m_serviceView.disconnectSignalListener(m_qmlSignalListenerId); + } + } + + public void onQmlChangeColorButtonClicked(String signal, Object o) + { + m_mainView.setBackgroundColor(Color.parseColor(getRandomColorString())); + } + + private String getRandomColorString() + { + Random rand = new Random(); + return String.format("#%06x", rand.nextInt(0xffffff + 1)); + } +} diff --git a/tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/res/layout/view_main.xml b/tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/res/layout/view_main.xml new file mode 100644 index 0000000000..65b6b3fe6c --- /dev/null +++ b/tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/res/layout/view_main.xml @@ -0,0 +1,142 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/mainLinear" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:baselineAligned="false" + android:background="#AF93DF"> + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:layout_marginTop="8dp" + android:gravity="center_horizontal" + android:includeFontPadding="false" + android:text="@string/java" + android:textColor="#FFFFFF" + android:textSize="24sp" + android:textStyle="bold" /> + + <TextView + android:id="@+id/qmlStatus" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:layout_marginTop="16dp" + android:gravity="center_horizontal" + android:text="@string/qml_view_status" + android:textColor="#FFFFFF"/> + + <LinearLayout + android:id="@+id/buttonAndSwitchLayout" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:gravity="center_horizontal" + android:orientation="horizontal" + android:layout_marginTop="16dp"> + + <LinearLayout + android:id="@+id/buttonLinearLayout" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_weight="1"> + + <TextView + android:id="@+id/changeColorText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:gravity="center_horizontal" + android:maxLines="3" + android:text="@string/change_qml_background" + android:textColor="#FFFFFF" /> + + <Button + android:id="@+id/button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:layout_marginTop="8dp" + android:text="@string/button" + android:textSize="14sp" /> + </LinearLayout> + + <LinearLayout + android:id="@+id/switchLinearLayout" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_weight="1"> + + <TextView + android:id="@+id/switchText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:gravity="center_horizontal" + android:maxLines="3" + android:text="@string/connect_qml_button_signal_listener" + android:textColor="#FFFFFF" /> + + <Switch + android:id="@+id/switch1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:textOff="@string/off" + android:textOn="@string/on" + android:showText="true" + android:checked="true" + tools:ignore="UseSwitchCompatOrMaterialXml" /> + </LinearLayout> + </LinearLayout> + + <LinearLayout + android:id="@+id/qmlColorLinear" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:padding="10dp"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_weight="1"> + + <TextView + android:id="@+id/qmlViewBackgroundText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:gravity="center_horizontal" + android:maxLines="2" + android:text="@string/qml_view_background_color" + android:textColor="#FFFFFF" /> + + <TextView + android:id="@+id/qmlBackgroundColorText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:gravity="center_horizontal" + android:textColor="#FFFFFF" /> + </LinearLayout> + + <View + android:id="@+id/box" + android:layout_width="100dp" + android:layout_height="50dp" + android:layout_gravity="center_horizontal" + android:background="@android:color/transparent" + android:layout_weight="0"/> + </LinearLayout> +</LinearLayout> diff --git a/tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/res/values/strings.xml b/tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/res/values/strings.xml new file mode 100644 index 0000000000..39d33f40c9 --- /dev/null +++ b/tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/res/values/strings.xml @@ -0,0 +1,12 @@ +<resources> + <string name="app_name">qml_in_java_based_android_project</string> + <string name="button">Change color</string> + <string name="java">Java</string> + <string name="change_qml_background">Tap button to change QML view background color</string> + <string name="connect_qml_button_signal_listener">QML Button listener connected</string> + <string name="on">On</string> + <string name="off">Off</string> + <string name="qml_view_status">QML view status: </string> + <string name="qml_view_background_color">QML view background color:</string> +</resources> + diff --git a/tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/res/xml/backup_rules.xml b/tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/res/xml/backup_rules.xml new file mode 100644 index 0000000000..04dd1acfe3 --- /dev/null +++ b/tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Sample backup rules file; uncomment and customize as necessary. + See https://developer.android.com/guide/topics/data/autobackup + for details. + Note: This file is ignored for devices older that API 31 + See https://developer.android.com/about/versions/12/backup-restore +--> +<full-backup-content> +</full-backup-content> + diff --git a/tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/res/xml/data_extraction_rules.xml b/tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 0000000000..9840b57766 --- /dev/null +++ b/tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Sample data extraction rules file; uncomment and customize as necessary. + See https://developer.android.com/about/versions/12/backup-restore#xml-changes + for details. +--> +<data-extraction-rules> + <cloud-backup> + </cloud-backup> +</data-extraction-rules> + diff --git a/tests/manual/platforms/android/qml_in_java_based_android_project/build.gradle b/tests/manual/platforms/android/qml_in_java_based_android_project/build.gradle new file mode 100644 index 0000000000..b92d690313 --- /dev/null +++ b/tests/manual/platforms/android/qml_in_java_based_android_project/build.gradle @@ -0,0 +1,4 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { +id 'com.android.application' version '7.4.1' apply false +} diff --git a/tests/manual/platforms/android/qml_in_java_based_android_project/gradle.properties b/tests/manual/platforms/android/qml_in_java_based_android_project/gradle.properties new file mode 100644 index 0000000000..dacb776f4a --- /dev/null +++ b/tests/manual/platforms/android/qml_in_java_based_android_project/gradle.properties @@ -0,0 +1,22 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true + diff --git a/tests/manual/platforms/android/qml_in_java_based_android_project/gradle/wrapper/gradle-wrapper.properties b/tests/manual/platforms/android/qml_in_java_based_android_project/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..62f495dfed --- /dev/null +++ b/tests/manual/platforms/android/qml_in_java_based_android_project/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/tests/manual/platforms/android/qml_in_java_based_android_project/settings.gradle b/tests/manual/platforms/android/qml_in_java_based_android_project/settings.gradle new file mode 100644 index 0000000000..8a59ffb868 --- /dev/null +++ b/tests/manual/platforms/android/qml_in_java_based_android_project/settings.gradle @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + google() + mavenCentral() + maven { + url "https://android.qt.io/maven/snapshots" + } + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + maven { + url "https://android.qt.io/maven/snapshots" + } + } +} + +rootProject.name = "qml_in_java_based_android_project" +include ':app' diff --git a/tests/manual/quickcontrols/CMakeLists.txt b/tests/manual/quickcontrols/CMakeLists.txt index e7f07e6110..fa3bf67e9d 100644 --- a/tests/manual/quickcontrols/CMakeLists.txt +++ b/tests/manual/quickcontrols/CMakeLists.txt @@ -10,6 +10,7 @@ if(LINUX) endif() add_subdirectory(headerview) add_subdirectory(imagine/musicplayer) +add_subdirectory(menus) add_subdirectory(qquickdialog) add_subdirectory(screenshots) add_subdirectory(sidepanel) diff --git a/tests/manual/quickcontrols/menus/CMakeLists.txt b/tests/manual/quickcontrols/menus/CMakeLists.txt new file mode 100644 index 0000000000..ce757613a1 --- /dev/null +++ b/tests/manual/quickcontrols/menus/CMakeLists.txt @@ -0,0 +1,47 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(menus VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 REQUIRED COMPONENTS Quick QuickControls2) + +qt_standard_project_setup(REQUIRES 6.8) + +qt_add_executable(appmenus + main.cpp +) + +qt_add_qml_module(appmenus + URI Menus + VERSION 1.0 + QML_FILES + Main.qml + SOURCES + cppsettings.cpp + cppsettings.h + main.cpp + RESOURCES + icons/warning.png + icons/warning@2x.png +) + +# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. +# If you are developing for iOS or macOS you should consider setting an +# explicit, fixed bundle identifier manually though. +set_target_properties(appmenus PROPERTIES +# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.appmenus + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +target_link_libraries(appmenus + PRIVATE + Qt6::Quick + Qt6::QuickControls2 +) diff --git a/tests/manual/quickcontrols/menus/Main.qml b/tests/manual/quickcontrols/menus/Main.qml new file mode 100644 index 0000000000..a75f2afd6c --- /dev/null +++ b/tests/manual/quickcontrols/menus/Main.qml @@ -0,0 +1,453 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtCore +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Dialogs + +ApplicationWindow { + id: window + width: 800 + height: 600 + visible: true + title: qsTr("Menus - style: %1").arg(currentStyle) + + required property string currentStyle + + Shortcut { + sequence: "Ctrl+Q" + onActivated: Qt.quit() + } + + Settings { + id: settings + + property alias windowX: window.x + property alias windowY: window.y + } + + menuBar: MenuBar { + visible: menuBarVisibleSwitch.checked + + Menu { + id: fileMenu + objectName: "file" + title: qsTr("&File") + popupType: popupTypeCombo.currentIndex + ContextAction { text: qsTr("&New...") } + ContextMenuItem { text: "menuItem" } + ContextAction { text: qsTr("&Open...") } + ContextAction { text: qsTr("&Save") } + ContextAction { text: qsTr("Save &As...") } + Menu { + title: qsTr("Sub...") + ContextAction { text: qsTr("Sub action 1") } + ContextAction { text: qsTr("Sub action 2") } + } + MenuSeparator { } + ContextAction { + text: qsTr("&Quit") + // This is needed for macOS since it takes priority over the Shortcut. + onTriggered: Qt.quit() + } + Action { + text: qsTr("Remove menu") + onTriggered: menuBar.removeMenu(fileMenu) + } + } + Menu { + id: editMenu + objectName: "edit" + title: qsTr("&Edit") + popupType: popupTypeCombo.currentIndex + ContextAction { + id: cutAction + text: qsTr("Cut") + enabled: textArea.selectedText.length > 0 + } + ContextAction { + text: qsTr("Copy") + enabled: textArea.selectedText.length > 0 + } + ContextAction { + text: qsTr("Paste") + enabled: textArea.activeFocus + } + + MenuSeparator {} + + Action { + text: qsTr("Checkable menu") + checkable: true + checked: true + } + Action { + text: qsTr("Remove menu") + onTriggered: menuBar.removeMenu(editMenu) + } + Menu { + id: editSubMenu + title: qsTr("Find / Replace") + Action { text: qsTr("&Find") } + } + + MenuSeparator {} + + ContextAction { + text: qsTr("Dummy Action") + shortcut: "Ctrl+I" + } + } + MenuBarItem { + id: explicitMenuBarItem + menu: Menu { + id: menuBarItemMenu + objectName: "MenuBarItem" + title: "MenuBarItem" + popupType: popupTypeCombo.currentIndex + ContextAction { text: qsTr("Action") } + Action { + text: qsTr("Remove menu") + onTriggered: menuBar.removeMenu(menuBarItemMenu) + } + } + } + } + + Component { + id: extraMenuComp + Menu { + id: extraMenu + objectName: "Extra" + title: qsTr("&Extra") + ContextAction { text: qsTr("&Trigger") } + Action { + text: qsTr("Remove Extra menu") + onTriggered: menuBar.removeMenu(extraMenu) + } + } + } + + ColumnLayout { + anchors.fill: parent + + Label { + text: qsTr("Right click on the window background to open a context menu. " + + "Right click on the TextArea to access its edit context menu.\n\n" + + "Things to check:\n\n" + + "- Do the menu items trigger their actions (check console for output)?\n" + + "- Do checkable menu items work?\n" + + "- Do the Edit menu items (in the MenuBar menu and edit context menu)" + + " work as expected with the TextArea?\n" + + " - Are they enabled/disabled as expected?\n" + + " - Does the TextArea keep focus after interacting with the Edit menu items?\n" + + "- Does adding and removing menu items work?\n" + + "- Do the menus in the MenuBar work?\n" + + "- Can you add and remove menus from the MenuBar?\n" + + "- Do shortcuts work?") + verticalAlignment: Text.AlignVCenter + wrapMode: Text.Wrap + + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: window.width * 0.5 + Layout.fillHeight: true + } + + GroupBox { + title: qsTr("Context menu") + + Layout.fillWidth: true + + ColumnLayout { + anchors.fill: parent + + RowLayout { + Label { + text: qsTr("Popup type") + } + + ComboBox { + id: popupTypeCombo + model: ["Default", "Item", "Window", "Native"] + onCurrentIndexChanged: CppSettings.popupType = currentIndex + currentIndex: CppSettings.popupType + } + } + + Row { + Button { + text: qsTr("Add action") + onClicked: backgroundContextMenu.appendAction() + } + Button { + text: qsTr("Remove action") + onClicked: backgroundContextMenu.removeLastAction() + } + + Button { + text: qsTr("Add sub-menu action") + onClicked: subMenu.appendAction() + } + Button { + text: qsTr("Remove sub-menu action") + onClicked: subMenu.removeLastAction() + } + } + } + } + + TextArea { + id: textArea + text: qsTr("Dummy TextArea to test disabled menu items") + + Layout.fillWidth: true + Layout.minimumHeight: 100 + + TapHandler { + objectName: "textAreaMouseTapHandler" + acceptedButtons: Qt.RightButton + onPressedChanged: if (pressed) editContextMenu.popup() + } + TapHandler { + objectName: "textAreaTouchTapHandler" + acceptedDevices: PointerDevice.TouchScreen + onLongPressed: editContextMenu.popup() + } + } + + Component { + id: menuBarItemComp + MenuBarItem { + } + } + + MessageDialog { + id: restartNeededDialog + buttons: MessageDialog.Ok + text: "Your current changes requires a restart to take effect!" + } + + GroupBox { + title: qsTr("MenuBar") + + Layout.fillWidth: true + + ColumnLayout { + anchors.fill: parent + + Row { + Switch { + text: qsTr("Don't use native menu bar") + checked: CppSettings.dontUseNativeMenuBar + + onClicked: { + CppSettings.dontUseNativeMenuBar = checked + restartNeededDialog.open() + } + } + Switch { + id: menuBarVisibleSwitch + text: qsTr("MenuBar visible") + checked: true + } + } + Row { + Button { + text: "Append menu" + onClicked: { + let menu = extraMenuComp.createObject(menuBar, { title: "Extra " + menuBar.count }) + menuBar.addMenu(menu) + } + } + Button { + text: "Prepend menu" + onClicked: { + let menu = extraMenuComp.createObject(menuBar, { title: "Extra " + menuBar.count }) + menuBar.insertMenu(0, menu) + } + } + Button { + text: qsTr("Add file menu") + onClicked: menuBar.addMenu(fileMenu) + } + Button { + text: "Change labels" + onClicked: { + fileMenu.title = "File changed" + cutAction.text = "Cut changed" + } + } + Button { + text: "toggle delegate" + onClicked: menuBar.delegate = menuBar.delegate ? null : menuBarItemComp + } + Switch { + text: "MenuBarItem visible" + checked: true + onCheckedChanged: explicitMenuBarItem.visible = checked + } + } + } + } + } + + TapHandler { + objectName: "backgroundMouseTapHandler" + acceptedButtons: Qt.RightButton + onPressedChanged: if (pressed) backgroundContextMenu.popup() + } + TapHandler { + objectName: "backgroundTouchTapHandler" + acceptedDevices: PointerDevice.TouchScreen + onLongPressed: backgroundContextMenu.popup() + } + + Component { + id: actionComponent + + Action {} + } + + component ContextAction: Action { + onCheckedChanged: (checked) => print("checked of \"" + text + "\" changed to " + checked) + onTriggered: print("triggered \"" + text + "\"") + } + + component ContextMenuItem: MenuItem { + onCheckedChanged: print("checked of \"" + text + "\" changed to " + checked) + onTriggered: print("triggered \"" + text + "\"") + } + + Menu { + id: backgroundContextMenu + objectName: "backgroundContextMenu" + popupType: popupTypeCombo.currentIndex + + function appendAction() { + let action = actionComponent.createObject(null, { text: qsTr("Extra context menu item") }) + backgroundContextMenu.addAction(action) + } + + function removeLastAction() { + // TODO: Can't use count here because it's 0: it uses contentModel->count(), but native menu items + // are not Qt Quick items, so we either need to document that you should use contentData.count + // or add an "actions" property. The problem with contentData is that it could contain + // non-Action objects. Another potential issue is that "It is not re-ordered when items are inserted or moved", + // making it unreliable as a general purpose container of actions if users add or remove them dynamically. + backgroundContextMenu.removeAction(backgroundContextMenu.actionAt(backgroundContextMenu.contentData.length - 1)) + } + + ContextAction { + text: qsTr("Context menu item") + shortcut: "A" + } + ContextMenuItem { + text: qsTr("Checkable context menu item") + checkable: true + } + ContextAction { + text: qsTr("Checked context menu item") + checkable: true + checked: true + shortcut: "C" + } + ContextAction { + text: qsTr("Disabled context menu item") + enabled: false + shortcut: "D" + } + ContextAction { + text: qsTr("Checked and disabled context menu item") + checkable: true + checked: true + enabled: false + shortcut: "E" + } + + MenuSeparator {} + + ContextAction { + text: qsTr("Context menu item with icon (name)") + icon.name: "mail-send" + } + + ContextAction { + text: qsTr("Context menu item with icon (source)") + icon.source: "qrc:/qt/qml/Menus/icons/warning.png" + } + + ContextAction { + text: qsTr("Context menu item with disabled icon (source)") + icon.source: "qrc:/qt/qml/Menus/icons/warning.png" + enabled: false + } + + MenuSeparator {} + + Menu { + id: subMenu + title: qsTr("Sub-menu") + objectName: title + popupType: backgroundContextMenu.popupType + + function appendAction() { + let action = actionComponent.createObject(null, { text: qsTr("Extra sub-menu item") }) + subMenu.addAction(action) + } + + function removeLastAction() { + subMenu.removeAction(subMenu.actionAt(subMenu.contentData.length - 1)) + } + + ContextAction { + text: qsTr("Sub-menu item") + } + ContextAction { + text: qsTr("Checkable sub-menu item") + checkable: true + shortcut: "G" + } + ContextAction { + text: qsTr("Checked sub-menu item") + checkable: true + checked: true + } + + MenuSeparator {} + + ContextAction { + text: qsTr("Disabled sub-menu item") + enabled: false + shortcut: "I" + } + ContextAction { + text: qsTr("Checked and disabled sub-menu item") + checkable: true + checked: true + enabled: false + shortcut: "J" + } + } + } + + Menu { + id: editContextMenu + objectName: "editContextMenu" + + ContextAction { + text: qsTr("Cut") + enabled: textArea.selectedText.length > 0 + } + ContextAction { + text: qsTr("Copy") + enabled: textArea.selectedText.length > 0 + } + ContextAction { + text: qsTr("Paste") + enabled: textArea.activeFocus + } + } +} + diff --git a/tests/manual/quickcontrols/menus/Menu.qml b/tests/manual/quickcontrols/menus/Menu.qml new file mode 100644 index 0000000000..0d18fca2ab --- /dev/null +++ b/tests/manual/quickcontrols/menus/Menu.qml @@ -0,0 +1,6 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick.Controls + +Menu {} diff --git a/tests/manual/quickcontrols/menus/cppsettings.cpp b/tests/manual/quickcontrols/menus/cppsettings.cpp new file mode 100644 index 0000000000..589cea916b --- /dev/null +++ b/tests/manual/quickcontrols/menus/cppsettings.cpp @@ -0,0 +1,43 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "cppsettings.h" + +#include <QCoreApplication> + +CppSettings::CppSettings(QObject *parent) : + QObject(parent), + mSettings("QtProject", "menus") +{ + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, dontUseNativeMenuBar()); +} + +bool CppSettings::dontUseNativeMenuBar() const +{ + return mSettings.value("dontUseNativeMenuBar").toBool(); +} + +void CppSettings::setDontUseNativeMenuBar(bool dontUseNativeMenuBar) +{ + const bool oldValue = this->dontUseNativeMenuBar(); + if (dontUseNativeMenuBar == oldValue) + return; + + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, dontUseNativeMenuBar); + mSettings.setValue("dontUseNativeMenuBar", dontUseNativeMenuBar); + emit dontUseNativeMenuBarChanged(); +} + +int CppSettings::popupType() const +{ + return mSettings.value("popupType").toInt(); +} + +void CppSettings::setPopupType(int newPopupType) +{ + const int oldValue = popupType(); + if (oldValue == newPopupType) + return; + mSettings.setValue("popupType", newPopupType); + emit popupTypeChanged(); +} diff --git a/tests/manual/quickcontrols/menus/cppsettings.h b/tests/manual/quickcontrols/menus/cppsettings.h new file mode 100644 index 0000000000..b6af1f9f09 --- /dev/null +++ b/tests/manual/quickcontrols/menus/cppsettings.h @@ -0,0 +1,38 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef CPPSETTINGS_H +#define CPPSETTINGS_H + +#include <QObject> +#include <QQmlEngine> +#include <QSettings> + +class CppSettings : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool dontUseNativeMenuBar READ dontUseNativeMenuBar WRITE setDontUseNativeMenuBar + NOTIFY dontUseNativeMenuBarChanged FINAL) + Q_PROPERTY(int popupType READ popupType WRITE setPopupType + NOTIFY popupTypeChanged FINAL) + QML_ELEMENT + QML_SINGLETON + +public: + explicit CppSettings(QObject *parent = nullptr); + + bool dontUseNativeMenuBar() const; + void setDontUseNativeMenuBar(bool dontUseNativeMenuBar); + + int popupType() const; + void setPopupType(int newPopupType); + +signals: + void dontUseNativeMenuBarChanged(); + void popupTypeChanged(); + +private: + QSettings mSettings; +}; + +#endif // CPPSETTINGS_H diff --git a/tests/manual/quickcontrols/menus/icons/warning.png b/tests/manual/quickcontrols/menus/icons/warning.png Binary files differnew file mode 100644 index 0000000000..590a61eb80 --- /dev/null +++ b/tests/manual/quickcontrols/menus/icons/warning.png diff --git a/tests/manual/quickcontrols/menus/icons/warning@2x.png b/tests/manual/quickcontrols/menus/icons/warning@2x.png Binary files differnew file mode 100644 index 0000000000..487fbafcfd --- /dev/null +++ b/tests/manual/quickcontrols/menus/icons/warning@2x.png diff --git a/tests/manual/quickcontrols/menus/main.cpp b/tests/manual/quickcontrols/menus/main.cpp new file mode 100644 index 0000000000..e9b4e6d5eb --- /dev/null +++ b/tests/manual/quickcontrols/menus/main.cpp @@ -0,0 +1,27 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include <QGuiApplication> +#include <QQmlApplicationEngine> +#include <QQuickStyle> + +int main(int argc, char *argv[]) +{ + QGuiApplication::setOrganizationName("QtProject"); + QGuiApplication::setApplicationName("menus"); + + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + engine.setInitialProperties({{ "currentStyle", QQuickStyle::name() }}); + QObject::connect( + &engine, + &QQmlApplicationEngine::objectCreationFailed, + &app, + []() { QCoreApplication::exit(-1); }, + Qt::QueuedConnection); + engine.loadFromModule("Menus", "Main"); + + return app.exec(); +} + diff --git a/tests/manual/svg/data/image/1.svg b/tests/manual/svg/data/image/1.svg new file mode 100644 index 0000000000..d5f27450c2 --- /dev/null +++ b/tests/manual/svg/data/image/1.svg @@ -0,0 +1,3 @@ +<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg"> + <image xlink:href="data.png" height="200" width="200" /> +</svg> diff --git a/tests/manual/svg/data/image/2.svg b/tests/manual/svg/data/image/2.svg new file mode 100644 index 0000000000..3f71e5ca23 --- /dev/null +++ b/tests/manual/svg/data/image/2.svg @@ -0,0 +1,3 @@ +<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg"> + <image xlink:href="qtlogo.png" height="200" width="200" /> +</svg> diff --git a/tests/manual/svg/data/image/3.svg b/tests/manual/svg/data/image/3.svg new file mode 100644 index 0000000000..85751587d9 --- /dev/null +++ b/tests/manual/svg/data/image/3.svg @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg viewBox="0 0 210 297"> +<image width="200" height="200" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFQAAAA8CAYAAAGPy61gAAAAAXNSR0IArs4c6QAAAJZlWElmTU0A KgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgExAAIAAAARAAAAWodp AAQAAAABAAAAbAAAAAAAAABIAAAAAQAAAEgAAAABQWRvYmUgSW1hZ2VSZWFkeQAAAAOgAQADAAAA AQABAACgAgAEAAAAAQAAAFSgAwAEAAAAAQAAADwAAAAAm/SbDAAAAAlwSFlzAAALEwAACxMBAJqc GAAAAi1pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6 bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDYuMC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRm PSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJk ZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6eG1wPSJodHRwOi8v bnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMu YWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+QWRvYmUgSW1h Z2VSZWFkeTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8dGlmZjpZUmVzb2x1dGlvbj43Mjwv dGlmZjpZUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmll bnRhdGlvbj4KICAgICAgICAgPHRpZmY6WFJlc29sdXRpb24+NzI8L3RpZmY6WFJlc29sdXRpb24+ CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgpg60/Z AAAXv0lEQVR4Ae1caZBdxXXuvve+bXaNFhAIJI0EWmZGCEaIJYVLBlxFlkpBbBGXKwRXYkOFwmAW B20uPYFGQrahAnHiiMTlCrHLCYqdhB/g2NiocDDgMEbSLEhCmhkJAVJmkWZ9672d7+v77tvfmzej wdYPGt7cvr2cPn369OlzTp8rKZBaetsvwuM08+WS0Xb8yXo08BrGjJCP7VWxTkZM2udSFZGupq1B J5KIzlua8LMsMDxPP72OhpcBrIE1p79JiMGhfv8W6TNFR9s9SemIP/DaZBrLzNCO45gqYau2jucs ZYiXChsLseDQxV+TMmCJ4NmTO9FAash495IEJbInE7Hm1YTMo0F/rHEwjkZxc06V3z47qdtnurnd Q8nBcWHPn4wLWxekG0bt00HJojzobre8v0ZL7y7HK7Pm1girsdp7zXmCCkpDBW1lcnhcGFV+oVRi MUkGPNONM+RiEaYXP3VWBJsWfiQd+9bk4ORtQmo4In9CaQjKMF+x5gWEN/MciByWv+jJoSs4tFEd SHdMN+TMySxyIrHCDPi6HVBJRUhSN+UMnRyawNqIIwKNmPQ7nmZ9CMVIrSd2z1GOM4jJpEdg+UxT /rJrOGZdUMzpHPPt37DdXU+WSqkwFa6P5FyFoxqN2uCwfc5lCd0x9acAqLWgVhyoHjZb+v02Fspt hvkYIX/NoYsenWz98BtO56WPuWuB2kW/eijUsHD+ZLotynLoRAjJ/xt7TSzdrqS1R4D9WaTX2JlM 7N0o9t39btwWre8/qfxn5vjt5ZF4chi09QZ3WxfSEJh+SuzYIZWd5kROWETt+H37xEYHANIVKg9Y CmZRTMVVD9bbB+c8JJe/92wgEB/2dTeHx9mhpdeXwMNQSVvUjn2kTg02BoOmaBCGPA0aezBFAU3T NbmZeK0v1DASHV9pmOZvcqty37hPNMGX9IWDdaHqCBlzNpLZrML+KlEfdcaiswFPw7Cs4bqYPVLI a0YN9hzoJP2WAC+SDcCclSXDHonkt1SUKYcWPGoc+ocJ8+CzI2bX85DbSnlS1WG9mVCLXOLldy/C p1LKV9kMu2cAj2pu3UQdhMqJ47fHFyyNq6TLUZZljdsqs+GyQRfs9c6lW25JNZiLZxDY+IOmf7S2 7RKlkuogsNRk6Fi2aYQYK6keywbIfD7QWH4DvoOmkf0ynJSG2tzau/NTLNvwathqPvbEzZDCy/ie nXK3qXQPhOXvheuyGyHvcraSfZDk3Xg3xy4PVkvD+Xn+FmW/XEwdd4WPXREeZWVW0vzsCLkkBT5V ldlFWW3zgEqRkd1ZrcyGqtAGFbYMoXZlFZfM5mKKZi3H23+Rav0az18eBQfnjJj9/ZAThrzag8SF olyVGCo/FZZI8WnSVEUSn5GmUeNTkzgpt6sa4RtMSS6juW/X7ja11xcfl3Uqoe7LB1pcoEDU6UO0 NiiM2oAYPnE6dO2S1Yl3+95LegD0qYx2lPw5YhINCjFlL8hJFU/iMBsX8f4hUaN8j+yTd9okh5co nFmfB1DFg4H5xTH1enpPKSCc9bqX2f9SWTLeeGBp+Fz6rKFAtkLjNcGaqkFgQV5J13mwf5dP2dwd 9geDwVCywX8OAmsGCEr0wTbjtIpvhVmZn2U0hGKxmK2EK1XLUFEqsz4IHTmyu2vZ1i3FRm/p33mj NMzXVcJJAvEMo6QaSz9WOTUZtHEnWAxQkTJLxTQzl0YQNSZ2wMF5j2Dfhw0xN6xFdMsHOy9TUfkF qEJ1ylG/7F7+9Z90LU6+KeQ2DQsSnucJ1XtuBAc7yehctEmvoK9KVKk59We1vlThKhTMOn8y0mcJ NTjZRCQ3YNDBvvavgCrtIiaqXc0Wq44MEIuLPmF1pQ5HnAihNWe+pZyJmMIBahipnRhcvVDZ/R9p dVNgIblzHexcFQXBSpNrSuXEFqZhdi3enAZx1dDTqsjh681PSUP+Uefi+E+WHxvyBa2LN2Oi2yE2 qoaDCevI/MfGdEMc+isG99RYKqLMUVDX8N2rDPl4ttbkAfSexeWTVwv7QDrqLRe4kje8/3QIllum tjAXARvcIWTYWXRqLjRb8WPKuXGZmAwKSx9ObW/v9a3tD9dX1daMmnbVmKqqOiMsoyySHGbKpS92 TBbil13imkw5JfGk4yiYjKlkOkGVhGniRDHpCnk03dkDkvf0YUmu02XYNW9c9nDEqNKGc16z9GvQ EMaL5OdTixpN8O7tqCmuE6W7VJaZiqIWz73m409e3t0UPbVhvzCGRiL3AvTf4keM07yLPPRaGTjU tOVF5MUxIWLYTDvURNzdMamWtW0fqbEOkZBzg2xWcZqKovosNWusE8uPNfpOLRoyO5dufQ47Ooif YTiiFafgdaomUctdPu9EHNqri3zrqT16x2Nt6T0wKD2Y9ovtdkebiI4PGKC+ugG1UEvUt2j3lEuV ncsuBAVFSsKIfl0Yzjb4Ct7sXxrW1gF1yf2fDqe1C4gqbhza8DnnOJUFyO32RNzac2SlKwFW9bcv NBz5l9IQT5Tb9dNBNHfCJIDeCDhClbJx6lidl23SZKFuFesf/FfUk0cLVy3dF7XZ+dwRvDcH9pgx c0Q9MFlPa36tSAyMbzQsuQ/yF9TTEl0jn9Ws0qxeweTEyNxYtHFiqs1UKVDdLjkwRgLtg90Cq16f tDNA0tUprHPxOdGPRiIwr7U1WwAI+vj/YrQ1GJnn9AWTcija3LuTtvtq/C4oJEmtNKItvTsPgLNX gJoBd5Ow+sJJWnzApXgIKK3Er6h9V4BummGoNGNq6feClrNWYLb27YJmplYAYiXLjR0CjXguNDz4 PCCj4df1CbMuBBCQUgknjuoc2TlbmFrweK0CsEJZVzgClEYck6a5/kD9V9/Nr6ZJI0PWP8Fhepcz keCxmUNn2LQYBUX4X7v2tAzOh1L6nQhWgqQN+I/DBKntWvzXh0E9HIthCEo8w+6TYkRFkl86dNHX pFkbIJJpVIikYzt3W1FnQeelmwwQ5x3UT8vnVQmSUQx5oLNp6xN6vvBNYsFVc691R3Nv+67mu3wP tp58soF1RLbt7XCVMx5bBWWGyKYTusSM6vgEKIrZSZ4E00rpXV+0F4bC+R6Ef3OdpiAatZwwV1iP PtuTPDNGftRSHc+nW4/v/EXnsm23dKwL05l3uKWvHW5Zfd67CMNT5bfr0vihMJ0vOnZeYXlEHZGA EuJ6EaG1s681+kwPry+AXHrzQavHjIybW/t33eAf7H97pL7FkM7ovUrKfyFfGnCQA066PcDUQjcI GdiIrK/ETC+PqBS0O75NBMULG80116xdlRykcyJ3o6S0HjoF/qZj3XOuoi3ED+BQ/z6Mtk6sCE+6 dIJKSFGYTrxvgt+XhCjJilMgKuFGdl7isreJhUasb+jL1JRAzcJ+EFiwNNenR+eoULrtSMJccXhP 7cIVEe1qZH3bh+Gq6FlhmHOrFa6uJiphgpIzADL6ngYW+fGNYrXsEPdAPKnfg5Xp8lw2RswrZeJu wy2lJGARbxikCJm+5Prh46LR4/NYxFqNO6A2J5K8FrrrGlzOsHlxuKxBKo0oKunZgA9fW4/c6Si6 GGK9OECWejXbkUdy6NyQcqlVG3jFlr5bbzhVlzr55PcgovYbAfNVOCEPuupgurfbOe9v4RJmNaBj YXR0JNs4S5saWc0yWU6FaV+3izKwwZtUsDYlWSaT6ImOwgr1DCdvipkWebmyFOU4dXV1mSPREBRJ Hjp5oPDqDbex2W3DmTLxzdArol8JozQQt0n+3/KI4sZqLBFxXeU8hRxxGsK6+Bgs9Wp2uMMYwbIL lo9L2ffSiGJQXhVJ227aJ3pUm3gOWoj4FWSmh04+YAd+Jrcs7MpcKi4lUykoJTqURpQdlJp0DAP3 vGGnY98rjmPbe7GEGVbIBRqDQ51neDoprEipBKaYFqplpowhlPCBzR5Abru4c5/dg12yduwZmRwY LxDOMIVDMp64bwPuJfpxjVDrWJ91InQ+uJwrDSPtYgHj8nDwOLrUXHLKyyNqCB/Oeq1weDJQ9Dtz IAPPcjsoqhYYTmtHE/G9OOvfTEFPQhn/nse0+ohVaomnqaLXAfS5Cc4xAbksIE9RVELspQBOsfRw HJ2djMHg6+byCxFW5tC7EwyEiJ9LzrMT9vUibq/i+7yTifsJk3ophPifYQJZLh9943vbG98d1UyM I/QB+KTXwxlxle+DyWojZGGPYquWSZXa9RhAvtDVtOXPQUnZ3LPDF5wjLO8YXHHR4iivTUj1lj7f 54yg79+0py4zsGNU+w3qqrzTXdIvkgMLcDWIxNtmun/gnixgp0x3jI7Zg+wVpShOkSCUjM24s/lB 1/Jt7+f0wrG55G7hr074VhpV1jv0i+Yl7iwwi7m+u2nTQV3HiX1oXSoSxkkQgNc5JVd4OogSNp2z 8GBgrRi7QD5NOMIejZzFcjbqwfEHk+/Bg3ZY7sAKKFX7fYz1YF8yOCkPRZvZcik+XURdYATqrQM3 kwF8HPt2ZzL5sufZWDv6jCqqEmb3JbT8d3cE96+ukzFMqjN3xtmNyuU9JNkGebCCMufW/ieRpHhi MZRrmODZDVmKlF+U/+62cv86IoariB7YatfOjKLZwDJ5Xte8hBVtsOqCt9hcTr28mQbTyinqFerd rqZta9mvvBydFmTB8J/PYrWUPRYlnbhwM01Qt+QR8L1GkkBmE1FvWc8HQeLk4DQ8iiiHHPOlKFBe 4B674oHYlR+G5/kjvv82G6uugeCn+y/bQCPQT1IeBXI2/fL3vqIthUBwuBqi8J1QqH4AjLYaxERM xSfEzKNd0VdNUKoRrK2x5oZAyINWdf0QXleBkLyK4YkMLxNbfJKmooDe8gy+jYrk61ZDVTN8K9za WfrZVCCmqOcI0AfwHyMNPH2fC8mfK3JYC5mEN1fPU5CZxTwcaHShJx6fBxFPugbhF5gwbaHyWnbF E4KWhOglWAchkUSgsHScd6DGvgUfBhRw45RCxJCwJewUqPeWDBiOPRfbYQngU8hfD+1qOW/57LOw WEq5kNCwROICFe4pqpBFT40SUGZQzNOzVQczaI4oxGGaMPVEsECMTvwPK67uf6f+qwwcdBMFyI6w hJXoph2pp/eOKDAsKgvTiDT3ta9FyT8jrHeNPRolB7OBbsSGJRI8UAr6kVY90QQ3y3CLoVcD4PuQ J/ypYJQAXb54NhW8KO5Jgoiifgr6zqN6WHhTaWDrPH0sroutPEaszWpL68q7v8ZuegshNOthC4Cw pHzObnJg0yKiAXLDVp/rXrL5RwwI6Wi7l6JGwftajWjlV6GxXGsPT0RQFsos29QoVdri/PU7yj3o 35hoMD44cWvPsq0/11ck9OeQmLw2gY199fvfWJjobd8NxP4UHByQVsrTQj4Bw9CdYo9HhwDt29Fj D+xm6MRG9YIJs1Zfm9Dr23HJ1utw//mUVRt62B6D1o+e+BVwGsSLBu6/aMJq69grOsS9Cd+EbcUQ 66rHclcDXWc/5ahNMwIPWQnOtOyJ+Bc1MQlkO7YutzfSBuw3cNZRp8b/AV5vx89HEcMQSX7woJ/4 VgDWAMlTi14P1V6+Moo+/6h9BOhAwnZcEp6kWtfdtO0RtP2RUePXS4FqJHgWIZTpsWbYZRlvpqMj rF0rmuJDm+6pp3sgEtx5pJkT1OWLSQSlBrDNf4Yt9rzGg9ucWzvlpBvs83XAYXIFiEjk6WLNjOnx WHoCkG9S1CU+PDeJr2C+hCurPazynC9XL79JOzMgDh/Gto/oLc6dL9UkeI/urjdBsF87hkm1TwxE LYeh/cxHo1GMLzuT5ya7Qfxf4/em/kmdfw2NTlJcuKmE6zdVW+5xPjKUXJHEKe5Ljsa+0L108w89 bCC7rI519yZa+tv/wqwJfRfEpBybjniJ44T3w3U2pkx5fffiLT1e/A91ZnoN4K77ISKPP8/7FtwM dHYt25ZjApabdHZdGl7vrkcRcvZN7SlztCsvs/DZHabIz6hTCmZCBi1u31HHSb7NMm5NPJTHFZCH f0KZBWJ6S5/qOtVD+kBMfptVi/P5JrbWwVQpMcJ3gPwpn/pskpLcn5sou73EQ65E8vxOynCg25Vo NI3i6XBNBqweWNq8aLWjyZOJhvozrBzY38Ma6U4eE+oVKxi4yjL+qTjpw0Mk4SWkBbcy0y8s56/G MjGZ4jCI7t4ICnEVZC4IIvmRiAG639ktt/z7IoSOXnTmcLJDygSNF3wQ+DKCHW7ARcgIICBUA3vM XSQcq8J0JnS0EqFnFoNv00gz7qg5j58dSHEu0DisT+L5G1YDQS2yxIojIXxdoOpSl83TI6g3AYAC wLSrlZeA8wXGQKJhgO0OcqRAU2l3w1KlmVIgXDBtHjQ+vb2CTrojO7PMfWa3nGF+ZhyaPZhLv+yS TD4110zBDHJltmsWNJLEh0MKcQvQ4xK2ZpTL0CDNc25j1zeuTWvlXiDNBo5ZiMyYQ0FHWOZav66P HXOvNPSWT3HMkRURBJHIUYY5IpUjexY6eVk9WeNsuhS3vwNCixUhE2aDjqhJ7Yh0mwoyGvdZJqQ3 7MwIqjGCPccob6EuD8jAAgLUWx4FPJH1hZlSRxmegTQ9HQ8RPOhjMpQTW/4oAbgprAZ6XPnmWOJK qGMsnh7sFKSP6zEzgrrY8CROwOqpl6atBRX0RT25sdqF7vpL48dat5suf+KLPkSFBPB1UgSAfsnh 3EXKcDpI/hkt+WbK/R8TRWdOUFeQ06NEO+WLKfwUo6Gog/IdN6XUQQ/AqkFwONx3lSZEV5lz+CGm /A78Aridgtq0QThUy6jmrHlvzyLQ8Q73VEZ0eQVpxNH3MvoQwyJNd4krGMFtUhEyRaG5KFXBSoqZ jTW34TL+Lt1O2+8ZvQ8EuRoT/x9rfg0OQL2VEfam9Ubqjtk/crfmcN/F9VXJM6NPdTdteYQwtX4L f0CP6NHyQ5nJv4PvoApmJtpnxtJOJBxK0pFz2I/J04mPLQ+PQQsYSIl4XuByLESWpXDxfLG618z/ zJygmTH9yeHJBLjw+ea+3bexeMP+HXriOg95CqLedKDuQYqBL+P3CiZOu56BiORacjO+fRR92ML/ xX/v4Z2q+yU9Vm1v3+OexGjAfLeEhdS76wVZE/hjmJ5YUk9nQgMekvwSmjLbcLQI+ig5qqgT82MF 1MLUd7YlhyY70RQqHWJV8WU7iMwIzGH86IE673Q+pmdmcM5LqQgsmxCuTb4PYmhu5aUyD5Hu1WEQ LSwZ3fbGIkRm6AiPTHcvx4n7T01Ybyx6mJOlTivxjwMYtOXX9O66CUfUT0GwIPRPclcxZrBl0GfC FIXz2lnXdcXXj8O0rIGYQMioK4e10eENmPVs6dv1Vzjk/t6JAdXzMD1nh6AZxCgI6H3yI3zgkDDl jloz+DI/zco0mSKXcvdptx+bgviw21+35lXfCO5imAwDJcrxEj6lsUwEhUQg3T/vfUk1xaii9fju W6HJ/oziFV/Nl1qwqcBMK8plSmB5DbQTl59IM8AJnEsHyQngewqq4wCOklFIMerdsHEULgLlKtx0 P9+1bMt3XGJu5wGivCvt5uM7dyOwf5Oj/aBagJcjKsDKhPRBaUN8MuT8KMbu0mMLcU7LcOXUwAqY D0gLIdoXoW6uiz/lfNkFy5tm7utsc2gudL654gDLnr6kIzVIDO/HVuSIGDi7HpNPiwzKTcRGJ1zn cniytb/993Gr8ZKOVKvkGoMjUKfFvzCD0XgAUpB490rEgz8mtqTVmJb9LJxGIpwE7sH8Hz9BK8IK k6YahiBKyOEqfLr8m4Bj3sx/qiBNVDg3+E5wzcfbjyAE9Urowd7iVDTKrDdybyvi2IVBXER2AP8/ LCbYZ33cqQFii9GElKIKoiEGL9Y1McP+oPX4463kUPaPK/tGeJRexI+67++amFzIGOI9Sb/D0fHJ eV1Lt6zDB84jFwiHkmQ5yeU86pTwBTDOWCXgqovjdKf+6H4YxW36W05avsKLEPLj2v0QcNvQuXjz WU/OE5kLhEML6OISC8HdVJFgGCCGlCJQyz/Kut8yMSmHcTuBfz4CYx+1zsYboBpe5Vweow4rGAfG J9OFSlAXO5dwwHHmp64H6DyeSgbwnZgUPfhuloRsxf2UVgNpaOTD/X+TqEOdRtk7pQAAAABJRU5E rkJggg== " id="image1" x="10" y="10" /> +</svg> diff --git a/tests/manual/svg/data/image/data.png b/tests/manual/svg/data/image/data.png Binary files differnew file mode 100644 index 0000000000..7b660be3de --- /dev/null +++ b/tests/manual/svg/data/image/data.png diff --git a/tests/manual/svg/data/image/qtlogo.png b/tests/manual/svg/data/image/qtlogo.png Binary files differnew file mode 100644 index 0000000000..7b660be3de --- /dev/null +++ b/tests/manual/svg/data/image/qtlogo.png diff --git a/tests/manual/tableview/abstracttablemodel/main.qml b/tests/manual/tableview/abstracttablemodel/main.qml index 5578e86dba..9c43af2f99 100644 --- a/tests/manual/tableview/abstracttablemodel/main.qml +++ b/tests/manual/tableview/abstracttablemodel/main.qml @@ -85,6 +85,22 @@ ApplicationWindow { } CheckBox { + id: movableColumnEnabled + checkable: true + checked: false + Layout.fillWidth: false + text: "Reorder columns" + } + + CheckBox { + id: movableRowEnabled + checkable: true + checked: false + Layout.fillWidth: false + text: "Reorder rows" + } + + CheckBox { id: enableAnimation checkable: true checked: true @@ -202,6 +218,14 @@ ApplicationWindow { text: "Clear selection" onClicked: tableView.selectionModel.clearSelection() } + Button { + text: "Clear column reordering" + onClicked: tableView.clearColumnReordering() + } + Button { + text: "Clear row reordering" + onClicked: tableView.clearRowReordering() + } } } @@ -450,7 +474,7 @@ ApplicationWindow { } } - TableView { + HorizontalHeaderView { id: topHeader objectName: "topHeader" anchors.left: centerScrollView.left @@ -465,8 +489,11 @@ ApplicationWindow { } delegate: Rectangle { + required property bool containsDrag implicitHeight: topHeader.height implicitWidth: 20 + border.width: containsDrag ? 1 : 0 + border.color: containsDrag ? window.palette.text : window.palette.alternateBase color: window.palette.alternateBase Label { anchors.centerIn: parent @@ -481,10 +508,11 @@ ApplicationWindow { syncView: tableView syncDirection: Qt.Horizontal + movableColumns: movableColumnEnabled.checked resizableColumns: resizableColumnsEnabled.checked } - TableView { + VerticalHeaderView { id: leftHeader objectName: "leftHeader" anchors.left: menu.right @@ -499,8 +527,11 @@ ApplicationWindow { } delegate: Rectangle { + required property bool containsDrag implicitHeight: 50 implicitWidth: leftHeader.width + border.width: containsDrag ? 1 : 0 + border.color: containsDrag ? window.palette.text : window.palette.alternateBase color: window.palette.alternateBase Label { anchors.centerIn: parent @@ -515,6 +546,7 @@ ApplicationWindow { syncView: tableView syncDirection: Qt.Vertical + movableRows: movableRowEnabled.checked resizableRows: resizableRowsEnabled.checked } |