┌   ┐
54
└   ┘

summaryrefslogtreecommitdiff
path: root/src/tests
diff options
context:
space:
mode:
Diffstat (limited to 'src/tests')
-rw-r--r--src/tests/CMakeLists.txt5
-rw-r--r--src/tests/placespaneltest.cpp363
2 files changed, 368 insertions, 0 deletions
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 <KFilePlacesModel>
+#include <KLocalizedString>
+
+#include <QApplication>
+#include <QContextMenuEvent>
+#include <QItemSelectionModel>
+#include <QMenu>
+#include <QSignalSpy>
+#include <QStandardPaths>
+#include <QTemporaryDir>
+#include <QTest>
+#include <QTimer>
+
+// 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<KFilePlacesModel *>(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<KFilePlacesModel *>(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<KFilePlacesModel *>(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<KFilePlacesModel *>(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<KFilePlacesModel *>(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<KFilePlacesModel *>(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"