aboutsummaryrefslogtreecommitdiffstats
path: root/tests/auto/quickcontrols/qquickmenu
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/quickcontrols/qquickmenu')
-rw-r--r--tests/auto/quickcontrols/qquickmenu/BLACKLIST7
-rw-r--r--tests/auto/quickcontrols/qquickmenu/CMakeLists.txt57
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/actionShortcuts.qml50
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/actions.qml20
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/addItem.qml24
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/applicationWindowScrollable.qml26
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/applicationwindow.qml46
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/customMenuCullItems.qml55
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/customMenuUseRepeaterAsTheContentItem.qml65
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/delegateFromSeparateComponent.qml60
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/disableWhenTriggered.qml74
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/disabledMenuItemKeyNavigation.qml27
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/giveMenuItemFocusOnButtonPress.qml50
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/menuItemWidths.qml56
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/menuSeparator.qml37
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/mnemonics.qml40
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/order.qml34
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/popup.qml77
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/pressAndHold.qml26
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/removeTakeItem.qml40
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/repeater.qml22
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/scrollableWithPadding.qml32
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/subMenuDisabled.qml32
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/subMenus.qml90
-rw-r--r--tests/auto/quickcontrols/qquickmenu/data/windowScrollable.qml27
-rw-r--r--tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp2120
26 files changed, 3194 insertions, 0 deletions
diff --git a/tests/auto/quickcontrols/qquickmenu/BLACKLIST b/tests/auto/quickcontrols/qquickmenu/BLACKLIST
new file mode 100644
index 0000000000..fed4c6e561
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/BLACKLIST
@@ -0,0 +1,7 @@
+# QTBUG-98491
+[disableWhenTriggered]
+macos
+
+# QTBUG-98491
+[subMenuPosition]
+macos
diff --git a/tests/auto/quickcontrols/qquickmenu/CMakeLists.txt b/tests/auto/quickcontrols/qquickmenu/CMakeLists.txt
new file mode 100644
index 0000000000..84499748b1
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/CMakeLists.txt
@@ -0,0 +1,57 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Generated from qquickmenu.pro.
+
+if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_qquickmenu LANGUAGES C CXX ASM)
+ find_package(Qt6BuildInternals COMPONENTS STANDALONE_TEST)
+endif()
+
+#####################################################################
+## tst_qquickmenu Test:
+#####################################################################
+
+# Collect test data
+file(GLOB_RECURSE test_data_glob
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ data/*)
+list(APPEND test_data ${test_data_glob})
+
+qt_internal_add_test(tst_qquickmenu
+ SOURCES
+ tst_qquickmenu.cpp
+ DEFINES
+ QQC2_IMPORT_PATH=\\\"${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/quickcontrols\\\"
+ LIBRARIES
+ Qt::CorePrivate
+ Qt::Gui
+ Qt::GuiPrivate
+ Qt::QmlPrivate
+ Qt::QuickControls2
+ Qt::QuickControls2Private
+ Qt::QuickControlsTestUtilsPrivate
+ Qt::QuickPrivate
+ Qt::QuickTemplates2Private
+ Qt::QuickTest
+ Qt::QuickTestUtilsPrivate
+ Qt::TestPrivate
+ TESTDATA ${test_data}
+)
+
+#### Keys ignored in scope 1:.:.:qquickmenu.pro:<TRUE>:
+# OTHER_FILES = "data/*.qml"
+
+## Scopes:
+#####################################################################
+
+qt_internal_extend_target(tst_qquickmenu CONDITION ANDROID OR IOS
+ DEFINES
+ QT_QMLTEST_DATADIR=\\\":/data\\\"
+)
+
+qt_internal_extend_target(tst_qquickmenu CONDITION NOT ANDROID AND NOT IOS
+ DEFINES
+ QT_QMLTEST_DATADIR=\\\"${CMAKE_CURRENT_SOURCE_DIR}/data\\\"
+)
diff --git a/tests/auto/quickcontrols/qquickmenu/data/actionShortcuts.qml b/tests/auto/quickcontrols/qquickmenu/data/actionShortcuts.qml
new file mode 100644
index 0000000000..3d839b98d4
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/actionShortcuts.qml
@@ -0,0 +1,50 @@
+// Copyright (C) 2022 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 menu: menu
+ property alias subMenu: subMenu
+ property alias buttonMenu: buttonMenu
+
+ Menu {
+ id: menu
+ objectName: "menu"
+
+ Action {
+ objectName: text
+ text: "action1"
+ shortcut: "A"
+ }
+
+ Menu {
+ id: subMenu
+ objectName: "subMenu"
+
+ Action {
+ objectName: text
+ text: "subAction1"
+ shortcut: "B"
+ }
+ }
+ }
+
+ Button {
+ text: "Menu button"
+
+ Menu {
+ id: buttonMenu
+
+ Action {
+ objectName: text
+ text: "buttonMenuAction1"
+ shortcut: "C"
+ }
+ }
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/actions.qml b/tests/auto/quickcontrols/qquickmenu/data/actions.qml
new file mode 100644
index 0000000000..0c4449d888
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/actions.qml
@@ -0,0 +1,20 @@
+// Copyright (C) 2017 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 menu: menu
+
+ Menu {
+ id: menu
+ Action { text: "action1" }
+ MenuItem { text: "menuitem2" }
+ Action { text: "action3" }
+ MenuItem { text: "menuitem4" }
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/addItem.qml b/tests/auto/quickcontrols/qquickmenu/data/addItem.qml
new file mode 100644
index 0000000000..5678210e25
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/addItem.qml
@@ -0,0 +1,24 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+ApplicationWindow {
+ width: 200
+ height: 200
+
+ property alias menu: menu
+
+ MenuItem {
+ id: newMenuItem
+ text: qsTr("New")
+ }
+
+ Menu {
+ id: menu
+ y: parent.height
+
+ Component.onCompleted: addItem(newMenuItem)
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/applicationWindowScrollable.qml b/tests/auto/quickcontrols/qquickmenu/data/applicationWindowScrollable.qml
new file mode 100644
index 0000000000..eb8140eea5
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/applicationWindowScrollable.qml
@@ -0,0 +1,26 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+ApplicationWindow {
+ title: "Test Application Window"
+ width: 300
+ height: 300
+
+ property alias menu: menu
+
+ Menu {
+ id: menu
+
+ Repeater {
+ model: 20
+
+ delegate: MenuItem {
+ objectName: text
+ text: (index + 1)
+ }
+ }
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/applicationwindow.qml b/tests/auto/quickcontrols/qquickmenu/data/applicationwindow.qml
new file mode 100644
index 0000000000..1b42ee6965
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/applicationwindow.qml
@@ -0,0 +1,46 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+ApplicationWindow {
+ title: "Test Application Window"
+ width: 400
+ height: 400
+
+ property alias emptyMenu: emptyMenu
+ property alias menu: menu
+ property alias menuButton: menuButton
+ property Overlay overlay: menu.Overlay.overlay
+
+ Menu {
+ id: emptyMenu
+ }
+
+ Menu {
+ id: menu
+ cascade: true
+
+ MenuItem {
+ objectName: "firstMenuItem"
+ text: "A"
+ }
+ MenuItem {
+ objectName: "secondMenuItem"
+ text: "B"
+ }
+ MenuItem {
+ objectName: "thirdMenuItem"
+ text: "C"
+ }
+ }
+
+ Button {
+ id: menuButton
+ x: 250
+ visible: false
+ text: "Open Menu"
+ onClicked: menu.open()
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/customMenuCullItems.qml b/tests/auto/quickcontrols/qquickmenu/data/customMenuCullItems.qml
new file mode 100644
index 0000000000..0f56ecdd87
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/customMenuCullItems.qml
@@ -0,0 +1,55 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+ApplicationWindow {
+ width: 200
+ height: 200
+ property alias menu: menu
+
+ Menu {
+ id: menu
+
+ contentItem: FocusScope {
+ implicitHeight: view.implicitHeight
+ Button {
+ anchors {
+ top: parent.top
+ topMargin: 5
+ horizontalCenter: parent.horizontalCenter
+ }
+ z: 1
+ text: "Button Up"
+ visible: view.interactive
+ }
+ ListView {
+ id: view
+ width: parent.width
+ implicitHeight: Math.min(contentHeight, 300)
+ model: menu.contentModel
+
+ clip: true
+ currentIndex: menu.currentIndex
+ ScrollIndicator.vertical: ScrollIndicator {}
+ }
+ Button {
+ anchors {
+ bottom: parent.bottom
+ bottomMargin: 5
+ horizontalCenter: parent.horizontalCenter
+ }
+ z: 1
+ text: "Button Down"
+ visible: view.interactive
+ }
+ }
+
+ Repeater {
+ model: 20
+ MenuItem {
+ objectName: "Item: " + modelData
+ text: objectName
+ }
+ }
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/customMenuUseRepeaterAsTheContentItem.qml b/tests/auto/quickcontrols/qquickmenu/data/customMenuUseRepeaterAsTheContentItem.qml
new file mode 100644
index 0000000000..bfa8f66be5
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/customMenuUseRepeaterAsTheContentItem.qml
@@ -0,0 +1,65 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+ApplicationWindow {
+ width: 200
+ height: 200
+ property alias menu: menu
+
+ Menu {
+ id: menu
+ visible: true
+
+ contentItem: FocusScope {
+ implicitHeight: flickable.height
+
+ Button {
+ anchors {
+ top: parent.top
+ topMargin: 5
+ horizontalCenter: parent.horizontalCenter
+ }
+ z: 1
+ text: "Button Up"
+ }
+
+ Flickable {
+ id: flickable
+ width: parent.width
+ height: Math.min(contentHeight, 300)
+ contentHeight: repeaterLayout.implicitHeight
+ clip: true
+
+ ScrollIndicator.vertical: ScrollIndicator {}
+
+ ColumnLayout {
+ id: repeaterLayout
+ width: parent.width
+
+ Repeater {
+ model: menu.contentModel
+ }
+ }
+ }
+
+ Button {
+ anchors {
+ bottom: parent.bottom
+ bottomMargin: 5
+ horizontalCenter: parent.horizontalCenter
+ }
+ z: 1
+ text: "Button Down"
+ }
+ }
+
+ Repeater {
+ model: 20
+ MenuItem {
+ objectName: "Item: " + modelData
+ text: objectName
+ }
+ }
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/delegateFromSeparateComponent.qml b/tests/auto/quickcontrols/qquickmenu/data/delegateFromSeparateComponent.qml
new file mode 100644
index 0000000000..44a7d8d54a
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/delegateFromSeparateComponent.qml
@@ -0,0 +1,60 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+ApplicationWindow {
+ width: 800
+ height: 800
+
+ property alias menu: menu
+
+ Component {
+ id: menuItemComponent
+
+ MenuItem {
+ contentItem: Text {
+ text: parent.text
+ color: "blue"
+ }
+ background: Rectangle {
+ color: "#00ff00"
+ }
+ }
+ }
+
+ Menu {
+ id: menu
+ title: "Root Menu"
+
+ Action {
+ text: "Action Item 1"
+ }
+ Menu {
+ title: "Sub-menu"
+ delegate: menuItemComponent
+
+ Action {
+ text: "Sub-menu Action Item 1"
+ }
+ Menu {
+ title: "Sub-sub-menu"
+ delegate: menuItemComponent
+
+ Action {
+ text: "Sub-sub-menu Action Item 1"
+ }
+ }
+ Action {
+ text: "Sub-menu Action Item 2"
+ }
+ }
+ Action {
+ text: "Action Item 2"
+ }
+
+ delegate: menuItemComponent
+ visible: true
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/disableWhenTriggered.qml b/tests/auto/quickcontrols/qquickmenu/data/disableWhenTriggered.qml
new file mode 100644
index 0000000000..e59026677a
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/disableWhenTriggered.qml
@@ -0,0 +1,74 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+ApplicationWindow {
+ width: 400
+ height: 400
+
+ Action {
+ id: actionOutsideMenu
+ text: "Action declared outside menu"
+ onTriggered: enabled = false
+ }
+
+ menuBar: MenuBar {
+ Menu {
+ title: "Menu"
+ objectName: title
+
+ Action {
+ text: "Action"
+ objectName: text
+ onTriggered: enabled = false
+ }
+ MenuItem {
+ objectName: "MenuItem with Action"
+ action: Action {
+ text: "Action declared inside MenuItem"
+ objectName: text
+ onTriggered: enabled = false
+ }
+ }
+ MenuItem {
+ objectName: "MenuItem with Action declared outside menu"
+ action: actionOutsideMenu
+ }
+ MenuItem {
+ text: "MenuItem with no Action"
+ objectName: text
+ onTriggered: enabled = false
+ }
+
+ Menu {
+ title: "Submenu"
+ objectName: title
+
+ Action {
+ text: "Sub-Action"
+ objectName: text
+ onTriggered: enabled = false
+ }
+ MenuItem {
+ objectName: "Sub-MenuItem with Action declared inside"
+ action: Action {
+ text: "Action declared inside Sub-MenuItem"
+ objectName: text
+ onTriggered: enabled = false
+ }
+ }
+ MenuItem {
+ objectName: "Sub-MenuItem with Action declared outside menu"
+ action: actionOutsideMenu
+ }
+ MenuItem {
+ text: "Sub-MenuItem with no Action"
+ objectName: text
+ onTriggered: enabled = false
+ }
+ }
+ }
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/disabledMenuItemKeyNavigation.qml b/tests/auto/quickcontrols/qquickmenu/data/disabledMenuItemKeyNavigation.qml
new file mode 100644
index 0000000000..c9c893bb9c
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/disabledMenuItemKeyNavigation.qml
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+ApplicationWindow {
+ width: 200
+ height: 200
+
+ property alias menu: menu
+
+ Menu {
+ id: menu
+
+ MenuItem {
+ text: qsTr("Enabled 1")
+ }
+ MenuItem {
+ text: qsTr("Disabled 1")
+ enabled: false
+ }
+ MenuItem {
+ text: qsTr("Enabled 2")
+ }
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/giveMenuItemFocusOnButtonPress.qml b/tests/auto/quickcontrols/qquickmenu/data/giveMenuItemFocusOnButtonPress.qml
new file mode 100644
index 0000000000..1db3e351bc
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/giveMenuItemFocusOnButtonPress.qml
@@ -0,0 +1,50 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+ApplicationWindow {
+ width: 640
+ height: 480
+
+ property alias menuButton: menuButton
+ property alias menu: menu
+
+ signal menuButtonClicked
+
+ Button {
+ id: menuButton
+ text: "Open menu"
+
+ // Buttons do not emit clicked() for enter/return, hence the Keys usage.
+ // The signal is just for the test to ensure that the return was actually handled.
+ Keys.onReturnPressed: {
+ menuButtonClicked()
+ menu.open()
+ }
+ }
+
+ Menu {
+ id: menu
+ parent: menuButton
+
+ onOpened: command1.forceActiveFocus()
+
+ MenuItem {
+ id: command1
+ objectName: text
+ text: "Command 1"
+ }
+
+ MenuItem {
+ objectName: text
+ text: "Command 2"
+ }
+
+ MenuItem {
+ objectName: text
+ text: "Command 3"
+ }
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/menuItemWidths.qml b/tests/auto/quickcontrols/qquickmenu/data/menuItemWidths.qml
new file mode 100644
index 0000000000..ff99abd399
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/menuItemWidths.qml
@@ -0,0 +1,56 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+ApplicationWindow {
+ width: 600
+ height: 600
+
+ property alias menu: menu
+
+ Menu {
+ id: menu
+ MenuItem {
+ objectName: "MenuItem"
+ text: "MenuItem"
+ }
+ MenuSeparator {
+ objectName: "MenuSeparator"
+ }
+ Menu {
+ title: "Sub-menu"
+ objectName: "Sub-menu"
+
+ MenuItem {
+ objectName: "SubMenuItem"
+ text: "SubMenuItem"
+ }
+ }
+ Rectangle {
+ objectName: "CustomSeparator"
+ height: 2
+ color: "salmon"
+ }
+ Rectangle {
+ // Use a binding to test retranslate(), which re-evaluates all bindings.
+ implicitWidth: someValue
+ objectName: "CustomRectangleSeparator"
+ height: 2
+ color: "salmon"
+
+ property int someValue: 120
+ }
+ Control {
+ objectName: "CustomControlSeparator"
+ implicitWidth: someOtherValue
+ height: 2
+ background: Rectangle {
+ color: "navajowhite"
+ }
+
+ property int someOtherValue: 180
+ }
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/menuSeparator.qml b/tests/auto/quickcontrols/qquickmenu/data/menuSeparator.qml
new file mode 100644
index 0000000000..b13cd534fd
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/menuSeparator.qml
@@ -0,0 +1,37 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+ApplicationWindow {
+ width: 200
+ height: 200
+
+ property alias menu: menu
+
+ MenuItem {
+ id: newMenuItem
+ text: qsTr("New")
+ }
+
+ MenuSeparator {
+ id: menuSeparator
+ }
+
+ MenuItem {
+ id: saveMenuItem
+ text: qsTr("Save")
+ }
+
+ Menu {
+ id: menu
+ cascade: true
+
+ Component.onCompleted: {
+ addItem(newMenuItem)
+ addItem(menuSeparator)
+ addItem(saveMenuItem)
+ }
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/mnemonics.qml b/tests/auto/quickcontrols/qquickmenu/data/mnemonics.qml
new file mode 100644
index 0000000000..8929b00275
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/mnemonics.qml
@@ -0,0 +1,40 @@
+// Copyright (C) 2017 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 menu: menu
+ property alias action: action
+ property alias menuItem: menuItem
+ property alias subMenu: subMenu
+ property alias subMenuItem: subMenuItem
+
+ Menu {
+ id: menu
+
+ Action {
+ id: action
+ text: "&Action"
+ }
+
+ MenuItem {
+ id: menuItem
+ text: "Menu &Item"
+ }
+
+ Menu {
+ id: subMenu
+ title: "Sub &Menu"
+
+ MenuItem {
+ id: subMenuItem
+ text: "&Sub Menu Item"
+ }
+ }
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/order.qml b/tests/auto/quickcontrols/qquickmenu/data/order.qml
new file mode 100644
index 0000000000..185c9e45c3
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/order.qml
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+ApplicationWindow {
+ width: 200
+ height: 200
+
+ property alias menu: menu
+
+ Component {
+ id: menuItem
+ MenuItem { }
+ }
+
+ Menu {
+ id: menu
+ property alias repeater: repeater
+ MenuItem { text: "static_1" }
+ Repeater {
+ id: repeater
+ model: 2
+ MenuItem { text: "repeated_" + (index + 2) }
+ }
+ MenuItem { text: "static_4" }
+ Component.onCompleted: {
+ addItem(menuItem.createObject(menu.contentItem, {text: "dynamic_5"}))
+ addItem(menuItem.createObject(menu.contentItem, {text: "dynamic_6"}))
+ insertItem(0, menuItem.createObject(menu.contentItem, {text: "dynamic_0"}))
+ }
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/popup.qml b/tests/auto/quickcontrols/qquickmenu/data/popup.qml
new file mode 100644
index 0000000000..8201c9e03f
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/popup.qml
@@ -0,0 +1,77 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+ApplicationWindow {
+ width: 500
+ height: 600
+
+ property alias menu: menu
+ property alias menuItem1: menuItem1
+ property alias menuItem2: menuItem2
+ property alias menuItem3: menuItem3
+ property alias button: button
+
+ function popupAtCursor() {
+ menu.popup()
+ }
+
+ function popupAtPos(pos) {
+ menu.popup(pos)
+ }
+
+ function popupAtCoord(x, y) {
+ menu.popup(x, y)
+ }
+
+ function popupItemAtCursor(item) {
+ menu.popup(item)
+ }
+
+ function popupItemAtPos(pos, item) {
+ menu.popup(pos, item)
+ }
+
+ function popupItemAtCoord(x, y, item) {
+ menu.popup(x, y, item)
+ }
+
+ function popupAtParentCursor(parent) {
+ menu.popup(parent)
+ }
+
+ function popupAtParentPos(parent, pos) {
+ menu.popup(parent, pos)
+ }
+
+ function popupAtParentCoord(parent, x, y) {
+ menu.popup(parent, x, y)
+ }
+
+ function popupItemAtParentCursor(parent, item) {
+ menu.popup(parent, item)
+ }
+
+ function popupItemAtParentPos(parent, pos, item) {
+ menu.popup(parent, pos, item)
+ }
+
+ function popupItemAtParentCoord(parent, x, y, item) {
+ menu.popup(parent, x, y, item)
+ }
+
+ Menu {
+ id: menu
+ MenuItem { id: menuItem1; text: "Foo" }
+ MenuItem { id: menuItem2; text: "Bar" }
+ MenuItem { id: menuItem3; text: "Baz" }
+ }
+
+ Button {
+ id: button
+ text: "Button"
+ anchors.centerIn: parent
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/pressAndHold.qml b/tests/auto/quickcontrols/qquickmenu/data/pressAndHold.qml
new file mode 100644
index 0000000000..ac0f394604
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/pressAndHold.qml
@@ -0,0 +1,26 @@
+// Copyright (C) 2017 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 menu: menu
+
+ MouseArea {
+ anchors.fill: parent
+ onPressAndHold: menu.open()
+ }
+
+ Menu {
+ id: menu
+ x: (parent.width - width) / 2
+ y: (parent.height - height) / 2
+ MenuItem { text: "One" }
+ MenuItem { text: "Two" }
+ MenuItem { text: "Three" }
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/removeTakeItem.qml b/tests/auto/quickcontrols/qquickmenu/data/removeTakeItem.qml
new file mode 100644
index 0000000000..aa321cb3a9
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/removeTakeItem.qml
@@ -0,0 +1,40 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+ApplicationWindow {
+ width: 200
+ height: 200
+
+ property alias menu: menu
+ property alias menuItem1: menuItem1
+ property alias menuItem2: menuItem2
+ property alias menuItem3: menuItem3
+
+ function takeSecondItem() {
+ return menu.takeItem(1)
+ }
+
+ function removeFirstItem() {
+ menu.removeItem(menuItem1)
+ }
+
+ function removeNullItem() {
+ menu.removeItem(null)
+ }
+
+ Menu {
+ id: menu
+ MenuItem {
+ id: menuItem1
+ }
+ MenuItem {
+ id: menuItem2
+ }
+ MenuItem {
+ id: menuItem3
+ }
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/repeater.qml b/tests/auto/quickcontrols/qquickmenu/data/repeater.qml
new file mode 100644
index 0000000000..3c056a1d38
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/repeater.qml
@@ -0,0 +1,22 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+ApplicationWindow {
+ width: 200
+ height: 200
+
+ property alias menu: menu
+ property alias repeater: repeater
+
+ Menu {
+ id: menu
+ Repeater {
+ id: repeater
+ model: 5
+ MenuItem { property int idx: index }
+ }
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/scrollableWithPadding.qml b/tests/auto/quickcontrols/qquickmenu/data/scrollableWithPadding.qml
new file mode 100644
index 0000000000..842cb33bfd
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/scrollableWithPadding.qml
@@ -0,0 +1,32 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Window
+
+Window {
+ title: "Test Window"
+ width: 300
+ height: dummyItem.height * 10
+
+ property alias menu: menu
+ MenuItem {
+ id: dummyItem
+ objectName: "Dummy"
+ text: objectName
+ }
+
+ Menu {
+ id: menu
+ topPadding: 10
+ Repeater {
+ model: 10
+
+ delegate: MenuItem {
+ objectName: text
+ text: (index + 1)
+ }
+ }
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/subMenuDisabled.qml b/tests/auto/quickcontrols/qquickmenu/data/subMenuDisabled.qml
new file mode 100644
index 0000000000..4d5db0bd67
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/subMenuDisabled.qml
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+ApplicationWindow {
+ width: 600
+ height: 400
+
+ property alias mainMenu: mainMenu
+ property alias subMenu: subMenu
+
+ Menu {
+ id: mainMenu
+ title: "Menu"
+
+ Menu {
+ id: subMenu
+ title: "Sub Menu"
+ MenuItem {
+ id: subMenuItem1
+ text: "Sub Menu Item 1"
+ enabled: false
+ }
+ MenuItem {
+ id: subMenuItem2
+ text: "Sub Menu Item 2"
+ }
+ }
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/subMenus.qml b/tests/auto/quickcontrols/qquickmenu/data/subMenus.qml
new file mode 100644
index 0000000000..280fd404e8
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/subMenus.qml
@@ -0,0 +1,90 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+ApplicationWindow {
+ width: 600
+ height: 400
+
+ property alias mainMenu: mainMenu
+ property alias subMenu1: subMenu1
+ property alias subMenu2: subMenu2
+ property alias subSubMenu1: subSubMenu1
+
+ Menu {
+ id: mainMenu
+ MenuItem {
+ id: mainMenuItem1
+ objectName: "mainMenuItem1"
+ text: "Main 1"
+ }
+
+ Menu {
+ overlap: 0
+ id: subMenu1
+ objectName: "subMenu1"
+ title: "Sub Menu 1"
+
+ MenuItem {
+ id: subMenuItem1
+ objectName: "subMenuItem1"
+ text: "Sub 1"
+ }
+
+ MenuItem {
+ id: subMenuItem2
+ objectName: "subMenuItem2"
+ text: "Sub 2"
+ }
+
+ Menu {
+ overlap: 0
+ id: subSubMenu1
+ objectName: "subSubMenu1"
+ title: "Sub Sub Menu 1"
+
+ MenuItem {
+ id: subSubMenuItem1
+ objectName: "subSubMenuItem1"
+ text: "Sub Sub 1"
+ }
+ MenuItem {
+ id: subSubMenuItem2
+ objectName: "subSubMenuItem2"
+ text: "Sub Sub 2"
+ }
+ }
+ }
+
+ MenuItem {
+ id: mainMenuItem2
+ objectName: "mainMenuItem2"
+ text: "Main 2"
+ }
+
+ Menu {
+ id: subMenu2
+ objectName: "subMenu2"
+ title: "Sub Menu 2"
+
+ MenuItem {
+ id: subMenuItem3
+ objectName: "subMenuItem3"
+ text: "Sub 3"
+ }
+ MenuItem {
+ id: subMenuItem4
+ objectName: "subMenuItem4"
+ text: "Sub 4"
+ }
+ }
+
+ MenuItem {
+ id: mainMenuItem3
+ objectName: "mainMenuItem3"
+ text: "Main 3"
+ }
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/data/windowScrollable.qml b/tests/auto/quickcontrols/qquickmenu/data/windowScrollable.qml
new file mode 100644
index 0000000000..97a06da63d
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/data/windowScrollable.qml
@@ -0,0 +1,27 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Window
+
+Window {
+ title: "Test Window"
+ width: 300
+ height: 300
+
+ property alias menu: menu
+
+ Menu {
+ id: menu
+
+ Repeater {
+ model: 20
+
+ delegate: MenuItem {
+ objectName: text
+ text: (index + 1)
+ }
+ }
+ }
+}
diff --git a/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp b/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp
new file mode 100644
index 0000000000..6a1d1ab803
--- /dev/null
+++ b/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp
@@ -0,0 +1,2120 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <QtTest/qtest.h>
+#include <QtTest/qsignalspy.h>
+#include <QtGui/qcursor.h>
+#if QT_CONFIG(shortcut)
+#include <QtGui/qkeysequence.h>
+#endif
+#include <QtGui/qstylehints.h>
+#include <QtGui/qpa/qplatformintegration.h>
+#include <QtGui/private/qguiapplication_p.h>
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlcomponent.h>
+#include <QtQml/qqmlcontext.h>
+#include <QtQuick/qquickview.h>
+#include <QtQuick/private/qquickitem_p.h>
+#include <QtQuickTestUtils/private/qmlutils_p.h>
+#include <QtQuickTestUtils/private/visualtestutils_p.h>
+#include <QtQuickControlsTestUtils/private/controlstestutils_p.h>
+#include <QtQuickControlsTestUtils/private/qtest_quickcontrols_p.h>
+
+#include <QtQuickTemplates2/private/qquickaction_p.h>
+#include <QtQuickTemplates2/private/qquickapplicationwindow_p.h>
+#include <QtQuickTemplates2/private/qquickoverlay_p.h>
+#include <QtQuickTemplates2/private/qquickbutton_p.h>
+#include <QtQuickTemplates2/private/qquickicon_p.h>
+#include <QtQuickTemplates2/private/qquickmenu_p.h>
+#include <QtQuickTemplates2/private/qquickmenuitem_p.h>
+#include <QtQuickTemplates2/private/qquickmenuseparator_p.h>
+
+using namespace QQuickVisualTestUtils;
+using namespace QQuickControlsTestUtils;
+
+class tst_QQuickMenu : public QQmlDataTest
+{
+ Q_OBJECT
+
+public:
+ tst_QQuickMenu();
+
+private slots:
+ void defaults();
+ void count();
+ void mouse();
+ void pressAndHold();
+ void contextMenuKeyboard();
+ void disabledMenuItemKeyNavigation();
+ void mnemonics();
+ void menuButton();
+ void addItem();
+ void menuSeparator();
+ void repeater();
+ void order();
+#if QT_CONFIG(cursor)
+ void popup();
+#endif
+ void actions();
+#if QT_CONFIG(shortcut)
+ void actionShortcuts();
+#endif
+ void removeTakeItem();
+ void subMenuMouse_data();
+ void subMenuMouse();
+ void subMenuDisabledMouse_data();
+ void subMenuDisabledMouse();
+ void subMenuKeyboard_data();
+ void subMenuKeyboard();
+ void subMenuDisabledKeyboard_data();
+ void subMenuDisabledKeyboard();
+ void subMenuPosition_data();
+ void subMenuPosition();
+ void subMenuWithIcon();
+ void addRemoveSubMenus();
+ void scrollable_data();
+ void scrollable();
+ void disableWhenTriggered_data();
+ void disableWhenTriggered();
+ void menuItemWidth_data();
+ void menuItemWidth();
+ void menuItemWidthAfterMenuWidthChanged_data();
+ void menuItemWidthAfterMenuWidthChanged();
+ void menuItemWidthAfterImplicitWidthChanged_data();
+ void menuItemWidthAfterImplicitWidthChanged();
+ void menuItemWidthAfterRetranslate();
+ void giveMenuItemFocusOnButtonPress();
+ void customMenuCullItems();
+ void customMenuUseRepeaterAsTheContentItem();
+
+private:
+ static bool hasWindowActivation();
+};
+
+tst_QQuickMenu::tst_QQuickMenu()
+ : QQmlDataTest(QT_QMLTEST_DATADIR)
+{
+}
+
+bool tst_QQuickMenu::hasWindowActivation()
+{
+ return (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation));
+}
+
+void tst_QQuickMenu::defaults()
+{
+ QQuickControlsApplicationHelper helper(this, QLatin1String("applicationwindow.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+
+ QQuickMenu *emptyMenu = helper.appWindow->property("emptyMenu").value<QQuickMenu*>();
+ QCOMPARE(emptyMenu->isVisible(), false);
+ QVERIFY(emptyMenu->hasFocus());
+ QCOMPARE(emptyMenu->currentIndex(), -1);
+ QCOMPARE(emptyMenu->contentItem()->property("currentIndex"), QVariant(-1));
+ QCOMPARE(emptyMenu->count(), 0);
+}
+
+void tst_QQuickMenu::count()
+{
+ QQuickControlsApplicationHelper helper(this, QLatin1String("applicationwindow.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+
+ QQuickMenu *menu = helper.window->property("emptyMenu").value<QQuickMenu*>();
+ QVERIFY(menu);
+
+ QSignalSpy countSpy(menu, &QQuickMenu::countChanged);
+ QVERIFY(countSpy.isValid());
+
+ menu->addItem(new QQuickItem);
+ QCOMPARE(menu->count(), 1);
+ QCOMPARE(countSpy.size(), 1);
+
+ menu->insertItem(0, new QQuickItem);
+ QCOMPARE(menu->count(), 2);
+ QCOMPARE(countSpy.size(), 2);
+
+ menu->removeItem(menu->itemAt(1));
+ QCOMPARE(menu->count(), 1);
+ QCOMPARE(countSpy.size(), 3);
+
+ QScopedPointer<QQuickItem> item(menu->takeItem(0));
+ QVERIFY(item);
+ QCOMPARE(menu->count(), 0);
+ QCOMPARE(countSpy.size(), 4);
+}
+
+void tst_QQuickMenu::mouse()
+{
+ if (!hasWindowActivation())
+ QSKIP("Window activation is not supported");
+
+ if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
+ || (QGuiApplication::platformName() == QLatin1String("minimal")))
+ QSKIP("Mouse hovering not functional on offscreen/minimal platforms");
+
+ QQuickControlsApplicationHelper helper(this, QLatin1String("applicationwindow.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+
+ QQuickApplicationWindow *window = helper.appWindow;
+ centerOnScreen(window);
+ moveMouseAway(window);
+ window->show();
+ QVERIFY(QTest::qWaitForWindowActive(window));
+
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu*>();
+ QVERIFY(menu);
+ menu->open();
+ QVERIFY(menu->isVisible());
+ QQuickOverlay *overlay = window->property("overlay").value<QQuickOverlay*>();
+ QVERIFY(overlay);
+ QVERIFY(overlay->childItems().contains(menu->contentItem()->parentItem()));
+ QTRY_VERIFY(menu->isOpened());
+
+ QQuickItem *firstItem = menu->itemAt(0);
+ QSignalSpy clickedSpy(firstItem, SIGNAL(clicked()));
+ QSignalSpy triggeredSpy(firstItem, SIGNAL(triggered()));
+ QSignalSpy visibleSpy(menu, SIGNAL(visibleChanged()));
+
+ // Ensure that presses cause the current index to change,
+ // so that the highlight acts as a way of illustrating press state.
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier,
+ QPoint(menu->x() + menu->leftPadding() + firstItem->width() / 2, menu->y() + menu->topPadding() + firstItem->height() / 2));
+ QVERIFY(firstItem->hasActiveFocus());
+ QCOMPARE(menu->currentIndex(), 0);
+ QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(0));
+ QVERIFY(menu->isVisible());
+
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier,
+ QPoint(menu->x() + menu->leftPadding() + firstItem->width() / 2, menu->y() + menu->topPadding() + firstItem->height() / 2));
+ QCOMPARE(clickedSpy.size(), 1);
+ QCOMPARE(triggeredSpy.size(), 1);
+ QTRY_COMPARE(visibleSpy.size(), 1);
+ QVERIFY(!menu->isVisible());
+ QVERIFY(!overlay->childItems().contains(menu->contentItem()));
+ QCOMPARE(menu->currentIndex(), -1);
+ QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1));
+
+ menu->open();
+ QCOMPARE(visibleSpy.size(), 2);
+ QVERIFY(menu->isVisible());
+ QVERIFY(overlay->childItems().contains(menu->contentItem()->parentItem()));
+ QTRY_VERIFY(menu->isOpened());
+
+ // Ensure that we have enough space to click outside of the menu.
+ QVERIFY(window->width() > menu->contentItem()->width());
+ QVERIFY(window->height() > menu->contentItem()->height());
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier,
+ QPoint(menu->contentItem()->x() + menu->contentItem()->width() + 1,
+ menu->contentItem()->y() + menu->contentItem()->height() + 1));
+ QTRY_COMPARE(visibleSpy.size(), 3);
+ QVERIFY(!menu->isVisible());
+ QVERIFY(!overlay->childItems().contains(menu->contentItem()->parentItem()));
+
+ menu->open();
+ QCOMPARE(visibleSpy.size(), 4);
+ QVERIFY(menu->isVisible());
+ QVERIFY(overlay->childItems().contains(menu->contentItem()->parentItem()));
+ QTRY_VERIFY(menu->isOpened());
+
+ // Hover-highlighting does not work on Android
+#ifndef Q_OS_ANDROID
+ // Hover-highlight through the menu items one by one
+ QQuickItem *prevHoverItem = nullptr;
+ QQuickItem *listView = menu->contentItem();
+ for (int y = menu->topPadding(); y < listView->height(); ++y) {
+ QQuickItem *hoverItem = nullptr;
+ QVERIFY(QMetaObject::invokeMethod(listView, "itemAt", Q_RETURN_ARG(QQuickItem *, hoverItem), Q_ARG(qreal, 0), Q_ARG(qreal, listView->property("contentY").toReal() + y)));
+ if (!hoverItem || !hoverItem->isVisible() || hoverItem == prevHoverItem)
+ continue;
+ QTest::mouseMove(window, QPoint(
+ menu->x() + menu->leftPadding() + hoverItem->x() + hoverItem->width() / 2,
+ menu->y() + menu->topPadding() + hoverItem->y() + hoverItem->height() / 2));
+ QTRY_VERIFY(hoverItem->property("highlighted").toBool());
+ if (prevHoverItem)
+ QVERIFY(!prevHoverItem->property("highlighted").toBool());
+ prevHoverItem = hoverItem;
+ }
+#endif
+
+ // Try pressing within the menu and releasing outside of it; it should close.
+ // TODO: won't work until QQuickPopup::releasedOutside() actually gets emitted
+// QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, QPoint(firstItem->width() / 2, firstItem->height() / 2));
+// QVERIFY(firstItem->hasActiveFocus());
+// QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(0));
+// QVERIFY(menu->isVisible());
+// QCOMPARE(triggeredSpy.count(), 1);
+
+// QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, QPoint(menu->contentItem()->width() + 1, firstItem->height() / 2));
+// QCOMPARE(clickedSpy.count(), 1);
+// QCOMPARE(triggeredSpy.count(), 1);
+// QCOMPARE(visibleSpy.count(), 5);
+// QVERIFY(!menu->isVisible());
+// QVERIFY(!overlay->childItems().contains(menu->contentItem()));
+// QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1));
+}
+
+void tst_QQuickMenu::pressAndHold()
+{
+ QQuickControlsApplicationHelper helper(this, QLatin1String("pressAndHold.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+
+ QQuickWindow *window = helper.window;
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu *>();
+ QVERIFY(menu);
+
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, QPoint(1, 1));
+ QTRY_VERIFY(menu->isVisible());
+
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, QPoint(1, 1));
+ QVERIFY(menu->isVisible());
+
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, QPoint(1, 1));
+ QTRY_VERIFY(!menu->isVisible());
+}
+
+void tst_QQuickMenu::contextMenuKeyboard()
+{
+ if (!hasWindowActivation())
+ QSKIP("Window activation is not supported");
+
+ if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls)
+ QSKIP("This platform only allows tab focus for text controls");
+
+ QQuickControlsApplicationHelper helper(this, QLatin1String("applicationwindow.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+
+ QQuickApplicationWindow *window = helper.appWindow;
+ centerOnScreen(window);
+ moveMouseAway(window);
+ window->show();
+ window->requestActivate();
+ QVERIFY(QTest::qWaitForWindowActive(window));
+ QVERIFY(QGuiApplication::focusWindow() == window);
+
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu*>();
+ QCOMPARE(menu->currentIndex(), -1);
+ QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1));
+
+ QQuickMenuItem *firstItem = qobject_cast<QQuickMenuItem *>(menu->itemAt(0));
+ QVERIFY(firstItem);
+ QSignalSpy visibleSpy(menu, SIGNAL(visibleChanged()));
+
+ QVERIFY(menu->hasFocus());
+ menu->open();
+ QCOMPARE(visibleSpy.size(), 1);
+ QVERIFY(menu->isVisible());
+ QVERIFY(menu->hasActiveFocus());
+ QQuickOverlay *overlay = window->property("overlay").value<QQuickOverlay*>();
+ QVERIFY(overlay);
+ QVERIFY(overlay->childItems().contains(menu->contentItem()->parentItem()));
+ QTRY_VERIFY(menu->isOpened());
+ QVERIFY(!firstItem->hasActiveFocus());
+ QVERIFY(!firstItem->property("highlighted").toBool());
+ QCOMPARE(menu->currentIndex(), -1);
+ QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1));
+
+ QTest::keyClick(window, Qt::Key_Tab);
+ QVERIFY(firstItem->hasActiveFocus());
+ QVERIFY(firstItem->hasVisualFocus());
+ QVERIFY(firstItem->isHighlighted());
+ QCOMPARE(firstItem->focusReason(), Qt::TabFocusReason);
+ QCOMPARE(menu->currentIndex(), 0);
+ QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(0));
+
+ QQuickMenuItem *secondItem = qobject_cast<QQuickMenuItem *>(menu->itemAt(1));
+ QVERIFY(secondItem);
+ QTest::keyClick(window, Qt::Key_Tab);
+ QVERIFY(!firstItem->hasActiveFocus());
+ QVERIFY(!firstItem->hasVisualFocus());
+ QVERIFY(!firstItem->isHighlighted());
+ QVERIFY(secondItem->hasActiveFocus());
+ QVERIFY(secondItem->hasVisualFocus());
+ QVERIFY(secondItem->isHighlighted());
+ QCOMPARE(secondItem->focusReason(), Qt::TabFocusReason);
+ QCOMPARE(menu->currentIndex(), 1);
+ QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(1));
+
+ QSignalSpy secondTriggeredSpy(secondItem, SIGNAL(triggered()));
+ QTest::keyClick(window, Qt::Key_Space);
+ QCOMPARE(secondTriggeredSpy.size(), 1);
+ QTRY_COMPARE(visibleSpy.size(), 2);
+ QVERIFY(!menu->isVisible());
+ QVERIFY(!overlay->childItems().contains(menu->contentItem()));
+ QVERIFY(!firstItem->hasActiveFocus());
+ QVERIFY(!firstItem->hasVisualFocus());
+ QVERIFY(!firstItem->isHighlighted());
+ QVERIFY(!secondItem->hasActiveFocus());
+ QVERIFY(!secondItem->hasVisualFocus());
+ QVERIFY(!secondItem->isHighlighted());
+ QCOMPARE(menu->currentIndex(), -1);
+ QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1));
+
+ // Enter/return should also work.
+ // Open the menu.
+ menu->open();
+ QCOMPARE(visibleSpy.size(), 3);
+ QVERIFY(menu->isVisible());
+ QTRY_VERIFY(menu->isOpened());
+ // Give the first item focus.
+ QTest::keyClick(window, Qt::Key_Tab);
+ QVERIFY(firstItem->hasActiveFocus());
+ QVERIFY(firstItem->hasVisualFocus());
+ QVERIFY(firstItem->isHighlighted());
+ QCOMPARE(firstItem->focusReason(), Qt::TabFocusReason);
+ QCOMPARE(menu->currentIndex(), 0);
+ QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(0));
+ // Press enter.
+ QSignalSpy firstTriggeredSpy(firstItem, SIGNAL(triggered()));
+ QTest::keyClick(window, Qt::Key_Return);
+ QCOMPARE(firstTriggeredSpy.size(), 1);
+ QTRY_COMPARE(visibleSpy.size(), 4);
+ QVERIFY(!menu->isVisible());
+ QVERIFY(!overlay->childItems().contains(menu->contentItem()));
+ QVERIFY(!firstItem->hasActiveFocus());
+ QVERIFY(!firstItem->hasVisualFocus());
+ QVERIFY(!firstItem->isHighlighted());
+ QVERIFY(!secondItem->hasActiveFocus());
+ QVERIFY(!secondItem->hasVisualFocus());
+ QVERIFY(!secondItem->isHighlighted());
+ QCOMPARE(menu->currentIndex(), -1);
+ QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1));
+
+ menu->open();
+ QCOMPARE(visibleSpy.size(), 5);
+ QVERIFY(menu->isVisible());
+ QVERIFY(overlay->childItems().contains(menu->contentItem()->parentItem()));
+ QTRY_VERIFY(menu->isOpened());
+ QVERIFY(!firstItem->hasActiveFocus());
+ QVERIFY(!firstItem->hasVisualFocus());
+ QVERIFY(!firstItem->isHighlighted());
+ QVERIFY(!secondItem->hasActiveFocus());
+ QVERIFY(!secondItem->hasVisualFocus());
+ QVERIFY(!secondItem->isHighlighted());
+ QCOMPARE(menu->currentIndex(), -1);
+ QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1));
+
+ QTest::keyClick(window, Qt::Key_Down);
+ QVERIFY(firstItem->hasActiveFocus());
+ QVERIFY(firstItem->hasVisualFocus());
+ QVERIFY(firstItem->isHighlighted());
+ QCOMPARE(firstItem->focusReason(), Qt::TabFocusReason);
+
+ QTest::keyClick(window, Qt::Key_Down);
+ QVERIFY(secondItem->hasActiveFocus());
+ QVERIFY(secondItem->hasVisualFocus());
+ QVERIFY(secondItem->isHighlighted());
+ QCOMPARE(secondItem->focusReason(), Qt::TabFocusReason);
+
+ QTest::keyClick(window, Qt::Key_Down);
+ QQuickMenuItem *thirdItem = qobject_cast<QQuickMenuItem *>(menu->itemAt(2));
+ QVERIFY(thirdItem);
+ QVERIFY(!firstItem->hasActiveFocus());
+ QVERIFY(!firstItem->hasVisualFocus());
+ QVERIFY(!firstItem->isHighlighted());
+ QVERIFY(!secondItem->hasActiveFocus());
+ QVERIFY(!secondItem->hasVisualFocus());
+ QVERIFY(!secondItem->isHighlighted());
+ QVERIFY(thirdItem->hasActiveFocus());
+ QVERIFY(thirdItem->hasVisualFocus());
+ QVERIFY(thirdItem->isHighlighted());
+ QCOMPARE(thirdItem->focusReason(), Qt::TabFocusReason);
+
+ // Key navigation shouldn't wrap by default.
+ QTest::keyClick(window, Qt::Key_Down);
+ QVERIFY(!firstItem->hasActiveFocus());
+ QVERIFY(!firstItem->hasVisualFocus());
+ QVERIFY(!firstItem->isHighlighted());
+ QVERIFY(!secondItem->hasActiveFocus());
+ QVERIFY(!secondItem->hasVisualFocus());
+ QVERIFY(!secondItem->isHighlighted());
+ QVERIFY(thirdItem->hasActiveFocus());
+ QVERIFY(thirdItem->hasVisualFocus());
+ QVERIFY(thirdItem->isHighlighted());
+ QCOMPARE(thirdItem->focusReason(), Qt::TabFocusReason);
+
+ QTest::keyClick(window, Qt::Key_Up);
+ QVERIFY(!firstItem->hasActiveFocus());
+ QVERIFY(!firstItem->hasVisualFocus());
+ QVERIFY(!firstItem->isHighlighted());
+ QVERIFY(secondItem->hasActiveFocus());
+ QVERIFY(secondItem->hasVisualFocus());
+ QVERIFY(secondItem->isHighlighted());
+ QCOMPARE(secondItem->focusReason(), Qt::BacktabFocusReason);
+ QVERIFY(!thirdItem->hasActiveFocus());
+ QVERIFY(!thirdItem->hasVisualFocus());
+ QVERIFY(!thirdItem->isHighlighted());
+
+ QTest::keyClick(window, Qt::Key_Backtab);
+ QVERIFY(firstItem->hasActiveFocus());
+ QVERIFY(firstItem->hasVisualFocus());
+ QVERIFY(firstItem->isHighlighted());
+ QCOMPARE(firstItem->focusReason(), Qt::BacktabFocusReason);
+ QVERIFY(!secondItem->hasActiveFocus());
+ QVERIFY(!secondItem->hasVisualFocus());
+ QVERIFY(!secondItem->isHighlighted());
+ QVERIFY(!thirdItem->hasActiveFocus());
+ QVERIFY(!thirdItem->hasVisualFocus());
+ QVERIFY(!thirdItem->isHighlighted());
+
+ QTest::keyClick(window, Qt::Key_Escape);
+ QTRY_COMPARE(visibleSpy.size(), 6);
+ QVERIFY(!menu->isVisible());
+}
+
+// QTBUG-70181
+void tst_QQuickMenu::disabledMenuItemKeyNavigation()
+{
+ if (!hasWindowActivation())
+ QSKIP("Window activation is not supported");
+
+ if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls)
+ QSKIP("This platform only allows tab focus for text controls");
+
+ QQuickControlsApplicationHelper helper(this, QLatin1String("disabledMenuItemKeyNavigation.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+
+ QQuickApplicationWindow *window = helper.appWindow;
+ centerOnScreen(window);
+ moveMouseAway(window);
+ window->show();
+ window->requestActivate();
+ QVERIFY(QTest::qWaitForWindowActive(window));
+ QVERIFY(QGuiApplication::focusWindow() == window);
+
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu*>();
+ QCOMPARE(menu->currentIndex(), -1);
+ QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1));
+
+ QQuickMenuItem *firstItem = qobject_cast<QQuickMenuItem *>(menu->itemAt(0));
+ QVERIFY(firstItem);
+
+ QQuickMenuItem *secondItem = qobject_cast<QQuickMenuItem *>(menu->itemAt(1));
+ QVERIFY(secondItem);
+
+ QQuickMenuItem *thirdItem = qobject_cast<QQuickMenuItem *>(menu->itemAt(2));
+ QVERIFY(thirdItem);
+
+ menu->setFocus(true);
+ menu->open();
+ QVERIFY(menu->isVisible());
+ QTRY_VERIFY(menu->isOpened());
+ QVERIFY(!firstItem->hasActiveFocus());
+ QVERIFY(!firstItem->property("highlighted").toBool());
+ QCOMPARE(menu->currentIndex(), -1);
+
+ QTest::keyClick(window, Qt::Key_Tab);
+ QVERIFY(firstItem->hasActiveFocus());
+ QVERIFY(firstItem->hasVisualFocus());
+ QVERIFY(firstItem->isHighlighted());
+ QCOMPARE(firstItem->focusReason(), Qt::TabFocusReason);
+ QCOMPARE(menu->currentIndex(), 0);
+
+ // Shouldn't be possible to give focus to a disabled menu item.
+ QTest::keyClick(window, Qt::Key_Down);
+ QVERIFY(!secondItem->hasActiveFocus());
+ QVERIFY(!secondItem->hasVisualFocus());
+ QVERIFY(!secondItem->isHighlighted());
+ QVERIFY(thirdItem->hasActiveFocus());
+ QVERIFY(thirdItem->hasVisualFocus());
+ QVERIFY(thirdItem->isHighlighted());
+ QCOMPARE(thirdItem->focusReason(), Qt::TabFocusReason);
+
+ QTest::keyClick(window, Qt::Key_Up);
+ QVERIFY(firstItem->hasActiveFocus());
+ QVERIFY(firstItem->hasVisualFocus());
+ QVERIFY(firstItem->isHighlighted());
+ QCOMPARE(firstItem->focusReason(), Qt::BacktabFocusReason);
+
+ QTest::keyClick(window, Qt::Key_Escape);
+ QTRY_VERIFY(!menu->isVisible());
+}
+
+void tst_QQuickMenu::mnemonics()
+{
+ if (!hasWindowActivation())
+ QSKIP("Window activation is not supported");
+
+#ifdef Q_OS_MACOS
+ QSKIP("Mnemonics are not used on macOS");
+#endif
+
+ QQuickControlsApplicationHelper helper(this, QLatin1String("mnemonics.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+
+ QQuickWindow *window = helper.window;
+ window->show();
+ window->requestActivate();
+ QVERIFY(QTest::qWaitForWindowActive(window));
+
+ MnemonicKeySimulator keySim(window);
+
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu *>();
+ QQuickAction *action = window->property("action").value<QQuickAction *>();
+ QQuickMenuItem *menuItem = window->property("menuItem").value<QQuickMenuItem *>();
+ QQuickMenu *subMenu = window->property("subMenu").value<QQuickMenu *>();
+ QQuickMenuItem *subMenuItem = window->property("subMenuItem").value<QQuickMenuItem *>();
+ QVERIFY(menu && action && menuItem && subMenu && subMenuItem);
+
+ keySim.press(Qt::Key_Alt);
+ menu->open();
+ QTRY_VERIFY(menu->isOpened());
+
+ QSignalSpy actionSpy(action, &QQuickAction::triggered);
+ QVERIFY(actionSpy.isValid());
+ keySim.click(Qt::Key_A); // "&Action"
+ QCOMPARE(actionSpy.size(), 1);
+
+ menu->open();
+ QTRY_VERIFY(menu->isOpened());
+
+ QSignalSpy menuItemSpy(menuItem, &QQuickMenuItem::triggered);
+ QVERIFY(menuItemSpy.isValid());
+ keySim.click(Qt::Key_I); // "Menu &Item"
+ keySim.release(Qt::Key_Alt);
+ QCOMPARE(menuItemSpy.size(), 1);
+
+ keySim.press(Qt::Key_Alt);
+ menu->open();
+ QTRY_VERIFY(menu->isOpened());
+
+ keySim.click(Qt::Key_M); // "Sub &Menu"
+ QTRY_VERIFY(subMenu->isOpened());
+
+ QSignalSpy subMenuItemSpy(subMenuItem, &QQuickMenuItem::triggered);
+ QVERIFY(subMenuItemSpy.isValid());
+ keySim.click(Qt::Key_S); // "&Sub Menu Item"
+ keySim.release(Qt::Key_Alt);
+ QCOMPARE(subMenuItemSpy.size(), 1);
+}
+
+void tst_QQuickMenu::menuButton()
+{
+ if (!hasWindowActivation())
+ QSKIP("Window activation is not supported");
+
+ if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls)
+ QSKIP("This platform only allows tab focus for text controls");
+
+ QQuickControlsApplicationHelper helper(this, QLatin1String("applicationwindow.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+
+ QQuickApplicationWindow *window = helper.appWindow;
+ window->show();
+ window->requestActivate();
+ QVERIFY(QTest::qWaitForWindowActive(window));
+ QVERIFY(QGuiApplication::focusWindow() == window);
+
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu*>();
+ QQuickButton *menuButton = window->property("menuButton").value<QQuickButton*>();
+ QSignalSpy visibleSpy(menu, SIGNAL(visibleChanged()));
+
+ menuButton->setVisible(true);
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier,
+ menuButton->mapToScene(QPointF(menuButton->width() / 2, menuButton->height() / 2)).toPoint());
+ QCOMPARE(visibleSpy.size(), 1);
+ QVERIFY(menu->isVisible());
+ QTRY_VERIFY(menu->isOpened());
+
+ QTest::keyClick(window, Qt::Key_Tab);
+ QQuickItem *firstItem = menu->itemAt(0);
+ QVERIFY(firstItem->hasActiveFocus());
+}
+
+void tst_QQuickMenu::addItem()
+{
+ QQuickControlsApplicationHelper helper(this, QLatin1String("addItem.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickApplicationWindow *window = helper.appWindow;
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu*>();
+ QVERIFY(menu);
+ menu->open();
+ QVERIFY(menu->isVisible());
+
+ QQuickItem *menuItem = menu->itemAt(0);
+ QVERIFY(menuItem);
+ QTRY_VERIFY(!QQuickItemPrivate::get(menuItem)->culled); // QTBUG-53262
+
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier,
+ menuItem->mapToScene(QPointF(menuItem->width() / 2, menuItem->height() / 2)).toPoint());
+ QTRY_VERIFY(!menu->isVisible());
+}
+
+void tst_QQuickMenu::menuSeparator()
+{
+ if (!hasWindowActivation())
+ QSKIP("Window activation is not supported");
+
+ QQuickControlsApplicationHelper helper(this, QLatin1String("menuSeparator.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickWindow *window = helper.window;
+ centerOnScreen(window);
+ moveMouseAway(window);
+ window->show();
+ QVERIFY(QTest::qWaitForWindowActive(window));
+
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu*>();
+ QVERIFY(menu);
+ menu->open();
+ QVERIFY(menu->isVisible());
+
+ QQuickMenuItem *newMenuItem = qobject_cast<QQuickMenuItem*>(menu->itemAt(0));
+ QVERIFY(newMenuItem);
+ QCOMPARE(newMenuItem->text(), QStringLiteral("New"));
+
+ QQuickMenuSeparator *menuSeparator = qobject_cast<QQuickMenuSeparator*>(menu->itemAt(1));
+ QVERIFY(menuSeparator);
+
+ QQuickMenuItem *saveMenuItem = qobject_cast<QQuickMenuItem*>(menu->itemAt(2));
+ QVERIFY(saveMenuItem);
+ QCOMPARE(saveMenuItem->text(), QStringLiteral("Save"));
+ QTRY_VERIFY(!QQuickItemPrivate::get(saveMenuItem)->culled); // QTBUG-53262
+ QTRY_VERIFY(menu->isOpened());
+
+ // Clicking on items should still close the menu.
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier,
+ newMenuItem->mapToScene(QPointF(newMenuItem->width() / 2, newMenuItem->height() / 2)).toPoint());
+ QTRY_VERIFY(!menu->isVisible());
+
+ menu->open();
+ QTRY_VERIFY(menu->isOpened());
+
+ // Clicking on a separator shouldn't close the menu.
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier,
+ menuSeparator->mapToScene(QPointF(menuSeparator->width() / 2, menuSeparator->height() / 2)).toPoint());
+ QVERIFY(menu->isVisible());
+
+ // Clicking on items should still close the menu.
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier,
+ saveMenuItem->mapToScene(QPointF(saveMenuItem->width() / 2, saveMenuItem->height() / 2)).toPoint());
+ QTRY_VERIFY(!menu->isVisible());
+
+ moveMouseAway(window);
+
+ menu->open();
+ QVERIFY(menu->isVisible());
+ QTRY_VERIFY(menu->isOpened());
+
+ // Key navigation skips separators
+ QTest::keyClick(window, Qt::Key_Down);
+ QVERIFY(newMenuItem->hasActiveFocus());
+ QVERIFY(newMenuItem->hasVisualFocus());
+ QCOMPARE(newMenuItem->focusReason(), Qt::TabFocusReason);
+
+ QTest::keyClick(window, Qt::Key_Down);
+ QVERIFY(saveMenuItem->hasActiveFocus());
+ QVERIFY(saveMenuItem->hasVisualFocus());
+ QCOMPARE(saveMenuItem->focusReason(), Qt::TabFocusReason);
+
+ QTest::keyClick(window, Qt::Key_Down);
+ QVERIFY(saveMenuItem->hasActiveFocus());
+ QVERIFY(saveMenuItem->hasVisualFocus());
+ QCOMPARE(saveMenuItem->focusReason(), Qt::TabFocusReason);
+
+ QTest::keyClick(window, Qt::Key_Up);
+ QVERIFY(newMenuItem->hasActiveFocus());
+ QVERIFY(newMenuItem->hasVisualFocus());
+ QCOMPARE(newMenuItem->focusReason(), Qt::BacktabFocusReason);
+
+ QTest::keyClick(window, Qt::Key_Up);
+ QVERIFY(newMenuItem->hasActiveFocus());
+ QVERIFY(newMenuItem->hasVisualFocus());
+ QCOMPARE(newMenuItem->focusReason(), Qt::BacktabFocusReason);
+}
+
+void tst_QQuickMenu::repeater()
+{
+ QQuickControlsApplicationHelper helper(this, QLatin1String("repeater.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickWindow *window = helper.window;
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu*>();
+ QVERIFY(menu);
+ menu->open();
+ QVERIFY(menu->isVisible());
+
+ QObject *repeater = window->property("repeater").value<QObject*>();
+ QVERIFY(repeater);
+
+ int count = repeater->property("count").toInt();
+ QCOMPARE(count, 5);
+
+ for (int i = 0; i < count; ++i) {
+ QQuickItem *item = menu->itemAt(i);
+ QVERIFY(item);
+ QCOMPARE(item->property("idx").toInt(), i);
+
+ QQuickItem *repeaterItem = nullptr;
+ QVERIFY(QMetaObject::invokeMethod(repeater, "itemAt", Q_RETURN_ARG(QQuickItem*, repeaterItem), Q_ARG(int, i)));
+ QCOMPARE(item, repeaterItem);
+ }
+
+ repeater->setProperty("model", 3);
+
+ count = repeater->property("count").toInt();
+ QCOMPARE(count, 3);
+
+ for (int i = 0; i < count; ++i) {
+ QQuickItem *item = menu->itemAt(i);
+ QVERIFY(item);
+ QCOMPARE(item->property("idx").toInt(), i);
+
+ QQuickItem *repeaterItem = nullptr;
+ QVERIFY(QMetaObject::invokeMethod(repeater, "itemAt", Q_RETURN_ARG(QQuickItem*, repeaterItem), Q_ARG(int, i)));
+ QCOMPARE(item, repeaterItem);
+ }
+}
+
+void tst_QQuickMenu::order()
+{
+ QQuickControlsApplicationHelper helper(this, QLatin1String("order.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickWindow *window = helper.window;
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu*>();
+ QVERIFY(menu);
+ menu->open();
+ QVERIFY(menu->isVisible());
+
+ const QStringList texts = {"dynamic_0", "static_1", "repeated_2", "repeated_3", "static_4", "dynamic_5", "dynamic_6"};
+
+ for (int i = 0; i < texts.size(); ++i) {
+ QQuickItem *item = menu->itemAt(i);
+ QVERIFY(item);
+ QCOMPARE(item->property("text").toString(), texts.at(i));
+ }
+}
+
+#if QT_CONFIG(cursor)
+void tst_QQuickMenu::popup()
+{
+#if defined(Q_OS_ANDROID)
+ QSKIP("Setting cursor position is not supported on Android");
+#endif
+ if (QGuiApplication::platformName().startsWith(QLatin1String("wayland")))
+ QSKIP("Setting cursor position is not supported on Wayland");
+
+ // Try moving the cursor from the current position
+ // Skip if it fails since the test relies on moving the cursor
+ const QPoint point = QCursor::pos() + QPoint(1, 1);
+ QCursor::setPos(point);
+ if (!QTest::qWaitFor([point]{ return QCursor::pos() == point; }))
+ QSKIP("Setting cursor position is not supported on this platform");
+
+ QQuickControlsApplicationHelper helper(this, QLatin1String("popup.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickApplicationWindow *window = helper.appWindow;
+ centerOnScreen(window);
+ moveMouseAway(window);
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu *>();
+ QVERIFY(menu);
+
+ QQuickMenuItem *menuItem1 = window->property("menuItem1").value<QQuickMenuItem *>();
+ QVERIFY(menuItem1);
+
+ QQuickMenuItem *menuItem2 = window->property("menuItem2").value<QQuickMenuItem *>();
+ QVERIFY(menuItem2);
+
+ QQuickMenuItem *menuItem3 = window->property("menuItem3").value<QQuickMenuItem *>();
+ QVERIFY(menuItem3);
+
+ QQuickItem *button = window->property("button").value<QQuickItem *>();
+ QVERIFY(button);
+
+ QPoint oldCursorPos = QCursor::pos();
+ QPoint cursorPos = window->mapToGlobal(QPoint(11, 22));
+ QCursor::setPos(cursorPos);
+ QTRY_COMPARE(QCursor::pos(), cursorPos);
+
+ QVERIFY(QMetaObject::invokeMethod(window, "popupAtCursor"));
+ QCOMPARE(menu->parentItem(), window->contentItem());
+ QCOMPARE(menu->currentIndex(), -1);
+ QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), -1);
+ const qreal elevenOrLeftMargin = qMax(qreal(11), menu->leftMargin());
+ const qreal twentyTwoOrTopMargin = qMax(qreal(22), menu->topMargin());
+ // If the Menu has large margins, it may be moved to stay within them.
+ // QTBUG-75503: QTRY_COMPARE doesn't use qFuzzyCompare() in all cases,
+ // meaning a lot of these comparisons could trigger a 10 second wait;
+ // use QTRY_VERIFY and qFuzzyCompare instead.
+ QTRY_VERIFY(qFuzzyCompare(menu->x(), elevenOrLeftMargin));
+ QTRY_VERIFY(qFuzzyCompare(menu->y(), twentyTwoOrTopMargin));
+ menu->close();
+
+ QVERIFY(QMetaObject::invokeMethod(window, "popupAtPos", Q_ARG(QVariant, QPointF(33, 44))));
+ QCOMPARE(menu->parentItem(), window->contentItem());
+ QCOMPARE(menu->currentIndex(), -1);
+ QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), -1);
+ QTRY_VERIFY(qFuzzyCompare(menu->x(), qMax(qreal(33), menu->leftMargin())));
+ QTRY_VERIFY(qFuzzyCompare(menu->y(), qMax(qreal(44), menu->topMargin())));
+ menu->close();
+
+ QVERIFY(QMetaObject::invokeMethod(window, "popupAtCoord", Q_ARG(QVariant, 55), Q_ARG(QVariant, 66)));
+ QCOMPARE(menu->parentItem(), window->contentItem());
+ QCOMPARE(menu->currentIndex(), -1);
+ QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), -1);
+ QTRY_VERIFY(qFuzzyCompare(menu->x(), qMax(qreal(55), menu->leftMargin())));
+ QTRY_VERIFY(qFuzzyCompare(menu->y(), qMax(qreal(66), menu->topMargin())));
+ menu->close();
+
+ menu->setParentItem(nullptr);
+ QVERIFY(QMetaObject::invokeMethod(window, "popupAtParentCursor", Q_ARG(QVariant, QVariant::fromValue(button))));
+ QCOMPARE(menu->parentItem(), button);
+ QCOMPARE(menu->currentIndex(), -1);
+ QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), -1);
+ QTRY_VERIFY(qFuzzyCompare(menu->x(), button->mapFromScene(QPointF(elevenOrLeftMargin, twentyTwoOrTopMargin)).x()));
+ QTRY_VERIFY(qFuzzyCompare(menu->y(), button->mapFromScene(QPointF(elevenOrLeftMargin, twentyTwoOrTopMargin)).y()));
+ menu->close();
+
+ menu->setParentItem(nullptr);
+ QVERIFY(QMetaObject::invokeMethod(window, "popupAtParentPos", Q_ARG(QVariant, QVariant::fromValue(button)), Q_ARG(QVariant, QPointF(-11, -22))));
+ QCOMPARE(menu->parentItem(), button);
+ QCOMPARE(menu->currentIndex(), -1);
+ QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), -1);
+ // Don't need to worry about margins here because we're opening close
+ // to the center of the window.
+ QTRY_VERIFY(qFuzzyCompare(menu->x(), -11));
+ QTRY_VERIFY(qFuzzyCompare(menu->y(), -22));
+ QCOMPARE(menu->popupItem()->position(), button->mapToScene(QPointF(-11, -22)));
+ menu->close();
+
+ menu->setParentItem(nullptr);
+ QVERIFY(QMetaObject::invokeMethod(window, "popupAtParentCoord", Q_ARG(QVariant, QVariant::fromValue(button)), Q_ARG(QVariant, -33), Q_ARG(QVariant, -44)));
+ QCOMPARE(menu->parentItem(), button);
+ QCOMPARE(menu->currentIndex(), -1);
+ QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), -1);
+ QTRY_VERIFY(qFuzzyCompare(menu->x(), -33));
+ QTRY_VERIFY(qFuzzyCompare(menu->y(), -44));
+ QCOMPARE(menu->popupItem()->position(), button->mapToScene(QPointF(-33, -44)));
+ menu->close();
+
+ const qreal twelveOrLeftMargin = qMax(qreal(12), menu->leftMargin());
+ cursorPos = window->mapToGlobal(QPoint(twelveOrLeftMargin, window->height() / 2));
+ QCursor::setPos(cursorPos);
+ QTRY_COMPARE(QCursor::pos(), cursorPos);
+
+ const QList<QQuickMenuItem *> menuItems = QList<QQuickMenuItem *>() << menuItem1 << menuItem2 << menuItem3;
+ for (QQuickMenuItem *menuItem : menuItems) {
+ menu->resetParentItem();
+
+ QVERIFY(QMetaObject::invokeMethod(window, "popupItemAtCursor", Q_ARG(QVariant, QVariant::fromValue(menuItem))));
+ QCOMPARE(menu->currentIndex(), menuItems.indexOf(menuItem));
+ QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), menuItems.indexOf(menuItem));
+ QTRY_VERIFY(qFuzzyCompare(menu->x(), twelveOrLeftMargin));
+ QTRY_VERIFY(qFuzzyCompare(menu->y(), window->height() / 2 - menu->topPadding() - menuItem->y()));
+ menu->close();
+
+ QVERIFY(QMetaObject::invokeMethod(window, "popupItemAtPos", Q_ARG(QVariant, QPointF(33, window->height() / 3)), Q_ARG(QVariant, QVariant::fromValue(menuItem))));
+ QCOMPARE(menu->currentIndex(), menuItems.indexOf(menuItem));
+ QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), menuItems.indexOf(menuItem));
+ QTRY_VERIFY(qFuzzyCompare(menu->x(), 33));
+ QTRY_VERIFY(qFuzzyCompare(menu->y(), window->height() / 3 - menu->topPadding() - menuItem->y()));
+ menu->close();
+
+ QVERIFY(QMetaObject::invokeMethod(window, "popupItemAtCoord", Q_ARG(QVariant, 55), Q_ARG(QVariant, window->height() / 3 * 2), Q_ARG(QVariant, QVariant::fromValue(menuItem))));
+ QCOMPARE(menu->currentIndex(), menuItems.indexOf(menuItem));
+ QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), menuItems.indexOf(menuItem));
+ QTRY_VERIFY(qFuzzyCompare(menu->x(), 55));
+ QTRY_COMPARE_WITH_TIMEOUT(menu->y(), window->height() / 3 * 2 - menu->topPadding() - menuItem->y(), 500);
+ menu->close();
+
+ menu->setParentItem(nullptr);
+ QVERIFY(QMetaObject::invokeMethod(window, "popupItemAtParentCursor", Q_ARG(QVariant, QVariant::fromValue(button)), Q_ARG(QVariant, QVariant::fromValue(menuItem))));
+ QCOMPARE(menu->parentItem(), button);
+ QCOMPARE(menu->currentIndex(), menuItems.indexOf(menuItem));
+ QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), menuItems.indexOf(menuItem));
+ QTRY_VERIFY(qFuzzyCompare(menu->x(), button->mapFromScene(QPoint(twelveOrLeftMargin, window->height() / 2)).x()));
+ QTRY_VERIFY(qFuzzyCompare(menu->y(), button->mapFromScene(QPoint(twelveOrLeftMargin, window->height() / 2)).y() - menu->topPadding() - menuItem->y()));
+ menu->close();
+
+ menu->setParentItem(nullptr);
+ QVERIFY(QMetaObject::invokeMethod(window, "popupItemAtParentPos", Q_ARG(QVariant, QVariant::fromValue(button)), Q_ARG(QVariant, QPointF(-11, -22)), Q_ARG(QVariant, QVariant::fromValue(menuItem))));
+ QCOMPARE(menu->parentItem(), button);
+ QCOMPARE(menu->currentIndex(), menuItems.indexOf(menuItem));
+ QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), menuItems.indexOf(menuItem));
+ QTRY_VERIFY(qFuzzyCompare(menu->x(), -11));
+ QTRY_VERIFY(qFuzzyCompare(menu->y(), -22 - menu->topPadding() - menuItem->y()));
+ QCOMPARE(menu->popupItem()->position(), button->mapToScene(QPointF(-11, -22 - menu->topPadding() - menuItem->y())));
+ menu->close();
+
+ menu->setParentItem(nullptr);
+ QVERIFY(QMetaObject::invokeMethod(window, "popupItemAtParentCoord", Q_ARG(QVariant, QVariant::fromValue(button)), Q_ARG(QVariant, -33), Q_ARG(QVariant, -44), Q_ARG(QVariant, QVariant::fromValue(menuItem))));
+ QCOMPARE(menu->parentItem(), button);
+ QCOMPARE(menu->currentIndex(), menuItems.indexOf(menuItem));
+ QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), menuItems.indexOf(menuItem));
+ QTRY_VERIFY(qFuzzyCompare(menu->x(), -33));
+ QTRY_VERIFY(qFuzzyCompare(menu->y(), -44 - menu->topPadding() - menuItem->y()));
+ QCOMPARE(menu->popupItem()->position(), button->mapToScene(QPointF(-33, -44 - menu->topPadding() - menuItem->y())));
+ menu->close();
+ }
+
+ QCursor::setPos(oldCursorPos);
+ QTRY_COMPARE(QCursor::pos(), oldCursorPos);
+}
+#endif // QT_CONFIG(cursor)
+
+void tst_QQuickMenu::actions()
+{
+ QQuickControlsApplicationHelper helper(this, QLatin1String("actions.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickWindow *window = helper.window;
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu *>();
+ QVERIFY(menu);
+
+ QPointer<QQuickAction> action1 = menu->actionAt(0);
+ QVERIFY(!action1.isNull());
+
+ QPointer<QQuickAction> action3 = menu->actionAt(2);
+ QVERIFY(!action3.isNull());
+
+ QVERIFY(!menu->actionAt(1));
+ QVERIFY(!menu->actionAt(3));
+
+ QPointer<QQuickMenuItem> menuItem1 = qobject_cast<QQuickMenuItem *>(menu->itemAt(0));
+ QVERIFY(!menuItem1.isNull());
+ QCOMPARE(menuItem1->action(), action1.data());
+ QCOMPARE(menuItem1->text(), "action1");
+
+ QPointer<QQuickMenuItem> menuItem2 = qobject_cast<QQuickMenuItem *>(menu->itemAt(1));
+ QVERIFY(!menuItem2.isNull());
+ QVERIFY(!menuItem2->action());
+ QCOMPARE(menuItem2->text(), "menuitem2");
+
+ QPointer<QQuickMenuItem> menuItem3 = qobject_cast<QQuickMenuItem *>(menu->itemAt(2));
+ QVERIFY(!menuItem3.isNull());
+ QCOMPARE(menuItem3->action(), action3.data());
+ QCOMPARE(menuItem3->text(), "action3");
+
+ QPointer<QQuickMenuItem> menuItem4 = qobject_cast<QQuickMenuItem *>(menu->itemAt(3));
+ QVERIFY(!menuItem4.isNull());
+ QVERIFY(!menuItem4->action());
+ QCOMPARE(menuItem4->text(), "menuitem4");
+
+ // takeAction(int) does not destroy the action, but does destroy the respective item
+ QCOMPARE(menu->takeAction(0), action1.data());
+ QVERIFY(!menu->itemAt(3));
+ QCoreApplication::sendPostedEvents(action1, QEvent::DeferredDelete);
+ QVERIFY(!action1.isNull());
+ QCoreApplication::sendPostedEvents(menuItem1, QEvent::DeferredDelete);
+ QVERIFY(menuItem1.isNull());
+
+ // takeAction(int) does not destroy an item that doesn't have an action
+ QVERIFY(!menuItem2->subMenu());
+ QVERIFY(!menu->takeAction(0));
+ QCoreApplication::sendPostedEvents(menuItem2, QEvent::DeferredDelete);
+ QVERIFY(!menuItem2.isNull());
+
+ // addAction(Action) re-creates the respective item in the menu
+ menu->addAction(action1);
+ menuItem1 = qobject_cast<QQuickMenuItem *>(menu->itemAt(3));
+ QVERIFY(!menuItem1.isNull());
+ QCOMPARE(menuItem1->action(), action1.data());
+
+ // removeAction(Action) destroys both the action and the respective item
+ menu->removeAction(action1);
+ QVERIFY(!menu->itemAt(3));
+ QCoreApplication::sendPostedEvents(action1, QEvent::DeferredDelete);
+ QVERIFY(action1.isNull());
+ QCoreApplication::sendPostedEvents(menuItem1, QEvent::DeferredDelete);
+ QVERIFY(menuItem1.isNull());
+}
+
+#if QT_CONFIG(shortcut)
+void tst_QQuickMenu::actionShortcuts()
+{
+ if (!hasWindowActivation())
+ QSKIP("Window activation is not supported");
+
+ QQuickControlsApplicationHelper helper(this, QLatin1String("actionShortcuts.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickWindow *window = helper.window;
+ window->show();
+ QVERIFY(QTest::qWaitForWindowActive(window));
+
+ // Try the menu's shortcut.
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu *>();
+ QVERIFY(menu);
+ QPointer<QQuickAction> action1 = menu->actionAt(0);
+ QVERIFY(action1);
+ QCOMPARE(action1->shortcut(), QKeySequence(Qt::Key_A));
+
+ QSignalSpy action1TriggeredSpy(action1, SIGNAL(triggered()));
+ QVERIFY(action1TriggeredSpy.isValid());
+
+ QTest::keyClick(window, Qt::Key_A);
+ QCOMPARE(action1TriggeredSpy.size(), 1);
+
+ // Try the sub-menu.
+ QQuickMenu *subMenu = window->property("subMenu").value<QQuickMenu *>();
+ QVERIFY(subMenu);
+ QPointer<QQuickAction> subMenuAction1 = subMenu->actionAt(0);
+ QVERIFY(subMenuAction1);
+ QCOMPARE(subMenuAction1->shortcut(), QKeySequence(Qt::Key_B));
+
+ QSignalSpy subMenuAction1TriggeredSpy(subMenuAction1, SIGNAL(triggered()));
+ QVERIFY(subMenuAction1TriggeredSpy.isValid());
+
+ QTest::keyClick(window, Qt::Key_B);
+ QCOMPARE(subMenuAction1TriggeredSpy.size(), 1);
+
+ // Try the button menu.
+ QQuickMenu *buttonMenu = window->property("buttonMenu").value<QQuickMenu *>();
+ QVERIFY(buttonMenu);
+ QPointer<QQuickAction> buttonMenuAction1 = buttonMenu->actionAt(0);
+ QVERIFY(buttonMenuAction1);
+ QCOMPARE(buttonMenuAction1->shortcut(), QKeySequence(Qt::Key_C));
+
+ QSignalSpy buttonMenuAction1TriggeredSpy(buttonMenuAction1, SIGNAL(triggered()));
+ QVERIFY(buttonMenuAction1TriggeredSpy.isValid());
+
+ QTest::keyClick(window, Qt::Key_C);
+ QCOMPARE(buttonMenuAction1TriggeredSpy.size(), 1);
+}
+#endif
+
+void tst_QQuickMenu::removeTakeItem()
+{
+ QQuickControlsApplicationHelper helper(this, QLatin1String("removeTakeItem.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickWindow *window = helper.window;
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu *>();
+ QVERIFY(menu);
+
+ QPointer<QQuickMenuItem> menuItem1 = window->property("menuItem1").value<QQuickMenuItem *>();
+ QVERIFY(!menuItem1.isNull());
+ QCOMPARE(menuItem1->menu(), menu);
+
+ QPointer<QQuickMenuItem> menuItem2 = window->property("menuItem2").value<QQuickMenuItem *>();
+ QVERIFY(!menuItem2.isNull());
+ QCOMPARE(menuItem2->menu(), menu);
+
+ QPointer<QQuickMenuItem> menuItem3 = window->property("menuItem3").value<QQuickMenuItem *>();
+ QVERIFY(!menuItem3.isNull());
+ QCOMPARE(menuItem3->menu(), menu);
+
+ // takeItem(int) does not destroy
+ QVariant ret;
+ QVERIFY(QMetaObject::invokeMethod(window, "takeSecondItem", Q_RETURN_ARG(QVariant, ret)));
+ QCOMPARE(ret.value<QQuickMenuItem *>(), menuItem2);
+ QVERIFY(!menuItem2->menu());
+ QCoreApplication::sendPostedEvents(menuItem2, QEvent::DeferredDelete);
+ QVERIFY(!menuItem2.isNull());
+
+ // removeItem(Item) destroys
+ QVERIFY(QMetaObject::invokeMethod(window, "removeFirstItem"));
+ QVERIFY(!menuItem1->menu());
+ QCoreApplication::sendPostedEvents(menuItem1, QEvent::DeferredDelete);
+ QVERIFY(menuItem1.isNull());
+
+ // removeItem(null) must not call removeItem(0)
+ QVERIFY(QMetaObject::invokeMethod(window, "removeNullItem"));
+ QCOMPARE(menuItem3->menu(), menu);
+ QCoreApplication::sendPostedEvents(menuItem3, QEvent::DeferredDelete);
+ QVERIFY(!menuItem3.isNull());
+}
+
+void tst_QQuickMenu::subMenuMouse_data()
+{
+ QTest::addColumn<bool>("cascade");
+
+ QTest::newRow("cascading") << true;
+ QTest::newRow("non-cascading") << false;
+}
+
+void tst_QQuickMenu::subMenuMouse()
+{
+ if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
+ || (QGuiApplication::platformName() == QLatin1String("minimal")))
+ QSKIP("Mouse hovering not functional on offscreen/minimal platforms");
+
+ QFETCH(bool, cascade);
+
+ QQuickControlsApplicationHelper helper(this, QLatin1String("subMenus.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickApplicationWindow *window = helper.appWindow;
+ centerOnScreen(window);
+ moveMouseAway(window);
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ QQuickMenu *mainMenu = window->property("mainMenu").value<QQuickMenu *>();
+ QVERIFY(mainMenu);
+ mainMenu->setCascade(cascade);
+ QCOMPARE(mainMenu->cascade(), cascade);
+
+ QQuickMenu *subMenu1 = window->property("subMenu1").value<QQuickMenu *>();
+ QVERIFY(subMenu1);
+ subMenu1->setCascade(cascade);
+ QCOMPARE(subMenu1->cascade(), cascade);
+
+ QQuickMenu *subMenu2 = window->property("subMenu2").value<QQuickMenu *>();
+ QVERIFY(subMenu2);
+ subMenu2->setCascade(cascade);
+ QCOMPARE(subMenu2->cascade(), cascade);
+
+ QQuickMenu *subSubMenu1 = window->property("subSubMenu1").value<QQuickMenu *>();
+ QVERIFY(subSubMenu1);
+ subSubMenu1->setCascade(cascade);
+ QCOMPARE(subSubMenu1->cascade(), cascade);
+
+ mainMenu->open();
+ QVERIFY(mainMenu->isVisible());
+ QTRY_VERIFY(mainMenu->isOpened());
+ QVERIFY(!subMenu1->isVisible());
+ QVERIFY(!subMenu2->isVisible());
+ QVERIFY(!subSubMenu1->isVisible());
+
+ // open the sub-menu with mouse click
+ QQuickMenuItem *subMenu1Item = qobject_cast<QQuickMenuItem *>(mainMenu->itemAt(1));
+ QVERIFY(subMenu1Item);
+ QCOMPARE(subMenu1Item->subMenu(), subMenu1);
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, subMenu1Item->mapToScene(QPoint(1, 1)).toPoint());
+ QTRY_COMPARE(mainMenu->isVisible(), cascade);
+ QVERIFY(subMenu1->isVisible());
+ QTRY_VERIFY(subMenu1->isOpened());
+ QVERIFY(!subMenu2->isVisible());
+ QVERIFY(!subSubMenu1->isVisible());
+
+ // on Android mouse hover will not open and close sub-menus
+#ifndef Q_OS_ANDROID
+ // open the cascading sub-sub-menu with mouse hover
+ QQuickMenuItem *subSubMenu1Item = qobject_cast<QQuickMenuItem *>(subMenu1->itemAt(2));
+ QVERIFY(subSubMenu1Item);
+ QCOMPARE(subSubMenu1Item->subMenu(), subSubMenu1);
+ QTest::mouseMove(window, subSubMenu1Item->mapToScene(QPoint(1, 1)).toPoint());
+ QCOMPARE(mainMenu->isVisible(), cascade);
+ QVERIFY(subMenu1->isVisible());
+ QVERIFY(!subMenu2->isVisible());
+ QVERIFY(!subSubMenu1->isVisible());
+ if (cascade) {
+ QTRY_VERIFY(subSubMenu1->isVisible());
+ QTRY_VERIFY(subSubMenu1->isOpened());
+ }
+
+ // close the sub-sub-menu with mouse hover over another parent menu item
+ QQuickMenuItem *subMenuItem1 = qobject_cast<QQuickMenuItem *>(subMenu1->itemAt(0));
+ QVERIFY(subMenuItem1);
+ QVERIFY(!subMenuItem1->subMenu());
+ QTest::mouseMove(window, subMenuItem1->mapToScene(QPoint(1, 1)).toPoint());
+ QCOMPARE(mainMenu->isVisible(), cascade);
+ QVERIFY(subMenu1->isVisible());
+ QVERIFY(!subMenu2->isVisible());
+ QTRY_VERIFY(!subSubMenu1->isVisible());
+
+ // re-open the sub-sub-menu with mouse hover
+ QTest::mouseMove(window, subSubMenu1Item->mapToScene(QPoint(1, 1)).toPoint());
+ QCOMPARE(mainMenu->isVisible(), cascade);
+ QVERIFY(subMenu1->isVisible());
+ QVERIFY(!subMenu2->isVisible());
+ if (!cascade) {
+ QVERIFY(!subSubMenu1->isVisible());
+ } else {
+ QTRY_VERIFY(subSubMenu1->isVisible());
+ QTRY_VERIFY(subSubMenu1->isOpened());
+ }
+
+ // close sub-menu and sub-sub-menu with mouse hover in the main menu
+ QQuickMenuItem *mainMenuItem1 = qobject_cast<QQuickMenuItem *>(mainMenu->itemAt(0));
+ QVERIFY(mainMenuItem1);
+ QTest::mouseMove(window, mainMenuItem1->mapToScene(QPoint(1, 1)).toPoint());
+ QCOMPARE(mainMenu->isVisible(), cascade);
+ QTRY_COMPARE(subMenu1->isVisible(), !cascade);
+ QVERIFY(!subMenu2->isVisible());
+ QVERIFY(!subSubMenu1->isVisible());
+#else
+ QQuickMenuItem *mainMenuItem1 = qobject_cast<QQuickMenuItem *>(mainMenu->itemAt(0));
+ QVERIFY(mainMenuItem1);
+#endif // !Q_OS_ANDROID
+
+ // close all menus by click triggering an item
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, mainMenuItem1->mapToScene(QPoint(1, 1)).toPoint());
+ QTRY_VERIFY(!mainMenu->isVisible());
+ QTRY_VERIFY(!subMenu1->isVisible());
+ QVERIFY(!subMenu2->isVisible());
+ QVERIFY(!subSubMenu1->isVisible());
+}
+
+void tst_QQuickMenu::subMenuDisabledMouse_data()
+{
+ subMenuMouse_data();
+}
+
+// QTBUG-69540
+void tst_QQuickMenu::subMenuDisabledMouse()
+{
+ if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
+ || (QGuiApplication::platformName() == QLatin1String("minimal")))
+ QSKIP("Mouse hovering not functional on offscreen/minimal platforms");
+
+ QFETCH(bool, cascade);
+
+ QQuickControlsApplicationHelper helper(this, QLatin1String("subMenuDisabled.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickApplicationWindow *window = helper.appWindow;
+ centerOnScreen(window);
+ moveMouseAway(window);
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ QQuickMenu *mainMenu = window->property("mainMenu").value<QQuickMenu *>();
+ QVERIFY(mainMenu);
+ mainMenu->setCascade(cascade);
+ QCOMPARE(mainMenu->cascade(), cascade);
+
+ QQuickMenuItem *menuItem1 = qobject_cast<QQuickMenuItem *>(mainMenu->itemAt(0));
+ QVERIFY(menuItem1);
+
+ QQuickMenu *subMenu = window->property("subMenu").value<QQuickMenu *>();
+ QVERIFY(subMenu);
+
+ mainMenu->open();
+ QTRY_VERIFY(mainMenu->isOpened());
+ QVERIFY(!menuItem1->isHighlighted());
+ QVERIFY(!subMenu->isVisible());
+
+ // Hover-highlighting does not work on Android
+#ifndef Q_OS_ANDROID
+ // Generate a hover event to set the current index
+ QTest::mouseMove(window, menuItem1->mapToScene(QPoint(2, 2)).toPoint());
+ QTRY_VERIFY(menuItem1->isHighlighted());
+#endif
+ // Open the sub-menu with a mouse click.
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, menuItem1->mapToScene(QPoint(1, 1)).toPoint());
+ // Need to use the TRY variant here,
+ // as e.g. Material, iOS style menus have transitions and don't open/close immediately.
+ QTRY_COMPARE(mainMenu->isVisible(), cascade);
+ QTRY_VERIFY(subMenu->isOpened());
+ QTRY_VERIFY(menuItem1->isHighlighted());
+ // Now the sub-menu is open. The current behavior is that the first menu item
+ // in the new menu is highlighted; make sure that we choose the next item if
+ // the first is disabled.
+ QQuickMenuItem *subMenuItem1 = qobject_cast<QQuickMenuItem *>(subMenu->itemAt(0));
+ QVERIFY(subMenuItem1);
+ QQuickMenuItem *subMenuItem2 = qobject_cast<QQuickMenuItem *>(subMenu->itemAt(1));
+ QVERIFY(subMenuItem2);
+ QVERIFY(!subMenuItem1->isHighlighted());
+ QVERIFY(subMenuItem2->isHighlighted());
+
+ // Close all menus by clicking on the item that isn't disabled.
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, subMenuItem2->mapToScene(QPoint(1, 1)).toPoint());
+ QTRY_VERIFY(!mainMenu->isVisible());
+ QTRY_VERIFY(!subMenu->isVisible());
+}
+
+void tst_QQuickMenu::subMenuKeyboard_data()
+{
+ QTest::addColumn<bool>("cascade");
+ QTest::addColumn<bool>("mirrored");
+
+ QTest::newRow("cascading") << true << false;
+ QTest::newRow("cascading,mirrored") << true << true;
+ QTest::newRow("non-cascading") << false << false;
+ QTest::newRow("non-cascading,mirrored") << false << true;
+}
+
+void tst_QQuickMenu::subMenuKeyboard()
+{
+ if (!hasWindowActivation())
+ QSKIP("Window activation is not supported");
+
+ QFETCH(bool, cascade);
+ QFETCH(bool, mirrored);
+
+ QQuickControlsApplicationHelper helper(this, QLatin1String("subMenus.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickApplicationWindow *window = helper.appWindow;
+ centerOnScreen(window);
+ moveMouseAway(window);
+ window->show();
+ QVERIFY(QTest::qWaitForWindowActive(window));
+
+ if (mirrored) {
+ QQmlExpression mirroringExpression(qmlContext(window), window,
+ "LayoutMirroring.childrenInherit = true; LayoutMirroring.enabled = true");
+ QVERIFY2(mirroringExpression.evaluate().isValid(), qPrintable(mirroringExpression.error().toString()));
+ }
+
+ QQuickMenu *mainMenu = window->property("mainMenu").value<QQuickMenu *>();
+ QVERIFY(mainMenu);
+ mainMenu->setCascade(cascade);
+ QCOMPARE(mainMenu->cascade(), cascade);
+
+ QQuickMenu *subMenu1 = window->property("subMenu1").value<QQuickMenu *>();
+ QVERIFY(subMenu1);
+
+ QQuickMenu *subMenu2 = window->property("subMenu2").value<QQuickMenu *>();
+ QVERIFY(subMenu2);
+
+ QQuickMenu *subSubMenu1 = window->property("subSubMenu1").value<QQuickMenu *>();
+ QVERIFY(subSubMenu1);
+
+ mainMenu->open();
+ QVERIFY(mainMenu->isVisible());
+ QTRY_VERIFY(mainMenu->isOpened());
+ QVERIFY(!subMenu1->isVisible());
+ QVERIFY(!subMenu2->isVisible());
+ QVERIFY(!subSubMenu1->isVisible());
+
+ // navigate to the sub-menu item and trigger it to open the sub-menu
+ QQuickMenuItem *subMenu1Item = qobject_cast<QQuickMenuItem *>(mainMenu->itemAt(1));
+ QVERIFY(subMenu1Item);
+ QVERIFY(!subMenu1Item->isHighlighted());
+ QCOMPARE(subMenu1Item->subMenu(), subMenu1);
+ QTest::keyClick(window, Qt::Key_Down);
+ QTest::keyClick(window, Qt::Key_Down);
+ QVERIFY(subMenu1Item->isHighlighted());
+ QTest::keyClick(window, Qt::Key_Space);
+ QTRY_COMPARE(mainMenu->isVisible(), cascade);
+ QVERIFY(subMenu1->isVisible());
+ QTRY_VERIFY(subMenu1->isOpened());
+ QVERIFY(!subMenu2->isVisible());
+ QVERIFY(!subSubMenu1->isVisible());
+
+ // navigate to the sub-sub-menu item and open it with the arrow key
+ QQuickMenuItem *subSubMenu1Item = qobject_cast<QQuickMenuItem *>(subMenu1->itemAt(2));
+ QVERIFY(subSubMenu1Item);
+ QVERIFY(!subSubMenu1Item->isHighlighted());
+ QCOMPARE(subSubMenu1Item->subMenu(), subSubMenu1);
+ QTest::keyClick(window, Qt::Key_Down);
+ QTest::keyClick(window, Qt::Key_Down);
+ QTest::keyClick(window, Qt::Key_Down);
+ QVERIFY(subSubMenu1Item->isHighlighted());
+ QTRY_COMPARE(mainMenu->isVisible(), cascade);
+ QVERIFY(subMenu1->isVisible());
+ QVERIFY(!subMenu2->isVisible());
+ QVERIFY(!subSubMenu1->isVisible());
+ QTest::keyClick(window, mirrored ? Qt::Key_Left : Qt::Key_Right);
+ QCOMPARE(mainMenu->isVisible(), cascade);
+ QTRY_COMPARE(subMenu1->isVisible(), cascade);
+ QVERIFY(!subMenu2->isVisible());
+ QVERIFY(subSubMenu1->isVisible());
+ QTRY_VERIFY(subSubMenu1->isOpened());
+
+ // navigate within the sub-sub-menu
+ QQuickMenuItem *subSubMenuItem1 = qobject_cast<QQuickMenuItem *>(subSubMenu1->itemAt(0));
+ QVERIFY(subSubMenuItem1);
+ QQuickMenuItem *subSubMenuItem2 = qobject_cast<QQuickMenuItem *>(subSubMenu1->itemAt(1));
+ QVERIFY(subSubMenuItem2);
+ QVERIFY(subSubMenuItem1->isHighlighted());
+ QVERIFY(!subSubMenuItem2->isHighlighted());
+ QTest::keyClick(window, Qt::Key_Down);
+ QVERIFY(!subSubMenuItem1->isHighlighted());
+ QVERIFY(subSubMenuItem2->isHighlighted());
+
+ // navigate to the parent menu with the arrow key
+ QTest::keyClick(window, mirrored ? Qt::Key_Right : Qt::Key_Left);
+ QVERIFY(subSubMenu1Item->isHighlighted());
+ QCOMPARE(mainMenu->isVisible(), cascade);
+ QVERIFY(subMenu1->isVisible());
+ QVERIFY(!subMenu2->isVisible());
+ QTRY_VERIFY(!subSubMenu1->isVisible());
+
+ // navigate within the sub-menu
+ QQuickMenuItem *subMenuItem1 = qobject_cast<QQuickMenuItem *>(subMenu1->itemAt(0));
+ QVERIFY(subMenuItem1);
+ QQuickMenuItem *subMenuItem2 = qobject_cast<QQuickMenuItem *>(subMenu1->itemAt(1));
+ QVERIFY(subMenuItem2);
+ QVERIFY(!subMenuItem1->isHighlighted());
+ QVERIFY(!subMenuItem2->isHighlighted());
+ QVERIFY(subSubMenu1Item->isHighlighted());
+ QTest::keyClick(window, Qt::Key_Up);
+ QVERIFY(!subMenuItem1->isHighlighted());
+ QVERIFY(subMenuItem2->isHighlighted());
+ QVERIFY(!subSubMenu1Item->isHighlighted());
+
+ // close the menus with esc
+ QTest::keyClick(window, Qt::Key_Escape);
+ QCOMPARE(mainMenu->isVisible(), cascade);
+ QTRY_VERIFY(!subMenu1->isVisible());
+ QVERIFY(!subMenu2->isVisible());
+ QVERIFY(!subSubMenu1->isVisible());
+ QTest::keyClick(window, Qt::Key_Escape);
+ QTRY_VERIFY(!mainMenu->isVisible());
+ QVERIFY(!subMenu1->isVisible());
+ QVERIFY(!subMenu2->isVisible());
+ QVERIFY(!subSubMenu1->isVisible());
+}
+
+void tst_QQuickMenu::subMenuDisabledKeyboard_data()
+{
+ subMenuKeyboard_data();
+}
+
+// QTBUG-69540
+void tst_QQuickMenu::subMenuDisabledKeyboard()
+{
+ if (!hasWindowActivation())
+ QSKIP("Window activation is not supported");
+
+ QFETCH(bool, cascade);
+ QFETCH(bool, mirrored);
+
+ QQuickControlsApplicationHelper helper(this, QLatin1String("subMenuDisabled.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickApplicationWindow *window = helper.appWindow;
+ centerOnScreen(window);
+ moveMouseAway(window);
+ window->show();
+ QVERIFY(QTest::qWaitForWindowActive(window));
+
+ if (mirrored) {
+ QQmlExpression mirroringExpression(qmlContext(window), window,
+ "LayoutMirroring.childrenInherit = true; LayoutMirroring.enabled = true");
+ QVERIFY2(mirroringExpression.evaluate().isValid(), qPrintable(mirroringExpression.error().toString()));
+ }
+
+ QQuickMenu *mainMenu = window->property("mainMenu").value<QQuickMenu *>();
+ QVERIFY(mainMenu);
+ mainMenu->setCascade(cascade);
+ QCOMPARE(mainMenu->cascade(), cascade);
+
+ QQuickMenuItem *menuItem1 = qobject_cast<QQuickMenuItem *>(mainMenu->itemAt(0));
+ QVERIFY(menuItem1);
+
+ QQuickMenu *subMenu = window->property("subMenu").value<QQuickMenu *>();
+ QVERIFY(subMenu);
+
+ mainMenu->open();
+ QVERIFY(mainMenu->isVisible());
+ QTRY_VERIFY(mainMenu->isOpened());
+ QVERIFY(!menuItem1->isHighlighted());
+ QVERIFY(!subMenu->isVisible());
+
+ // Highlight the top-level menu item.
+ QTest::keyClick(window, Qt::Key_Down);
+ QVERIFY(menuItem1->isHighlighted());
+
+ QQuickMenuItem *subMenuItem1 = qobject_cast<QQuickMenuItem *>(subMenu->itemAt(0));
+ QVERIFY(subMenuItem1);
+ QQuickMenuItem *subMenuItem2 = qobject_cast<QQuickMenuItem *>(subMenu->itemAt(1));
+ QVERIFY(subMenuItem2);
+
+ // Open the sub-menu.
+ QTest::keyClick(window, mirrored ? Qt::Key_Left : Qt::Key_Right);
+ // The first sub-menu item is disabled, so it should highlight the second one.
+ QVERIFY(!subMenuItem1->isHighlighted());
+ QVERIFY(subMenuItem2->isHighlighted());
+
+ // Close the menus with escape.
+ QTest::keyClick(window, Qt::Key_Escape);
+ QTRY_COMPARE(mainMenu->isVisible(), cascade);
+ QTRY_VERIFY(!subMenu->isVisible());
+ QTest::keyClick(window, Qt::Key_Escape);
+ QTRY_VERIFY(!mainMenu->isVisible());
+ QVERIFY(!subMenu->isVisible());
+}
+
+/*
+ QCOMPARE() compares doubles with 1-in-1e12 precision, which is too fine for these tests.
+ Casting to floats, compared with 1-in-1e5 precision, gives more robust results.
+*/
+#define FLOAT_EQ(u, v) QCOMPARE(float(u), float(v))
+
+void tst_QQuickMenu::subMenuPosition_data()
+{
+ QTest::addColumn<bool>("cascade");
+ QTest::addColumn<bool>("flip");
+ QTest::addColumn<bool>("mirrored");
+ QTest::addColumn<qreal>("overlap");
+
+ QTest::newRow("cascading") << true << false << false << 0.0;
+ QTest::newRow("cascading,flip") << true << true << false << 0.0;
+ QTest::newRow("cascading,overlap") << true << false << false << 10.0;
+ QTest::newRow("cascading,flip,overlap") << true << true << false << 10.0;
+ QTest::newRow("cascading,mirrored") << true << false << true << 0.0;
+ QTest::newRow("cascading,mirrored,flip") << true << true << true << 0.0;
+ QTest::newRow("cascading,mirrored,overlap") << true << false << true << 10.0;
+ QTest::newRow("cascading,mirrored,flip,overlap") << true << true << true << 10.0;
+ QTest::newRow("non-cascading") << false << false << false << 0.0;
+}
+
+void tst_QQuickMenu::subMenuPosition()
+{
+ QFETCH(bool, cascade);
+ QFETCH(bool, flip);
+ QFETCH(bool, mirrored);
+ QFETCH(qreal, overlap);
+
+ QQuickControlsApplicationHelper helper(this, QLatin1String("subMenus.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickApplicationWindow *window = helper.appWindow;
+
+ // Ensure that the default size of the window fits three menus side by side.
+ QQuickMenu *mainMenu = window->property("mainMenu").value<QQuickMenu *>();
+ QVERIFY(mainMenu);
+ window->setWidth(mainMenu->width() * 3 + mainMenu->leftMargin() + mainMenu->rightMargin());
+
+ // the default size of the window fits three menus side by side.
+ // when testing flipping, we resize the window so that the first
+ // sub-menu fits, but the second doesn't
+ if (flip)
+ window->setWidth(window->width() - mainMenu->width());
+
+ centerOnScreen(window);
+ moveMouseAway(window);
+#ifndef Q_OS_ANDROID
+ window->show();
+#else
+ // On Android the desired size does not fit into the screen, so we just
+ // call showNormal. This will make the window larger than the screen, but
+ // all the geometry calculations will be correct. Otherwise we'll get
+ // unpredictable results
+ window->showNormal();
+#endif
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ if (mirrored) {
+ QQmlExpression mirroringExpression(qmlContext(window), window,
+ "LayoutMirroring.childrenInherit = true; LayoutMirroring.enabled = true");
+ QVERIFY2(mirroringExpression.evaluate().isValid(), qPrintable(mirroringExpression.error().toString()));
+ }
+
+ mainMenu->setCascade(cascade);
+ QCOMPARE(mainMenu->cascade(), cascade);
+ mainMenu->setOverlap(overlap);
+ QCOMPARE(mainMenu->overlap(), overlap);
+
+ QQuickMenu *subMenu1 = window->property("subMenu1").value<QQuickMenu *>();
+ QVERIFY(subMenu1);
+ subMenu1->setCascade(cascade);
+ QCOMPARE(subMenu1->cascade(), cascade);
+ subMenu1->setOverlap(overlap);
+ QCOMPARE(subMenu1->overlap(), overlap);
+
+ QQuickMenu *subMenu2 = window->property("subMenu2").value<QQuickMenu *>();
+ QVERIFY(subMenu2);
+ subMenu2->setCascade(cascade);
+ QCOMPARE(subMenu2->cascade(), cascade);
+ subMenu2->setOverlap(overlap);
+ QCOMPARE(subMenu2->overlap(), overlap);
+
+ QQuickMenu *subSubMenu1 = window->property("subSubMenu1").value<QQuickMenu *>();
+ QVERIFY(subSubMenu1);
+ subSubMenu1->setCascade(cascade);
+ QCOMPARE(subSubMenu1->cascade(), cascade);
+ subSubMenu1->setOverlap(overlap);
+ QCOMPARE(subSubMenu1->overlap(), overlap);
+
+ // choose the main menu position so that there's room for the
+ // sub-menus to cascade to the left when mirrored
+ if (mirrored)
+ mainMenu->setX(window->width() - mainMenu->width());
+
+ mainMenu->open();
+ QVERIFY(mainMenu->isVisible());
+ QTRY_VERIFY(mainMenu->isOpened());
+ QVERIFY(!subMenu1->isVisible());
+ QVERIFY(!subMenu2->isVisible());
+ QVERIFY(!subSubMenu1->isVisible());
+
+ // open the sub-menu (never flips)
+ QQuickMenuItem *subMenu1Item = qobject_cast<QQuickMenuItem *>(mainMenu->itemAt(1));
+ QVERIFY(subMenu1Item);
+ QCOMPARE(subMenu1Item->subMenu(), subMenu1);
+ emit subMenu1Item->triggered();
+ QTRY_COMPARE(mainMenu->isVisible(), cascade);
+ QVERIFY(subMenu1->isVisible());
+ QTRY_VERIFY(subMenu1->isOpened());
+ QVERIFY(!subMenu2->isVisible());
+ QVERIFY(!subSubMenu1->isVisible());
+
+ if (cascade) {
+ QCOMPARE(subMenu1->parentItem(), subMenu1Item);
+ // vertically aligned to the parent menu item
+ // We cast to float here because we want to use its larger tolerance for equality (because it has less precision than double).
+ FLOAT_EQ(subMenu1->popupItem()->y(), mainMenu->popupItem()->y() + subMenu1Item->y());
+ if (mirrored) {
+ // on the left of the parent menu
+ FLOAT_EQ(subMenu1->popupItem()->x(), mainMenu->popupItem()->x() - subMenu1->width() + overlap);
+ } else {
+ // on the right of the parent menu
+ FLOAT_EQ(subMenu1->popupItem()->x(), mainMenu->popupItem()->x() + mainMenu->width() - overlap);
+ }
+ } else {
+ QCOMPARE(subMenu1->parentItem(), mainMenu->parentItem());
+ // centered over the parent menu
+ FLOAT_EQ(subMenu1->popupItem()->x(), mainMenu->popupItem()->x() + (mainMenu->width() - subMenu1->width()) / 2);
+ FLOAT_EQ(subMenu1->popupItem()->y(), mainMenu->popupItem()->y() + (mainMenu->height() - subMenu1->height()) / 2);
+ }
+
+ // open the sub-sub-menu (can flip)
+ QQuickMenuItem *subSubMenu1Item = qobject_cast<QQuickMenuItem *>(subMenu1->itemAt(2));
+ QVERIFY(subSubMenu1Item);
+ QCOMPARE(subSubMenu1Item->subMenu(), subSubMenu1);
+ emit subSubMenu1Item->triggered();
+ QTRY_COMPARE(mainMenu->isVisible(), cascade);
+ QTRY_COMPARE(subMenu1->isVisible(), cascade);
+ QVERIFY(!subMenu2->isVisible());
+ QVERIFY(subSubMenu1->isVisible());
+ QTRY_VERIFY(subSubMenu1->isOpened());
+
+ if (cascade) {
+ QCOMPARE(subSubMenu1->parentItem(), subSubMenu1Item);
+ // vertically aligned to the parent menu item
+ FLOAT_EQ(subSubMenu1->popupItem()->y(), subMenu1->popupItem()->y() + subSubMenu1Item->y());
+ if (mirrored != flip) {
+ // on the left of the parent menu
+ FLOAT_EQ(subSubMenu1->popupItem()->x(), subMenu1->popupItem()->x() - subSubMenu1->width() + overlap);
+ } else {
+ // on the right of the parent menu
+ FLOAT_EQ(subSubMenu1->popupItem()->x(), subMenu1->popupItem()->x() + subMenu1->width() - overlap);
+ }
+ } else {
+ QCOMPARE(subSubMenu1->parentItem(), subMenu1->parentItem());
+ // centered over the parent menu
+ FLOAT_EQ(subSubMenu1->popupItem()->x(), subMenu1->popupItem()->x() + (subMenu1->width() - subSubMenu1->width()) / 2);
+ FLOAT_EQ(subSubMenu1->popupItem()->y(), subMenu1->popupItem()->y() + (subMenu1->height() - subSubMenu1->height()) / 2);
+ }
+}
+
+#undef FLOAT_EQ
+
+void tst_QQuickMenu::subMenuWithIcon()
+{
+ QQuickControlsApplicationHelper helper(this, QLatin1String("subMenus.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickApplicationWindow *window = helper.appWindow;
+
+ QQuickMenu *mainMenu = window->property("mainMenu").value<QQuickMenu *>();
+ QVERIFY(mainMenu);
+
+ QQuickMenu *subMenu = window->property("subMenu1").value<QQuickMenu *>();
+ QVERIFY(subMenu);
+
+ const int iconWidth = 14;
+ const int iconHeight = 20;
+ const QUrl iconSource("qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png");
+
+ QQuickIcon icon;
+ icon.setSource(iconSource);
+ icon.setWidth(iconWidth);
+ icon.setHeight(iconHeight);
+
+ subMenu->setIcon(icon);
+ QCOMPARE(subMenu->icon().source(), iconSource);
+ QCOMPARE(subMenu->icon().width(), iconWidth);
+ QCOMPARE(subMenu->icon().height(), iconHeight);
+
+ QQuickMenuItem *subMenuItem = qobject_cast<QQuickMenuItem *>(mainMenu->itemAt(1));
+ QVERIFY(subMenuItem);
+ QCOMPARE(subMenuItem->icon().source(), iconSource);
+ QCOMPARE(subMenuItem->icon().width(), iconWidth);
+ QCOMPARE(subMenuItem->icon().height(), iconHeight);
+}
+
+void tst_QQuickMenu::addRemoveSubMenus()
+{
+ QQuickControlsApplicationHelper helper(this, QLatin1String("subMenus.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickWindow *window = helper.window;
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ QQuickMenu *mainMenu = window->property("mainMenu").value<QQuickMenu *>();
+ QVERIFY(mainMenu);
+
+ QVERIFY(!mainMenu->menuAt(0));
+
+ QPointer<QQuickMenu> subMenu1 = window->property("subMenu1").value<QQuickMenu *>();
+ QVERIFY(!subMenu1.isNull());
+ QCOMPARE(mainMenu->menuAt(1), subMenu1.data());
+
+ QVERIFY(!mainMenu->menuAt(2));
+
+ QPointer<QQuickMenu> subMenu2 = window->property("subMenu2").value<QQuickMenu *>();
+ QVERIFY(!subMenu2.isNull());
+ QCOMPARE(mainMenu->menuAt(3), subMenu2.data());
+
+ QVERIFY(!mainMenu->menuAt(4));
+
+ QPointer<QQuickMenu> subSubMenu1 = window->property("subSubMenu1").value<QQuickMenu *>();
+ QVERIFY(!subSubMenu1.isNull());
+
+ // takeMenu(int) does not destroy the menu, but does destroy the respective item in the parent menu
+ QPointer<QQuickMenuItem> subSubMenu1Item = qobject_cast<QQuickMenuItem *>(subMenu1->itemAt(2));
+ QVERIFY(subSubMenu1Item);
+ QCOMPARE(subSubMenu1Item->subMenu(), subSubMenu1.data());
+ QCOMPARE(subMenu1->takeMenu(2), subSubMenu1.data());
+ QVERIFY(!subMenu1->itemAt(2));
+ QCoreApplication::sendPostedEvents(subSubMenu1, QEvent::DeferredDelete);
+ QVERIFY(!subSubMenu1.isNull());
+ QCoreApplication::sendPostedEvents(subSubMenu1Item, QEvent::DeferredDelete);
+ QVERIFY(subSubMenu1Item.isNull());
+
+ // takeMenu(int) does not destroy an item that doesn't present a menu
+ QPointer<QQuickMenuItem> subMenuItem1 = qobject_cast<QQuickMenuItem *>(subMenu1->itemAt(0));
+ QVERIFY(subMenuItem1);
+ QVERIFY(!subMenuItem1->subMenu());
+ QVERIFY(!subMenu1->takeMenu(0));
+ QCoreApplication::sendPostedEvents(subMenuItem1, QEvent::DeferredDelete);
+ QVERIFY(!subMenuItem1.isNull());
+
+ // addMenu(Menu) re-creates the respective item in the parent menu
+ subMenu1->addMenu(subSubMenu1);
+ subSubMenu1Item = qobject_cast<QQuickMenuItem *>(subMenu1->itemAt(2));
+ QVERIFY(!subSubMenu1Item.isNull());
+
+ // removeMenu(Menu) destroys both the menu and the respective item in the parent menu
+ subMenu1->removeMenu(subSubMenu1);
+ QVERIFY(!subMenu1->itemAt(2));
+ QCoreApplication::sendPostedEvents(subSubMenu1, QEvent::DeferredDelete);
+ QVERIFY(subSubMenu1.isNull());
+ QCoreApplication::sendPostedEvents(subSubMenu1Item, QEvent::DeferredDelete);
+ QVERIFY(subSubMenu1Item.isNull());
+}
+
+void tst_QQuickMenu::scrollable_data()
+{
+ QTest::addColumn<QString>("qmlFilePath");
+
+ QTest::addRow("Window") << QString::fromLatin1("windowScrollable.qml");
+ QTest::addRow("ApplicationWindow") << QString::fromLatin1("applicationWindowScrollable.qml");
+ QTest::addRow("WithPadding") << QString::fromLatin1("scrollableWithPadding.qml");
+}
+
+void tst_QQuickMenu::scrollable()
+{
+ QFETCH(QString, qmlFilePath);
+
+ QQuickControlsApplicationHelper helper(this, qmlFilePath);
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickWindow *window = helper.window;
+#ifndef Q_OS_ANDROID
+ window->show();
+#else
+ window->showNormal();
+#endif
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu*>();
+ menu->open();
+ QVERIFY(menu->isVisible());
+
+ QQuickItem *contentItem = menu->contentItem();
+ QCOMPARE(contentItem->property("interactive").toBool(), true);
+}
+
+void tst_QQuickMenu::disableWhenTriggered_data()
+{
+ QTest::addColumn<int>("menuItemIndex");
+ QTest::addColumn<int>("subMenuItemIndex");
+
+ QTest::addRow("Action") << 0 << -1;
+ QTest::addRow("MenuItem with Action") << 1 << -1;
+ QTest::addRow("MenuItem with Action declared outside menu") << 2 << -1;
+ QTest::addRow("MenuItem with no Action") << 3 << -1;
+
+ QTest::addRow("Sub-Action") << 4 << 0;
+ QTest::addRow("Sub-MenuItem with Action declared inside") << 4 << 1;
+ QTest::addRow("Sub-MenuItem with Action declared outside menu") << 4 << 2;
+ QTest::addRow("Sub-MenuItem with no Action") << 4 << 3;
+}
+
+// Tests that the menu is dismissed when a menu item sets "enabled = false" in onTriggered().
+void tst_QQuickMenu::disableWhenTriggered()
+{
+ if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
+ || (QGuiApplication::platformName() == QLatin1String("minimal")))
+ QSKIP("Mouse hovering not functional on offscreen/minimal platforms");
+
+ QFETCH(int, menuItemIndex);
+ QFETCH(int, subMenuItemIndex);
+
+ QQuickControlsApplicationHelper helper(this, QLatin1String("disableWhenTriggered.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickWindow *window = helper.window;
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ QQuickMenu *menu = window->findChild<QQuickMenu*>("Menu");
+ QVERIFY(menu);
+
+ menu->open();
+ QVERIFY(menu->isVisible());
+ QTRY_VERIFY(menu->isOpened());
+
+ QPointer<QQuickMenuItem> menuItem = qobject_cast<QQuickMenuItem*>(menu->itemAt(menuItemIndex));
+ QVERIFY(menuItem);
+
+ if (subMenuItemIndex == -1) {
+ // Click a top-level menu item.
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier,
+ menuItem->mapToScene(QPointF(menuItem->width() / 2, menuItem->height() / 2)).toPoint());
+ QCOMPARE(menuItem->isEnabled(), false);
+ QTRY_VERIFY(!menu->isVisible());
+ } else {
+ // Click a sub-menu item.
+ QPointer<QQuickMenu> subMenu = menuItem->subMenu();
+ QVERIFY(subMenu);
+
+ QPointer<QQuickMenuItem> subMenuItem = qobject_cast<QQuickMenuItem*>(subMenu->itemAt(subMenuItemIndex));
+ QVERIFY(subMenuItem);
+
+ // First, open the sub-menu.
+#if !defined(Q_OS_ANDROID) and !defined(Q_OS_WEBOS)
+ QTest::mouseMove(window, menuItem->mapToScene(QPoint(1, 1)).toPoint());
+#else
+ // On Android and webOS mouseHover does not open sub-menu, so just click on it
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier,
+ menuItem->mapToScene(QPointF(menuItem->width() / 2, menuItem->height() / 2)).toPoint());
+#endif
+
+ QTRY_VERIFY(subMenu->isVisible());
+#ifndef Q_OS_ANDROID
+ QVERIFY(menuItem->isHovered());
+ QTRY_VERIFY(subMenu->contentItem()->property("contentHeight").toReal() > 0.0);
+#endif
+
+ // Click the item.
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier,
+ subMenuItem->mapToScene(QPointF(subMenuItem->width() / 2, subMenuItem->height() / 2)).toPoint());
+ QCOMPARE(subMenuItem->isEnabled(), false);
+ QTRY_VERIFY(!menu->isVisible());
+ }
+}
+
+void tst_QQuickMenu::menuItemWidth_data()
+{
+ QTest::addColumn<bool>("mirrored");
+
+ QTest::newRow("non-mirrored") << false;
+ QTest::newRow("mirrored") << true;
+}
+
+void tst_QQuickMenu::menuItemWidth()
+{
+ QFETCH(bool, mirrored);
+
+ QQuickControlsApplicationHelper helper(this, QLatin1String("menuItemWidths.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickApplicationWindow *window = helper.appWindow;
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ if (mirrored) {
+ QQmlExpression mirroringExpression(qmlContext(window), window,
+ "LayoutMirroring.childrenInherit = true; LayoutMirroring.enabled = true");
+ QVERIFY2(mirroringExpression.evaluate().isValid(), qPrintable(mirroringExpression.error().toString()));
+ }
+
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu *>();
+ QVERIFY(menu);
+ menu->open();
+ QTRY_VERIFY(menu->isOpened());
+ for (int i = 0; i < menu->count(); ++i)
+ QCOMPARE(menu->itemAt(i)->width(), menu->availableWidth());
+}
+
+void tst_QQuickMenu::menuItemWidthAfterMenuWidthChanged_data()
+{
+ QTest::addColumn<bool>("mirrored");
+
+ QTest::newRow("non-mirrored") << false;
+ QTest::newRow("mirrored") << true;
+}
+
+void tst_QQuickMenu::menuItemWidthAfterMenuWidthChanged()
+{
+ QFETCH(bool, mirrored);
+
+ QQuickControlsApplicationHelper helper(this, QLatin1String("menuItemWidths.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickApplicationWindow *window = helper.appWindow;
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ if (mirrored) {
+ QQmlExpression mirroringExpression(qmlContext(window), window,
+ "LayoutMirroring.childrenInherit = true; LayoutMirroring.enabled = true");
+ QVERIFY2(mirroringExpression.evaluate().isValid(), qPrintable(mirroringExpression.error().toString()));
+ }
+
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu *>();
+ QVERIFY(menu);
+ menu->open();
+ QTRY_VERIFY(menu->isOpened());
+ for (int i = 0; i < menu->count(); ++i) {
+ // Check that the width of menu items is correct before we resize the menu.
+ const QQuickItem *item = menu->itemAt(i);
+ QVERIFY2(qFuzzyCompare(item->width(), menu->availableWidth()),
+ qPrintable(QString::fromLatin1("Expected width of %1 to be %2, but it's %3")
+ .arg(item->objectName()).arg(menu->availableWidth()).arg(item->width())));
+ }
+
+ menu->setWidth(menu->width() + 10);
+
+ // Check that the width of menu items is correct after we resize the menu.
+ for (int i = 0; i < menu->count(); ++i) {
+ // Check that the width of menu items is correct after we resize the menu.
+ const QQuickItem *item = menu->itemAt(i);
+ QVERIFY2(qFuzzyCompare(item->width(), menu->availableWidth()),
+ qPrintable(QString::fromLatin1("Expected width of %1 to be %2, but it's %3")
+ .arg(item->objectName()).arg(menu->availableWidth()).arg(item->width())));
+ }
+}
+
+void tst_QQuickMenu::menuItemWidthAfterImplicitWidthChanged_data()
+{
+ QTest::addColumn<bool>("mirrored");
+
+ QTest::newRow("non-mirrored") << false;
+ QTest::newRow("mirrored") << true;
+}
+
+void tst_QQuickMenu::menuItemWidthAfterImplicitWidthChanged()
+{
+ QFETCH(bool, mirrored);
+
+ QQuickControlsApplicationHelper helper(this, QLatin1String("menuItemWidths.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickApplicationWindow *window = helper.appWindow;
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ if (mirrored) {
+ QQmlExpression mirroringExpression(qmlContext(window), window,
+ "LayoutMirroring.childrenInherit = true; LayoutMirroring.enabled = true");
+ QVERIFY2(mirroringExpression.evaluate().isValid(), qPrintable(mirroringExpression.error().toString()));
+ }
+
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu *>();
+ QVERIFY(menu);
+ menu->open();
+ QTRY_VERIFY(menu->isOpened());
+ // Check that the width of the menu item is correct before we change its font size.
+ QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem*>(menu->itemAt(0));
+ QCOMPARE(menuItem->width(), menu->availableWidth());
+
+ // Add some text to increase the implicitWidth of the MenuItem.
+ const qreal oldImplicitWidth = menuItem->implicitWidth();
+ for (int i = 0; menuItem->implicitWidth() <= oldImplicitWidth; ++i) {
+ menuItem->setText(menuItem->text() + QLatin1String("---"));
+ if (i == 100)
+ QFAIL("Shouldn't need 100 iterations to increase MenuItem's implicitWidth; something is wrong here");
+ }
+
+ // Check that the width of the menu item is correct after we change its font size.
+ QCOMPARE(menuItem->width(), menu->availableWidth());
+}
+
+void tst_QQuickMenu::menuItemWidthAfterRetranslate()
+{
+ QQuickControlsApplicationHelper helper(this, QLatin1String("menuItemWidths.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickApplicationWindow *window = helper.appWindow;
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu *>();
+ QVERIFY(menu);
+ menu->open();
+ QTRY_VERIFY(menu->isOpened());
+ for (int i = 0; i < menu->count(); ++i) {
+ // Check that the width of each menu item is correct before we retranslate.
+ const QQuickItem *item = menu->itemAt(i);
+ QVERIFY2(qFuzzyCompare(item->width(), menu->availableWidth()),
+ qPrintable(QString::fromLatin1("Expected width of %1 to be %2, but it's %3")
+ .arg(item->objectName()).arg(menu->availableWidth()).arg(item->width())));
+ }
+
+ // Call retranslate() and cause all bindings to be re-evaluated.
+ helper.engine.retranslate();
+
+ for (int i = 0; i < menu->count(); ++i) {
+ // Check that the width of each menu item is correct after we retranslate.
+ const QQuickItem *item = menu->itemAt(i);
+ QVERIFY2(qFuzzyCompare(item->width(), menu->availableWidth()),
+ qPrintable(QString::fromLatin1("Expected width of %1 to be %2, but it's %3")
+ .arg(item->objectName()).arg(menu->availableWidth()).arg(item->width())));
+ }
+}
+
+void tst_QQuickMenu::giveMenuItemFocusOnButtonPress()
+{
+ if (!hasWindowActivation())
+ QSKIP("Window activation is not supported");
+
+ QQuickControlsApplicationHelper helper(this, QLatin1String("giveMenuItemFocusOnButtonPress.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickApplicationWindow *window = helper.appWindow;
+ window->show();
+ QVERIFY(QTest::qWaitForWindowActive(window));
+
+ // Press enter on the button to open the menu.
+ QQuickButton *menuButton = window->property("menuButton").value<QQuickButton*>();
+ QVERIFY(menuButton);
+ menuButton->forceActiveFocus();
+ QVERIFY(menuButton->hasActiveFocus());
+
+ QSignalSpy clickedSpy(window, SIGNAL(menuButtonClicked()));
+ QVERIFY(clickedSpy.isValid());
+
+ QTest::keyClick(window, Qt::Key_Return);
+ QCOMPARE(clickedSpy.size(), 1);
+
+ // The menu should still be open.
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu*>();
+ QVERIFY(menu);
+ QTRY_VERIFY(menu->isOpened());
+}
+
+void tst_QQuickMenu::customMenuCullItems()
+{
+ QQuickControlsApplicationHelper helper(this, QLatin1String("customMenuCullItems.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickApplicationWindow *window = helper.appWindow;
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu*>();
+ QVERIFY(menu);
+ menu->open();
+ QTRY_VERIFY(menu->isOpened());
+
+ QQuickItem *menuItemFirst = menu->itemAt(0);
+ QQuickItem *menuItemLast = menu->itemAt(menu->count() - 1);
+ QVERIFY(menuItemFirst);
+ QVERIFY(menuItemLast);
+ QTRY_VERIFY(!QQuickItemPrivate::get(menuItemFirst)->culled);
+ QTRY_VERIFY(QQuickItemPrivate::get(menuItemLast)->culled);
+}
+
+void tst_QQuickMenu::customMenuUseRepeaterAsTheContentItem()
+{
+ QQuickControlsApplicationHelper helper(this, QLatin1String("customMenuUseRepeaterAsTheContentItem.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickApplicationWindow *window = helper.appWindow;
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ QQuickMenu *menu = window->property("menu").value<QQuickMenu*>();
+ QVERIFY(menu);
+ menu->open();
+ QTRY_VERIFY(menu->isVisible());
+
+ QQuickItem *menuItemFirst = menu->itemAt(0);
+ QQuickItem *menuItemLast = menu->itemAt(menu->count() - 1);
+ QTRY_VERIFY(!QQuickItemPrivate::get(menuItemFirst)->culled);
+ QTRY_VERIFY(!QQuickItemPrivate::get(menuItemLast)->culled);
+}
+
+QTEST_QUICKCONTROLS_MAIN(tst_QQuickMenu)
+
+#include "tst_qquickmenu.moc"