1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL3$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or later as published by the Free
28** Software Foundation and appearing in the file LICENSE.GPL included in
29** the packaging of this file. Please review the following information to
30** ensure the GNU General Public License version 2.0 requirements will be
31** met: http://www.gnu.org/licenses/gpl-2.0.html.
32**
33** $QT_END_LICENSE$
34**
35****************************************************************************/
36
37#include <QtTest/qtest.h>
38#include <QtTest/qsignalspy.h>
39#include <QtGui/qcursor.h>
40#include <QtGui/qstylehints.h>
41#include <QtQml/qqmlengine.h>
42#include <QtQml/qqmlcomponent.h>
43#include <QtQml/qqmlcontext.h>
44#include <QtQuick/qquickview.h>
45#include <QtQuick/private/qquickitem_p.h>
46#include "../shared/util.h"
47#include "../shared/visualtestutil.h"
48#include "../shared/qtest_quickcontrols.h"
49
50#include <QtQuickTemplates2/private/qquickaction_p.h>
51#include <QtQuickTemplates2/private/qquickapplicationwindow_p.h>
52#include <QtQuickTemplates2/private/qquickoverlay_p.h>
53#include <QtQuickTemplates2/private/qquickbutton_p.h>
54#include <QtQuickTemplates2/private/qquickmenu_p.h>
55#include <QtQuickTemplates2/private/qquickmenuitem_p.h>
56#include <QtQuickTemplates2/private/qquickmenuseparator_p.h>
57
58using namespace QQuickVisualTestUtil;
59
60class tst_QQuickMenu : public QQmlDataTest
61{
62 Q_OBJECT
63
64public:
65
66private slots:
67 void defaults();
68 void count();
69 void mouse();
70 void pressAndHold();
71 void contextMenuKeyboard();
72 void disabledMenuItemKeyNavigation();
73 void mnemonics();
74 void menuButton();
75 void addItem();
76 void menuSeparator();
77 void repeater();
78 void order();
79 void popup();
80 void actions();
81 void removeTakeItem();
82 void subMenuMouse_data();
83 void subMenuMouse();
84 void subMenuDisabledMouse_data();
85 void subMenuDisabledMouse();
86 void subMenuKeyboard_data();
87 void subMenuKeyboard();
88 void subMenuDisabledKeyboard_data();
89 void subMenuDisabledKeyboard();
90 void subMenuPosition_data();
91 void subMenuPosition();
92 void addRemoveSubMenus();
93 void scrollable_data();
94 void scrollable();
95 void disableWhenTriggered_data();
96 void disableWhenTriggered();
97 void menuItemWidth_data();
98 void menuItemWidth();
99 void menuItemWidthAfterMenuWidthChanged_data();
100 void menuItemWidthAfterMenuWidthChanged();
101 void menuItemWidthAfterImplicitWidthChanged_data();
102 void menuItemWidthAfterImplicitWidthChanged();
103 void menuItemWidthAfterRetranslate();
104 void giveMenuItemFocusOnButtonPress();
105};
106
107void tst_QQuickMenu::defaults()
108{
109 QQuickApplicationHelper helper(this, QLatin1String("applicationwindow.qml"));
110 QVERIFY2(helper.ready, helper.failureMessage());
111
112 QQuickMenu *emptyMenu = helper.appWindow->property(name: "emptyMenu").value<QQuickMenu*>();
113 QCOMPARE(emptyMenu->isVisible(), false);
114 QCOMPARE(emptyMenu->currentIndex(), -1);
115 QCOMPARE(emptyMenu->contentItem()->property("currentIndex"), QVariant(-1));
116 QCOMPARE(emptyMenu->count(), 0);
117}
118
119void tst_QQuickMenu::count()
120{
121 QQuickApplicationHelper helper(this, QLatin1String("applicationwindow.qml"));
122 QVERIFY2(helper.ready, helper.failureMessage());
123
124 QQuickMenu *menu = helper.window->property(name: "emptyMenu").value<QQuickMenu*>();
125 QVERIFY(menu);
126
127 QSignalSpy countSpy(menu, &QQuickMenu::countChanged);
128 QVERIFY(countSpy.isValid());
129
130 menu->addItem(item: new QQuickItem);
131 QCOMPARE(menu->count(), 1);
132 QCOMPARE(countSpy.count(), 1);
133
134 menu->insertItem(index: 0, item: new QQuickItem);
135 QCOMPARE(menu->count(), 2);
136 QCOMPARE(countSpy.count(), 2);
137
138 menu->removeItem(item: menu->itemAt(index: 1));
139 QCOMPARE(menu->count(), 1);
140 QCOMPARE(countSpy.count(), 3);
141
142 QScopedPointer<QQuickItem> item(menu->takeItem(index: 0));
143 QVERIFY(item);
144 QCOMPARE(menu->count(), 0);
145 QCOMPARE(countSpy.count(), 4);
146}
147
148void tst_QQuickMenu::mouse()
149{
150 if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
151 || (QGuiApplication::platformName() == QLatin1String("minimal")))
152 QSKIP("Mouse hovering not functional on offscreen/minimal platforms");
153
154 QQuickApplicationHelper helper(this, QLatin1String("applicationwindow.qml"));
155 QVERIFY2(helper.ready, helper.failureMessage());
156
157 QQuickApplicationWindow *window = helper.appWindow;
158 centerOnScreen(window);
159 moveMouseAway(window);
160 window->show();
161 QVERIFY(QTest::qWaitForWindowActive(window));
162
163 QQuickMenu *menu = window->property(name: "menu").value<QQuickMenu*>();
164 menu->open();
165 QVERIFY(menu->isVisible());
166 QVERIFY(window->overlay()->childItems().contains(menu->contentItem()->parentItem()));
167 QTRY_VERIFY(menu->isOpened());
168
169 QQuickItem *firstItem = menu->itemAt(index: 0);
170 QSignalSpy clickedSpy(firstItem, SIGNAL(clicked()));
171 QSignalSpy triggeredSpy(firstItem, SIGNAL(triggered()));
172 QSignalSpy visibleSpy(menu, SIGNAL(visibleChanged()));
173
174 // Ensure that presses cause the current index to change,
175 // so that the highlight acts as a way of illustrating press state.
176 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier,
177 pos: QPoint(menu->leftPadding() + firstItem->width() / 2, menu->topPadding() + firstItem->height() / 2));
178 QVERIFY(firstItem->hasActiveFocus());
179 QCOMPARE(menu->currentIndex(), 0);
180 QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(0));
181 QVERIFY(menu->isVisible());
182
183 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier,
184 pos: QPoint(menu->leftPadding() + firstItem->width() / 2, menu->topPadding() + firstItem->height() / 2));
185 QCOMPARE(clickedSpy.count(), 1);
186 QCOMPARE(triggeredSpy.count(), 1);
187 QTRY_COMPARE(visibleSpy.count(), 1);
188 QVERIFY(!menu->isVisible());
189 QVERIFY(!window->overlay()->childItems().contains(menu->contentItem()));
190 QCOMPARE(menu->currentIndex(), -1);
191 QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1));
192
193 menu->open();
194 QCOMPARE(visibleSpy.count(), 2);
195 QVERIFY(menu->isVisible());
196 QVERIFY(window->overlay()->childItems().contains(menu->contentItem()->parentItem()));
197 QTRY_VERIFY(menu->isOpened());
198
199 // Ensure that we have enough space to click outside of the menu.
200 QVERIFY(window->width() > menu->contentItem()->width());
201 QVERIFY(window->height() > menu->contentItem()->height());
202 QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier,
203 pos: QPoint(menu->contentItem()->width() + 1, menu->contentItem()->height() + 1));
204 QTRY_COMPARE(visibleSpy.count(), 3);
205 QVERIFY(!menu->isVisible());
206 QVERIFY(!window->overlay()->childItems().contains(menu->contentItem()->parentItem()));
207
208 menu->open();
209 QCOMPARE(visibleSpy.count(), 4);
210 QVERIFY(menu->isVisible());
211 QVERIFY(window->overlay()->childItems().contains(menu->contentItem()->parentItem()));
212 QTRY_VERIFY(menu->isOpened());
213
214 // Hover-highlight through the menu items one by one
215 QQuickItem *prevHoverItem = nullptr;
216 QQuickItem *listView = menu->contentItem();
217 for (int y = menu->topPadding(); y < listView->height(); ++y) {
218 QQuickItem *hoverItem = nullptr;
219 QVERIFY(QMetaObject::invokeMethod(listView, "itemAt", Q_RETURN_ARG(QQuickItem *, hoverItem), Q_ARG(qreal, 0), Q_ARG(qreal, listView->property("contentY").toReal() + y)));
220 if (!hoverItem || !hoverItem->isVisible() || hoverItem == prevHoverItem)
221 continue;
222 QTest::mouseMove(window, pos: QPoint(
223 menu->leftPadding() + hoverItem->x() + hoverItem->width() / 2,
224 menu->topPadding() + hoverItem->y() + hoverItem->height() / 2));
225 QTRY_VERIFY(hoverItem->property("highlighted").toBool());
226 if (prevHoverItem)
227 QVERIFY(!prevHoverItem->property("highlighted").toBool());
228 prevHoverItem = hoverItem;
229 }
230
231 // Try pressing within the menu and releasing outside of it; it should close.
232 // TODO: won't work until QQuickPopup::releasedOutside() actually gets emitted
233// QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, QPoint(firstItem->width() / 2, firstItem->height() / 2));
234// QVERIFY(firstItem->hasActiveFocus());
235// QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(0));
236// QVERIFY(menu->isVisible());
237// QCOMPARE(triggeredSpy.count(), 1);
238
239// QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, QPoint(menu->contentItem()->width() + 1, firstItem->height() / 2));
240// QCOMPARE(clickedSpy.count(), 1);
241// QCOMPARE(triggeredSpy.count(), 1);
242// QCOMPARE(visibleSpy.count(), 5);
243// QVERIFY(!menu->isVisible());
244// QVERIFY(!window->overlay()->childItems().contains(menu->contentItem()));
245// QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1));
246}
247
248void tst_QQuickMenu::pressAndHold()
249{
250 QQuickApplicationHelper helper(this, QLatin1String("pressAndHold.qml"));
251 QVERIFY2(helper.ready, helper.failureMessage());
252
253 QQuickWindow *window = helper.window;
254 window->show();
255 QVERIFY(QTest::qWaitForWindowActive(window));
256
257 QQuickMenu *menu = window->property(name: "menu").value<QQuickMenu *>();
258 QVERIFY(menu);
259
260 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(1, 1));
261 QTRY_VERIFY(menu->isVisible());
262
263 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(1, 1));
264 QVERIFY(menu->isVisible());
265
266 QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(1, 1));
267 QTRY_VERIFY(!menu->isVisible());
268}
269
270void tst_QQuickMenu::contextMenuKeyboard()
271{
272 if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls)
273 QSKIP("This platform only allows tab focus for text controls");
274
275 QQuickApplicationHelper helper(this, QLatin1String("applicationwindow.qml"));
276 QVERIFY2(helper.ready, helper.failureMessage());
277
278 QQuickApplicationWindow *window = helper.appWindow;
279 centerOnScreen(window);
280 moveMouseAway(window);
281 window->show();
282 window->requestActivate();
283 QVERIFY(QTest::qWaitForWindowActive(window));
284 QVERIFY(QGuiApplication::focusWindow() == window);
285
286 QQuickMenu *menu = window->property(name: "menu").value<QQuickMenu*>();
287 QCOMPARE(menu->currentIndex(), -1);
288 QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1));
289
290 QQuickMenuItem *firstItem = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 0));
291 QVERIFY(firstItem);
292 QSignalSpy visibleSpy(menu, SIGNAL(visibleChanged()));
293
294 menu->setFocus(true);
295 menu->open();
296 QCOMPARE(visibleSpy.count(), 1);
297 QVERIFY(menu->isVisible());
298 QVERIFY(window->overlay()->childItems().contains(menu->contentItem()->parentItem()));
299 QTRY_VERIFY(menu->isOpened());
300 QVERIFY(!firstItem->hasActiveFocus());
301 QVERIFY(!firstItem->property("highlighted").toBool());
302 QCOMPARE(menu->currentIndex(), -1);
303 QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1));
304
305 QTest::keyClick(window, key: Qt::Key_Tab);
306 QVERIFY(firstItem->hasActiveFocus());
307 QVERIFY(firstItem->hasVisualFocus());
308 QVERIFY(firstItem->isHighlighted());
309 QCOMPARE(firstItem->focusReason(), Qt::TabFocusReason);
310 QCOMPARE(menu->currentIndex(), 0);
311 QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(0));
312
313 QQuickMenuItem *secondItem = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 1));
314 QVERIFY(secondItem);
315 QTest::keyClick(window, key: Qt::Key_Tab);
316 QVERIFY(!firstItem->hasActiveFocus());
317 QVERIFY(!firstItem->hasVisualFocus());
318 QVERIFY(!firstItem->isHighlighted());
319 QVERIFY(secondItem->hasActiveFocus());
320 QVERIFY(secondItem->hasVisualFocus());
321 QVERIFY(secondItem->isHighlighted());
322 QCOMPARE(secondItem->focusReason(), Qt::TabFocusReason);
323 QCOMPARE(menu->currentIndex(), 1);
324 QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(1));
325
326 QSignalSpy secondTriggeredSpy(secondItem, SIGNAL(triggered()));
327 QTest::keyClick(window, key: Qt::Key_Space);
328 QCOMPARE(secondTriggeredSpy.count(), 1);
329 QTRY_COMPARE(visibleSpy.count(), 2);
330 QVERIFY(!menu->isVisible());
331 QVERIFY(!window->overlay()->childItems().contains(menu->contentItem()));
332 QVERIFY(!firstItem->hasActiveFocus());
333 QVERIFY(!firstItem->hasVisualFocus());
334 QVERIFY(!firstItem->isHighlighted());
335 QVERIFY(!secondItem->hasActiveFocus());
336 QVERIFY(!secondItem->hasVisualFocus());
337 QVERIFY(!secondItem->isHighlighted());
338 QCOMPARE(menu->currentIndex(), -1);
339 QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1));
340
341 // Enter/return should also work.
342 // Open the menu.
343 menu->open();
344 QCOMPARE(visibleSpy.count(), 3);
345 QVERIFY(menu->isVisible());
346 QTRY_VERIFY(menu->isOpened());
347 // Give the first item focus.
348 QTest::keyClick(window, key: Qt::Key_Tab);
349 QVERIFY(firstItem->hasActiveFocus());
350 QVERIFY(firstItem->hasVisualFocus());
351 QVERIFY(firstItem->isHighlighted());
352 QCOMPARE(firstItem->focusReason(), Qt::TabFocusReason);
353 QCOMPARE(menu->currentIndex(), 0);
354 QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(0));
355 // Press enter.
356 QSignalSpy firstTriggeredSpy(firstItem, SIGNAL(triggered()));
357 QTest::keyClick(window, key: Qt::Key_Return);
358 QCOMPARE(firstTriggeredSpy.count(), 1);
359 QTRY_COMPARE(visibleSpy.count(), 4);
360 QVERIFY(!menu->isVisible());
361 QVERIFY(!window->overlay()->childItems().contains(menu->contentItem()));
362 QVERIFY(!firstItem->hasActiveFocus());
363 QVERIFY(!firstItem->hasVisualFocus());
364 QVERIFY(!firstItem->isHighlighted());
365 QVERIFY(!secondItem->hasActiveFocus());
366 QVERIFY(!secondItem->hasVisualFocus());
367 QVERIFY(!secondItem->isHighlighted());
368 QCOMPARE(menu->currentIndex(), -1);
369 QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1));
370
371 menu->open();
372 QCOMPARE(visibleSpy.count(), 5);
373 QVERIFY(menu->isVisible());
374 QVERIFY(window->overlay()->childItems().contains(menu->contentItem()->parentItem()));
375 QTRY_VERIFY(menu->isOpened());
376 QVERIFY(!firstItem->hasActiveFocus());
377 QVERIFY(!firstItem->hasVisualFocus());
378 QVERIFY(!firstItem->isHighlighted());
379 QVERIFY(!secondItem->hasActiveFocus());
380 QVERIFY(!secondItem->hasVisualFocus());
381 QVERIFY(!secondItem->isHighlighted());
382 QCOMPARE(menu->currentIndex(), -1);
383 QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1));
384
385 QTest::keyClick(window, key: Qt::Key_Down);
386 QVERIFY(firstItem->hasActiveFocus());
387 QVERIFY(firstItem->hasVisualFocus());
388 QVERIFY(firstItem->isHighlighted());
389 QCOMPARE(firstItem->focusReason(), Qt::TabFocusReason);
390
391 QTest::keyClick(window, key: Qt::Key_Down);
392 QVERIFY(secondItem->hasActiveFocus());
393 QVERIFY(secondItem->hasVisualFocus());
394 QVERIFY(secondItem->isHighlighted());
395 QCOMPARE(secondItem->focusReason(), Qt::TabFocusReason);
396
397 QTest::keyClick(window, key: Qt::Key_Down);
398 QQuickMenuItem *thirdItem = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 2));
399 QVERIFY(thirdItem);
400 QVERIFY(!firstItem->hasActiveFocus());
401 QVERIFY(!firstItem->hasVisualFocus());
402 QVERIFY(!firstItem->isHighlighted());
403 QVERIFY(!secondItem->hasActiveFocus());
404 QVERIFY(!secondItem->hasVisualFocus());
405 QVERIFY(!secondItem->isHighlighted());
406 QVERIFY(thirdItem->hasActiveFocus());
407 QVERIFY(thirdItem->hasVisualFocus());
408 QVERIFY(thirdItem->isHighlighted());
409 QCOMPARE(thirdItem->focusReason(), Qt::TabFocusReason);
410
411 // Key navigation shouldn't wrap by default.
412 QTest::keyClick(window, key: Qt::Key_Down);
413 QVERIFY(!firstItem->hasActiveFocus());
414 QVERIFY(!firstItem->hasVisualFocus());
415 QVERIFY(!firstItem->isHighlighted());
416 QVERIFY(!secondItem->hasActiveFocus());
417 QVERIFY(!secondItem->hasVisualFocus());
418 QVERIFY(!secondItem->isHighlighted());
419 QVERIFY(thirdItem->hasActiveFocus());
420 QVERIFY(thirdItem->hasVisualFocus());
421 QVERIFY(thirdItem->isHighlighted());
422 QCOMPARE(thirdItem->focusReason(), Qt::TabFocusReason);
423
424 QTest::keyClick(window, key: Qt::Key_Up);
425 QVERIFY(!firstItem->hasActiveFocus());
426 QVERIFY(!firstItem->hasVisualFocus());
427 QVERIFY(!firstItem->isHighlighted());
428 QVERIFY(secondItem->hasActiveFocus());
429 QVERIFY(secondItem->hasVisualFocus());
430 QVERIFY(secondItem->isHighlighted());
431 QCOMPARE(secondItem->focusReason(), Qt::BacktabFocusReason);
432 QVERIFY(!thirdItem->hasActiveFocus());
433 QVERIFY(!thirdItem->hasVisualFocus());
434 QVERIFY(!thirdItem->isHighlighted());
435
436 QTest::keyClick(window, key: Qt::Key_Backtab);
437 QVERIFY(firstItem->hasActiveFocus());
438 QVERIFY(firstItem->hasVisualFocus());
439 QVERIFY(firstItem->isHighlighted());
440 QCOMPARE(firstItem->focusReason(), Qt::BacktabFocusReason);
441 QVERIFY(!secondItem->hasActiveFocus());
442 QVERIFY(!secondItem->hasVisualFocus());
443 QVERIFY(!secondItem->isHighlighted());
444 QVERIFY(!thirdItem->hasActiveFocus());
445 QVERIFY(!thirdItem->hasVisualFocus());
446 QVERIFY(!thirdItem->isHighlighted());
447
448 QTest::keyClick(window, key: Qt::Key_Escape);
449 QTRY_COMPARE(visibleSpy.count(), 6);
450 QVERIFY(!menu->isVisible());
451}
452
453// QTBUG-70181
454void tst_QQuickMenu::disabledMenuItemKeyNavigation()
455{
456 if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls)
457 QSKIP("This platform only allows tab focus for text controls");
458
459 QQuickApplicationHelper helper(this, QLatin1String("disabledMenuItemKeyNavigation.qml"));
460 QVERIFY2(helper.ready, helper.failureMessage());
461
462 QQuickApplicationWindow *window = helper.appWindow;
463 centerOnScreen(window);
464 moveMouseAway(window);
465 window->show();
466 window->requestActivate();
467 QVERIFY(QTest::qWaitForWindowActive(window));
468 QVERIFY(QGuiApplication::focusWindow() == window);
469
470 QQuickMenu *menu = window->property(name: "menu").value<QQuickMenu*>();
471 QCOMPARE(menu->currentIndex(), -1);
472 QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1));
473
474 QQuickMenuItem *firstItem = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 0));
475 QVERIFY(firstItem);
476
477 QQuickMenuItem *secondItem = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 1));
478 QVERIFY(secondItem);
479
480 QQuickMenuItem *thirdItem = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 2));
481 QVERIFY(thirdItem);
482
483 menu->setFocus(true);
484 menu->open();
485 QVERIFY(menu->isVisible());
486 QTRY_VERIFY(menu->isOpened());
487 QVERIFY(!firstItem->hasActiveFocus());
488 QVERIFY(!firstItem->property("highlighted").toBool());
489 QCOMPARE(menu->currentIndex(), -1);
490
491 QTest::keyClick(window, key: Qt::Key_Tab);
492 QVERIFY(firstItem->hasActiveFocus());
493 QVERIFY(firstItem->hasVisualFocus());
494 QVERIFY(firstItem->isHighlighted());
495 QCOMPARE(firstItem->focusReason(), Qt::TabFocusReason);
496 QCOMPARE(menu->currentIndex(), 0);
497
498 // Shouldn't be possible to give focus to a disabled menu item.
499 QTest::keyClick(window, key: Qt::Key_Down);
500 QVERIFY(!secondItem->hasActiveFocus());
501 QVERIFY(!secondItem->hasVisualFocus());
502 QVERIFY(!secondItem->isHighlighted());
503 QVERIFY(thirdItem->hasActiveFocus());
504 QVERIFY(thirdItem->hasVisualFocus());
505 QVERIFY(thirdItem->isHighlighted());
506 QCOMPARE(thirdItem->focusReason(), Qt::TabFocusReason);
507
508 QTest::keyClick(window, key: Qt::Key_Up);
509 QVERIFY(firstItem->hasActiveFocus());
510 QVERIFY(firstItem->hasVisualFocus());
511 QVERIFY(firstItem->isHighlighted());
512 QCOMPARE(firstItem->focusReason(), Qt::BacktabFocusReason);
513
514 QTest::keyClick(window, key: Qt::Key_Escape);
515 QTRY_VERIFY(!menu->isVisible());
516}
517
518void tst_QQuickMenu::mnemonics()
519{
520#ifdef Q_OS_MACOS
521 QSKIP("Mnemonics are not used on macOS");
522#endif
523
524 QQuickApplicationHelper helper(this, QLatin1String("mnemonics.qml"));
525 QVERIFY2(helper.ready, helper.failureMessage());
526
527 QQuickWindow *window = helper.window;
528 window->show();
529 window->requestActivate();
530 QVERIFY(QTest::qWaitForWindowActive(window));
531
532 QQuickMenu *menu = window->property(name: "menu").value<QQuickMenu *>();
533 QQuickAction *action = window->property(name: "action").value<QQuickAction *>();
534 QQuickMenuItem *menuItem = window->property(name: "menuItem").value<QQuickMenuItem *>();
535 QQuickMenu *subMenu = window->property(name: "subMenu").value<QQuickMenu *>();
536 QQuickMenuItem *subMenuItem = window->property(name: "subMenuItem").value<QQuickMenuItem *>();
537 QVERIFY(menu && action && menuItem && subMenu && subMenuItem);
538
539 menu->open();
540 QTRY_VERIFY(menu->isOpened());
541
542 QSignalSpy actionSpy(action, &QQuickAction::triggered);
543 QVERIFY(actionSpy.isValid());
544 QTest::keyClick(window, key: Qt::Key_A, modifier: Qt::AltModifier); // "&Action"
545 QCOMPARE(actionSpy.count(), 1);
546
547 menu->open();
548 QTRY_VERIFY(menu->isOpened());
549
550 QSignalSpy menuItemSpy(menuItem, &QQuickMenuItem::triggered);
551 QVERIFY(menuItemSpy.isValid());
552 QTest::keyClick(window, key: Qt::Key_I, modifier: Qt::AltModifier); // "Menu &Item"
553 QCOMPARE(menuItemSpy.count(), 1);
554
555 menu->open();
556 QTRY_VERIFY(menu->isOpened());
557
558 QTest::keyClick(window, key: Qt::Key_M, modifier: Qt::AltModifier); // "Sub &Menu"
559 QTRY_VERIFY(subMenu->isOpened());
560
561 QSignalSpy subMenuItemSpy(subMenuItem, &QQuickMenuItem::triggered);
562 QVERIFY(subMenuItemSpy.isValid());
563 QTest::keyClick(window, key: Qt::Key_S, modifier: Qt::AltModifier); // "&Sub Menu Item"
564 QCOMPARE(subMenuItemSpy.count(), 1);
565}
566
567void tst_QQuickMenu::menuButton()
568{
569 if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls)
570 QSKIP("This platform only allows tab focus for text controls");
571
572 QQuickApplicationHelper helper(this, QLatin1String("applicationwindow.qml"));
573 QVERIFY2(helper.ready, helper.failureMessage());
574
575 QQuickApplicationWindow *window = helper.appWindow;
576 window->show();
577 window->requestActivate();
578 QVERIFY(QTest::qWaitForWindowActive(window));
579 QVERIFY(QGuiApplication::focusWindow() == window);
580
581 QQuickMenu *menu = window->property(name: "menu").value<QQuickMenu*>();
582 QQuickButton *menuButton = window->property(name: "menuButton").value<QQuickButton*>();
583 QSignalSpy visibleSpy(menu, SIGNAL(visibleChanged()));
584
585 menuButton->setVisible(true);
586 QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier,
587 pos: menuButton->mapToScene(point: QPointF(menuButton->width() / 2, menuButton->height() / 2)).toPoint());
588 QCOMPARE(visibleSpy.count(), 1);
589 QVERIFY(menu->isVisible());
590 QTRY_VERIFY(menu->isOpened());
591
592 QTest::keyClick(window, key: Qt::Key_Tab);
593 QQuickItem *firstItem = menu->itemAt(index: 0);
594 QVERIFY(firstItem->hasActiveFocus());
595}
596
597void tst_QQuickMenu::addItem()
598{
599 QQuickApplicationHelper helper(this, QLatin1String("addItem.qml"));
600 QVERIFY2(helper.ready, helper.failureMessage());
601 QQuickApplicationWindow *window = helper.appWindow;
602 window->show();
603 QVERIFY(QTest::qWaitForWindowActive(window));
604
605 QQuickMenu *menu = window->property(name: "menu").value<QQuickMenu*>();
606 QVERIFY(menu);
607 menu->open();
608 QVERIFY(menu->isVisible());
609
610 QQuickItem *menuItem = menu->itemAt(index: 0);
611 QVERIFY(menuItem);
612 QTRY_VERIFY(!QQuickItemPrivate::get(menuItem)->culled); // QTBUG-53262
613
614 QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier,
615 pos: menuItem->mapToScene(point: QPointF(menuItem->width() / 2, menuItem->height() / 2)).toPoint());
616 QTRY_VERIFY(!menu->isVisible());
617}
618
619void tst_QQuickMenu::menuSeparator()
620{
621 QQuickApplicationHelper helper(this, QLatin1String("menuSeparator.qml"));
622 QVERIFY2(helper.ready, helper.failureMessage());
623 QQuickWindow *window = helper.window;
624 centerOnScreen(window);
625 moveMouseAway(window);
626 window->show();
627 QVERIFY(QTest::qWaitForWindowActive(window));
628
629 QQuickMenu *menu = window->property(name: "menu").value<QQuickMenu*>();
630 QVERIFY(menu);
631 menu->open();
632 QVERIFY(menu->isVisible());
633
634 QQuickMenuItem *newMenuItem = qobject_cast<QQuickMenuItem*>(object: menu->itemAt(index: 0));
635 QVERIFY(newMenuItem);
636 QCOMPARE(newMenuItem->text(), QStringLiteral("New"));
637
638 QQuickMenuSeparator *menuSeparator = qobject_cast<QQuickMenuSeparator*>(object: menu->itemAt(index: 1));
639 QVERIFY(menuSeparator);
640
641 QQuickMenuItem *saveMenuItem = qobject_cast<QQuickMenuItem*>(object: menu->itemAt(index: 2));
642 QVERIFY(saveMenuItem);
643 QCOMPARE(saveMenuItem->text(), QStringLiteral("Save"));
644 QTRY_VERIFY(!QQuickItemPrivate::get(saveMenuItem)->culled); // QTBUG-53262
645
646 // Clicking on items should still close the menu.
647 QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier,
648 pos: newMenuItem->mapToScene(point: QPointF(newMenuItem->width() / 2, newMenuItem->height() / 2)).toPoint());
649 QTRY_VERIFY(!menu->isVisible());
650
651 menu->open();
652 QVERIFY(menu->isVisible());
653
654 // Clicking on a separator shouldn't close the menu.
655 QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier,
656 pos: menuSeparator->mapToScene(point: QPointF(menuSeparator->width() / 2, menuSeparator->height() / 2)).toPoint());
657 QVERIFY(menu->isVisible());
658
659 // Clicking on items should still close the menu.
660 QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier,
661 pos: saveMenuItem->mapToScene(point: QPointF(saveMenuItem->width() / 2, saveMenuItem->height() / 2)).toPoint());
662 QTRY_VERIFY(!menu->isVisible());
663
664 moveMouseAway(window);
665
666 menu->open();
667 QVERIFY(menu->isVisible());
668 QTRY_VERIFY(menu->isOpened());
669
670 // Key navigation skips separators
671 QTest::keyClick(window, key: Qt::Key_Down);
672 QVERIFY(newMenuItem->hasActiveFocus());
673 QVERIFY(newMenuItem->hasVisualFocus());
674 QCOMPARE(newMenuItem->focusReason(), Qt::TabFocusReason);
675
676 QTest::keyClick(window, key: Qt::Key_Down);
677 QVERIFY(saveMenuItem->hasActiveFocus());
678 QVERIFY(saveMenuItem->hasVisualFocus());
679 QCOMPARE(saveMenuItem->focusReason(), Qt::TabFocusReason);
680
681 QTest::keyClick(window, key: Qt::Key_Down);
682 QVERIFY(saveMenuItem->hasActiveFocus());
683 QVERIFY(saveMenuItem->hasVisualFocus());
684 QCOMPARE(saveMenuItem->focusReason(), Qt::TabFocusReason);
685
686 QTest::keyClick(window, key: Qt::Key_Up);
687 QVERIFY(newMenuItem->hasActiveFocus());
688 QVERIFY(newMenuItem->hasVisualFocus());
689 QCOMPARE(newMenuItem->focusReason(), Qt::BacktabFocusReason);
690
691 QTest::keyClick(window, key: Qt::Key_Up);
692 QVERIFY(newMenuItem->hasActiveFocus());
693 QVERIFY(newMenuItem->hasVisualFocus());
694 QCOMPARE(newMenuItem->focusReason(), Qt::BacktabFocusReason);
695}
696
697void tst_QQuickMenu::repeater()
698{
699 QQuickApplicationHelper helper(this, QLatin1String("repeater.qml"));
700 QVERIFY2(helper.ready, helper.failureMessage());
701 QQuickWindow *window = helper.window;
702 window->show();
703 QVERIFY(QTest::qWaitForWindowActive(window));
704
705 QQuickMenu *menu = window->property(name: "menu").value<QQuickMenu*>();
706 QVERIFY(menu);
707 menu->open();
708 QVERIFY(menu->isVisible());
709
710 QObject *repeater = window->property(name: "repeater").value<QObject*>();
711 QVERIFY(repeater);
712
713 int count = repeater->property(name: "count").toInt();
714 QCOMPARE(count, 5);
715
716 for (int i = 0; i < count; ++i) {
717 QQuickItem *item = menu->itemAt(index: i);
718 QVERIFY(item);
719 QCOMPARE(item->property("idx").toInt(), i);
720
721 QQuickItem *repeaterItem = nullptr;
722 QVERIFY(QMetaObject::invokeMethod(repeater, "itemAt", Q_RETURN_ARG(QQuickItem*, repeaterItem), Q_ARG(int, i)));
723 QCOMPARE(item, repeaterItem);
724 }
725
726 repeater->setProperty(name: "model", value: 3);
727
728 count = repeater->property(name: "count").toInt();
729 QCOMPARE(count, 3);
730
731 for (int i = 0; i < count; ++i) {
732 QQuickItem *item = menu->itemAt(index: i);
733 QVERIFY(item);
734 QCOMPARE(item->property("idx").toInt(), i);
735
736 QQuickItem *repeaterItem = nullptr;
737 QVERIFY(QMetaObject::invokeMethod(repeater, "itemAt", Q_RETURN_ARG(QQuickItem*, repeaterItem), Q_ARG(int, i)));
738 QCOMPARE(item, repeaterItem);
739 }
740}
741
742void tst_QQuickMenu::order()
743{
744 QQuickApplicationHelper helper(this, QLatin1String("order.qml"));
745 QVERIFY2(helper.ready, helper.failureMessage());
746 QQuickWindow *window = helper.window;
747 window->show();
748 QVERIFY(QTest::qWaitForWindowActive(window));
749
750 QQuickMenu *menu = window->property(name: "menu").value<QQuickMenu*>();
751 QVERIFY(menu);
752 menu->open();
753 QVERIFY(menu->isVisible());
754
755 const QStringList texts = {"dynamic_0", "static_1", "repeated_2", "repeated_3", "static_4", "dynamic_5", "dynamic_6"};
756
757 for (int i = 0; i < texts.count(); ++i) {
758 QQuickItem *item = menu->itemAt(index: i);
759 QVERIFY(item);
760 QCOMPARE(item->property("text").toString(), texts.at(i));
761 }
762}
763
764void tst_QQuickMenu::popup()
765{
766 QQuickApplicationHelper helper(this, QLatin1String("popup.qml"));
767 QVERIFY2(helper.ready, helper.failureMessage());
768 QQuickApplicationWindow *window = helper.appWindow;
769 centerOnScreen(window);
770 moveMouseAway(window);
771 window->show();
772 QVERIFY(QTest::qWaitForWindowActive(window));
773
774 QQuickMenu *menu = window->property(name: "menu").value<QQuickMenu *>();
775 QVERIFY(menu);
776
777 QQuickMenuItem *menuItem1 = window->property(name: "menuItem1").value<QQuickMenuItem *>();
778 QVERIFY(menuItem1);
779
780 QQuickMenuItem *menuItem2 = window->property(name: "menuItem2").value<QQuickMenuItem *>();
781 QVERIFY(menuItem2);
782
783 QQuickMenuItem *menuItem3 = window->property(name: "menuItem3").value<QQuickMenuItem *>();
784 QVERIFY(menuItem3);
785
786 QQuickItem *button = window->property(name: "button").value<QQuickItem *>();
787 QVERIFY(button);
788
789#if QT_CONFIG(cursor)
790 QPoint oldCursorPos = QCursor::pos();
791 QPoint cursorPos = window->mapToGlobal(pos: QPoint(11, 22));
792 QCursor::setPos(cursorPos);
793 QTRY_COMPARE(QCursor::pos(), cursorPos);
794
795 QVERIFY(QMetaObject::invokeMethod(window, "popupAtCursor"));
796 QCOMPARE(menu->parentItem(), window->contentItem());
797 QCOMPARE(menu->currentIndex(), -1);
798 QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), -1);
799 const qreal elevenOrLeftMargin = qMax(a: qreal(11), b: menu->leftMargin());
800 const qreal twentyTwoOrTopMargin = qMax(a: qreal(22), b: menu->topMargin());
801 // If the Menu has large margins, it may be moved to stay within them.
802 // QTBUG-75503: QTRY_COMPARE doesn't use qFuzzyCompare() in all cases,
803 // meaning a lot of these comparisons could trigger a 10 second wait;
804 // use QTRY_VERIFY and qFuzzyCompare instead.
805 QTRY_VERIFY(qFuzzyCompare(menu->x(), elevenOrLeftMargin));
806 QTRY_VERIFY(qFuzzyCompare(menu->y(), twentyTwoOrTopMargin));
807 menu->close();
808
809 QVERIFY(QMetaObject::invokeMethod(window, "popupAtPos", Q_ARG(QVariant, QPointF(33, 44))));
810 QCOMPARE(menu->parentItem(), window->contentItem());
811 QCOMPARE(menu->currentIndex(), -1);
812 QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), -1);
813 QTRY_VERIFY(qFuzzyCompare(menu->x(), 33));
814 QTRY_VERIFY(qFuzzyCompare(menu->y(), 44));
815 menu->close();
816
817 QVERIFY(QMetaObject::invokeMethod(window, "popupAtCoord", Q_ARG(QVariant, 55), Q_ARG(QVariant, 66)));
818 QCOMPARE(menu->parentItem(), window->contentItem());
819 QCOMPARE(menu->currentIndex(), -1);
820 QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), -1);
821 QTRY_VERIFY(qFuzzyCompare(menu->x(), 55));
822 QTRY_VERIFY(qFuzzyCompare(menu->y(), 66));
823 menu->close();
824
825 menu->setParentItem(nullptr);
826 QVERIFY(QMetaObject::invokeMethod(window, "popupAtParentCursor", Q_ARG(QVariant, QVariant::fromValue(button))));
827 QCOMPARE(menu->parentItem(), button);
828 QCOMPARE(menu->currentIndex(), -1);
829 QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), -1);
830 QTRY_VERIFY(qFuzzyCompare(menu->x(), button->mapFromScene(QPointF(elevenOrLeftMargin, twentyTwoOrTopMargin)).x()));
831 QTRY_VERIFY(qFuzzyCompare(menu->y(), button->mapFromScene(QPointF(elevenOrLeftMargin, twentyTwoOrTopMargin)).y()));
832 menu->close();
833
834 menu->setParentItem(nullptr);
835 QVERIFY(QMetaObject::invokeMethod(window, "popupAtParentPos", Q_ARG(QVariant, QVariant::fromValue(button)), Q_ARG(QVariant, QPointF(-11, -22))));
836 QCOMPARE(menu->parentItem(), button);
837 QCOMPARE(menu->currentIndex(), -1);
838 QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), -1);
839 // Don't need to worry about margins here because we're opening close
840 // to the center of the window.
841 QTRY_VERIFY(qFuzzyCompare(menu->x(), -11));
842 QTRY_VERIFY(qFuzzyCompare(menu->y(), -22));
843 QCOMPARE(menu->popupItem()->position(), button->mapToScene(QPointF(-11, -22)));
844 menu->close();
845
846 menu->setParentItem(nullptr);
847 QVERIFY(QMetaObject::invokeMethod(window, "popupAtParentCoord", Q_ARG(QVariant, QVariant::fromValue(button)), Q_ARG(QVariant, -33), Q_ARG(QVariant, -44)));
848 QCOMPARE(menu->parentItem(), button);
849 QCOMPARE(menu->currentIndex(), -1);
850 QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), -1);
851 QTRY_VERIFY(qFuzzyCompare(menu->x(), -33));
852 QTRY_VERIFY(qFuzzyCompare(menu->y(), -44));
853 QCOMPARE(menu->popupItem()->position(), button->mapToScene(QPointF(-33, -44)));
854 menu->close();
855
856 const qreal twelveOrLeftMargin = qMax(a: qreal(12), b: menu->leftMargin());
857 cursorPos = window->mapToGlobal(pos: QPoint(twelveOrLeftMargin, window->height() / 2));
858 QCursor::setPos(cursorPos);
859 QTRY_COMPARE(QCursor::pos(), cursorPos);
860
861 const QList<QQuickMenuItem *> menuItems = QList<QQuickMenuItem *>() << menuItem1 << menuItem2 << menuItem3;
862 for (QQuickMenuItem *menuItem : menuItems) {
863 menu->resetParentItem();
864
865 QVERIFY(QMetaObject::invokeMethod(window, "popupItemAtCursor", Q_ARG(QVariant, QVariant::fromValue(menuItem))));
866 QCOMPARE(menu->currentIndex(), menuItems.indexOf(menuItem));
867 QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), menuItems.indexOf(menuItem));
868 QTRY_VERIFY(qFuzzyCompare(menu->x(), twelveOrLeftMargin));
869 QTRY_VERIFY(qFuzzyCompare(menu->y(), window->height() / 2 - menu->topPadding() - menuItem->y()));
870 menu->close();
871
872 QVERIFY(QMetaObject::invokeMethod(window, "popupItemAtPos", Q_ARG(QVariant, QPointF(33, window->height() / 3)), Q_ARG(QVariant, QVariant::fromValue(menuItem))));
873 QCOMPARE(menu->currentIndex(), menuItems.indexOf(menuItem));
874 QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), menuItems.indexOf(menuItem));
875 QTRY_VERIFY(qFuzzyCompare(menu->x(), 33));
876 QTRY_VERIFY(qFuzzyCompare(menu->y(), window->height() / 3 - menu->topPadding() - menuItem->y()));
877 menu->close();
878
879 QVERIFY(QMetaObject::invokeMethod(window, "popupItemAtCoord", Q_ARG(QVariant, 55), Q_ARG(QVariant, window->height() / 3 * 2), Q_ARG(QVariant, QVariant::fromValue(menuItem))));
880 QCOMPARE(menu->currentIndex(), menuItems.indexOf(menuItem));
881 QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), menuItems.indexOf(menuItem));
882 QTRY_VERIFY(qFuzzyCompare(menu->x(), 55));
883 QTRY_COMPARE_WITH_TIMEOUT(menu->y(), window->height() / 3 * 2 - menu->topPadding() - menuItem->y(), 500);
884 menu->close();
885
886 menu->setParentItem(nullptr);
887 QVERIFY(QMetaObject::invokeMethod(window, "popupItemAtParentCursor", Q_ARG(QVariant, QVariant::fromValue(button)), Q_ARG(QVariant, QVariant::fromValue(menuItem))));
888 QCOMPARE(menu->parentItem(), button);
889 QCOMPARE(menu->currentIndex(), menuItems.indexOf(menuItem));
890 QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), menuItems.indexOf(menuItem));
891 QTRY_VERIFY(qFuzzyCompare(menu->x(), button->mapFromScene(QPoint(twelveOrLeftMargin, window->height() / 2)).x()));
892 QTRY_VERIFY(qFuzzyCompare(menu->y(), button->mapFromScene(QPoint(twelveOrLeftMargin, window->height() / 2)).y() - menu->topPadding() - menuItem->y()));
893 menu->close();
894
895 menu->setParentItem(nullptr);
896 QVERIFY(QMetaObject::invokeMethod(window, "popupItemAtParentPos", Q_ARG(QVariant, QVariant::fromValue(button)), Q_ARG(QVariant, QPointF(-11, -22)), Q_ARG(QVariant, QVariant::fromValue(menuItem))));
897 QCOMPARE(menu->parentItem(), button);
898 QCOMPARE(menu->currentIndex(), menuItems.indexOf(menuItem));
899 QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), menuItems.indexOf(menuItem));
900 QTRY_VERIFY(qFuzzyCompare(menu->x(), -11));
901 QTRY_VERIFY(qFuzzyCompare(menu->y(), -22 - menu->topPadding() - menuItem->y()));
902 QCOMPARE(menu->popupItem()->position(), button->mapToScene(QPointF(-11, -22 - menu->topPadding() - menuItem->y())));
903 menu->close();
904
905 menu->setParentItem(nullptr);
906 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))));
907 QCOMPARE(menu->parentItem(), button);
908 QCOMPARE(menu->currentIndex(), menuItems.indexOf(menuItem));
909 QCOMPARE(menu->contentItem()->property("currentIndex").toInt(), menuItems.indexOf(menuItem));
910 QTRY_VERIFY(qFuzzyCompare(menu->x(), -33));
911 QTRY_VERIFY(qFuzzyCompare(menu->y(), -44 - menu->topPadding() - menuItem->y()));
912 QCOMPARE(menu->popupItem()->position(), button->mapToScene(QPointF(-33, -44 - menu->topPadding() - menuItem->y())));
913 menu->close();
914 }
915
916 QCursor::setPos(oldCursorPos);
917 QTRY_COMPARE(QCursor::pos(), oldCursorPos);
918#endif
919}
920
921void tst_QQuickMenu::actions()
922{
923 QQuickApplicationHelper helper(this, QLatin1String("actions.qml"));
924 QVERIFY2(helper.ready, helper.failureMessage());
925 QQuickWindow *window = helper.window;
926 window->show();
927 QVERIFY(QTest::qWaitForWindowActive(window));
928
929 QQuickMenu *menu = window->property(name: "menu").value<QQuickMenu *>();
930 QVERIFY(menu);
931
932 QPointer<QQuickAction> action1 = menu->actionAt(index: 0);
933 QVERIFY(!action1.isNull());
934
935 QPointer<QQuickAction> action3 = menu->actionAt(index: 2);
936 QVERIFY(!action3.isNull());
937
938 QVERIFY(!menu->actionAt(1));
939 QVERIFY(!menu->actionAt(3));
940
941 QPointer<QQuickMenuItem> menuItem1 = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 0));
942 QVERIFY(!menuItem1.isNull());
943 QCOMPARE(menuItem1->action(), action1.data());
944 QCOMPARE(menuItem1->text(), "action1");
945
946 QPointer<QQuickMenuItem> menuItem2 = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 1));
947 QVERIFY(!menuItem2.isNull());
948 QVERIFY(!menuItem2->action());
949 QCOMPARE(menuItem2->text(), "menuitem2");
950
951 QPointer<QQuickMenuItem> menuItem3 = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 2));
952 QVERIFY(!menuItem3.isNull());
953 QCOMPARE(menuItem3->action(), action3.data());
954 QCOMPARE(menuItem3->text(), "action3");
955
956 QPointer<QQuickMenuItem> menuItem4 = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 3));
957 QVERIFY(!menuItem4.isNull());
958 QVERIFY(!menuItem4->action());
959 QCOMPARE(menuItem4->text(), "menuitem4");
960
961 // takeAction(int) does not destroy the action, but does destroy the respective item
962 QCOMPARE(menu->takeAction(0), action1.data());
963 QVERIFY(!menu->itemAt(3));
964 QCoreApplication::sendPostedEvents(receiver: action1, event_type: QEvent::DeferredDelete);
965 QVERIFY(!action1.isNull());
966 QCoreApplication::sendPostedEvents(receiver: menuItem1, event_type: QEvent::DeferredDelete);
967 QVERIFY(menuItem1.isNull());
968
969 // takeAction(int) does not destroy an item that doesn't have an action
970 QVERIFY(!menuItem2->subMenu());
971 QVERIFY(!menu->takeAction(0));
972 QCoreApplication::sendPostedEvents(receiver: menuItem2, event_type: QEvent::DeferredDelete);
973 QVERIFY(!menuItem2.isNull());
974
975 // addAction(Action) re-creates the respective item in the menu
976 menu->addAction(action: action1);
977 menuItem1 = qobject_cast<QQuickMenuItem *>(object: menu->itemAt(index: 3));
978 QVERIFY(!menuItem1.isNull());
979 QCOMPARE(menuItem1->action(), action1.data());
980
981 // removeAction(Action) destroys both the action and the respective item
982 menu->removeAction(action: action1);
983 QVERIFY(!menu->itemAt(3));
984 QCoreApplication::sendPostedEvents(receiver: action1, event_type: QEvent::DeferredDelete);
985 QVERIFY(action1.isNull());
986 QCoreApplication::sendPostedEvents(receiver: menuItem1, event_type: QEvent::DeferredDelete);
987 QVERIFY(menuItem1.isNull());
988}
989
990void tst_QQuickMenu::removeTakeItem()
991{
992 QQuickApplicationHelper helper(this, QLatin1String("removeTakeItem.qml"));
993 QVERIFY2(helper.ready, helper.failureMessage());
994 QQuickWindow *window = helper.window;
995 window->show();
996 QVERIFY(QTest::qWaitForWindowActive(window));
997
998 QQuickMenu *menu = window->property(name: "menu").value<QQuickMenu *>();
999 QVERIFY(menu);
1000
1001 QPointer<QQuickMenuItem> menuItem1 = window->property(name: "menuItem1").value<QQuickMenuItem *>();
1002 QVERIFY(!menuItem1.isNull());
1003 QCOMPARE(menuItem1->menu(), menu);
1004
1005 QPointer<QQuickMenuItem> menuItem2 = window->property(name: "menuItem2").value<QQuickMenuItem *>();
1006 QVERIFY(!menuItem2.isNull());
1007 QCOMPARE(menuItem2->menu(), menu);
1008
1009 QPointer<QQuickMenuItem> menuItem3 = window->property(name: "menuItem3").value<QQuickMenuItem *>();
1010 QVERIFY(!menuItem3.isNull());
1011 QCOMPARE(menuItem3->menu(), menu);
1012
1013 // takeItem(int) does not destroy
1014 QVariant ret;
1015 QVERIFY(QMetaObject::invokeMethod(window, "takeSecondItem", Q_RETURN_ARG(QVariant, ret)));
1016 QCOMPARE(ret.value<QQuickMenuItem *>(), menuItem2);
1017 QVERIFY(!menuItem2->menu());
1018 QCoreApplication::sendPostedEvents(receiver: menuItem2, event_type: QEvent::DeferredDelete);
1019 QVERIFY(!menuItem2.isNull());
1020
1021 // removeItem(Item) destroys
1022 QVERIFY(QMetaObject::invokeMethod(window, "removeFirstItem"));
1023 QVERIFY(!menuItem1->menu());
1024 QCoreApplication::sendPostedEvents(receiver: menuItem1, event_type: QEvent::DeferredDelete);
1025 QVERIFY(menuItem1.isNull());
1026
1027 // removeItem(null) must not call removeItem(0)
1028 QVERIFY(QMetaObject::invokeMethod(window, "removeNullItem"));
1029 QCOMPARE(menuItem3->menu(), menu);
1030 QCoreApplication::sendPostedEvents(receiver: menuItem3, event_type: QEvent::DeferredDelete);
1031 QVERIFY(!menuItem3.isNull());
1032
1033 // deprecated removeItem(int) does not destroy
1034 QVERIFY(QMetaObject::invokeMethod(window, "removeFirstIndex"));
1035 QVERIFY(!menuItem3->menu());
1036 QCoreApplication::sendPostedEvents(receiver: menuItem3, event_type: QEvent::DeferredDelete);
1037 QVERIFY(!menuItem3.isNull());
1038}
1039
1040void tst_QQuickMenu::subMenuMouse_data()
1041{
1042 QTest::addColumn<bool>(name: "cascade");
1043
1044 QTest::newRow(dataTag: "cascading") << true;
1045 QTest::newRow(dataTag: "non-cascading") << false;
1046}
1047
1048void tst_QQuickMenu::subMenuMouse()
1049{
1050 if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
1051 || (QGuiApplication::platformName() == QLatin1String("minimal")))
1052 QSKIP("Mouse hovering not functional on offscreen/minimal platforms");
1053
1054 QFETCH(bool, cascade);
1055
1056 QQuickApplicationHelper helper(this, QLatin1String("subMenus.qml"));
1057 QVERIFY2(helper.ready, helper.failureMessage());
1058 QQuickApplicationWindow *window = helper.appWindow;
1059 centerOnScreen(window);
1060 moveMouseAway(window);
1061 window->show();
1062 QVERIFY(QTest::qWaitForWindowActive(window));
1063
1064 QQuickMenu *mainMenu = window->property(name: "mainMenu").value<QQuickMenu *>();
1065 QVERIFY(mainMenu);
1066 mainMenu->setCascade(cascade);
1067 QCOMPARE(mainMenu->cascade(), cascade);
1068
1069 QQuickMenu *subMenu1 = window->property(name: "subMenu1").value<QQuickMenu *>();
1070 QVERIFY(subMenu1);
1071
1072 QQuickMenu *subMenu2 = window->property(name: "subMenu2").value<QQuickMenu *>();
1073 QVERIFY(subMenu2);
1074
1075 QQuickMenu *subSubMenu1 = window->property(name: "subSubMenu1").value<QQuickMenu *>();
1076 QVERIFY(subSubMenu1);
1077
1078 mainMenu->open();
1079 QVERIFY(mainMenu->isVisible());
1080 QTRY_VERIFY(mainMenu->isOpened());
1081 QVERIFY(!subMenu1->isVisible());
1082 QVERIFY(!subMenu2->isVisible());
1083 QVERIFY(!subSubMenu1->isVisible());
1084
1085 // open the sub-menu with mouse click
1086 QQuickMenuItem *subMenu1Item = qobject_cast<QQuickMenuItem *>(object: mainMenu->itemAt(index: 1));
1087 QVERIFY(subMenu1Item);
1088 QCOMPARE(subMenu1Item->subMenu(), subMenu1);
1089 QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: subMenu1Item->mapToScene(point: QPoint(1, 1)).toPoint());
1090 QTRY_COMPARE(mainMenu->isVisible(), cascade);
1091 QVERIFY(subMenu1->isVisible());
1092 QTRY_VERIFY(subMenu1->isOpened());
1093 QVERIFY(!subMenu2->isVisible());
1094 QVERIFY(!subSubMenu1->isVisible());
1095
1096 // open the cascading sub-sub-menu with mouse hover
1097 QQuickMenuItem *subSubMenu1Item = qobject_cast<QQuickMenuItem *>(object: subMenu1->itemAt(index: 2));
1098 QVERIFY(subSubMenu1Item);
1099 QCOMPARE(subSubMenu1Item->subMenu(), subSubMenu1);
1100 QTest::mouseMove(window, pos: subSubMenu1Item->mapToScene(point: QPoint(1, 1)).toPoint());
1101 QCOMPARE(mainMenu->isVisible(), cascade);
1102 QVERIFY(subMenu1->isVisible());
1103 QVERIFY(!subMenu2->isVisible());
1104 QVERIFY(!subSubMenu1->isVisible());
1105 if (cascade) {
1106 QTRY_VERIFY(subSubMenu1->isVisible());
1107 QTRY_VERIFY(subSubMenu1->isOpened());
1108 }
1109
1110 // close the sub-sub-menu with mouse hover over another parent menu item
1111 QQuickMenuItem *subMenuItem1 = qobject_cast<QQuickMenuItem *>(object: subMenu1->itemAt(index: 0));
1112 QVERIFY(subMenuItem1);
1113 QVERIFY(!subMenuItem1->subMenu());
1114 QTest::mouseMove(window, pos: subMenuItem1->mapToScene(point: QPoint(1, 1)).toPoint());
1115 QCOMPARE(mainMenu->isVisible(), cascade);
1116 QVERIFY(subMenu1->isVisible());
1117 QVERIFY(!subMenu2->isVisible());
1118 QTRY_VERIFY(!subSubMenu1->isVisible());
1119
1120 // re-open the sub-sub-menu with mouse hover
1121 QTest::mouseMove(window, pos: subSubMenu1Item->mapToScene(point: QPoint(1, 1)).toPoint());
1122 QCOMPARE(mainMenu->isVisible(), cascade);
1123 QVERIFY(subMenu1->isVisible());
1124 QVERIFY(!subMenu2->isVisible());
1125 if (!cascade) {
1126 QVERIFY(!subSubMenu1->isVisible());
1127 } else {
1128 QTRY_VERIFY(subSubMenu1->isVisible());
1129 QTRY_VERIFY(subSubMenu1->isOpened());
1130 }
1131
1132 // close sub-menu and sub-sub-menu with mouse hover in the main menu
1133 QQuickMenuItem *mainMenuItem1 = qobject_cast<QQuickMenuItem *>(object: mainMenu->itemAt(index: 0));
1134 QVERIFY(mainMenuItem1);
1135 QTest::mouseMove(window, pos: mainMenuItem1->mapToScene(point: QPoint(1, 1)).toPoint());
1136 QCOMPARE(mainMenu->isVisible(), cascade);
1137 QTRY_COMPARE(subMenu1->isVisible(), !cascade);
1138 QVERIFY(!subMenu2->isVisible());
1139 QVERIFY(!subSubMenu1->isVisible());
1140
1141 // close all menus by click triggering an item
1142 QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: mainMenuItem1->mapToScene(point: QPoint(1, 1)).toPoint());
1143 QTRY_VERIFY(!mainMenu->isVisible());
1144 QTRY_VERIFY(!subMenu1->isVisible());
1145 QVERIFY(!subMenu2->isVisible());
1146 QVERIFY(!subSubMenu1->isVisible());
1147}
1148
1149void tst_QQuickMenu::subMenuDisabledMouse_data()
1150{
1151 subMenuMouse_data();
1152}
1153
1154// QTBUG-69540
1155void tst_QQuickMenu::subMenuDisabledMouse()
1156{
1157 if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
1158 || (QGuiApplication::platformName() == QLatin1String("minimal")))
1159 QSKIP("Mouse hovering not functional on offscreen/minimal platforms");
1160
1161 QFETCH(bool, cascade);
1162
1163 QQuickApplicationHelper helper(this, QLatin1String("subMenuDisabled.qml"));
1164 QVERIFY2(helper.ready, helper.failureMessage());
1165 QQuickApplicationWindow *window = helper.appWindow;
1166 centerOnScreen(window);
1167 moveMouseAway(window);
1168 window->show();
1169 QVERIFY(QTest::qWaitForWindowActive(window));
1170
1171 QQuickMenu *mainMenu = window->property(name: "mainMenu").value<QQuickMenu *>();
1172 QVERIFY(mainMenu);
1173 mainMenu->setCascade(cascade);
1174 QCOMPARE(mainMenu->cascade(), cascade);
1175
1176 QQuickMenuItem *menuItem1 = qobject_cast<QQuickMenuItem *>(object: mainMenu->itemAt(index: 0));
1177 QVERIFY(menuItem1);
1178
1179 QQuickMenu *subMenu = window->property(name: "subMenu").value<QQuickMenu *>();
1180 QVERIFY(subMenu);
1181
1182 mainMenu->open();
1183 QVERIFY(mainMenu->isVisible());
1184 QVERIFY(!menuItem1->isHighlighted());
1185 QVERIFY(!subMenu->isVisible());
1186
1187 // Open the sub-menu with a mouse click.
1188 QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: menuItem1->mapToScene(point: QPoint(1, 1)).toPoint());
1189 // Need to use the TRY variant here when cascade is false,
1190 // as e.g. Material style menus have transitions and don't close immediately.
1191 QTRY_COMPARE(mainMenu->isVisible(), cascade);
1192 QVERIFY(subMenu->isVisible());
1193 QVERIFY(menuItem1->isHighlighted());
1194 // Now the sub-menu is open. The current behavior is that the first menu item
1195 // in the new menu is highlighted; make sure that we choose the next item if
1196 // the first is disabled.
1197 QQuickMenuItem *subMenuItem1 = qobject_cast<QQuickMenuItem *>(object: subMenu->itemAt(index: 0));
1198 QVERIFY(subMenuItem1);
1199 QQuickMenuItem *subMenuItem2 = qobject_cast<QQuickMenuItem *>(object: subMenu->itemAt(index: 1));
1200 QVERIFY(subMenuItem2);
1201 QVERIFY(!subMenuItem1->isHighlighted());
1202 QVERIFY(subMenuItem2->isHighlighted());
1203
1204 // Close all menus by clicking on the item that isn't disabled.
1205 QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: subMenuItem2->mapToScene(point: QPoint(1, 1)).toPoint());
1206 QTRY_VERIFY(!mainMenu->isVisible());
1207 QTRY_VERIFY(!subMenu->isVisible());
1208}
1209
1210void tst_QQuickMenu::subMenuKeyboard_data()
1211{
1212 QTest::addColumn<bool>(name: "cascade");
1213 QTest::addColumn<bool>(name: "mirrored");
1214
1215 QTest::newRow(dataTag: "cascading") << true << false;
1216 QTest::newRow(dataTag: "cascading,mirrored") << true << true;
1217 QTest::newRow(dataTag: "non-cascading") << false << false;
1218 QTest::newRow(dataTag: "non-cascading,mirrored") << false << true;
1219}
1220
1221void tst_QQuickMenu::subMenuKeyboard()
1222{
1223 QFETCH(bool, cascade);
1224 QFETCH(bool, mirrored);
1225
1226 QQuickApplicationHelper helper(this, QLatin1String("subMenus.qml"));
1227 QVERIFY2(helper.ready, helper.failureMessage());
1228 QQuickApplicationWindow *window = helper.appWindow;
1229 centerOnScreen(window);
1230 moveMouseAway(window);
1231 window->show();
1232 QVERIFY(QTest::qWaitForWindowActive(window));
1233
1234 if (mirrored)
1235 window->setLocale(QLocale("ar_EG"));
1236
1237 QQuickMenu *mainMenu = window->property(name: "mainMenu").value<QQuickMenu *>();
1238 QVERIFY(mainMenu);
1239 mainMenu->setCascade(cascade);
1240 QCOMPARE(mainMenu->cascade(), cascade);
1241
1242 QQuickMenu *subMenu1 = window->property(name: "subMenu1").value<QQuickMenu *>();
1243 QVERIFY(subMenu1);
1244
1245 QQuickMenu *subMenu2 = window->property(name: "subMenu2").value<QQuickMenu *>();
1246 QVERIFY(subMenu2);
1247
1248 QQuickMenu *subSubMenu1 = window->property(name: "subSubMenu1").value<QQuickMenu *>();
1249 QVERIFY(subSubMenu1);
1250
1251 mainMenu->open();
1252 QVERIFY(mainMenu->isVisible());
1253 QTRY_VERIFY(mainMenu->isOpened());
1254 QVERIFY(!subMenu1->isVisible());
1255 QVERIFY(!subMenu2->isVisible());
1256 QVERIFY(!subSubMenu1->isVisible());
1257
1258 // navigate to the sub-menu item and trigger it to open the sub-menu
1259 QQuickMenuItem *subMenu1Item = qobject_cast<QQuickMenuItem *>(object: mainMenu->itemAt(index: 1));
1260 QVERIFY(subMenu1Item);
1261 QVERIFY(!subMenu1Item->isHighlighted());
1262 QCOMPARE(subMenu1Item->subMenu(), subMenu1);
1263 QTest::keyClick(window, key: Qt::Key_Down);
1264 QTest::keyClick(window, key: Qt::Key_Down);
1265 QVERIFY(subMenu1Item->isHighlighted());
1266 QTest::keyClick(window, key: Qt::Key_Space);
1267 QTRY_COMPARE(mainMenu->isVisible(), cascade);
1268 QVERIFY(subMenu1->isVisible());
1269 QTRY_VERIFY(subMenu1->isOpened());
1270 QVERIFY(!subMenu2->isVisible());
1271 QVERIFY(!subSubMenu1->isVisible());
1272
1273 // navigate to the sub-sub-menu item and open it with the arrow key
1274 QQuickMenuItem *subSubMenu1Item = qobject_cast<QQuickMenuItem *>(object: subMenu1->itemAt(index: 2));
1275 QVERIFY(subSubMenu1Item);
1276 QVERIFY(!subSubMenu1Item->isHighlighted());
1277 QCOMPARE(subSubMenu1Item->subMenu(), subSubMenu1);
1278 QTest::keyClick(window, key: Qt::Key_Down);
1279 QTest::keyClick(window, key: Qt::Key_Down);
1280 QTest::keyClick(window, key: Qt::Key_Down);
1281 QVERIFY(subSubMenu1Item->isHighlighted());
1282 QTRY_COMPARE(mainMenu->isVisible(), cascade);
1283 QVERIFY(subMenu1->isVisible());
1284 QVERIFY(!subMenu2->isVisible());
1285 QVERIFY(!subSubMenu1->isVisible());
1286 QTest::keyClick(window, key: mirrored ? Qt::Key_Left : Qt::Key_Right);
1287 QCOMPARE(mainMenu->isVisible(), cascade);
1288 QTRY_COMPARE(subMenu1->isVisible(), cascade);
1289 QVERIFY(!subMenu2->isVisible());
1290 QVERIFY(subSubMenu1->isVisible());
1291 QTRY_VERIFY(subSubMenu1->isOpened());
1292
1293 // navigate within the sub-sub-menu
1294 QQuickMenuItem *subSubMenuItem1 = qobject_cast<QQuickMenuItem *>(object: subSubMenu1->itemAt(index: 0));
1295 QVERIFY(subSubMenuItem1);
1296 QQuickMenuItem *subSubMenuItem2 = qobject_cast<QQuickMenuItem *>(object: subSubMenu1->itemAt(index: 1));
1297 QVERIFY(subSubMenuItem2);
1298 QVERIFY(subSubMenuItem1->isHighlighted());
1299 QVERIFY(!subSubMenuItem2->isHighlighted());
1300 QTest::keyClick(window, key: Qt::Key_Down);
1301 QVERIFY(!subSubMenuItem1->isHighlighted());
1302 QVERIFY(subSubMenuItem2->isHighlighted());
1303
1304 // navigate to the parent menu with the arrow key
1305 QTest::keyClick(window, key: mirrored ? Qt::Key_Right : Qt::Key_Left);
1306 QVERIFY(subSubMenu1Item->isHighlighted());
1307 QCOMPARE(mainMenu->isVisible(), cascade);
1308 QVERIFY(subMenu1->isVisible());
1309 QVERIFY(!subMenu2->isVisible());
1310 QTRY_VERIFY(!subSubMenu1->isVisible());
1311
1312 // navigate within the sub-menu
1313 QQuickMenuItem *subMenuItem1 = qobject_cast<QQuickMenuItem *>(object: subMenu1->itemAt(index: 0));
1314 QVERIFY(subMenuItem1);
1315 QQuickMenuItem *subMenuItem2 = qobject_cast<QQuickMenuItem *>(object: subMenu1->itemAt(index: 1));
1316 QVERIFY(subMenuItem2);
1317 QVERIFY(!subMenuItem1->isHighlighted());
1318 QVERIFY(!subMenuItem2->isHighlighted());
1319 QVERIFY(subSubMenu1Item->isHighlighted());
1320 QTest::keyClick(window, key: Qt::Key_Up);
1321 QVERIFY(!subMenuItem1->isHighlighted());
1322 QVERIFY(subMenuItem2->isHighlighted());
1323 QVERIFY(!subSubMenu1Item->isHighlighted());
1324
1325 // close the menus with esc
1326 QTest::keyClick(window, key: Qt::Key_Escape);
1327 QCOMPARE(mainMenu->isVisible(), cascade);
1328 QTRY_VERIFY(!subMenu1->isVisible());
1329 QVERIFY(!subMenu2->isVisible());
1330 QVERIFY(!subSubMenu1->isVisible());
1331 QTest::keyClick(window, key: Qt::Key_Escape);
1332 QTRY_VERIFY(!mainMenu->isVisible());
1333 QVERIFY(!subMenu1->isVisible());
1334 QVERIFY(!subMenu2->isVisible());
1335 QVERIFY(!subSubMenu1->isVisible());
1336}
1337
1338void tst_QQuickMenu::subMenuDisabledKeyboard_data()
1339{
1340 subMenuKeyboard_data();
1341}
1342
1343// QTBUG-69540
1344void tst_QQuickMenu::subMenuDisabledKeyboard()
1345{
1346 QFETCH(bool, cascade);
1347 QFETCH(bool, mirrored);
1348
1349 QQuickApplicationHelper helper(this, QLatin1String("subMenuDisabled.qml"));
1350 QVERIFY2(helper.ready, helper.failureMessage());
1351 QQuickApplicationWindow *window = helper.appWindow;
1352 centerOnScreen(window);
1353 moveMouseAway(window);
1354 window->show();
1355 QVERIFY(QTest::qWaitForWindowActive(window));
1356
1357 if (mirrored)
1358 window->setLocale(QLocale("ar_EG"));
1359
1360 QQuickMenu *mainMenu = window->property(name: "mainMenu").value<QQuickMenu *>();
1361 QVERIFY(mainMenu);
1362 mainMenu->setCascade(cascade);
1363 QCOMPARE(mainMenu->cascade(), cascade);
1364
1365 QQuickMenuItem *menuItem1 = qobject_cast<QQuickMenuItem *>(object: mainMenu->itemAt(index: 0));
1366 QVERIFY(menuItem1);
1367
1368 QQuickMenu *subMenu = window->property(name: "subMenu").value<QQuickMenu *>();
1369 QVERIFY(subMenu);
1370
1371 mainMenu->open();
1372 QVERIFY(mainMenu->isVisible());
1373 QTRY_VERIFY(mainMenu->isOpened());
1374 QVERIFY(!menuItem1->isHighlighted());
1375 QVERIFY(!subMenu->isVisible());
1376
1377 // Highlight the top-level menu item.
1378 QTest::keyClick(window, key: Qt::Key_Down);
1379 QVERIFY(menuItem1->isHighlighted());
1380
1381 QQuickMenuItem *subMenuItem1 = qobject_cast<QQuickMenuItem *>(object: subMenu->itemAt(index: 0));
1382 QVERIFY(subMenuItem1);
1383 QQuickMenuItem *subMenuItem2 = qobject_cast<QQuickMenuItem *>(object: subMenu->itemAt(index: 1));
1384 QVERIFY(subMenuItem2);
1385
1386 // Open the sub-menu.
1387 QTest::keyClick(window, key: mirrored ? Qt::Key_Left : Qt::Key_Right);
1388 // The first sub-menu item is disabled, so it should highlight the second one.
1389 QVERIFY(!subMenuItem1->isHighlighted());
1390 QVERIFY(subMenuItem2->isHighlighted());
1391
1392 // Close the menus with escape.
1393 QTest::keyClick(window, key: Qt::Key_Escape);
1394 QTRY_COMPARE(mainMenu->isVisible(), cascade);
1395 QTRY_VERIFY(!subMenu->isVisible());
1396 QTest::keyClick(window, key: Qt::Key_Escape);
1397 QTRY_VERIFY(!mainMenu->isVisible());
1398 QVERIFY(!subMenu->isVisible());
1399}
1400
1401/*
1402 QCOMPARE() compares doubles with 1-in-1e12 precision, which is too fine for these tests.
1403 Casting to floats, compared with 1-in-1e5 precision, gives more robust results.
1404*/
1405#define FLOAT_EQ(u, v) QCOMPARE(float(u), float(v))
1406
1407void tst_QQuickMenu::subMenuPosition_data()
1408{
1409 QTest::addColumn<bool>(name: "cascade");
1410 QTest::addColumn<bool>(name: "flip");
1411 QTest::addColumn<bool>(name: "mirrored");
1412 QTest::addColumn<qreal>(name: "overlap");
1413
1414 QTest::newRow(dataTag: "cascading") << true << false << false << 0.0;
1415 QTest::newRow(dataTag: "cascading,flip") << true << true << false << 0.0;
1416 QTest::newRow(dataTag: "cascading,overlap") << true << false << false << 10.0;
1417 QTest::newRow(dataTag: "cascading,flip,overlap") << true << true << false << 10.0;
1418 QTest::newRow(dataTag: "cascading,mirrored") << true << false << true << 0.0;
1419 QTest::newRow(dataTag: "cascading,mirrored,flip") << true << true << true << 0.0;
1420 QTest::newRow(dataTag: "cascading,mirrored,overlap") << true << false << true << 10.0;
1421 QTest::newRow(dataTag: "cascading,mirrored,flip,overlap") << true << true << true << 10.0;
1422 QTest::newRow(dataTag: "non-cascading") << false << false << false << 0.0;
1423}
1424
1425void tst_QQuickMenu::subMenuPosition()
1426{
1427 QFETCH(bool, cascade);
1428 QFETCH(bool, flip);
1429 QFETCH(bool, mirrored);
1430 QFETCH(qreal, overlap);
1431
1432 QQuickApplicationHelper helper(this, QLatin1String("subMenus.qml"));
1433 QVERIFY2(helper.ready, helper.failureMessage());
1434 QQuickApplicationWindow *window = helper.appWindow;
1435
1436 // Ensure that the default size of the window fits three menus side by side.
1437 QQuickMenu *mainMenu = window->property(name: "mainMenu").value<QQuickMenu *>();
1438 QVERIFY(mainMenu);
1439 window->setWidth(mainMenu->width() * 3 + mainMenu->leftMargin() + mainMenu->rightMargin());
1440
1441 // the default size of the window fits three menus side by side.
1442 // when testing flipping, we resize the window so that the first
1443 // sub-menu fits, but the second doesn't
1444 if (flip)
1445 window->setWidth(window->width() - mainMenu->width());
1446
1447 centerOnScreen(window);
1448 moveMouseAway(window);
1449 window->show();
1450 QVERIFY(QTest::qWaitForWindowActive(window));
1451
1452 if (mirrored)
1453 window->setLocale(QLocale("ar_EG"));
1454
1455 mainMenu->setCascade(cascade);
1456 QCOMPARE(mainMenu->cascade(), cascade);
1457 mainMenu->setOverlap(overlap);
1458 QCOMPARE(mainMenu->overlap(), overlap);
1459
1460 QQuickMenu *subMenu1 = window->property(name: "subMenu1").value<QQuickMenu *>();
1461 QVERIFY(subMenu1);
1462 subMenu1->setCascade(cascade);
1463 QCOMPARE(subMenu1->cascade(), cascade);
1464 subMenu1->setOverlap(overlap);
1465 QCOMPARE(subMenu1->overlap(), overlap);
1466
1467 QQuickMenu *subMenu2 = window->property(name: "subMenu2").value<QQuickMenu *>();
1468 QVERIFY(subMenu2);
1469 subMenu2->setCascade(cascade);
1470 QCOMPARE(subMenu2->cascade(), cascade);
1471 subMenu2->setOverlap(overlap);
1472 QCOMPARE(subMenu2->overlap(), overlap);
1473
1474 QQuickMenu *subSubMenu1 = window->property(name: "subSubMenu1").value<QQuickMenu *>();
1475 QVERIFY(subSubMenu1);
1476 subSubMenu1->setCascade(cascade);
1477 QCOMPARE(subSubMenu1->cascade(), cascade);
1478 subSubMenu1->setOverlap(overlap);
1479 QCOMPARE(subSubMenu1->overlap(), overlap);
1480
1481 // choose the main menu position so that there's room for the
1482 // sub-menus to cascade to the left when mirrored
1483 if (mirrored)
1484 mainMenu->setX(window->width() - mainMenu->width());
1485
1486 mainMenu->open();
1487 QVERIFY(mainMenu->isVisible());
1488 QTRY_VERIFY(mainMenu->isOpened());
1489 QVERIFY(!subMenu1->isVisible());
1490 QVERIFY(!subMenu2->isVisible());
1491 QVERIFY(!subSubMenu1->isVisible());
1492
1493 // open the sub-menu (never flips)
1494 QQuickMenuItem *subMenu1Item = qobject_cast<QQuickMenuItem *>(object: mainMenu->itemAt(index: 1));
1495 QVERIFY(subMenu1Item);
1496 QCOMPARE(subMenu1Item->subMenu(), subMenu1);
1497 emit subMenu1Item->triggered();
1498 QTRY_COMPARE(mainMenu->isVisible(), cascade);
1499 QVERIFY(subMenu1->isVisible());
1500 QTRY_VERIFY(subMenu1->isOpened());
1501 QVERIFY(!subMenu2->isVisible());
1502 QVERIFY(!subSubMenu1->isVisible());
1503
1504 if (cascade) {
1505 QCOMPARE(subMenu1->parentItem(), subMenu1Item);
1506 // vertically aligned to the parent menu item
1507 // We cast to float here because we want to use its larger tolerance for equality (because it has less precision than double).
1508 FLOAT_EQ(subMenu1->popupItem()->y(), mainMenu->popupItem()->y() + subMenu1Item->y());
1509 if (mirrored) {
1510 // on the left of the parent menu
1511 FLOAT_EQ(subMenu1->popupItem()->x(), mainMenu->popupItem()->x() - subMenu1->width() + overlap);
1512 } else {
1513 // on the right of the parent menu
1514 FLOAT_EQ(subMenu1->popupItem()->x(), mainMenu->popupItem()->x() + mainMenu->width() - overlap);
1515 }
1516 } else {
1517 QCOMPARE(subMenu1->parentItem(), mainMenu->parentItem());
1518 // centered over the parent menu
1519 FLOAT_EQ(subMenu1->popupItem()->x(), mainMenu->popupItem()->x() + (mainMenu->width() - subMenu1->width()) / 2);
1520 FLOAT_EQ(subMenu1->popupItem()->y(), mainMenu->popupItem()->y() + (mainMenu->height() - subMenu1->height()) / 2);
1521 }
1522
1523 // open the sub-sub-menu (can flip)
1524 QQuickMenuItem *subSubMenu1Item = qobject_cast<QQuickMenuItem *>(object: subMenu1->itemAt(index: 2));
1525 QVERIFY(subSubMenu1Item);
1526 QCOMPARE(subSubMenu1Item->subMenu(), subSubMenu1);
1527 emit subSubMenu1Item->triggered();
1528 QTRY_COMPARE(mainMenu->isVisible(), cascade);
1529 QTRY_COMPARE(subMenu1->isVisible(), cascade);
1530 QVERIFY(!subMenu2->isVisible());
1531 QVERIFY(subSubMenu1->isVisible());
1532 QTRY_VERIFY(subSubMenu1->isOpened());
1533
1534 if (cascade) {
1535 QCOMPARE(subSubMenu1->parentItem(), subSubMenu1Item);
1536 // vertically aligned to the parent menu item
1537 FLOAT_EQ(subSubMenu1->popupItem()->y(), subMenu1->popupItem()->y() + subSubMenu1Item->y());
1538 if (mirrored != flip) {
1539 // on the left of the parent menu
1540 FLOAT_EQ(subSubMenu1->popupItem()->x(), subMenu1->popupItem()->x() - subSubMenu1->width() + overlap);
1541 } else {
1542 // on the right of the parent menu
1543 FLOAT_EQ(subSubMenu1->popupItem()->x(), subMenu1->popupItem()->x() + subMenu1->width() - overlap);
1544 }
1545 } else {
1546 QCOMPARE(subSubMenu1->parentItem(), subMenu1->parentItem());
1547 // centered over the parent menu
1548 FLOAT_EQ(subSubMenu1->popupItem()->x(), subMenu1->popupItem()->x() + (subMenu1->width() - subSubMenu1->width()) / 2);
1549 FLOAT_EQ(subSubMenu1->popupItem()->y(), subMenu1->popupItem()->y() + (subMenu1->height() - subSubMenu1->height()) / 2);
1550 }
1551}
1552
1553#undef FLOAT_EQ
1554
1555void tst_QQuickMenu::addRemoveSubMenus()
1556{
1557 QQuickApplicationHelper helper(this, QLatin1String("subMenus.qml"));
1558 QVERIFY2(helper.ready, helper.failureMessage());
1559 QQuickWindow *window = helper.window;
1560 window->show();
1561 QVERIFY(QTest::qWaitForWindowActive(window));
1562
1563 QQuickMenu *mainMenu = window->property(name: "mainMenu").value<QQuickMenu *>();
1564 QVERIFY(mainMenu);
1565
1566 QVERIFY(!mainMenu->menuAt(0));
1567
1568 QPointer<QQuickMenu> subMenu1 = window->property(name: "subMenu1").value<QQuickMenu *>();
1569 QVERIFY(!subMenu1.isNull());
1570 QCOMPARE(mainMenu->menuAt(1), subMenu1.data());
1571
1572 QVERIFY(!mainMenu->menuAt(2));
1573
1574 QPointer<QQuickMenu> subMenu2 = window->property(name: "subMenu2").value<QQuickMenu *>();
1575 QVERIFY(!subMenu2.isNull());
1576 QCOMPARE(mainMenu->menuAt(3), subMenu2.data());
1577
1578 QVERIFY(!mainMenu->menuAt(4));
1579
1580 QPointer<QQuickMenu> subSubMenu1 = window->property(name: "subSubMenu1").value<QQuickMenu *>();
1581 QVERIFY(!subSubMenu1.isNull());
1582
1583 // takeMenu(int) does not destroy the menu, but does destroy the respective item in the parent menu
1584 QPointer<QQuickMenuItem> subSubMenu1Item = qobject_cast<QQuickMenuItem *>(object: subMenu1->itemAt(index: 2));
1585 QVERIFY(subSubMenu1Item);
1586 QCOMPARE(subSubMenu1Item->subMenu(), subSubMenu1.data());
1587 QCOMPARE(subMenu1->takeMenu(2), subSubMenu1.data());
1588 QVERIFY(!subMenu1->itemAt(2));
1589 QCoreApplication::sendPostedEvents(receiver: subSubMenu1, event_type: QEvent::DeferredDelete);
1590 QVERIFY(!subSubMenu1.isNull());
1591 QCoreApplication::sendPostedEvents(receiver: subSubMenu1Item, event_type: QEvent::DeferredDelete);
1592 QVERIFY(subSubMenu1Item.isNull());
1593
1594 // takeMenu(int) does not destroy an item that doesn't present a menu
1595 QPointer<QQuickMenuItem> subMenuItem1 = qobject_cast<QQuickMenuItem *>(object: subMenu1->itemAt(index: 0));
1596 QVERIFY(subMenuItem1);
1597 QVERIFY(!subMenuItem1->subMenu());
1598 QVERIFY(!subMenu1->takeMenu(0));
1599 QCoreApplication::sendPostedEvents(receiver: subMenuItem1, event_type: QEvent::DeferredDelete);
1600 QVERIFY(!subMenuItem1.isNull());
1601
1602 // addMenu(Menu) re-creates the respective item in the parent menu
1603 subMenu1->addMenu(menu: subSubMenu1);
1604 subSubMenu1Item = qobject_cast<QQuickMenuItem *>(object: subMenu1->itemAt(index: 2));
1605 QVERIFY(!subSubMenu1Item.isNull());
1606
1607 // removeMenu(Menu) destroys both the menu and the respective item in the parent menu
1608 subMenu1->removeMenu(menu: subSubMenu1);
1609 QVERIFY(!subMenu1->itemAt(2));
1610 QCoreApplication::sendPostedEvents(receiver: subSubMenu1, event_type: QEvent::DeferredDelete);
1611 QVERIFY(subSubMenu1.isNull());
1612 QCoreApplication::sendPostedEvents(receiver: subSubMenu1Item, event_type: QEvent::DeferredDelete);
1613 QVERIFY(subSubMenu1Item.isNull());
1614}
1615
1616void tst_QQuickMenu::scrollable_data()
1617{
1618 QTest::addColumn<QString>(name: "qmlFilePath");
1619
1620 QTest::addRow(format: "Window") << QString::fromLatin1(str: "windowScrollable.qml");
1621 QTest::addRow(format: "ApplicationWindow") << QString::fromLatin1(str: "applicationWindowScrollable.qml");
1622 QTest::addRow(format: "WithPadding") << QString::fromLatin1(str: "scrollableWithPadding.qml");
1623}
1624
1625void tst_QQuickMenu::scrollable()
1626{
1627 QFETCH(QString, qmlFilePath);
1628
1629 QQuickApplicationHelper helper(this, qmlFilePath);
1630 QVERIFY2(helper.ready, helper.failureMessage());
1631 QQuickWindow *window = helper.window;
1632 window->show();
1633 QVERIFY(QTest::qWaitForWindowActive(window));
1634
1635 QQuickMenu *menu = window->property(name: "menu").value<QQuickMenu*>();
1636 menu->open();
1637 QVERIFY(menu->isVisible());
1638
1639 QQuickItem *contentItem = menu->contentItem();
1640 QCOMPARE(contentItem->property("interactive").toBool(), true);
1641}
1642
1643void tst_QQuickMenu::disableWhenTriggered_data()
1644{
1645 QTest::addColumn<int>(name: "menuItemIndex");
1646 QTest::addColumn<int>(name: "subMenuItemIndex");
1647
1648 QTest::addRow(format: "Action") << 0 << -1;
1649 QTest::addRow(format: "MenuItem with Action") << 1 << -1;
1650 QTest::addRow(format: "MenuItem with Action declared outside menu") << 2 << -1;
1651 QTest::addRow(format: "MenuItem with no Action") << 3 << -1;
1652
1653 QTest::addRow(format: "Sub-Action") << 4 << 0;
1654 QTest::addRow(format: "Sub-MenuItem with Action declared inside") << 4 << 1;
1655 QTest::addRow(format: "Sub-MenuItem with Action declared outside menu") << 4 << 2;
1656 QTest::addRow(format: "Sub-MenuItem with no Action") << 4 << 3;
1657}
1658
1659// Tests that the menu is dismissed when a menu item sets "enabled = false" in onTriggered().
1660void tst_QQuickMenu::disableWhenTriggered()
1661{
1662 if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
1663 || (QGuiApplication::platformName() == QLatin1String("minimal")))
1664 QSKIP("Mouse hovering not functional on offscreen/minimal platforms");
1665
1666 QFETCH(int, menuItemIndex);
1667 QFETCH(int, subMenuItemIndex);
1668
1669 QQuickApplicationHelper helper(this, QLatin1String("disableWhenTriggered.qml"));
1670 QVERIFY2(helper.ready, helper.failureMessage());
1671 QQuickWindow *window = helper.window;
1672 window->show();
1673 QVERIFY(QTest::qWaitForWindowActive(window));
1674
1675 QQuickMenu *menu = window->findChild<QQuickMenu*>(aName: "Menu");
1676 QVERIFY(menu);
1677
1678 menu->open();
1679 QVERIFY(menu->isVisible());
1680 QTRY_VERIFY(menu->isOpened());
1681
1682 QPointer<QQuickMenuItem> menuItem = qobject_cast<QQuickMenuItem*>(object: menu->itemAt(index: menuItemIndex));
1683 QVERIFY(menuItem);
1684
1685 if (subMenuItemIndex == -1) {
1686 // Click a top-level menu item.
1687 QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier,
1688 pos: menuItem->mapToScene(point: QPointF(menuItem->width() / 2, menuItem->height() / 2)).toPoint());
1689 QCOMPARE(menuItem->isEnabled(), false);
1690 QTRY_VERIFY(!menu->isVisible());
1691 } else {
1692 // Click a sub-menu item.
1693 QPointer<QQuickMenu> subMenu = menuItem->subMenu();
1694 QVERIFY(subMenu);
1695
1696 QPointer<QQuickMenuItem> subMenuItem = qobject_cast<QQuickMenuItem*>(object: subMenu->itemAt(index: subMenuItemIndex));
1697 QVERIFY(subMenuItem);
1698
1699 // First, open the sub-menu.
1700 QTest::mouseMove(window, pos: menuItem->mapToScene(point: QPoint(1, 1)).toPoint());
1701 QTRY_VERIFY(subMenu->isVisible());
1702 QVERIFY(menuItem->isHovered());
1703 QTRY_VERIFY(subMenu->contentItem()->property("contentHeight").toReal() > 0.0);
1704
1705 // Click the item.
1706 QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier,
1707 pos: subMenuItem->mapToScene(point: QPointF(subMenuItem->width() / 2, subMenuItem->height() / 2)).toPoint());
1708 QCOMPARE(subMenuItem->isEnabled(), false);
1709 QTRY_VERIFY(!menu->isVisible());
1710 }
1711}
1712
1713void tst_QQuickMenu::menuItemWidth_data()
1714{
1715 QTest::addColumn<bool>(name: "mirrored");
1716
1717 QTest::newRow(dataTag: "non-mirrored") << false;
1718 QTest::newRow(dataTag: "mirrored") << true;
1719}
1720
1721void tst_QQuickMenu::menuItemWidth()
1722{
1723 QFETCH(bool, mirrored);
1724
1725 QQuickApplicationHelper helper(this, QLatin1String("menuItemWidths.qml"));
1726 QVERIFY2(helper.ready, helper.failureMessage());
1727 QQuickApplicationWindow *window = helper.appWindow;
1728 window->show();
1729 QVERIFY(QTest::qWaitForWindowActive(window));
1730
1731 if (mirrored)
1732 window->setLocale(QLocale("ar_EG"));
1733
1734 QQuickMenu *menu = window->property(name: "menu").value<QQuickMenu *>();
1735 QVERIFY(menu);
1736 menu->open();
1737 QTRY_VERIFY(menu->isOpened());
1738 for (int i = 0; i < menu->count(); ++i)
1739 QCOMPARE(menu->itemAt(i)->width(), menu->availableWidth());
1740}
1741
1742void tst_QQuickMenu::menuItemWidthAfterMenuWidthChanged_data()
1743{
1744 QTest::addColumn<bool>(name: "mirrored");
1745
1746 QTest::newRow(dataTag: "non-mirrored") << false;
1747 QTest::newRow(dataTag: "mirrored") << true;
1748}
1749
1750void tst_QQuickMenu::menuItemWidthAfterMenuWidthChanged()
1751{
1752 QFETCH(bool, mirrored);
1753
1754 QQuickApplicationHelper helper(this, QLatin1String("menuItemWidths.qml"));
1755 QVERIFY2(helper.ready, helper.failureMessage());
1756 QQuickApplicationWindow *window = helper.appWindow;
1757 window->show();
1758 QVERIFY(QTest::qWaitForWindowActive(window));
1759
1760 if (mirrored)
1761 window->setLocale(QLocale("ar_EG"));
1762
1763 QQuickMenu *menu = window->property(name: "menu").value<QQuickMenu *>();
1764 QVERIFY(menu);
1765 menu->open();
1766 QTRY_VERIFY(menu->isOpened());
1767 for (int i = 0; i < menu->count(); ++i) {
1768 // Check that the width of menu items is correct before we resize the menu.
1769 const QQuickItem *item = menu->itemAt(index: i);
1770 QVERIFY2(qFuzzyCompare(item->width(), menu->availableWidth()),
1771 qPrintable(QString::fromLatin1("Expected width of %1 to be %2, but it's %3")
1772 .arg(item->objectName()).arg(menu->availableWidth()).arg(item->width())));
1773 }
1774
1775 menu->setWidth(menu->width() + 10);
1776
1777 // Check that the width of menu items is correct after we resize the menu.
1778 for (int i = 0; i < menu->count(); ++i) {
1779 // Check that the width of menu items is correct after we resize the menu.
1780 const QQuickItem *item = menu->itemAt(index: i);
1781 QVERIFY2(qFuzzyCompare(item->width(), menu->availableWidth()),
1782 qPrintable(QString::fromLatin1("Expected width of %1 to be %2, but it's %3")
1783 .arg(item->objectName()).arg(menu->availableWidth()).arg(item->width())));
1784 }
1785}
1786
1787void tst_QQuickMenu::menuItemWidthAfterImplicitWidthChanged_data()
1788{
1789 QTest::addColumn<bool>(name: "mirrored");
1790
1791 QTest::newRow(dataTag: "non-mirrored") << false;
1792 QTest::newRow(dataTag: "mirrored") << true;
1793}
1794
1795void tst_QQuickMenu::menuItemWidthAfterImplicitWidthChanged()
1796{
1797 QFETCH(bool, mirrored);
1798
1799 QQuickApplicationHelper helper(this, QLatin1String("menuItemWidths.qml"));
1800 QVERIFY2(helper.ready, helper.failureMessage());
1801 QQuickApplicationWindow *window = helper.appWindow;
1802 window->show();
1803 QVERIFY(QTest::qWaitForWindowActive(window));
1804
1805 if (mirrored)
1806 window->setLocale(QLocale("ar_EG"));
1807
1808 QQuickMenu *menu = window->property(name: "menu").value<QQuickMenu *>();
1809 QVERIFY(menu);
1810 menu->open();
1811 QTRY_VERIFY(menu->isOpened());
1812 // Check that the width of the menu item is correct before we change its font size.
1813 QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem*>(object: menu->itemAt(index: 0));
1814 QCOMPARE(menuItem->width(), menu->availableWidth());
1815
1816 // Add some text to increase the implicitWidth of the MenuItem.
1817 const qreal oldImplicitWidth = menuItem->implicitWidth();
1818 for (int i = 0; menuItem->implicitWidth() <= oldImplicitWidth; ++i) {
1819 menuItem->setText(menuItem->text() + QLatin1String("---"));
1820 if (i == 100)
1821 QFAIL("Shouldn't need 100 iterations to increase MenuItem's implicitWidth; something is wrong here");
1822 }
1823
1824 // Check that the width of the menu item is correct after we change its font size.
1825 QCOMPARE(menuItem->width(), menu->availableWidth());
1826}
1827
1828void tst_QQuickMenu::menuItemWidthAfterRetranslate()
1829{
1830 QQuickApplicationHelper helper(this, QLatin1String("menuItemWidths.qml"));
1831 QVERIFY2(helper.ready, helper.failureMessage());
1832 QQuickApplicationWindow *window = helper.appWindow;
1833 window->show();
1834 QVERIFY(QTest::qWaitForWindowActive(window));
1835
1836 QQuickMenu *menu = window->property(name: "menu").value<QQuickMenu *>();
1837 QVERIFY(menu);
1838 menu->open();
1839 QTRY_VERIFY(menu->isOpened());
1840 for (int i = 0; i < menu->count(); ++i) {
1841 // Check that the width of each menu item is correct before we retranslate.
1842 const QQuickItem *item = menu->itemAt(index: i);
1843 QVERIFY2(qFuzzyCompare(item->width(), menu->availableWidth()),
1844 qPrintable(QString::fromLatin1("Expected width of %1 to be %2, but it's %3")
1845 .arg(item->objectName()).arg(menu->availableWidth()).arg(item->width())));
1846 }
1847
1848 // Call retranslate() and cause all bindings to be re-evaluated.
1849 helper.engine.retranslate();
1850
1851 for (int i = 0; i < menu->count(); ++i) {
1852 // Check that the width of each menu item is correct after we retranslate.
1853 const QQuickItem *item = menu->itemAt(index: i);
1854 QVERIFY2(qFuzzyCompare(item->width(), menu->availableWidth()),
1855 qPrintable(QString::fromLatin1("Expected width of %1 to be %2, but it's %3")
1856 .arg(item->objectName()).arg(menu->availableWidth()).arg(item->width())));
1857 }
1858}
1859
1860void tst_QQuickMenu::giveMenuItemFocusOnButtonPress()
1861{
1862 QQuickApplicationHelper helper(this, QLatin1String("giveMenuItemFocusOnButtonPress.qml"));
1863 QVERIFY2(helper.ready, helper.failureMessage());
1864 QQuickApplicationWindow *window = helper.appWindow;
1865 window->show();
1866 QVERIFY(QTest::qWaitForWindowActive(window));
1867
1868 // Press enter on the button to open the menu.
1869 QQuickButton *menuButton = window->property(name: "menuButton").value<QQuickButton*>();
1870 QVERIFY(menuButton);
1871 menuButton->forceActiveFocus();
1872 QVERIFY(menuButton->hasActiveFocus());
1873
1874 QSignalSpy clickedSpy(window, SIGNAL(menuButtonClicked()));
1875 QVERIFY(clickedSpy.isValid());
1876
1877 QTest::keyClick(window, key: Qt::Key_Return);
1878 QCOMPARE(clickedSpy.count(), 1);
1879
1880 // The menu should still be open.
1881 QQuickMenu *menu = window->property(name: "menu").value<QQuickMenu*>();
1882 QVERIFY(menu);
1883 QTRY_VERIFY(menu->isOpened());
1884}
1885
1886QTEST_QUICKCONTROLS_MAIN(tst_QQuickMenu)
1887
1888#include "tst_qquickmenu.moc"
1889

source code of qtquickcontrols2/tests/auto/qquickmenu/tst_qquickmenu.cpp