aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/auto/cmake/CMakeLists.txt1
-rw-r--r--tests/auto/cmake/shared_qml_module/CMakeLists.txt14
-rw-r--r--tests/auto/cmake/shared_qml_module/Scheduler/CMakeLists.txt33
-rw-r--r--tests/auto/cmake/shared_qml_module/Scheduler/MainScreen.qml5
-rw-r--r--tests/auto/cmake/shared_qml_module/Scheduler/schedulerglobal.h15
-rw-r--r--tests/auto/cmake/shared_qml_module/Scheduler/task.cpp47
-rw-r--r--tests/auto/cmake/shared_qml_module/Scheduler/task.h34
-rw-r--r--tests/auto/cmake/shared_qml_module/SchedulerApp/CMakeLists.txt44
-rw-r--r--tests/auto/cmake/shared_qml_module/SchedulerApp/Main.qml12
-rw-r--r--tests/auto/cmake/shared_qml_module/SchedulerApp/main.cpp21
-rw-r--r--tests/auto/cmake/shared_qml_module/external/CMakeLists.txt1
-rw-r--r--tests/auto/cmake/shared_qml_module/external/nested/module/CMakeLists.txt15
-rw-r--r--tests/auto/cmake/shared_qml_module/external/nested/module/Test.qml3
-rw-r--r--tests/auto/cmake/shared_qml_module/tests/CMakeLists.txt1
-rw-r--r--tests/auto/cmake/shared_qml_module/tests/auto/CMakeLists.txt1
-rw-r--r--tests/auto/cmake/shared_qml_module/tests/auto/unit/CMakeLists.txt40
-rw-r--r--tests/auto/cmake/shared_qml_module/tests/auto/unit/dummy.cpp1
-rw-r--r--tests/auto/cmake/shared_qml_module/tests/auto/unit/tst_models.cpp45
-rw-r--r--tests/auto/cmake/test_generate_qmlls_ini/main.cpp12
-rw-r--r--tests/auto/core/CMakeLists.txt4
-rw-r--r--tests/auto/qml/CMakeLists.txt8
-rw-r--r--tests/auto/qml/debugger/shared/qqmldebugprocess.cpp2
-rw-r--r--tests/auto/qml/ecmascripttests/TestExpectations3
-rw-r--r--tests/auto/qml/ecmascripttests/test262runner.cpp18
-rw-r--r--tests/auto/qml/linebylinelex/BLACKLIST5
-rw-r--r--tests/auto/qml/linebylinelex/CMakeLists.txt22
-rw-r--r--tests/auto/qml/linebylinelex/tst_linebylinelex.cpp15
-rw-r--r--tests/auto/qml/qjsengine/tst_qjsengine.cpp2
-rw-r--r--tests/auto/qml/qjsonbinding/tst_qjsonbinding.cpp2
-rw-r--r--tests/auto/qml/qmlcachegen/CMakeLists.txt1
-rw-r--r--tests/auto/qml/qmlcachegen/data/aotstats/AotstatsClean.qml11
-rw-r--r--tests/auto/qml/qmlcachegen/data/aotstats/AotstatsMixed.qml7
-rw-r--r--tests/auto/qml/qmlcachegen/data/aotstats/cachegentest.qrc5
-rw-r--r--tests/auto/qml/qmlcachegen/data/aotstats/qmldir3
-rw-r--r--tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp133
-rw-r--r--tests/auto/qml/qmlcppcodegen/CMakeLists.txt4
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt29
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/writableVariantMap.h31
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/writeVariantMap.qml10
-rw-r--r--tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp17
-rw-r--r--tests/auto/qml/qmlformat/data/enumWithValues.formatted.qml12
-rw-r--r--tests/auto/qml/qmlformat/data/enumWithValues.qml12
-rw-r--r--tests/auto/qml/qmlformat/tst_qmlformat.cpp4
-rw-r--r--tests/auto/qml/qmlimportscanner/data/CompositeSingleton.json36
-rw-r--r--tests/auto/qml/qmlimportscanner/data/CompositeWithEnum.json32
-rw-r--r--tests/auto/qml/qmlimportscanner/data/CompositeWithinSingleton.json36
-rw-r--r--tests/auto/qml/qmlimportscanner/data/Drawer.qml.json32
-rw-r--r--tests/auto/qml/qmlimportscanner/data/Imports.json36
-rw-r--r--tests/auto/qml/qmlimportscanner/data/ListProperty.qml.json44
-rw-r--r--tests/auto/qml/qmlimportscanner/data/QTBUG-45916.js.json36
-rw-r--r--tests/auto/qml/qmlimportscanner/data/Simple.qml.json36
-rw-r--r--tests/auto/qml/qmlimportscanner/data/Singleton.json36
-rw-r--r--tests/auto/qml/qmlimportscanner/data/Things.json36
-rw-r--r--tests/auto/qml/qmlimportscanner/data/javascriptMethods.qml.json36
-rw-r--r--tests/auto/qml/qmlimportscanner/data/localImport.qml.json36
-rw-r--r--tests/auto/qml/qmlimportscanner/data/parentEnum.qml.json36
-rw-r--r--tests/auto/qml/qmlimportscanner/data/qmldirImportAndDepend.qml.json36
-rw-r--r--tests/auto/qml/qmlimportscanner/data/qtQmlOnly.qml.json32
-rw-r--r--tests/auto/qml/qmlimportscanner/data/rootPath.json42
-rw-r--r--tests/auto/qml/qmllint/data/Qtbug111015/qmldir3
-rw-r--r--tests/auto/qml/qmllint/data/Qtbug111015/qtbug111015.qmltypes20
-rw-r--r--tests/auto/qml/qmllint/data/jsonArrayIsRecognized.qml8
-rw-r--r--tests/auto/qml/qmllint/data/jsonObjectIsRecognized.qml8
-rw-r--r--tests/auto/qml/qmllint/data/something.qml2
-rw-r--r--tests/auto/qml/qmllint/lintplugin.cpp2
-rw-r--r--tests/auto/qml/qmllint/tst_qmllint.cpp21
-rw-r--r--tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp2
-rw-r--r--tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp96
-rw-r--r--tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h45
-rw-r--r--tests/auto/qml/qqmlbinding/CMakeLists.txt2
-rw-r--r--tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp16
-rw-r--r--tests/auto/qml/qqmlconnections/CMakeLists.txt1
-rw-r--r--tests/auto/qml/qqmlengine/tst_qqmlengine.cpp5
-rw-r--r--tests/auto/qml/qqmlimport/CMakeLists.txt8
-rw-r--r--tests/auto/qml/qqmlimport/qmlimports.qt.conf3
-rw-r--r--tests/auto/qml/qqmlimport/tst_qqmlimport.cpp18
-rw-r--r--tests/auto/qml/qqmllanguage/data/asValueType.qml7
-rw-r--r--tests/auto/qml/qqmllanguage/data/asValueTypeGood.qml35
-rw-r--r--tests/auto/qml/qqmllanguage/data/invokableCtors.qml12
-rw-r--r--tests/auto/qml/qqmllanguage/data/jsonArrayProperty.qml191
-rw-r--r--tests/auto/qml/qqmllanguage/data/nestedVectors.qml27
-rw-r--r--tests/auto/qml/qqmllanguage/data/optimizedSequenceShift.qml14
-rw-r--r--tests/auto/qml/qqmllanguage/testtypes.cpp8
-rw-r--r--tests/auto/qml/qqmllanguage/testtypes.h101
-rw-r--r--tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp281
-rw-r--r--tests/auto/qml/qqmlmoduleplugin/tst_qqmlmoduleplugin.cpp8
-rw-r--r--tests/auto/qml/qqmlparser/tst_qqmlparser.cpp72
-rw-r--r--tests/auto/qml/qqmlqt/data/qtbug_125495.qml5
-rw-r--r--tests/auto/qml/qqmlqt/tst_qqmlqt.cpp18
-rw-r--r--tests/auto/qml/qqmltimer/CMakeLists.txt1
-rw-r--r--tests/auto/qml/qqmltimer/tst_qqmltimer.cpp20
-rw-r--r--tests/auto/qml/qqmlvaluetypes/data/constructors.qml14
-rw-r--r--tests/auto/qml/qqmlvaluetypes/data/matrix4x4_invokables.qml4
-rw-r--r--tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp23
-rw-r--r--tests/auto/qml/qqmlxmllistmodel/tst_qqmlxmllistmodel.cpp12
-rw-r--r--tests/auto/qml/qv4estable/tst_qv4estable.cpp6
-rw-r--r--tests/auto/qml/qv4mm/tst_qv4mm.cpp146
-rw-r--r--tests/auto/qmldom/domdata/domitem/crashes/bracketsInBinding.qml5
-rw-r--r--tests/auto/qmldom/domdata/domitem/fileLocationRegions/comments.qml12
-rw-r--r--tests/auto/qmldom/domdata/domitem/lambdas.qml41
-rw-r--r--tests/auto/qmldom/domitem/tst_qmldomitem.h503
-rw-r--r--tests/auto/qmldom/reformatter/tst_reformatter.h7
-rw-r--r--tests/auto/qmlls/modules/data/highlighting/basic.qml11
-rw-r--r--tests/auto/qmlls/modules/data/highlighting/bigFile.qml351
-rw-r--r--tests/auto/qmlls/modules/data/renameUsages/RenameMe.qml5
-rw-r--r--tests/auto/qmlls/modules/data/renameUsages/RenameMe2.ui.qml5
-rw-r--r--tests/auto/qmlls/modules/data/renameUsages/main.qml6
-rw-r--r--tests/auto/qmlls/modules/tst_qmlls_modules.cpp276
-rw-r--r--tests/auto/qmlls/modules/tst_qmlls_modules.h7
-rw-r--r--tests/auto/qmlls/qqmlcodemodel/data/FileA.qml5
-rw-r--r--tests/auto/qmlls/qqmlcodemodel/data/FileA2.qml5
-rw-r--r--tests/auto/qmlls/qqmlcodemodel/data/FileB.qml5
-rw-r--r--tests/auto/qmlls/qqmlcodemodel/tst_qmlls_qqmlcodemodel.cpp54
-rw-r--r--tests/auto/qmlls/qqmlcodemodel/tst_qmlls_qqmlcodemodel.h2
-rw-r--r--tests/auto/qmlls/utils/CMakeLists.txt30
-rw-r--r--tests/auto/qmlls/utils/data/highlights/Identifiers.qml37
-rw-r--r--tests/auto/qmlls/utils/data/highlights/bindings.qml12
-rw-r--r--tests/auto/qmlls/utils/data/highlights/comments.qml19
-rw-r--r--tests/auto/qmlls/utils/data/highlights/enums.qml11
-rw-r--r--tests/auto/qmlls/utils/data/highlights/identifiers.qml37
-rw-r--r--tests/auto/qmlls/utils/data/highlights/imports.qml9
-rw-r--r--tests/auto/qmlls/utils/data/highlights/literals.qml14
-rw-r--r--tests/auto/qmlls/utils/data/highlights/methodAndSignal.qml11
-rw-r--r--tests/auto/qmlls/utils/data/highlights/objectAndComponent.qml11
-rw-r--r--tests/auto/qmlls/utils/data/highlights/pragmas.qml10
-rw-r--r--tests/auto/qmlls/utils/data/highlights/properties.qml13
-rw-r--r--tests/auto/qmlls/utils/data/highlights/scriptExpressions.qml116
-rw-r--r--tests/auto/qmlls/utils/data/renaming/RenameMe.qml5
-rw-r--r--tests/auto/qmlls/utils/data/renaming/RenameMe2.ui.qml5
-rw-r--r--tests/auto/qmlls/utils/data/renaming/RenamedByQmldir.qml4
-rw-r--r--tests/auto/qmlls/utils/data/renaming/UnrelatedFile.qml0
-rw-r--r--tests/auto/qmlls/utils/data/renaming/main.qml7
-rw-r--r--tests/auto/qmlls/utils/data/renaming/qmldir6
-rw-r--r--tests/auto/qmlls/utils/tst_qmlls_documentationHints.cpp134
-rw-r--r--tests/auto/qmlls/utils/tst_qmlls_documentationHints.h24
-rw-r--r--tests/auto/qmlls/utils/tst_qmlls_highlighting.cpp650
-rw-r--r--tests/auto/qmlls/utils/tst_qmlls_highlighting.h37
-rw-r--r--tests/auto/qmlls/utils/tst_qmlls_utils.cpp976
-rw-r--r--tests/auto/qmlls/utils/tst_qmlls_utils.h3
-rw-r--r--tests/auto/quick/CMakeLists.txt1
-rw-r--r--tests/auto/quick/platform/CMakeLists.txt6
-rw-r--r--tests/auto/quick/platform/android/CMakeLists.txt4
-rw-r--r--tests/auto/quick/platform/android/qtandroiditemmodel/CMakeLists.txt25
-rw-r--r--tests/auto/quick/platform/android/qtandroiditemmodel/testdata/src/org/qtproject/qt/android/tests/TestModel.java120
-rw-r--r--tests/auto/quick/platform/android/qtandroiditemmodel/tst_qtandroiditemmodel.cpp182
-rw-r--r--tests/auto/quick/qquickaccessible/tst_qquickaccessible.cpp22
-rw-r--r--tests/auto/quick/qquickanimations/tst_qquickanimations.cpp45
-rw-r--r--tests/auto/quick/qquickflickable/tst_qquickflickable.cpp2
-rw-r--r--tests/auto/quick/qquickimage/data/multiframeAsyncRetain.qml7
-rw-r--r--tests/auto/quick/qquickimage/tst_qquickimage.cpp61
-rw-r--r--tests/auto/quick/qquickitem2/CMakeLists.txt5
-rw-r--r--tests/auto/quick/qquickitem2/data/embedded.qml30
-rw-r--r--tests/auto/quick/qquickitem2/data/embedded_FocusScope.qml37
-rw-r--r--tests/auto/quick/qquickitem2/tst_qquickitem.cpp82
-rw-r--r--tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml43
-rw-r--r--tests/auto/quick/qquicklistview/tst_qquicklistview.cpp2
-rw-r--r--tests/auto/quick/qquickpath/tst_qquickpath.cpp103
-rw-r--r--tests/auto/quick/qquickpathview/tst_qquickpathview.cpp2
-rw-r--r--tests/auto/quick/qquickpixmapcache/CMakeLists.txt6
-rw-r--r--tests/auto/quick/qquickpixmapcache/data/slowLoading.qml13
-rw-r--r--tests/auto/quick/qquickpixmapcache/deviceloadingimage.cpp35
-rw-r--r--tests/auto/quick/qquickpixmapcache/deviceloadingimage.h6
-rw-r--r--tests/auto/quick/qquickpixmapcache/tst_qquickpixmapcache.cpp48
-rw-r--r--tests/auto/quick/qquickshape/data/filltransform.qml58
-rw-r--r--tests/auto/quick/qquickshape/tst_qquickshape.cpp37
-rw-r--r--tests/auto/quick/qquicktableview/data/reordertableview.qml52
-rw-r--r--tests/auto/quick/qquicktableview/tst_qquicktableview.cpp247
-rw-r--r--tests/auto/quick/qquicktextedit/data/hAlignVisual.qml6
-rw-r--r--tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp56
-rw-r--r--tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp11
-rw-r--r--tests/auto/quick/qquickwindow/data/visibilityDoesntClobberWindowState.qml5
-rw-r--r--tests/auto/quick/qquickwindow/tst_qquickwindow.cpp36
-rw-r--r--tests/auto/quickcontrols/accessibility/data/actionAccessibility/button.qml12
-rw-r--r--tests/auto/quickcontrols/accessibility/tst_accessibility.cpp22
-rw-r--r--tests/auto/quickcontrols/controls/CMakeLists.txt1
-rw-r--r--tests/auto/quickcontrols/controls/basic/tst_basic.cpp3
-rw-r--r--tests/auto/quickcontrols/controls/data/combobox/shader.frag19
-rw-r--r--tests/auto/quickcontrols/controls/data/combobox/shader.frag.qsbbin0 -> 577 bytes
-rw-r--r--tests/auto/quickcontrols/controls/data/tst_abstractbutton.qml172
-rw-r--r--tests/auto/quickcontrols/controls/data/tst_combobox.qml40
-rw-r--r--tests/auto/quickcontrols/controls/data/tst_splitview.qml99
-rw-r--r--tests/auto/quickcontrols/controls/fluentwinui3/BLACKLIST10
-rw-r--r--tests/auto/quickcontrols/controls/fluentwinui3/CMakeLists.txt38
-rw-r--r--tests/auto/quickcontrols/controls/fluentwinui3/dependencies.qml6
-rw-r--r--tests/auto/quickcontrols/controls/fluentwinui3/dummy_imports.qml12
-rw-r--r--tests/auto/quickcontrols/controls/fluentwinui3/tst_fluentwinui3.cpp13
-rw-r--r--tests/auto/quickcontrols/controls/fusion/tst_fusion.cpp3
-rw-r--r--tests/auto/quickcontrols/controls/imagine/tst_imagine.cpp3
-rw-r--r--tests/auto/quickcontrols/controls/ios/tst_ios.cpp3
-rw-r--r--tests/auto/quickcontrols/controls/macos/tst_macos.cpp3
-rw-r--r--tests/auto/quickcontrols/controls/material/tst_material.cpp3
-rw-r--r--tests/auto/quickcontrols/controls/universal/tst_universal.cpp3
-rw-r--r--tests/auto/quickcontrols/controls/windows/tst_windows.cpp3
-rw-r--r--tests/auto/quickcontrols/focus/tst_focus.cpp14
-rw-r--r--tests/auto/quickcontrols/font/tst_font.cpp2
-rw-r--r--tests/auto/quickcontrols/palette/data/comboBoxPopupWithApplicationWindow.qml32
-rw-r--r--tests/auto/quickcontrols/palette/data/comboBoxPopupWithThemeDefault.qml17
-rw-r--r--tests/auto/quickcontrols/palette/data/comboBoxPopupWithWindow.qml33
-rw-r--r--tests/auto/quickcontrols/palette/tst_palette.cpp94
-rw-r--r--tests/auto/quickcontrols/platform/tst_platform.cpp9
-rw-r--r--tests/auto/quickcontrols/qquickapplicationwindow/tst_qquickapplicationwindow.cpp6
-rw-r--r--tests/auto/quickcontrols/qquickdrawer/tst_qquickdrawer.cpp7
-rw-r--r--tests/auto/quickcontrols/qquickiconlabel/tst_qquickiconlabel.cpp2
-rw-r--r--tests/auto/quickcontrols/qquickmaterialstyle/tst_qquickmaterialstyle.cpp16
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/nativeDynamicSubmenus.qml53
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/nativeEmptyMenu.qml51
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/nativeMenuSeparator.qml43
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/nativeMixedItems.qml69
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/nativeStatic.qml53
-rw-r--r--tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp592
-rw-r--r--tests/auto/quickcontrols/qquickmenubar/data/invaliddelegate.qml40
-rw-r--r--tests/auto/quickcontrols/qquickmenubar/data/menubarAsHeader.qml64
-rw-r--r--tests/auto/quickcontrols/qquickmenubar/data/menubaritems.qml (renamed from tests/auto/quickcontrols/qquickmenubar/data/menubar.qml)11
-rw-r--r--tests/auto/quickcontrols/qquickmenubar/data/menus.qml85
-rw-r--r--tests/auto/quickcontrols/qquickmenubar/data/mixed.qml55
-rw-r--r--tests/auto/quickcontrols/qquickmenubar/data/nodelegate.qml40
-rw-r--r--tests/auto/quickcontrols/qquickmenubar/data/showandhide.qml40
-rw-r--r--tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp980
-rw-r--r--tests/auto/quickcontrols/qquickpopup/BLACKLIST3
-rw-r--r--tests/auto/quickcontrols/qquickpopup/data/popupCenterIn.qml22
-rw-r--r--tests/auto/quickcontrols/qquickpopup/data/popupWindowFocusHandling.qml28
-rw-r--r--tests/auto/quickcontrols/qquickpopup/data/popupWithButtonInBackground.qml28
-rw-r--r--tests/auto/quickcontrols/qquickpopup/data/reparentingPopup.qml49
-rw-r--r--tests/auto/quickcontrols/qquickpopup/data/simplepopup.qml23
-rw-r--r--tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp406
-rw-r--r--tests/auto/quickcontrols/qquicktextarea/tst_qquicktextarea.cpp10
-rw-r--r--tests/auto/quickcontrols/qquickuniversalstyle/tst_qquickuniversalstyle.cpp16
-rw-r--r--tests/auto/quickcontrols/snippets/tst_snippets.cpp3
-rw-r--r--tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp28
-rw-r--r--tests/auto/quickdialogs/qquickfolderdialogimpl/tst_qquickfolderdialogimpl.cpp15
-rw-r--r--tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp4
-rw-r--r--tests/baseline/CMakeLists.txt6
-rw-r--r--tests/baseline/controls/BLACKLIST15
-rw-r--r--tests/baseline/controls/data/textarea/textarea.qml3
-rw-r--r--tests/baseline/controls/data/textfield/textfield.qml5
-rw-r--r--tests/baseline/scenegraph/data/shape/shape_fillItem.qml177
-rw-r--r--tests/baseline/scenegraph/data/shape/shape_gradient_xf.qml81
-rw-r--r--tests/baseline/scenegraph/data/shape/shape_intersecting8.qml64
-rw-r--r--tests/baseline/scenegraph/data/shape/shape_rectangle.qml151
-rw-r--r--tests/baseline/scenegraph/data/shape/shape_spread_xf.qml47
-rw-r--r--tests/baseline/scenegraph/data/shape/shape_updatecolor.qml77
-rw-r--r--tests/baseline/scenegraph/data/shape/shape_updatefill.qml214
-rw-r--r--tests/baseline/scenegraph/data/shape/shape_updategradient.qml105
-rw-r--r--tests/baseline/scenegraph/data/shared/qt_logo.svg26
-rw-r--r--tests/baseline/scenegraph/data/text/text_context_font_merging.qml18
-rw-r--r--tests/baseline/scenegraph/data/text/text_prefertypolinemetrics.qml24
-rw-r--r--tests/baseline/scenegraph/data/vectorimages/fillMode.qml59
-rw-r--r--tests/benchmarks/qml/js/qjsvalue/tst_qjsvalue.cpp4
-rw-r--r--tests/manual/painterpathquickshape/ControlPanel.qml14
-rw-r--r--tests/manual/painterpathquickshape/ControlledShape.qml1
-rw-r--r--tests/manual/platforms/android/qml_in_android_service/.gitignore2
-rw-r--r--tests/manual/platforms/android/qml_in_android_service/CMakeLists.txt33
-rw-r--r--tests/manual/platforms/android/qml_in_android_service/Main.qml100
-rw-r--r--tests/manual/platforms/android/qml_in_android_service/main.cpp10
-rw-r--r--tests/manual/platforms/android/qml_in_java_based_android_project/.gitignore33
-rw-r--r--tests/manual/platforms/android/qml_in_java_based_android_project/README.md35
-rw-r--r--tests/manual/platforms/android/qml_in_java_based_android_project/app/build.gradle83
-rw-r--r--tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/AndroidManifest.xml32
-rw-r--r--tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/java/com/example/qml_in_java_based_android_project/MainActivity.java20
-rw-r--r--tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/java/com/example/qml_in_java_based_android_project/QmlService.java204
-rw-r--r--tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/res/layout/view_main.xml142
-rw-r--r--tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/res/values/strings.xml12
-rw-r--r--tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/res/xml/backup_rules.xml10
-rw-r--r--tests/manual/platforms/android/qml_in_java_based_android_project/app/src/main/res/xml/data_extraction_rules.xml10
-rw-r--r--tests/manual/platforms/android/qml_in_java_based_android_project/build.gradle4
-rw-r--r--tests/manual/platforms/android/qml_in_java_based_android_project/gradle.properties22
-rw-r--r--tests/manual/platforms/android/qml_in_java_based_android_project/gradle/wrapper/gradle-wrapper.properties7
-rw-r--r--tests/manual/platforms/android/qml_in_java_based_android_project/settings.gradle23
-rw-r--r--tests/manual/quickcontrols/CMakeLists.txt1
-rw-r--r--tests/manual/quickcontrols/menus/CMakeLists.txt47
-rw-r--r--tests/manual/quickcontrols/menus/Main.qml453
-rw-r--r--tests/manual/quickcontrols/menus/Menu.qml6
-rw-r--r--tests/manual/quickcontrols/menus/cppsettings.cpp43
-rw-r--r--tests/manual/quickcontrols/menus/cppsettings.h38
-rw-r--r--tests/manual/quickcontrols/menus/icons/warning.pngbin0 -> 1212 bytes
-rw-r--r--tests/manual/quickcontrols/menus/icons/warning@2x.pngbin0 -> 2118 bytes
-rw-r--r--tests/manual/quickcontrols/menus/main.cpp27
-rw-r--r--tests/manual/svg/data/image/1.svg3
-rw-r--r--tests/manual/svg/data/image/2.svg3
-rw-r--r--tests/manual/svg/data/image/3.svg6
-rw-r--r--tests/manual/svg/data/image/data.pngbin0 -> 6901 bytes
-rw-r--r--tests/manual/svg/data/image/qtlogo.pngbin0 -> 6901 bytes
-rw-r--r--tests/manual/tableview/abstracttablemodel/main.qml36
283 files changed, 12304 insertions, 1167 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/core/CMakeLists.txt b/tests/auto/core/CMakeLists.txt
index fefa6ff1c1..8a5e039673 100644
--- a/tests/auto/core/CMakeLists.txt
+++ b/tests/auto/core/CMakeLists.txt
@@ -3,4 +3,6 @@
add_subdirectory(qqmlstandardpaths)
add_subdirectory(qqmlsysteminformation)
-add_subdirectory(qqmlsettings)
+if(QT_FEATURE_settings)
+ add_subdirectory(qqmlsettings)
+endif()
diff --git a/tests/auto/qml/CMakeLists.txt b/tests/auto/qml/CMakeLists.txt
index 6302df235d..6b81f4c616 100644
--- a/tests/auto/qml/CMakeLists.txt
+++ b/tests/auto/qml/CMakeLists.txt
@@ -34,7 +34,9 @@ add_subdirectory(qqmlerror)
add_subdirectory(qqmlincubator)
add_subdirectory(qqmlinfo)
add_subdirectory(qqmllistreference)
-add_subdirectory(qqmllocale)
+if(QT_FEATURE_qml_locale)
+ add_subdirectory(qqmllocale)
+endif()
add_subdirectory(qqmlmetaobject)
if(NOT ANDROID) # QTBUG-100003
add_subdirectory(qqmlmoduleplugin)
@@ -46,7 +48,9 @@ add_subdirectory(qqmlpromise)
add_subdirectory(qtqmlmodules)
add_subdirectory(qquickfolderlistmodel)
add_subdirectory(qqmlapplicationengine)
-add_subdirectory(qqmlsettings)
+if(QT_FEATURE_settings)
+ add_subdirectory(qqmlsettings)
+endif()
if(NOT INTEGRITY)
# There's no mounted filesystem on INTEGRITY therefore skipping qmldiskcache
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/qjsonbinding/tst_qjsonbinding.cpp b/tests/auto/qml/qjsonbinding/tst_qjsonbinding.cpp
index 76fa1328e7..450560833f 100644
--- a/tests/auto/qml/qjsonbinding/tst_qjsonbinding.cpp
+++ b/tests/auto/qml/qjsonbinding/tst_qjsonbinding.cpp
@@ -145,9 +145,11 @@ void tst_qjsonbinding::cppJsConversion()
{
QJSValue jsValue = eng.toScriptValue(jsonValue);
+#if QT_DEPRECATED_SINCE(6, 9)
QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED
QVERIFY(!jsValue.isVariant());
QT_WARNING_POP
+#endif
switch (jsonValue.type()) {
case QJsonValue::Null:
QVERIFY(jsValue.isNull());
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/qqmlengine/tst_qqmlengine.cpp b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp
index 56140836bf..3c25d29dfb 100644
--- a/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp
+++ b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp
@@ -36,8 +36,10 @@ public:
private slots:
void initTestCase() override;
void rootContext();
+#if QT_CONFIG(qml_network)
void networkAccessManager();
void synchronousNetworkAccessManager();
+#endif
void baseUrl();
void contextForObject();
void offlineStoragePath();
@@ -153,6 +155,7 @@ void tst_qqmlengine::rootContext()
QVERIFY(!engine.rootContext()->parentContext());
}
+#if QT_CONFIG(qml_network)
class NetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory
{
public:
@@ -228,7 +231,7 @@ void tst_qqmlengine::synchronousNetworkAccessManager()
// reply is finished, so should not be in loading state.
QVERIFY(!c.isLoading());
}
-
+#endif
void tst_qqmlengine::baseUrl()
{
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 0e1bb1abc3..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
@@ -131,7 +131,9 @@ void registerStaticPlugin(const char *uri)
PluginType::metaData.append(char(QT_VERSION_MAJOR));
PluginType::metaData.append(char(QT_VERSION_MINOR));
PluginType::metaData.append(char(qPluginArchRequirements()));
+#if QT_CONFIG(cborstreamwriter)
PluginType::metaData.append(QCborValue(QCborMap::fromJsonObject(md)).toCbor());
+#endif
auto rawMetaDataFunctor = []() -> QPluginMetaData {
return {reinterpret_cast<const uchar *>(PluginType::metaData.constData()), size_t(PluginType::metaData.size())};
@@ -240,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 710bbce17a..8139ec48d7 100644
--- a/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp
+++ b/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp
@@ -57,8 +57,10 @@ private slots:
void alpha();
void tint();
void color();
+#if QT_CONFIG(desktopservices)
void openUrlExternally();
void openUrlExternally_pragmaLibrary();
+#endif
void md5();
void createComponent();
void createComponent_pragmaLibrary();
@@ -83,6 +85,8 @@ private slots:
void timeRoundtrip_data();
void timeRoundtrip();
+ void fontSetsStyleName();
+
private:
QQmlEngine engine;
};
@@ -613,6 +617,7 @@ public slots:
void noteCall(const QUrl &url) { called++; last = url; }
};
+#if QT_CONFIG(desktopservices)
void tst_qqmlqt::openUrlExternally()
{
MyUrlHandler handler;
@@ -659,6 +664,7 @@ void tst_qqmlqt::openUrlExternally_pragmaLibrary()
QCOMPARE(handler.called,2);
QCOMPARE(handler.last, htmlTestFile);
}
+#endif
void tst_qqmlqt::md5()
{
@@ -1465,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/qquicklayouts/data/tst_rowlayout.qml b/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml
index 03c186fdb4..d31a0b0fb8 100644
--- a/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml
+++ b/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml
@@ -1579,6 +1579,49 @@ Item {
compare(rootRect.item1.width, 100)
}
+ //---------------------------
+ // Layout with negative size
+ Component {
+ id: negativeSize_Component
+ Item {
+ id: rootItem
+ width: 0
+ height: 0
+ // default width x height: (0 x 0)
+ RowLayout {
+ spacing: 0
+ anchors.fill: parent
+ anchors.leftMargin: 1 // since parent size == (0 x 0), it causes layout size
+ anchors.bottomMargin: 1 // to become (-1, -1)
+ Item {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ }
+ }
+ }
+ }
+
+ function test_negativeSize() {
+ let rootItem = createTemporaryObject(negativeSize_Component, container)
+ let rowLayout = rootItem.children[0]
+ let item = rowLayout.children[0]
+
+ const arr = [7, 1, 7, 0]
+ arr.forEach((n) => {
+ rootItem.width = n
+ rootItem.height = n
+
+ // n === 0 is special: It will cause the layout to have a
+ // negative size. In this case it will simply not rearrange its
+ // child (and leave it at its previous size, 6)
+ const expectedItemExtent = n === 0 ? 6 : n - 1
+
+ compare(item.width, expectedItemExtent)
+ compare(item.height, expectedItemExtent)
+ });
+ }
+
+
//---------------------------
Component {
id: rowlayoutWithTextItems_Component
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/CMakeLists.txt b/tests/auto/quick/qquickpixmapcache/CMakeLists.txt
index 1e1d35a8af..97735172f2 100644
--- a/tests/auto/quick/qquickpixmapcache/CMakeLists.txt
+++ b/tests/auto/quick/qquickpixmapcache/CMakeLists.txt
@@ -24,7 +24,6 @@ qt_internal_add_test(tst_qquickpixmapcache
tst_qquickpixmapcache.cpp
deviceloadingimage.h deviceloadingimage.cpp
LIBRARIES
- Qt::Concurrent
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
@@ -48,6 +47,11 @@ qt_internal_extend_target(tst_qquickpixmapcache CONDITION NOT ANDROID AND NOT IO
QT_QMLTEST_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/data"
)
+qt_internal_extend_target(tst_qquickpixmapcache CONDITION QT_FEATURE_concurrent
+ LIBRARIES
+ Qt::Concurrent
+)
+
qt_policy(SET QTP0001 NEW)
qt_add_qml_module(tst_qquickpixmapcache
URI PixmapCacheTest
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/quick/qquickwindow/data/visibilityDoesntClobberWindowState.qml b/tests/auto/quick/qquickwindow/data/visibilityDoesntClobberWindowState.qml
new file mode 100644
index 0000000000..af899ec5dd
--- /dev/null
+++ b/tests/auto/quick/qquickwindow/data/visibilityDoesntClobberWindowState.qml
@@ -0,0 +1,5 @@
+import QtQuick
+
+Window {
+
+}
diff --git a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp
index 1e2553c107..ff3edb3b64 100644
--- a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp
+++ b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp
@@ -549,6 +549,8 @@ private slots:
void visibleVsVisibility_data();
void visibleVsVisibility();
+ void visibilityDoesntClobberWindowState();
+
void eventTypes();
private:
@@ -4139,6 +4141,40 @@ void tst_qquickwindow::visibleVsVisibility()
QCOMPARE(window->isVisible(), expectVisible);
}
+void tst_qquickwindow::visibilityDoesntClobberWindowState()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine);
+ component.loadUrl(testFileUrl("visibilityDoesntClobberWindowState.qml"));
+ QObject *created = component.create();
+ QScopedPointer<QObject> cleanup(created);
+ QVERIFY(created);
+
+ QQuickWindow *window = qobject_cast<QQuickWindow*>(created);
+ QVERIFY(window);
+
+ window->showMaximized();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+ QCOMPARE(window->windowState(), Qt::WindowMaximized);
+
+ window->setProperty("visible", false);
+ window->setProperty("visible", true);
+
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+ QCOMPARE(window->windowState(), Qt::WindowMaximized);
+
+ EventFilter eventFilter;
+ window->installEventFilter(&eventFilter);
+ window->setProperty("visibility", QWindow::FullScreen);
+ QTRY_VERIFY(eventFilter.events.contains(QEvent::WindowStateChange));
+ QCOMPARE(window->windowState(), Qt::WindowFullScreen);
+
+ eventFilter.events.clear();
+ window->setWindowState(Qt::WindowMaximized);
+ QTRY_VERIFY(eventFilter.events.contains(QEvent::WindowStateChange));
+ QTRY_COMPARE(window->windowState(), Qt::WindowMaximized);
+}
+
void tst_qquickwindow::eventTypes()
{
QQmlEngine engine;
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
new file mode 100644
index 0000000000..b86ce9a76e
--- /dev/null
+++ b/tests/auto/quickcontrols/controls/data/combobox/shader.frag.qsb
Binary files differ
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 c6a82ebaba..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();
}
@@ -198,6 +206,7 @@ void tst_focus::policy()
control->setFocus(false);
QVERIFY(!control->hasActiveFocus());
+#if QT_CONFIG(wheelevent)
// Qt::WheelFocus
QWheelEvent wheelEvent(QPointF(control->width() / 2, control->height() / 2), QPointF(),
QPoint(), QPoint(0, 10), Qt::NoButton, Qt::NoModifier,
@@ -212,6 +221,7 @@ void tst_focus::policy()
QGuiApplication::sendEvent(control, &wheelEvent);
QVERIFY(control->hasActiveFocus());
QVERIFY(!control->hasVisualFocus());
+#endif
}
void tst_focus::reason()
@@ -396,6 +406,7 @@ void tst_focus::reason()
customItem->setFocusReason(Qt::NoFocusReason);
customText->setFocusReason(Qt::NoFocusReason);
+#if QT_CONFIG(wheelevent)
// Wheel focus -> MouseFocusReason
QWheelEvent wheelEvent(QPointF(customItem->width() / 2, customItem->height() / 2), QPointF(),
QPoint(), QPoint(0, 10), Qt::NoButton, Qt::NoModifier,
@@ -409,6 +420,7 @@ void tst_focus::reason()
QTRY_VERIFY(!customItem->hasActiveFocus());
QCOMPARE(customItem->focusReason(), Qt::PopupFocusReason);
QTest::keyClick(&view, Qt::Key_Escape); // close the popup
+#endif
}
void tst_focus::visualFocus()
@@ -502,6 +514,7 @@ void tst_focus::scope()
QVERIFY(child->hasActiveFocus());
QVERIFY(control->hasActiveFocus());
+#if QT_CONFIG(wheelevent)
// Qt::WheelFocus
QWheelEvent wheelEvent(QPointF(control->width() / 2, control->height() / 2), QPointF(),
QPoint(), QPoint(0, 10), Qt::NoButton, Qt::NoModifier,
@@ -509,6 +522,7 @@ void tst_focus::scope()
QGuiApplication::sendEvent(control, &wheelEvent);
QVERIFY(!child->hasActiveFocus());
QVERIFY(control->hasActiveFocus());
+#endif
}
QTEST_MAIN(tst_focus)
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/platform/tst_platform.cpp b/tests/auto/quickcontrols/platform/tst_platform.cpp
index eeb5e64486..dbcc383f03 100644
--- a/tests/auto/quickcontrols/platform/tst_platform.cpp
+++ b/tests/auto/quickcontrols/platform/tst_platform.cpp
@@ -10,14 +10,17 @@ class Setup : public QObject
{
Q_OBJECT
Q_PROPERTY(bool shortcutsSupported READ areShortcutsSupported CONSTANT FINAL)
-
- Q_PROPERTY(int shortcutInt MEMBER m_shortcutInt CONSTANT FINAL)
Q_PROPERTY(QString shortcutString MEMBER m_shortcutString CONSTANT FINAL)
+#if QT_CONFIG(shortcut)
+ Q_PROPERTY(int shortcutInt MEMBER m_shortcutInt CONSTANT FINAL)
Q_PROPERTY(QKeySequence shortcutKeySequence MEMBER m_shortcutKeySequence CONSTANT FINAL)
+#endif
- const int m_shortcutInt = QKeySequence::Print;
const QString m_shortcutString = u"CTRL+P"_s;
+#if QT_CONFIG(shortcut)
+ const int m_shortcutInt = QKeySequence::Print;
const QKeySequence m_shortcutKeySequence{ Qt::CTRL | Qt::Key_P };
+#endif
public:
bool areShortcutsSupported() const
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 cefb494c6f..d16bc7790f 100644
--- a/tests/auto/quickcontrols/qquickdrawer/tst_qquickdrawer.cpp
+++ b/tests/auto/quickcontrols/qquickdrawer/tst_qquickdrawer.cpp
@@ -63,8 +63,10 @@ private slots:
void hover_data();
void hover();
+#if QT_CONFIG(wheelevent)
void wheel_data();
void wheel();
+#endif
void multiple();
@@ -617,6 +619,7 @@ void tst_QQuickDrawer::hover()
QVERIFY(!drawerItem->isHovered());
}
+#if QT_CONFIG(wheelevent)
void tst_QQuickDrawer::wheel_data()
{
QTest::addColumn<QString>("source");
@@ -703,6 +706,7 @@ void tst_QQuickDrawer::wheel()
QVERIFY(qFuzzyCompare(drawerSlider->value(), oldDrawerValue)); // must not have moved
}
}
+#endif
void tst_QQuickDrawer::multiple()
{
@@ -1051,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 e774d055e1..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>
@@ -68,12 +71,16 @@ private slots:
void activeFocusItemAfterWindowInactive();
void hover_data();
void hover();
+#if QT_CONFIG(wheelevent)
void wheel_data();
void wheel();
+#endif
void parentDestroyed();
void nested();
+#if QT_CONFIG(wheelevent)
void nestedWheel();
void nestedWheelWithOverlayParent();
+#endif
void modelessOnModalOnModeless();
void grabber();
void cursorShape();
@@ -107,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());
};
@@ -122,6 +137,7 @@ tst_QQuickPopup::tst_QQuickPopup()
void tst_QQuickPopup::initTestCase()
{
QQmlDataTest::initTestCase();
+ QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows);
qputenv("QML_NO_TOUCH_COMPRESSION", "1");
}
@@ -132,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);
@@ -515,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);
@@ -658,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.
@@ -704,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
@@ -746,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.
@@ -782,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"));
@@ -834,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"));
@@ -886,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.
@@ -915,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());
@@ -957,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());
@@ -1068,6 +1070,7 @@ void tst_QQuickPopup::hover()
QVERIFY(parentButton->isHovered());
}
+#if QT_CONFIG(wheelevent)
void tst_QQuickPopup::wheel_data()
{
QTest::addColumn<QString>("source");
@@ -1181,6 +1184,7 @@ void tst_QQuickPopup::wheel()
QVERIFY(qFuzzyCompare(popupSlider->value(), oldPopupValue)); // must not have moved
}
}
+#endif
void tst_QQuickPopup::parentDestroyed()
{
@@ -1219,6 +1223,7 @@ void tst_QQuickPopup::nested()
QCOMPARE(modalPopup->isVisible(), true);
}
+#if QT_CONFIG(wheelevent)
void tst_QQuickPopup::nestedWheel()
{
QQuickControlsApplicationHelper helper(this, QStringLiteral("nested-wheel.qml"));
@@ -1283,6 +1288,7 @@ void tst_QQuickPopup::nestedWheelWithOverlayParent()
// Wheel over the list view, verify that it scrolls
QTRY_COMPARE(listView->contentY(), 72.);
}
+#endif
void tst_QQuickPopup::modelessOnModalOnModeless()
{
@@ -1440,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.
@@ -1510,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());
@@ -1621,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());
@@ -1661,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());
@@ -1770,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");
@@ -1883,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());
@@ -2379,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/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp b/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp
index aa462da5dc..f7f2bd8c85 100644
--- a/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp
+++ b/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp
@@ -118,8 +118,10 @@ private:
QDir oldCurrentDir;
+#if QT_CONFIG(shortcut)
const QKeySequence goUpKeySequence = QKeySequence(Qt::ALT | Qt::Key_Up);
const QKeySequence editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L);
+#endif
};
QStringList tst_QQuickFileDialogImpl::tempDirExpectedVisibleFiles(DelegateOrderPolicy order) const
@@ -391,6 +393,7 @@ void tst_QQuickFileDialogImpl::chooseFileViaTextEdit()
// below fail due to it being hidden when it loses activeFocus.
VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempDir.path()), QUrl::fromLocalFile(tempSubDir.path()), 0);
+#if QT_CONFIG(shortcut)
// Get the text edit visible with Ctrl+L.
QTest::keySequence(dialogHelper.window(), editPathKeySequence);
auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>();
@@ -410,6 +413,7 @@ void tst_QQuickFileDialogImpl::chooseFileViaTextEdit()
COMPARE_URLS(dialogHelper.dialog->selectedFiles(), { QUrl::fromLocalFile(tempFile2->fileName()) });
QVERIFY(!dialogHelper.dialog->isVisible());
QTRY_VERIFY(!dialogHelper.quickDialog->isVisible());
+#endif
}
void tst_QQuickFileDialogImpl::chooseFileViaEnter()
@@ -553,9 +557,11 @@ void tst_QQuickFileDialogImpl::chooseFolderViaTextEdit()
// See comment in chooseFileViaTextEdit for why we check for this.
VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempDir.path()), QUrl::fromLocalFile(tempSubDir.path()), 0);
+#if QT_CONFIG(shortcut)
// Get the text edit visible with Ctrl+L.
const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L);
QTest::keySequence(dialogHelper.window(), editPathKeySequence);
+#endif
auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>();
QVERIFY(breadcrumbBar);
QVERIFY(breadcrumbBar->textField()->isVisible());
@@ -614,8 +620,10 @@ void tst_QQuickFileDialogImpl::chooseFileAndThenFolderViaTextEdit()
// See comment in chooseFileViaTextEdit for why we check for this.
VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempDir.path()), QUrl::fromLocalFile(tempSubDir.path()), 0);
+#if QT_CONFIG(shortcut)
// Get the text edit visible with Ctrl+L.
QTest::keySequence(dialogHelper.window(), editPathKeySequence);
+#endif
auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>();
QVERIFY(breadcrumbBar);
QVERIFY(breadcrumbBar->textField()->isVisible());
@@ -642,8 +650,10 @@ void tst_QQuickFileDialogImpl::chooseFileAndThenFolderViaTextEdit()
// The breadcrumbs should be visible after opening, not the text edit.
QVERIFY(!breadcrumbBar->textField()->isVisible());
+#if QT_CONFIG(shortcut)
// Get the text edit visible with Ctrl+L.
QTest::keySequence(dialogHelper.window(), editPathKeySequence);
+#endif
QVERIFY(breadcrumbBar->textField()->isVisible());
// The text edit should show the directory that contains the last file that was selected.
QCOMPARE(breadcrumbBar->textField()->text(), tempDir.path());
@@ -677,8 +687,10 @@ void tst_QQuickFileDialogImpl::cancelDialogWhileTextEditHasFocus()
// See comment in chooseFileViaTextEdit for why we check for this.
VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempDir.path()), QUrl::fromLocalFile(tempSubDir.path()), 0);
+#if QT_CONFIG(shortcut)
// Get the text edit visible with Ctrl+L.
QTest::keySequence(dialogHelper.window(), editPathKeySequence);
+#endif
auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>();
QVERIFY(breadcrumbBar);
QVERIFY(breadcrumbBar->textField()->hasActiveFocus());
@@ -763,6 +775,7 @@ void tst_QQuickFileDialogImpl::goUp()
int expectedCurrentIndex = showDirsFirst ? 0 : 2;
VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempDir.path()), QUrl::fromLocalFile(tempSubDir.path()), expectedCurrentIndex);
+#if QT_CONFIG(shortcut)
// Go up a directory via the keyboard shortcut.
QDir tempParentDir(tempDir.path());
QVERIFY(tempParentDir.cdUp());
@@ -775,6 +788,7 @@ void tst_QQuickFileDialogImpl::goUp()
QVERIFY(expectedCurrentIndex != -1);
VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempParentDir.path()), QUrl::fromLocalFile(tempDir.path()), expectedCurrentIndex);
}
+#endif
}
void tst_QQuickFileDialogImpl::goUpWhileTextEditHasFocus()
@@ -786,8 +800,10 @@ void tst_QQuickFileDialogImpl::goUpWhileTextEditHasFocus()
// See comment in chooseFileViaTextEdit for why we check for this.
VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempSubDir.path()), QUrl::fromLocalFile(tempSubSubDir.path()), 0);
+#if QT_CONFIG(shortcut)
// Get the text edit visible with Ctrl+L.
QTest::keySequence(dialogHelper.window(), editPathKeySequence);
+#endif
auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>();
QVERIFY(breadcrumbBar);
QVERIFY(breadcrumbBar->textField()->hasActiveFocus());
@@ -854,6 +870,7 @@ void tst_QQuickFileDialogImpl::goUpIntoLargeFolder()
VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(largeTempDirLargeSubDir.path()),
QUrl::fromLocalFile(largeTempDirLargeSubDir.path() + "/sub-dir000"), 0);
+#if QT_CONFIG(shortcut)
// Go up a directory via the keyboard shortcut.
QTest::keySequence(dialogHelper.window(), goUpKeySequence);
QString failureMessage;
@@ -861,6 +878,7 @@ void tst_QQuickFileDialogImpl::goUpIntoLargeFolder()
largeTempDirPaths, failureMessage), qPrintable(failureMessage));
VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(largeTempDir.path()),
QUrl::fromLocalFile(largeTempDirLargeSubDir.path()), largeTempDirLargeSubDirIndex);
+#endif
}
void tst_QQuickFileDialogImpl::keyAndShortcutHandling()
@@ -870,16 +888,20 @@ void tst_QQuickFileDialogImpl::keyAndShortcutHandling()
OPEN_QUICK_DIALOG();
VERIFY_FILE_SELECTED_AND_FOCUSED(QUrl::fromLocalFile(tempDir.path()), QUrl::fromLocalFile(tempSubDir.path()), 0);
+#if QT_CONFIG(shortcut)
// Get the text edit visible with Ctrl+L.
QTest::keySequence(dialogHelper.window(), editPathKeySequence);
+#endif
auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>();
QVERIFY(breadcrumbBar);
QVERIFY(breadcrumbBar->textField()->isVisible());
QCOMPARE(breadcrumbBar->textField()->text(), dialogHelper.dialog->currentFolder().toLocalFile());
QCOMPARE(breadcrumbBar->textField()->selectedText(), breadcrumbBar->textField()->text());
+#if QT_CONFIG(shortcut)
// Ctrl+L shouldn't hide it.
QTest::keySequence(dialogHelper.window(), editPathKeySequence);
+#endif
QVERIFY(breadcrumbBar->textField()->isVisible());
// Cancel it with the escape key.
@@ -887,8 +909,10 @@ void tst_QQuickFileDialogImpl::keyAndShortcutHandling()
QVERIFY(!breadcrumbBar->textField()->isVisible());
QVERIFY(dialogHelper.dialog->isVisible());
+#if QT_CONFIG(shortcut)
// Make it visible.
QTest::keySequence(dialogHelper.window(), editPathKeySequence);
+#endif
QVERIFY(breadcrumbBar->textField()->isVisible());
// Cancel it with the escape key again.
@@ -1187,10 +1211,12 @@ void tst_QQuickFileDialogImpl::itemsDisabledWhenNecessary()
COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(anotherTempDir.path()));
COMPARE_URL(dialogHelper.quickDialog->currentFolder(), QUrl::fromLocalFile(anotherTempDir.path()));
+#if QT_CONFIG(shortcut)
// Get the text edit visible with Ctrl+L. The Open button should now be disabled.
QTest::keySequence(dialogHelper.window(), editPathKeySequence);
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);
@@ -1254,8 +1280,10 @@ void tst_QQuickFileDialogImpl::fileMode()
COMPARE_URLS(dialogHelper.dialog->currentFiles(), { QUrl::fromLocalFile(tempFile2->fileName()) });
}
+#if QT_CONFIG(shortcut)
// Get the text edit visible with Ctrl+L.
QTest::keySequence(dialogHelper.window(), editPathKeySequence);
+#endif
auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>();
QVERIFY(breadcrumbBar);
QVERIFY(breadcrumbBar->textField()->isVisible());
diff --git a/tests/auto/quickdialogs/qquickfolderdialogimpl/tst_qquickfolderdialogimpl.cpp b/tests/auto/quickdialogs/qquickfolderdialogimpl/tst_qquickfolderdialogimpl.cpp
index 0c29dad3da..f4e35a37bf 100644
--- a/tests/auto/quickdialogs/qquickfolderdialogimpl/tst_qquickfolderdialogimpl.cpp
+++ b/tests/auto/quickdialogs/qquickfolderdialogimpl/tst_qquickfolderdialogimpl.cpp
@@ -344,6 +344,7 @@ void tst_QQuickFolderDialogImpl::changeFolderViaTextEdit()
QVERIFY(dialogHelper.openDialog());
QTRY_VERIFY(dialogHelper.isQuickDialogOpen());
+#if QT_CONFIG(shortcut)
// Get the text edit visible with Ctrl+L.
const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L);
QTest::keySequence(dialogHelper.window(), editPathKeySequence);
@@ -356,6 +357,7 @@ void tst_QQuickFolderDialogImpl::changeFolderViaTextEdit()
// Enter the path to the folder in the text edit.
enterText(dialogHelper.window(), tempSubDir2.path());
QCOMPARE(breadcrumbBar->textField()->text(), tempSubDir2.path());
+#endif
// Hit enter to accept.
QTest::keyClick(dialogHelper.window(), Qt::Key_Return);
@@ -417,9 +419,11 @@ void tst_QQuickFolderDialogImpl::cancelDialogWhileTextEditHasFocus()
QVERIFY(dialogHelper.openDialog());
QTRY_VERIFY(dialogHelper.isQuickDialogOpen());
+#if QT_CONFIG(shortcut)
// Get the text edit visible with Ctrl+L.
const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L);
QTest::keySequence(dialogHelper.window(), editPathKeySequence);
+#endif
auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>();
QVERIFY(breadcrumbBar);
QVERIFY(breadcrumbBar->textField()->isVisible());
@@ -471,6 +475,7 @@ void tst_QQuickFolderDialogImpl::goUp()
QTRY_VERIFY(findViewDelegateItem(folderDialogListView, 0, subDirDelegate));
QCOMPARE(subDirDelegate->isHighlighted(), true);
+#if QT_CONFIG(shortcut)
// Go up a directory via the keyboard shortcut.
const auto goUpKeySequence = QKeySequence(Qt::ALT | Qt::Key_Up);
QTest::keySequence(dialogHelper.window(), goUpKeySequence);
@@ -478,6 +483,7 @@ void tst_QQuickFolderDialogImpl::goUp()
QVERIFY(tempParentDir.cdUp());
COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempParentDir.path()));
COMPARE_URL(dialogHelper.dialog->selectedFolder(), QUrl::fromLocalFile(tempDir.path()));
+#endif
}
void tst_QQuickFolderDialogImpl::goUpWhileTextEditHasFocus()
@@ -610,27 +616,33 @@ void tst_QQuickFolderDialogImpl::keyAndShortcutHandling()
QVERIFY(dialogHelper.openDialog());
QTRY_VERIFY(dialogHelper.isQuickDialogOpen());
+#if QT_CONFIG(shortcut)
// Get the text edit visible with Ctrl+L.
const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L);
QTest::keySequence(dialogHelper.window(), editPathKeySequence);
+#endif
auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>();
QVERIFY(breadcrumbBar);
QVERIFY(breadcrumbBar->textField()->isVisible());
QCOMPARE(breadcrumbBar->textField()->text(), dialogHelper.dialog->currentFolder().toLocalFile());
QCOMPARE(breadcrumbBar->textField()->selectedText(), breadcrumbBar->textField()->text());
+#if QT_CONFIG(shortcut)
// Ctrl+L shouldn't hide it.
QTest::keySequence(dialogHelper.window(), editPathKeySequence);
QVERIFY(breadcrumbBar->textField()->isVisible());
+#endif
// Cancel it with the escape key.
QTest::keyClick(dialogHelper.window(), Qt::Key_Escape);
QVERIFY(!breadcrumbBar->textField()->isVisible());
QVERIFY(dialogHelper.dialog->isVisible());
+#if QT_CONFIG(shortcut)
// Make it visible.
QTest::keySequence(dialogHelper.window(), editPathKeySequence);
QVERIFY(breadcrumbBar->textField()->isVisible());
+#endif
// Cancel it with the escape key again.
QTest::keyClick(dialogHelper.window(), Qt::Key_Escape);
@@ -795,12 +807,13 @@ void tst_QQuickFolderDialogImpl::itemsDisabledWhenNecessary()
COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempDir.path()));
COMPARE_URL(dialogHelper.quickDialog->currentFolder(), QUrl::fromLocalFile(tempDir.path()));
+#if QT_CONFIG(shortcut)
// Get the text edit visible with Ctrl+L. The Open button should now be disabled.
const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L);
QTest::keySequence(dialogHelper.window(), editPathKeySequence);
QVERIFY(breadcrumbBar->textField()->isVisible());
QCOMPARE(openButton->isEnabled(), false);
-
+#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/CMakeLists.txt b/tests/baseline/CMakeLists.txt
index f00e3f197d..346444d79b 100644
--- a/tests/baseline/CMakeLists.txt
+++ b/tests/baseline/CMakeLists.txt
@@ -7,6 +7,8 @@ endif()
# Special case: test includes the QBaselineTest module sources from qtbase directly
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../../../qtbase/tests/baseline/shared")
- add_subdirectory(scenegraph)
- add_subdirectory(controls)
+ if(QT_FEATURE_processenvironment)
+ add_subdirectory(scenegraph)
+ add_subdirectory(controls)
+ endif()
endif()
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/benchmarks/qml/js/qjsvalue/tst_qjsvalue.cpp b/tests/benchmarks/qml/js/qjsvalue/tst_qjsvalue.cpp
index 8cf6aa5a73..3a360ce0c8 100644
--- a/tests/benchmarks/qml/js/qjsvalue/tst_qjsvalue.cpp
+++ b/tests/benchmarks/qml/js/qjsvalue/tst_qjsvalue.cpp
@@ -78,8 +78,10 @@ private slots:
void isString();
void isUndefined_data();
void isUndefined();
+#if QT_DEPRECATED_SINCE(6, 9)
void isVariant_data();
void isVariant();
+#endif
void toBool_data();
void toBool();
void toDateTime_data();
@@ -547,6 +549,7 @@ void tst_QJSValue::isUndefined()
}
}
+#if QT_DEPRECATED_SINCE(6, 9)
void tst_QJSValue::isVariant_data()
{
defineStandardTestValues();
@@ -559,6 +562,7 @@ void tst_QJSValue::isVariant()
val.isVariant();
}
}
+#endif
void tst_QJSValue::toBool_data()
{
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
new file mode 100644
index 0000000000..590a61eb80
--- /dev/null
+++ b/tests/manual/quickcontrols/menus/icons/warning.png
Binary files differ
diff --git a/tests/manual/quickcontrols/menus/icons/warning@2x.png b/tests/manual/quickcontrols/menus/icons/warning@2x.png
new file mode 100644
index 0000000000..487fbafcfd
--- /dev/null
+++ b/tests/manual/quickcontrols/menus/icons/warning@2x.png
Binary files differ
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&#10;KgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgExAAIAAAARAAAAWodp&#10;AAQAAAABAAAAbAAAAAAAAABIAAAAAQAAAEgAAAABQWRvYmUgSW1hZ2VSZWFkeQAAAAOgAQADAAAA&#10;AQABAACgAgAEAAAAAQAAAFSgAwAEAAAAAQAAADwAAAAAm/SbDAAAAAlwSFlzAAALEwAACxMBAJqc&#10;GAAAAi1pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6&#10;bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDYuMC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRm&#10;PSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJk&#10;ZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6eG1wPSJodHRwOi8v&#10;bnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMu&#10;YWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+QWRvYmUgSW1h&#10;Z2VSZWFkeTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8dGlmZjpZUmVzb2x1dGlvbj43Mjwv&#10;dGlmZjpZUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmll&#10;bnRhdGlvbj4KICAgICAgICAgPHRpZmY6WFJlc29sdXRpb24+NzI8L3RpZmY6WFJlc29sdXRpb24+&#10;CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgpg60/Z&#10;AAAXv0lEQVR4Ae1caZBdxXXuvve+bXaNFhAIJI0EWmZGCEaIJYVLBlxFlkpBbBGXKwRXYkOFwmAW&#10;B20uPYFGQrahAnHiiMTlCrHLCYqdhB/g2NiocDDgMEbSLEhCmhkJAVJmkWZ9672d7+v77tvfmzej&#10;wdYPGt7cvr2cPn369OlzTp8rKZBaetsvwuM08+WS0Xb8yXo08BrGjJCP7VWxTkZM2udSFZGupq1B&#10;J5KIzlua8LMsMDxPP72OhpcBrIE1p79JiMGhfv8W6TNFR9s9SemIP/DaZBrLzNCO45gqYau2jucs&#10;ZYiXChsLseDQxV+TMmCJ4NmTO9FAash495IEJbInE7Hm1YTMo0F/rHEwjkZxc06V3z47qdtnurnd&#10;Q8nBcWHPn4wLWxekG0bt00HJojzobre8v0ZL7y7HK7Pm1girsdp7zXmCCkpDBW1lcnhcGFV+oVRi&#10;MUkGPNONM+RiEaYXP3VWBJsWfiQd+9bk4ORtQmo4In9CaQjKMF+x5gWEN/MciByWv+jJoSs4tFEd&#10;SHdMN+TMySxyIrHCDPi6HVBJRUhSN+UMnRyawNqIIwKNmPQ7nmZ9CMVIrSd2z1GOM4jJpEdg+UxT&#10;/rJrOGZdUMzpHPPt37DdXU+WSqkwFa6P5FyFoxqN2uCwfc5lCd0x9acAqLWgVhyoHjZb+v02Fspt&#10;hvkYIX/NoYsenWz98BtO56WPuWuB2kW/eijUsHD+ZLotynLoRAjJ/xt7TSzdrqS1R4D9WaTX2JlM&#10;7N0o9t39btwWre8/qfxn5vjt5ZF4chi09QZ3WxfSEJh+SuzYIZWd5kROWETt+H37xEYHANIVKg9Y&#10;CmZRTMVVD9bbB+c8JJe/92wgEB/2dTeHx9mhpdeXwMNQSVvUjn2kTg02BoOmaBCGPA0aezBFAU3T&#10;NbmZeK0v1DASHV9pmOZvcqty37hPNMGX9IWDdaHqCBlzNpLZrML+KlEfdcaiswFPw7Cs4bqYPVLI&#10;a0YN9hzoJP2WAC+SDcCclSXDHonkt1SUKYcWPGoc+ocJ8+CzI2bX85DbSnlS1WG9mVCLXOLldy/C&#10;p1LKV9kMu2cAj2pu3UQdhMqJ47fHFyyNq6TLUZZljdsqs+GyQRfs9c6lW25JNZiLZxDY+IOmf7S2&#10;7RKlkuogsNRk6Fi2aYQYK6keywbIfD7QWH4DvoOmkf0ynJSG2tzau/NTLNvwathqPvbEzZDCy/ie&#10;nXK3qXQPhOXvheuyGyHvcraSfZDk3Xg3xy4PVkvD+Xn+FmW/XEwdd4WPXREeZWVW0vzsCLkkBT5V&#10;ldlFWW3zgEqRkd1ZrcyGqtAGFbYMoXZlFZfM5mKKZi3H23+Rav0az18eBQfnjJj9/ZAThrzag8SF&#10;olyVGCo/FZZI8WnSVEUSn5GmUeNTkzgpt6sa4RtMSS6juW/X7ja11xcfl3Uqoe7LB1pcoEDU6UO0&#10;NiiM2oAYPnE6dO2S1Yl3+95LegD0qYx2lPw5YhINCjFlL8hJFU/iMBsX8f4hUaN8j+yTd9okh5co&#10;nFmfB1DFg4H5xTH1enpPKSCc9bqX2f9SWTLeeGBp+Fz6rKFAtkLjNcGaqkFgQV5J13mwf5dP2dwd&#10;9geDwVCywX8OAmsGCEr0wTbjtIpvhVmZn2U0hGKxmK2EK1XLUFEqsz4IHTmyu2vZ1i3FRm/p33mj&#10;NMzXVcJJAvEMo6QaSz9WOTUZtHEnWAxQkTJLxTQzl0YQNSZ2wMF5j2Dfhw0xN6xFdMsHOy9TUfkF&#10;qEJ1ylG/7F7+9Z90LU6+KeQ2DQsSnucJ1XtuBAc7yehctEmvoK9KVKk59We1vlThKhTMOn8y0mcJ&#10;NTjZRCQ3YNDBvvavgCrtIiaqXc0Wq44MEIuLPmF1pQ5HnAihNWe+pZyJmMIBahipnRhcvVDZ/R9p&#10;dVNgIblzHexcFQXBSpNrSuXEFqZhdi3enAZx1dDTqsjh681PSUP+Uefi+E+WHxvyBa2LN2Oi2yE2&#10;qoaDCevI/MfGdEMc+isG99RYKqLMUVDX8N2rDPl4ttbkAfSexeWTVwv7QDrqLRe4kje8/3QIllum&#10;tjAXARvcIWTYWXRqLjRb8WPKuXGZmAwKSx9ObW/v9a3tD9dX1daMmnbVmKqqOiMsoyySHGbKpS92&#10;TBbil13imkw5JfGk4yiYjKlkOkGVhGniRDHpCnk03dkDkvf0YUmu02XYNW9c9nDEqNKGc16z9GvQ&#10;EMaL5OdTixpN8O7tqCmuE6W7VJaZiqIWz73m409e3t0UPbVhvzCGRiL3AvTf4keM07yLPPRaGTjU&#10;tOVF5MUxIWLYTDvURNzdMamWtW0fqbEOkZBzg2xWcZqKovosNWusE8uPNfpOLRoyO5dufQ47Ooif&#10;YTiiFafgdaomUctdPu9EHNqri3zrqT16x2Nt6T0wKD2Y9ovtdkebiI4PGKC+ugG1UEvUt2j3lEuV&#10;ncsuBAVFSsKIfl0Yzjb4Ct7sXxrW1gF1yf2fDqe1C4gqbhza8DnnOJUFyO32RNzac2SlKwFW9bcv&#10;NBz5l9IQT5Tb9dNBNHfCJIDeCDhClbJx6lidl23SZKFuFesf/FfUk0cLVy3dF7XZ+dwRvDcH9pgx&#10;c0Q9MFlPa36tSAyMbzQsuQ/yF9TTEl0jn9Ws0qxeweTEyNxYtHFiqs1UKVDdLjkwRgLtg90Cq16f&#10;tDNA0tUprHPxOdGPRiIwr7U1WwAI+vj/YrQ1GJnn9AWTcija3LuTtvtq/C4oJEmtNKItvTsPgLNX&#10;gJoBd5Ow+sJJWnzApXgIKK3Er6h9V4BummGoNGNq6feClrNWYLb27YJmplYAYiXLjR0CjXguNDz4&#10;PCCj4df1CbMuBBCQUgknjuoc2TlbmFrweK0CsEJZVzgClEYck6a5/kD9V9/Nr6ZJI0PWP8Fhepcz&#10;keCxmUNn2LQYBUX4X7v2tAzOh1L6nQhWgqQN+I/DBKntWvzXh0E9HIthCEo8w+6TYkRFkl86dNHX&#10;pFkbIJJpVIikYzt3W1FnQeelmwwQ5x3UT8vnVQmSUQx5oLNp6xN6vvBNYsFVc691R3Nv+67mu3wP&#10;tp58soF1RLbt7XCVMx5bBWWGyKYTusSM6vgEKIrZSZ4E00rpXV+0F4bC+R6Ef3OdpiAatZwwV1iP&#10;PtuTPDNGftRSHc+nW4/v/EXnsm23dKwL05l3uKWvHW5Zfd67CMNT5bfr0vihMJ0vOnZeYXlEHZGA&#10;EuJ6EaG1s681+kwPry+AXHrzQavHjIybW/t33eAf7H97pL7FkM7ovUrKfyFfGnCQA066PcDUQjcI&#10;GdiIrK/ETC+PqBS0O75NBMULG80116xdlRykcyJ3o6S0HjoF/qZj3XOuoi3ED+BQ/z6Mtk6sCE+6&#10;dIJKSFGYTrxvgt+XhCjJilMgKuFGdl7isreJhUasb+jL1JRAzcJ+EFiwNNenR+eoULrtSMJccXhP&#10;7cIVEe1qZH3bh+Gq6FlhmHOrFa6uJiphgpIzADL6ngYW+fGNYrXsEPdAPKnfg5Xp8lw2RswrZeJu&#10;wy2lJGARbxikCJm+5Prh46LR4/NYxFqNO6A2J5K8FrrrGlzOsHlxuKxBKo0oKunZgA9fW4/c6Si6&#10;GGK9OECWejXbkUdy6NyQcqlVG3jFlr5bbzhVlzr55PcgovYbAfNVOCEPuupgurfbOe9v4RJmNaBj&#10;YXR0JNs4S5saWc0yWU6FaV+3izKwwZtUsDYlWSaT6ImOwgr1DCdvipkWebmyFOU4dXV1mSPREBRJ&#10;Hjp5oPDqDbex2W3DmTLxzdArol8JozQQt0n+3/KI4sZqLBFxXeU8hRxxGsK6+Bgs9Wp2uMMYwbIL&#10;lo9L2ffSiGJQXhVJ227aJ3pUm3gOWoj4FWSmh04+YAd+Jrcs7MpcKi4lUykoJTqURpQdlJp0DAP3&#10;vGGnY98rjmPbe7GEGVbIBRqDQ51neDoprEipBKaYFqplpowhlPCBzR5Abru4c5/dg12yduwZmRwY&#10;LxDOMIVDMp64bwPuJfpxjVDrWJ91InQ+uJwrDSPtYgHj8nDwOLrUXHLKyyNqCB/Oeq1weDJQ9Dtz&#10;IAPPcjsoqhYYTmtHE/G9OOvfTEFPQhn/nse0+ohVaomnqaLXAfS5Cc4xAbksIE9RVELspQBOsfRw&#10;HJ2djMHg6+byCxFW5tC7EwyEiJ9LzrMT9vUibq/i+7yTifsJk3ophPifYQJZLh9943vbG98d1UyM&#10;I/QB+KTXwxlxle+DyWojZGGPYquWSZXa9RhAvtDVtOXPQUnZ3LPDF5wjLO8YXHHR4iivTUj1lj7f&#10;54yg79+0py4zsGNU+w3qqrzTXdIvkgMLcDWIxNtmun/gnixgp0x3jI7Zg+wVpShOkSCUjM24s/lB&#10;1/Jt7+f0wrG55G7hr074VhpV1jv0i+Yl7iwwi7m+u2nTQV3HiX1oXSoSxkkQgNc5JVd4OogSNp2z&#10;8GBgrRi7QD5NOMIejZzFcjbqwfEHk+/Bg3ZY7sAKKFX7fYz1YF8yOCkPRZvZcik+XURdYATqrQM3&#10;kwF8HPt2ZzL5sufZWDv6jCqqEmb3JbT8d3cE96+ukzFMqjN3xtmNyuU9JNkGebCCMufW/ieRpHhi&#10;MZRrmODZDVmKlF+U/+62cv86IoariB7YatfOjKLZwDJ5Xte8hBVtsOqCt9hcTr28mQbTyinqFerd&#10;rqZta9mvvBydFmTB8J/PYrWUPRYlnbhwM01Qt+QR8L1GkkBmE1FvWc8HQeLk4DQ8iiiHHPOlKFBe&#10;4B674oHYlR+G5/kjvv82G6uugeCn+y/bQCPQT1IeBXI2/fL3vqIthUBwuBqi8J1QqH4AjLYaxERM&#10;xSfEzKNd0VdNUKoRrK2x5oZAyINWdf0QXleBkLyK4YkMLxNbfJKmooDe8gy+jYrk61ZDVTN8K9za&#10;WfrZVCCmqOcI0AfwHyMNPH2fC8mfK3JYC5mEN1fPU5CZxTwcaHShJx6fBxFPugbhF5gwbaHyWnbF&#10;E4KWhOglWAchkUSgsHScd6DGvgUfBhRw45RCxJCwJewUqPeWDBiOPRfbYQngU8hfD+1qOW/57LOw&#10;WEq5kNCwROICFe4pqpBFT40SUGZQzNOzVQczaI4oxGGaMPVEsECMTvwPK67uf6f+qwwcdBMFyI6w&#10;hJXoph2pp/eOKDAsKgvTiDT3ta9FyT8jrHeNPRolB7OBbsSGJRI8UAr6kVY90QQ3y3CLoVcD4PuQ&#10;J/ypYJQAXb54NhW8KO5Jgoiifgr6zqN6WHhTaWDrPH0sroutPEaszWpL68q7v8ZuegshNOthC4Cw&#10;pHzObnJg0yKiAXLDVp/rXrL5RwwI6Wi7l6JGwftajWjlV6GxXGsPT0RQFsos29QoVdri/PU7yj3o&#10;35hoMD44cWvPsq0/11ck9OeQmLw2gY199fvfWJjobd8NxP4UHByQVsrTQj4Bw9CdYo9HhwDt29Fj&#10;D+xm6MRG9YIJs1Zfm9Dr23HJ1utw//mUVRt62B6D1o+e+BVwGsSLBu6/aMJq69grOsS9Cd+EbcUQ&#10;66rHclcDXWc/5ahNMwIPWQnOtOyJ+Bc1MQlkO7YutzfSBuw3cNZRp8b/AV5vx89HEcMQSX7woJ/4&#10;VgDWAMlTi14P1V6+Moo+/6h9BOhAwnZcEp6kWtfdtO0RtP2RUePXS4FqJHgWIZTpsWbYZRlvpqMj&#10;rF0rmuJDm+6pp3sgEtx5pJkT1OWLSQSlBrDNf4Yt9rzGg9ucWzvlpBvs83XAYXIFiEjk6WLNjOnx&#10;WHoCkG9S1CU+PDeJr2C+hCurPazynC9XL79JOzMgDh/Gto/oLc6dL9UkeI/urjdBsF87hkm1TwxE&#10;LYeh/cxHo1GMLzuT5ya7Qfxf4/em/kmdfw2NTlJcuKmE6zdVW+5xPjKUXJHEKe5Ljsa+0L108w89&#10;bCC7rI519yZa+tv/wqwJfRfEpBybjniJ44T3w3U2pkx5fffiLT1e/A91ZnoN4K77ISKPP8/7FtwM&#10;dHYt25ZjApabdHZdGl7vrkcRcvZN7SlztCsvs/DZHabIz6hTCmZCBi1u31HHSb7NMm5NPJTHFZCH&#10;f0KZBWJ6S5/qOtVD+kBMfptVi/P5JrbWwVQpMcJ3gPwpn/pskpLcn5sou73EQ65E8vxOynCg25Vo&#10;NI3i6XBNBqweWNq8aLWjyZOJhvozrBzY38Ma6U4eE+oVKxi4yjL+qTjpw0Mk4SWkBbcy0y8s56/G&#10;MjGZ4jCI7t4ICnEVZC4IIvmRiAG639ktt/z7IoSOXnTmcLJDygSNF3wQ+DKCHW7ARcgIICBUA3vM&#10;XSQcq8J0JnS0EqFnFoNv00gz7qg5j58dSHEu0DisT+L5G1YDQS2yxIojIXxdoOpSl83TI6g3AYAC&#10;wLSrlZeA8wXGQKJhgO0OcqRAU2l3w1KlmVIgXDBtHjQ+vb2CTrojO7PMfWa3nGF+ZhyaPZhLv+yS&#10;TD4110zBDHJltmsWNJLEh0MKcQvQ4xK2ZpTL0CDNc25j1zeuTWvlXiDNBo5ZiMyYQ0FHWOZav66P&#10;HXOvNPSWT3HMkRURBJHIUYY5IpUjexY6eVk9WeNsuhS3vwNCixUhE2aDjqhJ7Yh0mwoyGvdZJqQ3&#10;7MwIqjGCPccob6EuD8jAAgLUWx4FPJH1hZlSRxmegTQ9HQ8RPOhjMpQTW/4oAbgprAZ6XPnmWOJK&#10;qGMsnh7sFKSP6zEzgrrY8CROwOqpl6atBRX0RT25sdqF7vpL48dat5suf+KLPkSFBPB1UgSAfsnh&#10;3EXKcDpI/hkt+WbK/R8TRWdOUFeQ06NEO+WLKfwUo6Gog/IdN6XUQQ/AqkFwONx3lSZEV5lz+CGm&#10;/A78Aridgtq0QThUy6jmrHlvzyLQ8Q73VEZ0eQVpxNH3MvoQwyJNd4krGMFtUhEyRaG5KFXBSoqZ&#10;jTW34TL+Lt1O2+8ZvQ8EuRoT/x9rfg0OQL2VEfam9Ubqjtk/crfmcN/F9VXJM6NPdTdteYQwtX4L&#10;f0CP6NHyQ5nJv4PvoApmJtpnxtJOJBxK0pFz2I/J04mPLQ+PQQsYSIl4XuByLESWpXDxfLG618z/&#10;zJygmTH9yeHJBLjw+ea+3bexeMP+HXriOg95CqLedKDuQYqBL+P3CiZOu56BiORacjO+fRR92ML/&#10;xX/v4Z2q+yU9Vm1v3+OexGjAfLeEhdS76wVZE/hjmJ5YUk9nQgMekvwSmjLbcLQI+ig5qqgT82MF&#10;1MLUd7YlhyY70RQqHWJV8WU7iMwIzGH86IE673Q+pmdmcM5LqQgsmxCuTb4PYmhu5aUyD5Hu1WEQ&#10;LSwZ3fbGIkRm6AiPTHcvx4n7T01Ybyx6mJOlTivxjwMYtOXX9O66CUfUT0GwIPRPclcxZrBl0GfC&#10;FIXz2lnXdcXXj8O0rIGYQMioK4e10eENmPVs6dv1Vzjk/t6JAdXzMD1nh6AZxCgI6H3yI3zgkDDl&#10;jloz+DI/zco0mSKXcvdptx+bgviw21+35lXfCO5imAwDJcrxEj6lsUwEhUQg3T/vfUk1xaii9fju&#10;W6HJ/oziFV/Nl1qwqcBMK8plSmB5DbQTl59IM8AJnEsHyQngewqq4wCOklFIMerdsHEULgLlKtx0&#10;P9+1bMt3XGJu5wGivCvt5uM7dyOwf5Oj/aBagJcjKsDKhPRBaUN8MuT8KMbu0mMLcU7LcOXUwAqY&#10;D0gLIdoXoW6uiz/lfNkFy5tm7utsc2gudL654gDLnr6kIzVIDO/HVuSIGDi7HpNPiwzKTcRGJ1zn&#10;cniytb/993Gr8ZKOVKvkGoMjUKfFvzCD0XgAUpB490rEgz8mtqTVmJb9LJxGIpwE7sH8Hz9BK8IK&#10;k6YahiBKyOEqfLr8m4Bj3sx/qiBNVDg3+E5wzcfbjyAE9Urowd7iVDTKrDdybyvi2IVBXER2AP8/&#10;LCbYZ33cqQFii9GElKIKoiEGL9Y1McP+oPX4463kUPaPK/tGeJRexI+67++amFzIGOI9Sb/D0fHJ&#10;eV1Lt6zDB84jFwiHkmQ5yeU86pTwBTDOWCXgqovjdKf+6H4YxW36W05avsKLEPLj2v0QcNvQuXjz&#10;WU/OE5kLhEML6OISC8HdVJFgGCCGlCJQyz/Kut8yMSmHcTuBfz4CYx+1zsYboBpe5Vweow4rGAfG&#10;J9OFSlAXO5dwwHHmp64H6DyeSgbwnZgUPfhuloRsxf2UVgNpaOTD/X+TqEOdRtk7pQAAAABJRU5E&#10;rkJggg==&#10;" 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
new file mode 100644
index 0000000000..7b660be3de
--- /dev/null
+++ b/tests/manual/svg/data/image/data.png
Binary files differ
diff --git a/tests/manual/svg/data/image/qtlogo.png b/tests/manual/svg/data/image/qtlogo.png
new file mode 100644
index 0000000000..7b660be3de
--- /dev/null
+++ b/tests/manual/svg/data/image/qtlogo.png
Binary files differ
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
}