/**************************************************************************** ** ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL3$ ** 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 http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPLv3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or later as published by the Free ** Software Foundation and appearing in the file LICENSE.GPL included in ** the packaging of this file. Please review the following information to ** ensure the GNU General Public License version 2.0 requirements will be ** met: http://www.gnu.org/licenses/gpl-2.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include "../shared/util.h" #include "../shared/visualtestutil.h" #include #include #include #include #include #include using namespace QQuickVisualTestUtil; class tst_menu : public QQmlDataTest { Q_OBJECT public: private slots: void defaults(); void mouse(); void pressAndHold(); void contextMenuKeyboard(); void menuButton(); void addItem(); void menuSeparator(); void repeater(); void order(); }; void tst_menu::defaults() { QQuickApplicationHelper helper(this, QLatin1String("applicationwindow.qml")); QQuickMenu *emptyMenu = helper.appWindow->property("emptyMenu").value(); QCOMPARE(emptyMenu->isVisible(), false); QCOMPARE(emptyMenu->contentItem()->property("currentIndex"), QVariant(-1)); } void tst_menu::mouse() { QQuickApplicationHelper helper(this, QLatin1String("applicationwindow.qml")); QQuickApplicationWindow *window = helper.appWindow; window->show(); QVERIFY(QTest::qWaitForWindowActive(window)); QQuickMenu *menu = window->property("menu").value(); menu->open(); QVERIFY(menu->isVisible()); QVERIFY(window->overlay()->childItems().contains(menu->contentItem()->parentItem())); QQuickItem *firstItem = menu->itemAt(0); QSignalSpy clickedSpy(firstItem, SIGNAL(clicked())); QSignalSpy triggeredSpy(firstItem, SIGNAL(triggered())); QSignalSpy visibleSpy(menu, SIGNAL(visibleChanged())); // Ensure that presses cause the current index to change, // so that the highlight acts as a way of illustrating press state. QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, QPoint(firstItem->width() / 2, firstItem->height() / 2)); QVERIFY(firstItem->hasActiveFocus()); QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(0)); QVERIFY(menu->isVisible()); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, QPoint(firstItem->width() / 2, firstItem->height() / 2)); QCOMPARE(clickedSpy.count(), 1); QCOMPARE(triggeredSpy.count(), 1); QCOMPARE(visibleSpy.count(), 1); QVERIFY(!menu->isVisible()); QVERIFY(!window->overlay()->childItems().contains(menu->contentItem())); QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1)); menu->open(); QCOMPARE(visibleSpy.count(), 2); QVERIFY(menu->isVisible()); QVERIFY(window->overlay()->childItems().contains(menu->contentItem()->parentItem())); // Ensure that we have enough space to click outside of the menu. QVERIFY(window->width() > menu->contentItem()->width()); QVERIFY(window->height() > menu->contentItem()->height()); QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, QPoint(menu->contentItem()->width() + 1, menu->contentItem()->height() + 1)); QCOMPARE(visibleSpy.count(), 3); QVERIFY(!menu->isVisible()); QVERIFY(!window->overlay()->childItems().contains(menu->contentItem()->parentItem())); menu->open(); QCOMPARE(visibleSpy.count(), 4); QVERIFY(menu->isVisible()); QVERIFY(window->overlay()->childItems().contains(menu->contentItem()->parentItem())); // Try pressing within the menu and releasing outside of it; it should close. // TODO: won't work until QQuickPopup::releasedOutside() actually gets emitted // QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, QPoint(firstItem->width() / 2, firstItem->height() / 2)); // QVERIFY(firstItem->hasActiveFocus()); // QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(0)); // QVERIFY(menu->isVisible()); // QCOMPARE(triggeredSpy.count(), 1); // QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, QPoint(menu->contentItem()->width() + 1, firstItem->height() / 2)); // QCOMPARE(clickedSpy.count(), 1); // QCOMPARE(triggeredSpy.count(), 1); // QCOMPARE(visibleSpy.count(), 5); // QVERIFY(!menu->isVisible()); // QVERIFY(!window->overlay()->childItems().contains(menu->contentItem())); // QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1)); } void tst_menu::pressAndHold() { QQuickApplicationHelper helper(this, QLatin1String("pressAndHold.qml")); QQuickWindow *window = helper.window; window->show(); QVERIFY(QTest::qWaitForWindowActive(window)); QQuickMenu *menu = window->property("menu").value(); QVERIFY(menu); QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, QPoint(1, 1)); QTRY_VERIFY(menu->isVisible()); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, QPoint(1, 1)); QVERIFY(menu->isVisible()); QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, QPoint(1, 1)); QTRY_VERIFY(!menu->isVisible()); } void tst_menu::contextMenuKeyboard() { if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls) QSKIP("This platform only allows tab focus for text controls"); QQuickApplicationHelper helper(this, QLatin1String("applicationwindow.qml")); QQuickApplicationWindow *window = helper.appWindow; window->show(); window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); QVERIFY(QGuiApplication::focusWindow() == window); QQuickMenu *menu = window->property("menu").value(); QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1)); QQuickMenuItem *firstItem = qobject_cast(menu->itemAt(0)); QVERIFY(firstItem); QSignalSpy visibleSpy(menu, SIGNAL(visibleChanged())); menu->setFocus(true); menu->open(); QCOMPARE(visibleSpy.count(), 1); QVERIFY(menu->isVisible()); QVERIFY(window->overlay()->childItems().contains(menu->contentItem()->parentItem())); QVERIFY(!firstItem->hasActiveFocus()); QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1)); QTest::keyClick(window, Qt::Key_Tab); QVERIFY(firstItem->hasActiveFocus()); QVERIFY(firstItem->hasVisualFocus()); QCOMPARE(firstItem->focusReason(), Qt::TabFocusReason); QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(0)); QQuickMenuItem *secondItem = qobject_cast(menu->itemAt(1)); QVERIFY(secondItem); QTest::keyClick(window, Qt::Key_Tab); QVERIFY(!firstItem->hasActiveFocus()); QVERIFY(!firstItem->hasVisualFocus()); QVERIFY(secondItem->hasActiveFocus()); QVERIFY(secondItem->hasVisualFocus()); QCOMPARE(secondItem->focusReason(), Qt::TabFocusReason); QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(1)); QSignalSpy secondTriggeredSpy(secondItem, SIGNAL(triggered())); QTest::keyClick(window, Qt::Key_Space); QCOMPARE(secondTriggeredSpy.count(), 1); QCOMPARE(visibleSpy.count(), 2); QVERIFY(!menu->isVisible()); QVERIFY(!window->overlay()->childItems().contains(menu->contentItem())); QVERIFY(!firstItem->hasActiveFocus()); QVERIFY(!firstItem->hasVisualFocus()); QVERIFY(!secondItem->hasActiveFocus()); QVERIFY(!secondItem->hasVisualFocus()); QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1)); menu->open(); QCOMPARE(visibleSpy.count(), 3); QVERIFY(menu->isVisible()); QVERIFY(window->overlay()->childItems().contains(menu->contentItem()->parentItem())); QVERIFY(!firstItem->hasActiveFocus()); QVERIFY(!firstItem->hasVisualFocus()); QVERIFY(!secondItem->hasActiveFocus()); QVERIFY(!secondItem->hasVisualFocus()); QCOMPARE(menu->contentItem()->property("currentIndex"), QVariant(-1)); QTest::keyClick(window, Qt::Key_Down); QVERIFY(firstItem->hasActiveFocus()); QVERIFY(firstItem->hasVisualFocus()); QCOMPARE(firstItem->focusReason(), Qt::TabFocusReason); QTest::keyClick(window, Qt::Key_Down); QVERIFY(secondItem->hasActiveFocus()); QVERIFY(secondItem->hasVisualFocus()); QCOMPARE(secondItem->focusReason(), Qt::TabFocusReason); QTest::keyClick(window, Qt::Key_Down); QQuickMenuItem *thirdItem = qobject_cast(menu->itemAt(2)); QVERIFY(thirdItem); QVERIFY(!firstItem->hasActiveFocus()); QVERIFY(!firstItem->hasVisualFocus()); QVERIFY(!secondItem->hasActiveFocus()); QVERIFY(!secondItem->hasVisualFocus()); QVERIFY(thirdItem->hasActiveFocus()); QVERIFY(thirdItem->hasVisualFocus()); QCOMPARE(thirdItem->focusReason(), Qt::TabFocusReason); // Key navigation shouldn't wrap by default. QTest::keyClick(window, Qt::Key_Down); QVERIFY(!firstItem->hasActiveFocus()); QVERIFY(!firstItem->hasVisualFocus()); QVERIFY(!secondItem->hasActiveFocus()); QVERIFY(!secondItem->hasVisualFocus()); QVERIFY(thirdItem->hasActiveFocus()); QVERIFY(thirdItem->hasVisualFocus()); QCOMPARE(thirdItem->focusReason(), Qt::TabFocusReason); QTest::keyClick(window, Qt::Key_Up); QVERIFY(!firstItem->hasActiveFocus()); QVERIFY(!firstItem->hasVisualFocus()); QVERIFY(secondItem->hasActiveFocus()); QVERIFY(secondItem->hasVisualFocus()); QCOMPARE(secondItem->focusReason(), Qt::BacktabFocusReason); QVERIFY(!thirdItem->hasActiveFocus()); QVERIFY(!thirdItem->hasVisualFocus()); QTest::keyClick(window, Qt::Key_Backtab); QVERIFY(firstItem->hasActiveFocus()); QVERIFY(firstItem->hasVisualFocus()); QCOMPARE(firstItem->focusReason(), Qt::BacktabFocusReason); QVERIFY(!secondItem->hasActiveFocus()); QVERIFY(!secondItem->hasVisualFocus()); QVERIFY(!thirdItem->hasActiveFocus()); QVERIFY(!thirdItem->hasVisualFocus()); QTest::keyClick(window, Qt::Key_Escape); QCOMPARE(visibleSpy.count(), 4); QVERIFY(!menu->isVisible()); } void tst_menu::menuButton() { if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls) QSKIP("This platform only allows tab focus for text controls"); QQuickApplicationHelper helper(this, QLatin1String("applicationwindow.qml")); QQuickApplicationWindow *window = helper.appWindow; window->show(); window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); QVERIFY(QGuiApplication::focusWindow() == window); QQuickMenu *menu = window->property("menu").value(); QQuickButton *menuButton = window->property("menuButton").value(); QSignalSpy visibleSpy(menu, SIGNAL(visibleChanged())); menuButton->setVisible(true); QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, menuButton->mapToScene(QPointF(menuButton->width() / 2, menuButton->height() / 2)).toPoint()); QCOMPARE(visibleSpy.count(), 1); QVERIFY(menu->isVisible()); QTest::keyClick(window, Qt::Key_Tab); QQuickItem *firstItem = menu->itemAt(0); QVERIFY(firstItem->hasActiveFocus()); } void tst_menu::addItem() { QQuickApplicationHelper helper(this, QLatin1String("addItem.qml")); QQuickApplicationWindow *window = helper.appWindow; window->show(); QVERIFY(QTest::qWaitForWindowActive(window)); QQuickMenu *menu = window->property("menu").value(); QVERIFY(menu); menu->open(); QVERIFY(menu->isVisible()); QQuickItem *menuItem = menu->itemAt(0); QVERIFY(menuItem); QTRY_VERIFY(!QQuickItemPrivate::get(menuItem)->culled); // QTBUG-53262 QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, menuItem->mapToScene(QPointF(menuItem->width() / 2, menuItem->height() / 2)).toPoint()); QTRY_VERIFY(!menu->isVisible()); } void tst_menu::menuSeparator() { QQuickApplicationHelper helper(this, QLatin1String("menuSeparator.qml")); QQuickWindow *window = helper.window; window->show(); QVERIFY(QTest::qWaitForWindowActive(window)); QQuickMenu *menu = window->property("menu").value(); QVERIFY(menu); menu->open(); QVERIFY(menu->isVisible()); QQuickMenuItem *newMenuItem = qobject_cast(menu->itemAt(0)); QVERIFY(newMenuItem); QCOMPARE(newMenuItem->text(), QStringLiteral("New")); QQuickMenuSeparator *menuSeparator = qobject_cast(menu->itemAt(1)); QVERIFY(menuSeparator); QQuickMenuItem *saveMenuItem = qobject_cast(menu->itemAt(2)); QVERIFY(saveMenuItem); QCOMPARE(saveMenuItem->text(), QStringLiteral("Save")); QTRY_VERIFY(!QQuickItemPrivate::get(saveMenuItem)->culled); // QTBUG-53262 // Clicking on items should still close the menu. QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, newMenuItem->mapToScene(QPointF(newMenuItem->width() / 2, newMenuItem->height() / 2)).toPoint()); QTRY_VERIFY(!menu->isVisible()); menu->open(); QVERIFY(menu->isVisible()); // Clicking on a separator shouldn't close the menu. QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, menuSeparator->mapToScene(QPointF(menuSeparator->width() / 2, menuSeparator->height() / 2)).toPoint()); QVERIFY(menu->isVisible()); // Clicking on items should still close the menu. QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, saveMenuItem->mapToScene(QPointF(saveMenuItem->width() / 2, saveMenuItem->height() / 2)).toPoint()); QTRY_VERIFY(!menu->isVisible()); menu->open(); QVERIFY(menu->isVisible()); // Key navigation skips separators QTest::keyClick(window, Qt::Key_Down); QVERIFY(newMenuItem->hasActiveFocus()); QVERIFY(newMenuItem->hasVisualFocus()); QCOMPARE(newMenuItem->focusReason(), Qt::TabFocusReason); QTest::keyClick(window, Qt::Key_Down); QVERIFY(saveMenuItem->hasActiveFocus()); QVERIFY(saveMenuItem->hasVisualFocus()); QCOMPARE(saveMenuItem->focusReason(), Qt::TabFocusReason); QTest::keyClick(window, Qt::Key_Down); QVERIFY(saveMenuItem->hasActiveFocus()); QVERIFY(saveMenuItem->hasVisualFocus()); QCOMPARE(saveMenuItem->focusReason(), Qt::TabFocusReason); QTest::keyClick(window, Qt::Key_Up); QVERIFY(newMenuItem->hasActiveFocus()); QVERIFY(newMenuItem->hasVisualFocus()); QCOMPARE(newMenuItem->focusReason(), Qt::BacktabFocusReason); QTest::keyClick(window, Qt::Key_Up); QVERIFY(newMenuItem->hasActiveFocus()); QVERIFY(newMenuItem->hasVisualFocus()); QCOMPARE(newMenuItem->focusReason(), Qt::BacktabFocusReason); } void tst_menu::repeater() { QQuickApplicationHelper helper(this, QLatin1String("repeater.qml")); QQuickWindow *window = helper.window; window->show(); QVERIFY(QTest::qWaitForWindowActive(window)); QQuickMenu *menu = window->property("menu").value(); QVERIFY(menu); menu->open(); QVERIFY(menu->isVisible()); QObject *repeater = window->property("repeater").value(); QVERIFY(repeater); int count = repeater->property("count").toInt(); QCOMPARE(count, 5); for (int i = 0; i < count; ++i) { QQuickItem *item = menu->itemAt(i); QVERIFY(item); QCOMPARE(item->property("idx").toInt(), i); QQuickItem *repeaterItem = nullptr; QVERIFY(QMetaObject::invokeMethod(repeater, "itemAt", Q_RETURN_ARG(QQuickItem*, repeaterItem), Q_ARG(int, i))); QCOMPARE(item, repeaterItem); } repeater->setProperty("model", 3); count = repeater->property("count").toInt(); QCOMPARE(count, 3); for (int i = 0; i < count; ++i) { QQuickItem *item = menu->itemAt(i); QVERIFY(item); QCOMPARE(item->property("idx").toInt(), i); QQuickItem *repeaterItem = nullptr; QVERIFY(QMetaObject::invokeMethod(repeater, "itemAt", Q_RETURN_ARG(QQuickItem*, repeaterItem), Q_ARG(int, i))); QCOMPARE(item, repeaterItem); } } void tst_menu::order() { QQuickApplicationHelper helper(this, QLatin1String("order.qml")); QQuickWindow *window = helper.window; window->show(); QVERIFY(QTest::qWaitForWindowActive(window)); QQuickMenu *menu = window->property("menu").value(); QVERIFY(menu); menu->open(); QVERIFY(menu->isVisible()); const QStringList texts = {"dynamic_0", "static_1", "repeated_2", "repeated_3", "static_4", "dynamic_5", "dynamic_6"}; for (int i = 0; i < texts.count(); ++i) { QQuickItem *item = menu->itemAt(i); QVERIFY(item); QCOMPARE(item->property("text").toString(), texts.at(i)); } } QTEST_MAIN(tst_menu) #include "tst_menu.moc"