File indexing completed on 2024-04-28 03:53:22
0001 /* 0002 SPDX-FileCopyrightText: 2023 Felix Ernst <felixernst@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include <QMainWindow> 0008 #include <QMenu> 0009 #include <QMenuBar> 0010 #include <QObject> 0011 #include <QSignalSpy> 0012 #include <QStandardPaths> 0013 #include <QTest> 0014 #include <QTimer> 0015 #include <QToolBar> 0016 #include <QToolButton> 0017 0018 #include "khamburgermenu.h" 0019 0020 class KHamburgerMenuTest : public QObject 0021 { 0022 Q_OBJECT 0023 0024 private Q_SLOTS: 0025 void initTestCase(); 0026 void init(); 0027 0028 /** 0029 * Tests whether the hamburger button actually hides when it is redundant. 0030 * Also makes sure that the "Menu" action, that can be added to context menus in situations in which no other menus are available, 0031 * only appears in such situations. 0032 * A special focus is set on making sure that the aboutToShowMenu() signal is emitted correctly to avoid premature population of menus. 0033 */ 0034 void visibilityTest(); 0035 0036 /** 0037 * Tests whether the populating of the hamburger menu happens correctly based on the provided information. 0038 * A special focus is set on making sure that the aboutToShowMenu() signal is emitted correctly to avoid premature population of menus. 0039 * We don't want those menus to be populated until the aboutToShowMenu() has been emitted, so the populating code is not triggered until needed. 0040 */ 0041 void menuContentsTest(); 0042 0043 /** 0044 * For accessibility we want the shortcut of KHamburgerMenu to always open a menu. 0045 */ 0046 void openMenuByShortcutTest(); 0047 0048 private: 0049 QScopedPointer<QMainWindow> m_mainWindow; 0050 QPointer<QMenuBar> m_menuBar; 0051 QPointer<QMenu> m_menuBarFileMenu; 0052 QPointer<QToolBar> m_toolBar; 0053 }; 0054 0055 void KHamburgerMenuTest::initTestCase() 0056 { 0057 QStandardPaths::setTestModeEnabled(true); 0058 } 0059 0060 void KHamburgerMenuTest::init() 0061 { 0062 m_mainWindow.reset(new QMainWindow()); 0063 m_toolBar = new QToolBar(m_mainWindow.get()); 0064 m_mainWindow->addToolBar(m_toolBar); 0065 0066 m_menuBar = m_mainWindow->menuBar(); 0067 m_menuBarFileMenu = new QMenu(QStringLiteral("File"), m_menuBar); 0068 m_menuBarFileMenu->addAction(QStringLiteral("New")); 0069 m_menuBarFileMenu->addAction(QStringLiteral("Open")); 0070 m_menuBarFileMenu->addAction(QStringLiteral("Quit")); 0071 m_menuBar->addMenu(m_menuBarFileMenu); 0072 m_menuBar->addAction(QStringLiteral("Help")); 0073 0074 m_mainWindow->show(); 0075 } 0076 0077 bool isHamburgerButtonVisibleOnToolBar(KHamburgerMenu *hamburgerMenuAction, const QToolBar *toolBar) 0078 { 0079 const QToolButton *hamburgerToolButton = qobject_cast<QToolButton *>(toolBar->widgetForAction(hamburgerMenuAction)); 0080 if (!hamburgerToolButton) { 0081 return false; 0082 } 0083 if (hamburgerToolButton->width() < 1 || hamburgerToolButton->height() < 1) { 0084 return false; 0085 } 0086 0087 bool actuallyVisible = hamburgerToolButton->isVisible(); 0088 const QWidget *ancestorWidget = hamburgerToolButton->parentWidget(); 0089 while (actuallyVisible && ancestorWidget) { 0090 actuallyVisible = ancestorWidget->isVisible(); 0091 ancestorWidget = ancestorWidget->parentWidget(); 0092 } 0093 return actuallyVisible; 0094 } 0095 0096 void KHamburgerMenuTest::visibilityTest() 0097 { 0098 KHamburgerMenu *hamburgerMenuAction = new KHamburgerMenu(m_mainWindow.get()); 0099 QSignalSpy aboutToShowMenuSignalsSpy(hamburgerMenuAction, &KHamburgerMenu::aboutToShowMenu); 0100 m_toolBar->addAction(hamburgerMenuAction); 0101 QTRY_VERIFY(isHamburgerButtonVisibleOnToolBar(hamburgerMenuAction, m_toolBar)); 0102 0103 /* 1. Test visibility interactions with menu bar */ 0104 0105 hamburgerMenuAction->setMenuBar(m_menuBar); 0106 QTRY_VERIFY2(!isHamburgerButtonVisibleOnToolBar(hamburgerMenuAction, m_toolBar), "KHamburgerMenu hides when there is a visible menu bar."); 0107 0108 m_menuBar->hide(); 0109 QTRY_VERIFY2(isHamburgerButtonVisibleOnToolBar(hamburgerMenuAction, m_toolBar), "KHamburgerMenu re-appears when the menu bar is hidden."); 0110 0111 /* 2. Test visibility interactions of the context menu action */ 0112 0113 QMenu *contextMenu = new QMenu(m_mainWindow.get()); 0114 hamburgerMenuAction->addToMenu(contextMenu); 0115 QCOMPARE(contextMenu->actions().count(), 1); 0116 contextMenu->addAction("test"); 0117 QMenu *hamburgerMenuMenu = new QMenu(m_mainWindow.get()); 0118 hamburgerMenuMenu->addAction("testActionInsideTheHamburgerMenu"); 0119 hamburgerMenuAction->setMenu(hamburgerMenuMenu); 0120 0121 m_menuBar->show(); 0122 hamburgerMenuAction->addToMenu(contextMenu); 0123 contextMenu->popup(QPoint()); 0124 QVERIFY2(!contextMenu->actions().last()->isVisible(), "The context menu action should be invisible because the menu bar is visible."); 0125 0126 m_menuBar->hide(); 0127 QTRY_VERIFY2(isHamburgerButtonVisibleOnToolBar(hamburgerMenuAction, m_toolBar), "KHamburgerMenu re-appears when the menu bar is hidden."); 0128 hamburgerMenuAction->addToMenu(contextMenu); 0129 contextMenu->popup(QPoint()); 0130 QVERIFY2(!contextMenu->actions().last()->isVisible(), "The context menu action should be invisible because KHamburgerMenu is visible in the toolbar."); 0131 0132 m_menuBar->hide(); 0133 m_toolBar->hide(); 0134 hamburgerMenuAction->addToMenu(contextMenu); 0135 QCOMPARE(aboutToShowMenuSignalsSpy.count(), 0); 0136 contextMenu->popup(QPoint()); 0137 QVERIFY2(contextMenu->actions().last()->isVisible(), "The context menu action should be visible because there is no other menu anywhere."); 0138 QVERIFY2(aboutToShowMenuSignalsSpy.count() > 0, 0139 "The hamburger menu within the context menu should already be populated at this point, " 0140 "so at least one aboutToShowMenu() signal should have been emitted."); 0141 0142 const int count = aboutToShowMenuSignalsSpy.count(); 0143 m_toolBar->show(); 0144 hamburgerMenuAction->addToMenu(contextMenu); 0145 contextMenu->popup(QPoint()); 0146 QVERIFY2(!contextMenu->actions().last()->isVisible(), 0147 "The context menu action should be hidden because the toolbar containing the hamburger menu is " 0148 "now visible."); 0149 QCOMPARE(aboutToShowMenuSignalsSpy.count(), count); // There is no reason why it would have changed. 0150 0151 /* 3. Test visibility interactions when there are multiple buttons for one KHamburgerMenu */ 0152 0153 auto secondToolBar = new QToolBar(m_mainWindow.get()); 0154 m_mainWindow->addToolBar(Qt::BottomToolBarArea, secondToolBar); 0155 secondToolBar->addAction(hamburgerMenuAction); 0156 QTRY_VERIFY(isHamburgerButtonVisibleOnToolBar(hamburgerMenuAction, secondToolBar)); 0157 0158 m_menuBar->show(); 0159 QTRY_VERIFY(!isHamburgerButtonVisibleOnToolBar(hamburgerMenuAction, secondToolBar)); 0160 0161 m_menuBar->hide(); 0162 QTRY_VERIFY(isHamburgerButtonVisibleOnToolBar(hamburgerMenuAction, secondToolBar)); 0163 0164 m_toolBar->hide(); 0165 hamburgerMenuAction->addToMenu(contextMenu); 0166 contextMenu->popup(QPoint()); 0167 QVERIFY2(!contextMenu->actions().last()->isVisible(), 0168 "The context menu action should be hidden because the secondToolBar containing the hamburger menu " 0169 "action is now visible."); 0170 0171 secondToolBar->hide(); 0172 hamburgerMenuAction->addToMenu(contextMenu); 0173 QCOMPARE(aboutToShowMenuSignalsSpy.count(), count); // There is no reason why it would have changed. 0174 contextMenu->popup(QPoint()); 0175 QVERIFY2(contextMenu->actions().last()->isVisible(), "The context menu action should be visible because there is no other menu anywhere."); 0176 QVERIFY(aboutToShowMenuSignalsSpy.count() > count); // It changed for the previous popup() because there actually was a menu in there that needed updating. 0177 } 0178 0179 void openAndCloseToolButton(QToolButton *button) 0180 { 0181 QTimer::singleShot(30, [button]() { 0182 button->menu()->close(); 0183 }); // This seems to be the only way because code that isn't already set in motion through a timer doesn't get executed after the menu pops up. 0184 0185 QTest::mouseClick(button, Qt::LeftButton); 0186 } 0187 0188 void KHamburgerMenuTest::menuContentsTest() 0189 { 0190 KHamburgerMenu *hamburgerMenuAction = new KHamburgerMenu(m_mainWindow.get()); 0191 QSignalSpy aboutToShowMenuSignalsSpy(hamburgerMenuAction, &KHamburgerMenu::aboutToShowMenu); 0192 m_toolBar->addAction(hamburgerMenuAction); 0193 QToolButton *hamburgerToolButton = qobject_cast<QToolButton *>(m_toolBar->widgetForAction(hamburgerMenuAction)); 0194 QVERIFY(hamburgerToolButton); 0195 QVERIFY2(!hamburgerToolButton->menu() || hamburgerToolButton->menu()->isEmpty(), 0196 "The hamburger menu button is not supposed to have a menu until it is " 0197 "pressed the first time."); 0198 0199 m_menuBar->hide(); 0200 hamburgerMenuAction->setMenuBar(m_menuBar); 0201 openAndCloseToolButton(hamburgerToolButton); 0202 QCOMPARE(aboutToShowMenuSignalsSpy.count(), 1); 0203 // The hamburger menu now contains the two menu bar actions "File" and "Help". 0204 QVERIFY(hamburgerToolButton->menu()); 0205 QVERIFY(!hamburgerToolButton->menu()->isEmpty()); 0206 QCOMPARE(hamburgerToolButton->menu()->actions().count(), 2); 0207 0208 QMenu *hamburgerMenuMenu = new QMenu(m_mainWindow.get()); 0209 hamburgerMenuMenu->addAction("testActionInsideTheHamburgerMenu"); 0210 hamburgerMenuAction->setMenu(hamburgerMenuMenu); 0211 QCOMPARE(hamburgerToolButton->menu()->actions().count(), 2); // The menu actions are not updated until the menu is opened again. 0212 0213 openAndCloseToolButton(hamburgerToolButton); 0214 QCOMPARE(aboutToShowMenuSignalsSpy.count(), 2); 0215 QVERIFY(!hamburgerToolButton->menu()->isEmpty()); 0216 QCOMPARE(hamburgerToolButton->menu()->actions().count(), 4); // The hamburger menu now contains 0217 // testActionInsideTheHamburgerMenu, the help menu, a separator and the menuBarAdvertisementsMenu. 0218 0219 hamburgerMenuMenu->addAction("testActionInsideTheHamburgerMenu2"); 0220 QCOMPARE(hamburgerToolButton->menu()->actions().count(), 4); // The menu actions are not updated until the menu is opened again. 0221 0222 openAndCloseToolButton(hamburgerToolButton); 0223 QCOMPARE(aboutToShowMenuSignalsSpy.count(), 3); 0224 QVERIFY(!hamburgerToolButton->menu()->isEmpty()); 0225 QCOMPARE(hamburgerToolButton->menu()->actions().count(), 5); // The hamburger menu now contains 0226 // testActionInsideTheHamburgerMenu, testActionInsideTheHamburgerMenu2, the help menu, a separator and the menuBarAdvertisementsMenu. 0227 0228 hamburgerToolButton->menu()->actions().last()->menu()->aboutToShow(); 0229 QCOMPARE(hamburgerToolButton->menu()->actions().last()->menu()->actions().count(), 2); // The "More" menu also known as the "menuBarAdvertisementMenu" 0230 // contains the a section header and the "File" menu. 0231 0232 hamburgerMenuAction->setShowMenuBarAction(new QAction(QStringLiteral("Show Menubar"), m_mainWindow.get())); 0233 m_menuBar->removeAction(m_menuBar->actions().first()); // Remove "File" menu. 0234 openAndCloseToolButton(hamburgerToolButton); 0235 hamburgerToolButton->menu()->actions().last()->menu()->aboutToShow(); 0236 QCOMPARE(hamburgerToolButton->menu()->actions().last()->menu()->actions().count(), 2); // The "More" menu also known as the "menuBarAdvertisementMenu" 0237 // contains the "Show Menubar"-action and an invisible section header. 0238 0239 m_menuBar->addAction(new QAction(QStringLiteral("Edit"), m_mainWindow.get())); 0240 openAndCloseToolButton(hamburgerToolButton); 0241 hamburgerToolButton->menu()->actions().last()->menu()->aboutToShow(); 0242 QCOMPARE(hamburgerToolButton->menu()->actions().last()->menu()->actions().count(), 0243 3); // The "More" menu also known as the "menuBarAdvertisementMenu" contains the "Show Menubar"-action, a section header and an "Edit" action. 0244 } 0245 0246 void KHamburgerMenuTest::openMenuByShortcutTest() 0247 { 0248 KHamburgerMenu *hamburgerMenuAction = new KHamburgerMenu(m_mainWindow.get()); 0249 QSignalSpy aboutToShowMenuSignalsSpy(hamburgerMenuAction, &KHamburgerMenu::aboutToShowMenu); 0250 QMenu *hamburgerMenuMenu = new QMenu(m_mainWindow.get()); 0251 QAction *testActionInsideHamburgerMenu = new QAction(QStringLiteral("testActionInsideTheHamburgerMenu"), m_mainWindow.get()); 0252 hamburgerMenuMenu->addAction(testActionInsideHamburgerMenu); 0253 hamburgerMenuAction->setMenu(hamburgerMenuMenu); 0254 0255 // Make sure a KHamburgerMenu button can be activated/opened by shortcut 0256 m_toolBar->addAction(hamburgerMenuAction); 0257 QToolButton *hamburgerToolButton = qobject_cast<QToolButton *>(m_toolBar->widgetForAction(hamburgerMenuAction)); 0258 hamburgerMenuAction->trigger(); 0259 QTRY_VERIFY(hamburgerToolButton->menu()->isVisible()); // Wait for the menu to be shown 0260 QVERIFY(hamburgerToolButton->menu()->actions().first() == testActionInsideHamburgerMenu); 0261 hamburgerToolButton->menu()->close(); 0262 QTRY_VERIFY(!hamburgerToolButton->menu()->isVisible()); 0263 QCOMPARE(aboutToShowMenuSignalsSpy.count(), 1); 0264 0265 // Make sure a KHamburgerMenu menu can be activated/opened by shortcut even if there is no visible button 0266 m_toolBar->hide(); 0267 hamburgerMenuAction->trigger(); 0268 QTRY_VERIFY(hamburgerToolButton->menu()->isVisible()); // We test for the toolButton's menu even though the menu that will be shown won't visually belong 0269 // to the button (which is invisible) because the menu should still be the same. 0270 QVERIFY(hamburgerToolButton->menu()->actions().first() == testActionInsideHamburgerMenu); 0271 QVERIFY(!hamburgerToolButton->isDown()); 0272 hamburgerToolButton->menu()->close(); 0273 QTRY_VERIFY(!hamburgerToolButton->menu()->isVisible()); 0274 QCOMPARE(aboutToShowMenuSignalsSpy.count(), 2); 0275 0276 // Make sure the first menu bar menu can be activated/opened by shortcut when it is visible 0277 hamburgerMenuAction->setMenuBar(m_menuBar); 0278 hamburgerMenuAction->trigger(); 0279 QTRY_VERIFY(m_menuBarFileMenu->isVisible()); 0280 QVERIFY(!hamburgerToolButton->menu()->isVisible()); 0281 m_menuBarFileMenu->close(); 0282 QTRY_VERIFY(!m_menuBarFileMenu->isVisible()); 0283 QCOMPARE(aboutToShowMenuSignalsSpy.count(), 2); // Unchanged because the menu was not opened. 0284 0285 // Make sure a KHamburgerMenu button will again be activated/opened by shortcut after the shortcut has been used to open the first menu bar menu. 0286 m_menuBar->hide(); 0287 m_toolBar->show(); 0288 hamburgerMenuAction->trigger(); 0289 QTRY_VERIFY(hamburgerToolButton->menu()->isVisible()); // Wait for the menu to be shown 0290 QVERIFY(hamburgerToolButton->menu()->actions().first() == testActionInsideHamburgerMenu); 0291 hamburgerToolButton->menu()->close(); 0292 QTRY_VERIFY(!hamburgerToolButton->menu()->isVisible()); 0293 QCOMPARE(aboutToShowMenuSignalsSpy.count(), 3); 0294 } 0295 0296 QTEST_MAIN(KHamburgerMenuTest) 0297 0298 #include "khamburgermenutest.moc"