From d56feb8151871065505c83eeaa8bd3d53750af10 Mon Sep 17 00:00:00 2001 From: Sebastian Englbrecht Date: Sat, 30 May 2026 08:52:32 +0000 Subject: placespaneltest: implement 8 unittests --- src/tests/CMakeLists.txt | 5 + src/tests/placespaneltest.cpp | 363 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 368 insertions(+) create mode 100644 src/tests/placespaneltest.cpp (limited to 'src/tests') diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 930a6f433..cac38deae 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -109,6 +109,11 @@ if(NOT WIN32) set_tests_properties(dolphin_smoketest PROPERTIES TIMEOUT 30) endif() +# PlacesPanelTest +ecm_add_test(placespaneltest.cpp + TEST_NAME placespaneltest + LINK_LIBRARIES dolphinprivate dolphinstatic Qt6::Test) + find_gem(test-unit) set_package_properties(Gem_test-unit PROPERTIES TYPE RECOMMENDED diff --git a/src/tests/placespaneltest.cpp b/src/tests/placespaneltest.cpp new file mode 100644 index 000000000..caabe1646 --- /dev/null +++ b/src/tests/placespaneltest.cpp @@ -0,0 +1,363 @@ +/* + * SPDX-FileCopyrightText: 2026 Sebastian Englbrecht + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "panels/places/placespanel.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Select the given index and open context menu via keyboard event. +static void triggerContextMenuForIndex(KFilePlacesView &view, const QModelIndex &index) +{ + view.selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); + QContextMenuEvent event(QContextMenuEvent::Keyboard, QPoint(), QPoint()); + QApplication::sendEvent(&view, &event); +} + +// Open context menu with no item selected (empty-area behaviour). +static void triggerContextMenuEmptyArea(KFilePlacesView &view) +{ + view.selectionModel()->clearCurrentIndex(); + view.selectionModel()->clearSelection(); + QContextMenuEvent event(QContextMenuEvent::Keyboard, QPoint(), QPoint()); + QApplication::sendEvent(&view, &event); +} + +// Collect visible action texts from the menu (top-level, non-separator, visible only). +static QStringList visibleMenuActionTexts(QMenu *menu) +{ + QStringList texts; + for (QAction *action : menu->actions()) { + if (!action->isSeparator() && action->isVisible()) { + texts << action->text(); + } + } + return texts; +} + +class PlacesPanelTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + void testOpenInSplitViewVisibleForPlace(); + void testOpenInSplitViewHiddenForEmptyArea(); + void testOpenInSplitViewEmitsSignal(); + void testConfigureTrashVisibleForTrash(); + void testConfigureTrashHiddenForOtherPlaces(); + void testCustomContextMenuActionsInEmptyArea(); + void testCustomContextMenuActionsHiddenForItems(); + void testCustomContextMenuActionsRoundTrip(); + +private: + // Find the first model index whose URL matches the given scheme. + QModelIndex indexForScheme(KFilePlacesModel *model, const QString &scheme) const; + + QTemporaryDir m_tmpHome; +}; + +void PlacesPanelTest::initTestCase() +{ + QVERIFY(m_tmpHome.isValid()); + qputenv("HOME", m_tmpHome.path().toUtf8()); + QStandardPaths::setTestModeEnabled(true); +} + +void PlacesPanelTest::cleanupTestCase() +{ + const QString bookmarks = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + u"/user-places.xbel"; + QFile::remove(bookmarks); +} + +QModelIndex PlacesPanelTest::indexForScheme(KFilePlacesModel *model, const QString &scheme) const +{ + for (int row = 0; row < model->rowCount(); ++row) { + const QModelIndex idx = model->index(row, 0); + if (model->url(idx).scheme() == scheme) { + return idx; + } + } + return {}; +} + +void PlacesPanelTest::testOpenInSplitViewVisibleForPlace() +{ + PlacesPanel panel(nullptr); + auto *model = qobject_cast(panel.model()); + QVERIFY(model); + + // Home is the first place and always has a valid URL. + const QModelIndex homeIndex = model->index(0, 0); + QVERIFY(homeIndex.isValid()); + QVERIFY(model->url(homeIndex).isValid()); + + QStringList visibleActions; + connect( + &panel, + &KFilePlacesView::contextMenuAboutToShow, + this, + [&visibleActions](const QModelIndex &, QMenu *menu) { + QTimer::singleShot(0, menu, [menu, &visibleActions]() { + visibleActions = visibleMenuActionTexts(menu); + menu->close(); + }); + }, + Qt::SingleShotConnection); + + triggerContextMenuForIndex(panel, homeIndex); + + QVERIFY(visibleActions.contains(i18nc("@action:inmenu", "Open in Split View"))); +} + +void PlacesPanelTest::testOpenInSplitViewHiddenForEmptyArea() +{ + PlacesPanel panel(nullptr); + + QStringList visibleActions; + connect( + &panel, + &KFilePlacesView::contextMenuAboutToShow, + this, + [&visibleActions](const QModelIndex &, QMenu *menu) { + QTimer::singleShot(0, menu, [menu, &visibleActions]() { + visibleActions = visibleMenuActionTexts(menu); + menu->close(); + }); + }, + Qt::SingleShotConnection); + + triggerContextMenuEmptyArea(panel); + + QVERIFY(!visibleActions.contains(i18nc("@action:inmenu", "Open in Split View"))); +} + +void PlacesPanelTest::testOpenInSplitViewEmitsSignal() +{ + PlacesPanel panel(nullptr); + auto *model = qobject_cast(panel.model()); + QVERIFY(model); + + const QModelIndex homeIndex = model->index(0, 0); + QVERIFY(homeIndex.isValid()); + const QUrl homeUrl = model->url(homeIndex); + + QSignalSpy splitSpy(&panel, &PlacesPanel::openInSplitViewRequested); + + connect( + &panel, + &KFilePlacesView::contextMenuAboutToShow, + this, + [](const QModelIndex &, QMenu *menu) { + QTimer::singleShot(0, menu, [menu]() { + for (QAction *action : menu->actions()) { + if (action->isVisible() && action->text() == i18nc("@action:inmenu", "Open in Split View")) { + menu->setActiveAction(action); + QTest::keyClick(menu, Qt::Key_Return); + return; + } + } + menu->close(); + }); + }, + Qt::SingleShotConnection); + + triggerContextMenuForIndex(panel, homeIndex); + + QCOMPARE(splitSpy.count(), 1); + QCOMPARE(splitSpy.first().first().toUrl(), homeUrl); +} + +void PlacesPanelTest::testConfigureTrashVisibleForTrash() +{ + PlacesPanel panel(nullptr); + auto *model = qobject_cast(panel.model()); + QVERIFY(model); + + const QModelIndex trashIndex = indexForScheme(model, QStringLiteral("trash")); + if (!trashIndex.isValid()) { + QSKIP("No trash place in model"); + } + + QStringList visibleActions; + connect( + &panel, + &KFilePlacesView::contextMenuAboutToShow, + this, + [&visibleActions](const QModelIndex &, QMenu *menu) { + QTimer::singleShot(0, menu, [menu, &visibleActions]() { + visibleActions = visibleMenuActionTexts(menu); + menu->close(); + }); + }, + Qt::SingleShotConnection); + + triggerContextMenuForIndex(panel, trashIndex); + + QVERIFY(visibleActions.contains(i18nc("@action:inmenu", "Configure Trash…"))); +} + +void PlacesPanelTest::testConfigureTrashHiddenForOtherPlaces() +{ + PlacesPanel panel(nullptr); + auto *model = qobject_cast(panel.model()); + QVERIFY(model); + + const QModelIndex homeIndex = model->index(0, 0); + QVERIFY(homeIndex.isValid()); + QCOMPARE(model->url(homeIndex).scheme(), QStringLiteral("file")); + + QStringList visibleActions; + connect( + &panel, + &KFilePlacesView::contextMenuAboutToShow, + this, + [&visibleActions](const QModelIndex &, QMenu *menu) { + QTimer::singleShot(0, menu, [menu, &visibleActions]() { + visibleActions = visibleMenuActionTexts(menu); + menu->close(); + }); + }, + Qt::SingleShotConnection); + + triggerContextMenuForIndex(panel, homeIndex); + + QVERIFY(!visibleActions.contains(i18nc("@action:inmenu", "Configure Trash…"))); +} + +void PlacesPanelTest::testCustomContextMenuActionsInEmptyArea() +{ + PlacesPanel panel(nullptr); + + QAction customAction(QStringLiteral("TestCustomAction")); + panel.setCustomContextMenuActions({&customAction}); + + QStringList allActions; + connect( + &panel, + &KFilePlacesView::contextMenuAboutToShow, + this, + [&allActions](const QModelIndex &, QMenu *menu) { + QTimer::singleShot(0, menu, [menu, &allActions]() { + for (QAction *action : menu->actions()) { + if (!action->isSeparator()) { + allActions << action->text(); + } + } + menu->close(); + }); + }, + Qt::SingleShotConnection); + + triggerContextMenuEmptyArea(panel); + + QVERIFY(allActions.contains(QStringLiteral("TestCustomAction"))); +} + +void PlacesPanelTest::testCustomContextMenuActionsHiddenForItems() +{ + PlacesPanel panel(nullptr); + auto *model = qobject_cast(panel.model()); + QVERIFY(model); + + QAction customAction(QStringLiteral("TestCustomActionForItem")); + panel.setCustomContextMenuActions({&customAction}); + + const QModelIndex homeIndex = model->index(0, 0); + QVERIFY(homeIndex.isValid()); + + QStringList allActions; + connect( + &panel, + &KFilePlacesView::contextMenuAboutToShow, + this, + [&allActions](const QModelIndex &, QMenu *menu) { + QTimer::singleShot(0, menu, [menu, &allActions]() { + for (QAction *action : menu->actions()) { + if (!action->isSeparator()) { + allActions << action->text(); + } + } + menu->close(); + }); + }, + Qt::SingleShotConnection); + + triggerContextMenuForIndex(panel, homeIndex); + + QVERIFY(!allActions.contains(QStringLiteral("TestCustomActionForItem"))); +} + +void PlacesPanelTest::testCustomContextMenuActionsRoundTrip() +{ + PlacesPanel panel(nullptr); + auto *model = qobject_cast(panel.model()); + QVERIFY(model); + + QAction customAction(QStringLiteral("TestRoundTripAction")); + panel.setCustomContextMenuActions({&customAction}); + + const QModelIndex homeIndex = model->index(0, 0); + QVERIFY(homeIndex.isValid()); + + // First: open item menu — custom action must not appear. + QStringList itemMenuActions; + connect( + &panel, + &KFilePlacesView::contextMenuAboutToShow, + this, + [&itemMenuActions](const QModelIndex &, QMenu *menu) { + QTimer::singleShot(0, menu, [menu, &itemMenuActions]() { + for (QAction *action : menu->actions()) { + if (!action->isSeparator()) { + itemMenuActions << action->text(); + } + } + menu->close(); + }); + }, + Qt::SingleShotConnection); + triggerContextMenuForIndex(panel, homeIndex); + + QVERIFY(!itemMenuActions.contains(QStringLiteral("TestRoundTripAction"))); + + // Second: open empty-area menu — custom action must be re-added. + QStringList emptyAreaActions; + connect( + &panel, + &KFilePlacesView::contextMenuAboutToShow, + this, + [&emptyAreaActions](const QModelIndex &, QMenu *menu) { + QTimer::singleShot(0, menu, [menu, &emptyAreaActions]() { + for (QAction *action : menu->actions()) { + if (!action->isSeparator()) { + emptyAreaActions << action->text(); + } + } + menu->close(); + }); + }, + Qt::SingleShotConnection); + triggerContextMenuEmptyArea(panel); + + QVERIFY(emptyAreaActions.contains(QStringLiteral("TestRoundTripAction"))); +} + +QTEST_MAIN(PlacesPanelTest) + +#include "placespaneltest.moc" -- cgit v1.3.1