summaryrefslogtreecommitdiffstats
path: root/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp')
-rw-r--r--tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp330
1 files changed, 271 insertions, 59 deletions
diff --git a/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp b/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp
index db465e9281..5602b8cd3f 100644
--- a/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp
+++ b/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp
@@ -1,30 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QTest>
#include <QtTest/private/qtesthelpers_p.h>
@@ -52,6 +27,9 @@
#include <qpa/qplatformtheme.h>
#include <qpa/qplatformintegration.h>
+#include <QtWidgets/private/qapplication_p.h>
+#include <QtWidgets/private/qmenu_p.h>
+
using namespace QTestPrivate;
Q_DECLARE_METATYPE(Qt::Key);
@@ -118,6 +96,7 @@ private slots:
void QTBUG_89082_actionTipsHide();
void QTBUG8122_widgetActionCrashOnClose();
void widgetActionTriggerClosesMenu();
+ void transientParent();
void QTBUG_10735_crashWithDialog();
#ifdef Q_OS_MAC
@@ -133,6 +112,10 @@ private slots:
void menuSize_Scrolling();
void tearOffMenuNotDisplayed();
void QTBUG_61039_menu_shortcuts();
+ void screenOrientationChangedCloseMenu();
+ void deleteWhenTriggered();
+
+ void nestedTearOffDetached();
protected slots:
void onActivated(QAction*);
@@ -175,7 +158,9 @@ void tst_QMenu::getSetCheck()
tst_QMenu::tst_QMenu()
: m_onStatusTipTimerExecuted(false)
{
+ QApplication::setEffectEnabled(Qt::UI_FadeTooltip, false);
QApplication::setEffectEnabled(Qt::UI_AnimateMenu, false);
+ QApplication::setEffectEnabled(Qt::UI_AnimateTooltip, false);
}
void tst_QMenu::initTestCase()
@@ -278,38 +263,93 @@ void tst_QMenu::onStatusMessageChanged(const QString &s)
void tst_QMenu::addActionsAndClear()
{
- QCOMPARE(menus[0]->actions().count(), 0);
+ QCOMPARE(menus[0]->actions().size(), 0);
createActions();
- QCOMPARE(menus[0]->actions().count(), 8);
+ QCOMPARE(menus[0]->actions().size(), 8);
menus[0]->clear();
- QCOMPARE(menus[0]->actions().count(), 0);
+ QCOMPARE(menus[0]->actions().size(), 0);
}
-static void testFunction() { }
+static void testFunction0() {}
+static void testFunction1(bool) {}
+
+template <typename T>
+struct ImplicitlyConvertibleTo {
+ T t;
+ operator const T() const { return t; }
+ operator T() { return t; }
+};
void tst_QMenu::addActionsConnect()
{
+ // almost exhaustive check of addAction() overloads:
+ // (text), (icon, text), (icon, text, shortcut), (text, shortcut)
+ // each with a good sample of ways to QObject::connect() to
+ // QAction::triggered(bool)
QMenu menu;
- const QString text = QLatin1String("bla");
- const QIcon icon;
- menu.addAction(text, &menu, SLOT(deleteLater()));
- menu.addAction(text, &menu, &QMenu::deleteLater);
- menu.addAction(text, testFunction);
- menu.addAction(text, &menu, testFunction);
- menu.addAction(icon, text, &menu, SLOT(deleteLater()));
- menu.addAction(icon, text, &menu, &QMenu::deleteLater);
- menu.addAction(icon, text, testFunction);
- menu.addAction(icon, text, &menu, testFunction);
+
+ // don't just pass QString etc - that'd be too easy (think QStringBuilder)
+ ImplicitlyConvertibleTo<QString> text = {QLatin1String("bla")};
+ ImplicitlyConvertibleTo<QIcon> icon;
+
+ const auto check = [&](auto &...args) { // don't need to perfectly-forward, only lvalues passed
+ menu.addAction(args...);
+
+ menu.addAction(args..., &menu, SLOT(deleteLater()));
+ menu.addAction(args..., &menu, &QMenu::deleteLater);
+ menu.addAction(args..., testFunction0);
+ menu.addAction(args..., &menu, testFunction0);
+ menu.addAction(args..., testFunction1);
+ menu.addAction(args..., &menu, testFunction1);
+ menu.addAction(args..., [&](bool b) { menu.setEnabled(b); });
+ menu.addAction(args..., &menu, [&](bool b) { menu.setEnabled(b); });
+
+ menu.addAction(args..., &menu, SLOT(deleteLater()), Qt::QueuedConnection);
+ menu.addAction(args..., &menu, &QMenu::deleteLater, Qt::QueuedConnection);
+ // doesn't exist: menu.addAction(args..., testFunction0, Qt::QueuedConnection);
+ menu.addAction(args..., &menu, testFunction0, Qt::QueuedConnection);
+ // doesn't exist: menu.addAction(args..., testFunction1, Qt::QueuedConnection);
+ menu.addAction(args..., &menu, testFunction1, Qt::QueuedConnection);
+ // doesn't exist: menu.addAction(args..., [&](bool b) { menu.setEnabled(b); }, Qt::QueuedConnection);
+ menu.addAction(args..., &menu, [&](bool b) { menu.setEnabled(b); }, Qt::QueuedConnection);
+ };
+ const auto check1 = [&](auto &arg, auto &...args) {
+ check(arg, args...);
+ check(std::as_const(arg), args...);
+ };
+ const auto check2 = [&](auto &arg1, auto &arg2, auto &...args) {
+ check1(arg1, arg2, args...);
+ check1(arg1, std::as_const(arg2), args...);
+ };
+ [[maybe_unused]]
+ const auto check3 = [&](auto &arg1, auto &arg2, auto &arg3) {
+ check2(arg1, arg2, arg3);
+ check2(arg1, arg2, std::as_const(arg3));
+ };
+
+ check1(text);
+ check2(icon, text);
#ifndef QT_NO_SHORTCUT
- const QKeySequence keySequence(Qt::CTRL | Qt::Key_C);
+ ImplicitlyConvertibleTo<QKeySequence> keySequence = {Qt::CTRL | Qt::Key_C};
+ check2(text, keySequence);
+ check3(icon, text, keySequence);
+#if QT_DEPRECATED_SINCE(6, 4)
+ QT_WARNING_PUSH
+ QT_WARNING_DISABLE_DEPRECATED
menu.addAction(text, &menu, SLOT(deleteLater()), keySequence);
menu.addAction(text, &menu, &QMenu::deleteLater, keySequence);
- menu.addAction(text, testFunction, keySequence);
- menu.addAction(text, &menu, testFunction, keySequence);
+ menu.addAction(text, testFunction0, keySequence);
+ menu.addAction(text, &menu, testFunction0, keySequence);
+ menu.addAction(text, testFunction1, keySequence);
+ menu.addAction(text, &menu, testFunction1, keySequence);
menu.addAction(icon, text, &menu, SLOT(deleteLater()), keySequence);
menu.addAction(icon, text, &menu, &QMenu::deleteLater, keySequence);
- menu.addAction(icon, text, testFunction, keySequence);
- menu.addAction(icon, text, &menu, testFunction, keySequence);
+ menu.addAction(icon, text, testFunction0, keySequence);
+ menu.addAction(icon, text, &menu, testFunction0, keySequence);
+ menu.addAction(icon, text, testFunction1, keySequence);
+ menu.addAction(icon, text, &menu, testFunction1, keySequence);
+ QT_WARNING_POP
+#endif
#endif // !QT_NO_SHORTCUT
}
@@ -447,7 +487,7 @@ void tst_QMenu::focus()
QPushButton button("Push me", &window);
centerOnScreen(&window);
window.show();
- qApp->setActiveWindow(&window);
+ QApplicationPrivate::setActiveWindow(&window);
QVERIFY(button.hasFocus());
QCOMPARE(QApplication::focusWidget(), (QWidget *)&button);
@@ -491,7 +531,7 @@ void tst_QMenu::overrideMenuAction()
m->addAction(aQuit);
w.show();
- QApplication::setActiveWindow(&w);
+ QApplicationPrivate::setActiveWindow(&w);
w.setFocus();
QVERIFY(QTest::qWaitForWindowActive(&w));
QVERIFY(w.hasFocus());
@@ -859,13 +899,20 @@ private:
void tst_QMenu::activeSubMenuPositionExec()
{
-
+#ifdef Q_OS_ANDROID
+ // QTBUG-87424
+ QSKIP("Android: This hangs. Figure out why.");
+#endif
SubMenuPositionExecMenu menu;
menu.exec(QGuiApplication::primaryScreen()->availableGeometry().center());
}
void tst_QMenu::task242454_sizeHint()
{
+#ifdef Q_OS_ANDROID
+ // QTBUG-87424
+ QSKIP("Android: This hangs. Figure out why.");
+#endif
QMenu menu;
QString s = QLatin1String("foo\nfoo\nfoo\nfoo");
menu.addAction(s);
@@ -994,12 +1041,16 @@ public:
// Mouse move related signals for Windows Mobile unavailable
void tst_QMenu::task258920_mouseBorder()
{
+ const QRect screenGeometry = QGuiApplication::primaryScreen()->availableGeometry();
Menu258920 menu;
+ QCursor::setPos(screenGeometry.topLeft());
+ if (!QTest::qWaitFor([screenGeometry]{ return QCursor::pos() == screenGeometry.topLeft(); }))
+ QSKIP("Can't move cursor out of the way");
// For styles which inherit from QWindowsStyle, styleHint(QStyle::SH_Menu_MouseTracking) is true.
menu.setMouseTracking(true);
QAction *action = menu.addAction("test");
- const QPoint center = QGuiApplication::primaryScreen()->availableGeometry().center();
+ const QPoint center = screenGeometry.center();
menu.popup(center);
QVERIFY(QTest::qWaitForWindowExposed(&menu));
QRect actionRect = menu.actionGeometry(action);
@@ -1095,14 +1146,18 @@ void tst_QMenu::pushButtonPopulateOnAboutToShow()
QSKIP("Your window manager won't allow a window against the bottom of the screen");
}
- QTimer::singleShot(300, buttonMenu, SLOT(hide()));
QTest::mouseClick(&b, Qt::LeftButton, Qt::NoModifier, b.rect().center());
+ QVERIFY(QTest::qWaitForWindowExposed(buttonMenu));
+ QTest::qWait(300);
+ buttonMenu->hide();
QVERIFY2(!buttonMenu->geometry().intersects(b.geometry()), msgGeometryIntersects(buttonMenu->geometry(), b.geometry()));
// note: we're assuming that, if we previously got the desired geometry, we'll get it here too
b.move(10, screen.bottom()-buttonMenu->height()-5);
- QTimer::singleShot(300, buttonMenu, SLOT(hide()));
QTest::mouseClick(&b, Qt::LeftButton, Qt::NoModifier, b.rect().center());
+ QVERIFY(QTest::qWaitForWindowExposed(buttonMenu));
+ QTest::qWait(300);
+ buttonMenu->hide();
QVERIFY2(!buttonMenu->geometry().intersects(b.geometry()), msgGeometryIntersects(buttonMenu->geometry(), b.geometry()));
}
@@ -1217,7 +1272,7 @@ void tst_QMenu::click_while_dismissing_submenu()
//the submenu must have been hidden for the bug to be triggered
QVERIFY(!sub.isVisible());
QTest::mouseRelease(menuWindow, Qt::LeftButton, {}, menu.rect().center() - QPoint(0, 2), 300);
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
}
#endif
@@ -1530,6 +1585,54 @@ void tst_QMenu::widgetActionTriggerClosesMenu()
QCOMPARE(actionTriggered, &widgetAction);
}
+void tst_QMenu::transientParent()
+{
+ QMainWindow window;
+ window.resize(480, 320);
+ window.menuBar()->setNativeMenuBar(false);
+ centerOnScreen(&window);
+
+ QMenu *fileMenu = new QMenu("&File");
+ QAction *exitAct = new QAction("Exit");
+ fileMenu->addAction(exitAct);
+
+ QMenu *editMenu = new QMenu("&Edit");
+ QAction *undoAct = new QAction("Undo");
+ editMenu->addAction(undoAct);
+
+ QMenuBar *menuBar = new QMenuBar;
+ menuBar->addMenu(fileMenu);
+ menuBar->addMenu(editMenu);
+ window.setMenuBar(menuBar);
+
+ // On Mac, we need to create native key events to test menu
+ // action activation, so skip this part of the test.
+#if QT_CONFIG(shortcut) && !defined(Q_OS_DARWIN)
+ window.show();
+ QVERIFY(QTest::qWaitForWindowActive(&window));
+ QWindow *topLevel = window.windowHandle();
+ QVERIFY(topLevel);
+
+ QApplicationPrivate::setActiveWindow(&window);
+ window.setFocus();
+ QVERIFY(QTest::qWaitForWindowActive(&window));
+ QVERIFY(window.hasFocus());
+
+ QTest::keyPress(&window, Qt::Key_F, Qt::AltModifier);
+ QTRY_VERIFY(QTest::qWaitForWindowExposed(fileMenu));
+ if (fileMenu->isWindow() && fileMenu->window() && fileMenu->window()->windowHandle())
+ QVERIFY(fileMenu->window()->windowHandle()->transientParent());
+ QTest::keyRelease(fileMenu, Qt::Key_F, Qt::AltModifier);
+
+ QTest::keyPress(fileMenu, Qt::Key_E, Qt::AltModifier);
+ QTRY_VERIFY(QTest::qWaitForWindowExposed(editMenu));
+ if (editMenu->isWindow() && editMenu->window() && editMenu->window()->windowHandle())
+ QVERIFY(editMenu->window()->windowHandle()->transientParent());
+ QTest::keyRelease(editMenu, Qt::Key_E, Qt::AltModifier);
+#endif // QT_CONFIG(shortcut) && !Q_OS_DARWIN
+
+}
+
class MyMenu : public QMenu
{
Q_OBJECT
@@ -1745,16 +1848,16 @@ void tst_QMenu::menuSize_Scrolling()
private:
void showEvent(QShowEvent *e) override
{
- QVERIFY(actions().length() == m_numItems);
+ QVERIFY(actions().size() == m_numItems);
int hmargin = style()->pixelMetric(QStyle::PM_MenuHMargin, nullptr, this);
int fw = style()->pixelMetric(QStyle::PM_MenuPanelWidth, nullptr, this);
const QMargins cm = contentsMargins();
- QRect lastItem = actionGeometry(actions().at(actions().length() - 1));
+ QRect lastItem = actionGeometry(actions().at(actions().size() - 1));
QSize s = size();
if (!QGuiApplication::platformName().compare(QLatin1String("minimal"), Qt::CaseInsensitive)
|| !QGuiApplication::platformName().compare(QLatin1String("offscreen"), Qt::CaseInsensitive)) {
- QWARN("Skipping test on minimal/offscreen platforms - QTBUG-73522");
+ qWarning("Skipping test on minimal/offscreen platforms - QTBUG-73522");
QMenu::showEvent(e);
return;
}
@@ -1816,7 +1919,7 @@ void tst_QMenu::menuSize_Scrolling()
QVERIFY(QTest::qWaitForWindowExposed(&menu));
QList<QAction *> actions = menu.actions();
- QCOMPARE(actions.length(), numItems);
+ QCOMPARE(actions.size(), numItems);
MenuMetrics mm(&menu);
QTest::keyClick(&menu, Qt::Key_Home);
@@ -1896,11 +1999,120 @@ void tst_QMenu::QTBUG_61039_menu_shortcuts()
QSignalSpy actionKamenSpy(actionKamen, &QAction::triggered);
QTest::keyClick(&widget, Qt::Key_K);
- QTRY_COMPARE(actionKamenSpy.count(), 1);
+ QTRY_COMPARE(actionKamenSpy.size(), 1);
QSignalSpy actionJoeSpy(actionJoe, &QAction::triggered);
QTest::keyClick(&widget, Qt::Key_J, Qt::ControlModifier);
- QTRY_COMPARE(actionJoeSpy.count(), 1);
+ QTRY_COMPARE(actionJoeSpy.size(), 1);
+}
+
+void tst_QMenu::screenOrientationChangedCloseMenu()
+{
+ QMenu menu;
+ menu.addAction("action1");
+ menu.show();
+
+ QTRY_COMPARE(menu.isVisible(),true);
+
+ Qt::ScreenOrientation orientation = menu.screen()->orientation() == Qt::PortraitOrientation ? Qt::LandscapeOrientation : Qt::PortraitOrientation;
+ QScreenOrientationChangeEvent event(menu.screen(), orientation);
+ QCoreApplication::sendEvent(QCoreApplication::instance(), &event);
+
+ QTRY_COMPARE(menu.isVisible(),false);
+}
+
+/*
+ Verify that deleting the menu in a slot connected to an
+ action's triggered signal doesn't crash.
+ QTBUG-106718
+*/
+void tst_QMenu::deleteWhenTriggered()
+{
+ QPointer<QMenu> menu = new QMenu;
+ QAction *action = menu->addAction("Action", [&menu]{
+ delete menu;
+ });
+ menu->popup(QGuiApplication::primaryScreen()->availableGeometry().center());
+ menu->setActiveAction(action);
+ QTest::keyClick(menu, Qt::Key_Return);
+ QTRY_VERIFY(!menu);
+}
+
+/*
+ QMenu uses the caused-stack to create the parent/child relationship
+ for tear-off menus. Since QTornOffMenu set the DeleteOnClose flag, closing a
+ tear-off in the parent chain will result in a null-pointer in the caused-stack.
+ Verify that we don't crash when traversing the chain, as reported in QTBUG-112217.
+
+ The test has to open the submenus by hovering of the menu action, otherwise
+ the caused-stack remains empty and the issue doesn't reproduce. Due to QMenu's
+ timing and "sloppiness", we need to move the mouse within the action, with some
+ waiting and event processing in between to trigger the opening of the submenu.
+ If this fails we skip, as we then can't test what we are trying to test.
+*/
+void tst_QMenu::nestedTearOffDetached()
+{
+ // Since QTornOffMenu is not declared in qmenuprivate.h we can't access the
+ // object even through QMenuPrivate. So use an event filter to watch out for
+ // a QTornOffMenu showing.
+ class TearOffWatcher : public QObject
+ {
+ public:
+ QMenu *tornOffMenu = nullptr;
+ protected:
+ bool eventFilter(QObject *receiver, QEvent *event) override
+ {
+ if (event->type() == QEvent::Show && receiver->inherits("QTornOffMenu"))
+ tornOffMenu = qobject_cast<QMenu *>(receiver);
+ return QObject::eventFilter(receiver, event);
+ }
+ } watcher;
+ qApp->installEventFilter(&watcher);
+
+ QWidget widget;
+ QMenu *menu = new QMenu("Context", &widget);
+
+ MenuMetrics mm(menu);
+ const int tearOffOffset = mm.fw + mm.vmargin + mm.tearOffHeight / 2;
+
+ QMenu *subMenu = menu->addMenu("SubMenu");
+ menu->setTearOffEnabled(true);
+ QMenu *subSubMenu = subMenu->addMenu("SubSubMenu");
+ subMenu->setTearOffEnabled(true);
+ subSubMenu->addAction("Action!");
+ subSubMenu->setTearOffEnabled(true);
+
+ widget.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&widget));
+
+ // open and tear off context menu
+ menu->popup(widget.geometry().center());
+ QTest::mouseClick(menu, Qt::LeftButton, {}, QPoint(menu->width() / 2, tearOffOffset));
+
+ QMenu *menuTorn = watcher.tornOffMenu;
+ watcher.tornOffMenu = nullptr;
+ QVERIFY(menuTorn);
+ QVERIFY(QTest::qWaitForWindowExposed(menuTorn));
+
+ // open second menu and tear-off
+ QTest::mouseMove(menuTorn, menuTorn->actionGeometry(subMenu->menuAction()).topLeft());
+ QTest::qWait(100);
+ QTest::mouseMove(menuTorn, menuTorn->actionGeometry(subMenu->menuAction()).center());
+ if (!QTest::qWaitFor([subMenu]{ return subMenu->isVisible(); }))
+ QSKIP("Menu failed to show, skipping test");
+
+ QTest::mouseClick(subMenu, Qt::LeftButton, {}, QPoint(subMenu->width() / 2, tearOffOffset));
+ menuTorn = watcher.tornOffMenu;
+ QVERIFY(menuTorn);
+ QVERIFY(QTest::qWaitForWindowExposed(menuTorn));
+ // close the top level tear off
+ menu->hideTearOffMenu();
+ // open third menu and tear-off
+ QTest::mouseMove(menuTorn, menuTorn->actionGeometry(subSubMenu->menuAction()).topLeft());
+ QTest::qWait(100);
+ QTest::mouseMove(menuTorn, menuTorn->actionGeometry(subSubMenu->menuAction()).center());
+ QTRY_VERIFY(subSubMenu->isVisible());
+ QTest::mouseClick(subSubMenu, Qt::LeftButton, {}, QPoint(subSubMenu->width() / 2, tearOffOffset));
}
QTEST_MAIN(tst_QMenu)