File indexing completed on 2024-04-28 05:45:19

0001 /*
0002  * SPDX-FileCopyrightText: 2017 Elvis Angelaccio <elvis.angelaccio@kde.org>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "dolphinmainwindow.h"
0008 #include "dolphinnewfilemenu.h"
0009 #include "dolphintabpage.h"
0010 #include "dolphintabwidget.h"
0011 #include "dolphinviewcontainer.h"
0012 #include "kitemviews/kitemlistcontainer.h"
0013 #include "testdir.h"
0014 
0015 #include <KActionCollection>
0016 #include <KConfig>
0017 #include <KConfigGui>
0018 
0019 #include <QAccessible>
0020 #include <QFileSystemWatcher>
0021 #include <QScopedPointer>
0022 #include <QSignalSpy>
0023 #include <QStandardPaths>
0024 #include <QTest>
0025 
0026 #include <set>
0027 
0028 class DolphinMainWindowTest : public QObject
0029 {
0030     Q_OBJECT
0031 
0032 private Q_SLOTS:
0033     void initTestCase();
0034     void init();
0035     void testClosingTabsWithSearchBoxVisible();
0036     void testActiveViewAfterClosingSplitView_data();
0037     void testActiveViewAfterClosingSplitView();
0038     void testUpdateWindowTitleAfterClosingSplitView();
0039     void testUpdateWindowTitleAfterChangingSplitView();
0040     void testOpenInNewTabTitle();
0041     void testNewFileMenuEnabled_data();
0042     void testNewFileMenuEnabled();
0043     void testWindowTitle_data();
0044     void testWindowTitle();
0045     void testPlacesPanelWidthResistance();
0046     void testGoActions();
0047     void testOpenFiles();
0048     void testAccessibilityAncestorTree();
0049     void testAutoSaveSession();
0050     void cleanupTestCase();
0051 
0052 private:
0053     QScopedPointer<DolphinMainWindow> m_mainWindow;
0054 };
0055 
0056 void DolphinMainWindowTest::initTestCase()
0057 {
0058     QStandardPaths::setTestModeEnabled(true);
0059 }
0060 
0061 void DolphinMainWindowTest::init()
0062 {
0063     m_mainWindow.reset(new DolphinMainWindow());
0064 }
0065 
0066 // See https://bugs.kde.org/show_bug.cgi?id=379135
0067 void DolphinMainWindowTest::testClosingTabsWithSearchBoxVisible()
0068 {
0069     m_mainWindow->openDirectories({QUrl::fromLocalFile(QDir::homePath())}, false);
0070     m_mainWindow->show();
0071     // Without this call the searchbox doesn't get FocusIn events.
0072     QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data()));
0073     QVERIFY(m_mainWindow->isVisible());
0074 
0075     auto tabWidget = m_mainWindow->findChild<DolphinTabWidget *>("tabWidget");
0076     QVERIFY(tabWidget);
0077 
0078     // Show search box on first tab.
0079     tabWidget->currentTabPage()->activeViewContainer()->setSearchModeEnabled(true);
0080 
0081     tabWidget->openNewActivatedTab(QUrl::fromLocalFile(QDir::homePath()));
0082     QCOMPARE(tabWidget->count(), 2);
0083 
0084     // Triggers the crash in bug #379135.
0085     tabWidget->closeTab();
0086     QCOMPARE(tabWidget->count(), 1);
0087 }
0088 
0089 void DolphinMainWindowTest::testActiveViewAfterClosingSplitView_data()
0090 {
0091     QTest::addColumn<bool>("closeLeftView");
0092 
0093     QTest::newRow("close left view") << true;
0094     QTest::newRow("close right view") << false;
0095 }
0096 
0097 void DolphinMainWindowTest::testActiveViewAfterClosingSplitView()
0098 {
0099     m_mainWindow->openDirectories({QUrl::fromLocalFile(QDir::homePath())}, false);
0100     m_mainWindow->show();
0101     QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data()));
0102     QVERIFY(m_mainWindow->isVisible());
0103 
0104     auto tabWidget = m_mainWindow->findChild<DolphinTabWidget *>("tabWidget");
0105     QVERIFY(tabWidget);
0106     QVERIFY(tabWidget->currentTabPage()->primaryViewContainer());
0107     QVERIFY(!tabWidget->currentTabPage()->secondaryViewContainer());
0108 
0109     // Open split view.
0110     m_mainWindow->actionCollection()->action(QStringLiteral("split_view"))->trigger();
0111     QVERIFY(tabWidget->currentTabPage()->splitViewEnabled());
0112     QVERIFY(tabWidget->currentTabPage()->secondaryViewContainer());
0113 
0114     // Make sure the right view is the active one.
0115     auto leftViewContainer = tabWidget->currentTabPage()->primaryViewContainer();
0116     auto rightViewContainer = tabWidget->currentTabPage()->secondaryViewContainer();
0117     QVERIFY(!leftViewContainer->isActive());
0118     QVERIFY(rightViewContainer->isActive());
0119 
0120     QFETCH(bool, closeLeftView);
0121     if (closeLeftView) {
0122         // Activate left view.
0123         leftViewContainer->setActive(true);
0124         QVERIFY(leftViewContainer->isActive());
0125         QVERIFY(!rightViewContainer->isActive());
0126 
0127         // Close left view. The secondary view (which was on the right) will become the primary one and must be active.
0128         m_mainWindow->actionCollection()->action(QStringLiteral("split_view"))->trigger();
0129         QVERIFY(!leftViewContainer->isActive());
0130         QVERIFY(rightViewContainer->isActive());
0131         QCOMPARE(rightViewContainer, tabWidget->currentTabPage()->activeViewContainer());
0132     } else {
0133         // Close right view. The left view will become active.
0134         m_mainWindow->actionCollection()->action(QStringLiteral("split_view"))->trigger();
0135         QVERIFY(leftViewContainer->isActive());
0136         QVERIFY(!rightViewContainer->isActive());
0137         QCOMPARE(leftViewContainer, tabWidget->currentTabPage()->activeViewContainer());
0138     }
0139 }
0140 
0141 // Test case for bug #385111
0142 void DolphinMainWindowTest::testUpdateWindowTitleAfterClosingSplitView()
0143 {
0144     m_mainWindow->openDirectories({QUrl::fromLocalFile(QDir::homePath())}, false);
0145     m_mainWindow->show();
0146     QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data()));
0147     QVERIFY(m_mainWindow->isVisible());
0148 
0149     auto tabWidget = m_mainWindow->findChild<DolphinTabWidget *>("tabWidget");
0150     QVERIFY(tabWidget);
0151     QVERIFY(tabWidget->currentTabPage()->primaryViewContainer());
0152     QVERIFY(!tabWidget->currentTabPage()->secondaryViewContainer());
0153 
0154     // Open split view.
0155     m_mainWindow->actionCollection()->action(QStringLiteral("split_view"))->trigger();
0156     QVERIFY(tabWidget->currentTabPage()->splitViewEnabled());
0157     QVERIFY(tabWidget->currentTabPage()->secondaryViewContainer());
0158 
0159     // Make sure the right view is the active one.
0160     auto leftViewContainer = tabWidget->currentTabPage()->primaryViewContainer();
0161     auto rightViewContainer = tabWidget->currentTabPage()->secondaryViewContainer();
0162     QVERIFY(!leftViewContainer->isActive());
0163     QVERIFY(rightViewContainer->isActive());
0164 
0165     // Activate left view.
0166     leftViewContainer->setActive(true);
0167     QVERIFY(leftViewContainer->isActive());
0168     QVERIFY(!rightViewContainer->isActive());
0169 
0170     // Close split view. The secondary view (which was on the right) will become the primary one and must be active.
0171     m_mainWindow->actionCollection()->action(QStringLiteral("split_view"))->trigger();
0172     QVERIFY(!leftViewContainer->isActive());
0173     QVERIFY(rightViewContainer->isActive());
0174     QCOMPARE(rightViewContainer, tabWidget->currentTabPage()->activeViewContainer());
0175 
0176     // Change URL and make sure we emit the currentUrlChanged signal (which triggers the window title update).
0177     QSignalSpy currentUrlChangedSpy(tabWidget, &DolphinTabWidget::currentUrlChanged);
0178     tabWidget->currentTabPage()->activeViewContainer()->setUrl(QUrl::fromLocalFile(QDir::rootPath()));
0179     QCOMPARE(currentUrlChangedSpy.count(), 1);
0180 }
0181 
0182 // Test case for bug #402641
0183 void DolphinMainWindowTest::testUpdateWindowTitleAfterChangingSplitView()
0184 {
0185     m_mainWindow->openDirectories({QUrl::fromLocalFile(QDir::homePath())}, false);
0186     m_mainWindow->show();
0187     QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data()));
0188     QVERIFY(m_mainWindow->isVisible());
0189 
0190     auto tabWidget = m_mainWindow->findChild<DolphinTabWidget *>("tabWidget");
0191     QVERIFY(tabWidget);
0192 
0193     // Open split view.
0194     m_mainWindow->actionCollection()->action(QStringLiteral("split_view"))->trigger();
0195     QVERIFY(tabWidget->currentTabPage()->splitViewEnabled());
0196 
0197     auto leftViewContainer = tabWidget->currentTabPage()->primaryViewContainer();
0198     auto rightViewContainer = tabWidget->currentTabPage()->secondaryViewContainer();
0199 
0200     // Store old window title.
0201     const auto oldTitle = m_mainWindow->windowTitle();
0202 
0203     // Change URL in the right view and make sure the title gets updated.
0204     rightViewContainer->setUrl(QUrl::fromLocalFile(QDir::rootPath()));
0205     QVERIFY(m_mainWindow->windowTitle() != oldTitle);
0206 
0207     // Activate back the left view and check whether the old title gets restored.
0208     leftViewContainer->setActive(true);
0209     QCOMPARE(m_mainWindow->windowTitle(), oldTitle);
0210 }
0211 
0212 // Test case for bug #397910
0213 void DolphinMainWindowTest::testOpenInNewTabTitle()
0214 {
0215     m_mainWindow->openDirectories({QUrl::fromLocalFile(QDir::homePath())}, false);
0216     m_mainWindow->show();
0217     QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data()));
0218     QVERIFY(m_mainWindow->isVisible());
0219 
0220     auto tabWidget = m_mainWindow->findChild<DolphinTabWidget *>("tabWidget");
0221     QVERIFY(tabWidget);
0222 
0223     tabWidget->openNewTab(QUrl::fromLocalFile(QDir::tempPath()));
0224     QCOMPARE(tabWidget->count(), 2);
0225     QVERIFY(tabWidget->tabText(0) != tabWidget->tabText(1));
0226     if (!tabWidget->tabIcon(0).isNull() && !tabWidget->tabIcon(1).isNull()) {
0227         QCOMPARE(QStringLiteral("inode-directory"), tabWidget->tabIcon(0).name());
0228         QCOMPARE(QStringLiteral("inode-directory"), tabWidget->tabIcon(1).name());
0229     }
0230 }
0231 
0232 void DolphinMainWindowTest::testNewFileMenuEnabled_data()
0233 {
0234     QTest::addColumn<QUrl>("activeViewUrl");
0235     QTest::addColumn<bool>("expectedEnabled");
0236 
0237     QTest::newRow("home") << QUrl::fromLocalFile(QDir::homePath()) << true;
0238     QTest::newRow("root") << QUrl::fromLocalFile(QDir::rootPath()) << false;
0239     QTest::newRow("trash") << QUrl::fromUserInput(QStringLiteral("trash:/")) << false;
0240 }
0241 
0242 void DolphinMainWindowTest::testNewFileMenuEnabled()
0243 {
0244     QFETCH(QUrl, activeViewUrl);
0245     m_mainWindow->openDirectories({activeViewUrl}, false);
0246     m_mainWindow->show();
0247     QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data()));
0248     QVERIFY(m_mainWindow->isVisible());
0249 
0250     auto newFileMenu = m_mainWindow->findChild<DolphinNewFileMenu *>("new_menu");
0251     QVERIFY(newFileMenu);
0252 
0253     QFETCH(bool, expectedEnabled);
0254     QTRY_COMPARE(newFileMenu->isEnabled(), expectedEnabled);
0255 }
0256 
0257 void DolphinMainWindowTest::testWindowTitle_data()
0258 {
0259     QTest::addColumn<QUrl>("activeViewUrl");
0260     QTest::addColumn<QString>("expectedWindowTitle");
0261 
0262     // TODO: this test should enforce the english locale.
0263     QTest::newRow("home") << QUrl::fromLocalFile(QDir::homePath()) << QStringLiteral("Home");
0264     QTest::newRow("home with trailing slash") << QUrl::fromLocalFile(QStringLiteral("%1/").arg(QDir::homePath())) << QStringLiteral("Home");
0265     QTest::newRow("trash") << QUrl::fromUserInput(QStringLiteral("trash:/")) << QStringLiteral("Trash");
0266 }
0267 
0268 void DolphinMainWindowTest::testWindowTitle()
0269 {
0270     QFETCH(QUrl, activeViewUrl);
0271     m_mainWindow->openDirectories({activeViewUrl}, false);
0272     m_mainWindow->show();
0273     QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data()));
0274     QVERIFY(m_mainWindow->isVisible());
0275 
0276     QFETCH(QString, expectedWindowTitle);
0277     QCOMPARE(m_mainWindow->windowTitle(), expectedWindowTitle);
0278 }
0279 
0280 /**
0281  * The places panel will resize itself if any of the other widgets requires too much horizontal space
0282  * but a user never wants the size of the places panel to change unless they resized it themselves explicitly.
0283  */
0284 void DolphinMainWindowTest::testPlacesPanelWidthResistance()
0285 {
0286     m_mainWindow->openDirectories({QUrl::fromLocalFile(QDir::homePath())}, false);
0287     m_mainWindow->show();
0288     m_mainWindow->resize(800, m_mainWindow->height()); // make sure the size is sufficient so a places panel resize shouldn't be necessary.
0289     QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data()));
0290     QVERIFY(m_mainWindow->isVisible());
0291 
0292     QWidget *placesPanel = reinterpret_cast<QWidget *>(m_mainWindow->m_placesPanel);
0293     QVERIFY2(QTest::qWaitFor(
0294                  [&]() {
0295                      return placesPanel && placesPanel->isVisible() && placesPanel->width() > 0;
0296                  },
0297                  5000),
0298              "The test couldn't be initialised properly. The places panel should be visible.");
0299     QTest::qWait(100);
0300     const int initialPlacesPanelWidth = placesPanel->width();
0301 
0302     m_mainWindow->actionCollection()->action(QStringLiteral("split_view"))->trigger(); // enable split view (starts animation)
0303     QTest::qWait(300); // wait for animation
0304     QCOMPARE(placesPanel->width(), initialPlacesPanelWidth);
0305 
0306     m_mainWindow->actionCollection()->action(QStringLiteral("show_filter_bar"))->trigger();
0307     QCOMPARE(placesPanel->width(), initialPlacesPanelWidth);
0308 
0309     // Make all selection mode bars appear and test for each that this doesn't affect the places panel's width.
0310     // One of the bottom bars (SelectionMode::BottomBar::GeneralContents) only shows up when at least one item is selected so we do that before we begin iterating.
0311     m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::SelectAll))->trigger();
0312     for (int selectionModeStates = SelectionMode::BottomBar::CopyContents; selectionModeStates != SelectionMode::BottomBar::RenameContents;
0313          selectionModeStates++) {
0314         const auto contents = static_cast<SelectionMode::BottomBar::Contents>(selectionModeStates);
0315         m_mainWindow->slotSetSelectionMode(true, contents);
0316         QTest::qWait(20); // give time for a paint/resize
0317         QCOMPARE(placesPanel->width(), initialPlacesPanelWidth);
0318     }
0319 
0320     m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::Find))->trigger();
0321     QCOMPARE(placesPanel->width(), initialPlacesPanelWidth);
0322 
0323 #if HAVE_BALOO
0324     m_mainWindow->actionCollection()->action(QStringLiteral("show_information_panel"))->setChecked(true); // toggle visible
0325     QCOMPARE(placesPanel->width(), initialPlacesPanelWidth);
0326 #endif
0327 
0328 #if HAVE_TERMINAL
0329     m_mainWindow->actionCollection()->action(QStringLiteral("show_terminal_panel"))->setChecked(true); // toggle visible
0330     QCOMPARE(placesPanel->width(), initialPlacesPanelWidth);
0331 #endif
0332 
0333     m_mainWindow->actionCollection()->action(QStringLiteral("split_view"))->trigger(); // disable split view (starts animation)
0334     QCOMPARE(placesPanel->width(), initialPlacesPanelWidth);
0335 
0336 #if HAVE_BALOO
0337     m_mainWindow->actionCollection()->action(QStringLiteral("show_information_panel"))->trigger(); // toggle invisible
0338     QCOMPARE(placesPanel->width(), initialPlacesPanelWidth);
0339 #endif
0340 
0341 #if HAVE_TERMINAL
0342     m_mainWindow->actionCollection()->action(QStringLiteral("show_terminal_panel"))->trigger(); // toggle invisible
0343     QCOMPARE(placesPanel->width(), initialPlacesPanelWidth);
0344 #endif
0345 
0346     m_mainWindow->showMaximized();
0347     QCOMPARE(placesPanel->width(), initialPlacesPanelWidth);
0348 
0349     QTest::qWait(300); // wait for split view closing animation
0350     QCOMPARE(placesPanel->width(), initialPlacesPanelWidth);
0351 }
0352 
0353 void DolphinMainWindowTest::testGoActions()
0354 {
0355     QScopedPointer<TestDir> testDir{new TestDir()};
0356     testDir->createDir("a");
0357     testDir->createDir("b");
0358     testDir->createDir("b/b-1");
0359     testDir->createFile("b/b-2");
0360     testDir->createDir("c");
0361     QUrl childDirUrl(QDir::cleanPath(testDir->url().toString() + "/b"));
0362     m_mainWindow->openDirectories({childDirUrl}, false); // Open "b" dir
0363     m_mainWindow->show();
0364     QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data()));
0365     QVERIFY(m_mainWindow->isVisible());
0366     QVERIFY(!m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::Forward))->isEnabled());
0367 
0368     m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::Up))->trigger();
0369     /**
0370      * Now, after going "up" in the file hierarchy (to "testDir"), the folder one has emerged from ("b") should have keyboard focus.
0371      * This is especially important when a user wants to peek into multiple folders in quick succession.
0372      */
0373     QSignalSpy spyDirectoryLoadingCompleted(m_mainWindow->m_activeViewContainer->view(), &DolphinView::directoryLoadingCompleted);
0374     QVERIFY(spyDirectoryLoadingCompleted.wait());
0375     QVERIFY(QTest::qWaitFor([&]() {
0376         return !m_mainWindow->actionCollection()->action(QStringLiteral("stop"))->isEnabled();
0377     })); // "Stop" command should be disabled because it finished loading
0378     QTest::qWait(500); // Somehow the item we emerged from doesn't have keyboard focus yet if we don't wait a split second.
0379     const QUrl parentDirUrl = m_mainWindow->activeViewContainer()->url();
0380     QVERIFY(parentDirUrl != childDirUrl);
0381 
0382     // The item we just emerged from should now have keyboard focus but this doesn't necessarily mean that it is selected.
0383     // To test if it has keyboard focus, we press "Down" to select "c" below and then "Up" so the folder "b" we just emerged from is actually selected.
0384     m_mainWindow->actionCollection()->action(QStringLiteral("compact"))->trigger();
0385     QTest::keyClick(m_mainWindow->activeViewContainer()->view()->m_container, Qt::Key::Key_Down, Qt::NoModifier);
0386     QCOMPARE(m_mainWindow->m_activeViewContainer->view()->selectedItems().count(), 1);
0387     QTest::keyClick(m_mainWindow->activeViewContainer()->view()->m_container, Qt::Key::Key_Up, Qt::NoModifier);
0388     QCOMPARE(m_mainWindow->m_activeViewContainer->view()->selectedItems().count(), 1);
0389     QTest::keyClick(m_mainWindow->activeViewContainer()->view()->m_container, Qt::Key::Key_Enter, Qt::NoModifier);
0390     QVERIFY(spyDirectoryLoadingCompleted.wait());
0391     QCOMPARE(m_mainWindow->activeViewContainer()->url(), childDirUrl);
0392     QVERIFY(m_mainWindow->isUrlOpen(childDirUrl.toString()));
0393 
0394     // Go back to the parent folder
0395     m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::Back))->trigger();
0396     QVERIFY(spyDirectoryLoadingCompleted.wait());
0397     QTest::qWait(100); // Somehow the item we emerged from doesn't have keyboard focus yet if we don't wait a split second.
0398     QCOMPARE(m_mainWindow->activeViewContainer()->url(), parentDirUrl);
0399     QVERIFY(m_mainWindow->isUrlOpen(parentDirUrl.toString()));
0400 
0401     // Open a new tab for the "b" child dir and verify that this doesn't interfere with anything.
0402     QTest::keyClick(m_mainWindow->activeViewContainer()->view()->m_container, Qt::Key::Key_Enter, Qt::ControlModifier); // Open new inactive tab
0403     QVERIFY(m_mainWindow->m_tabWidget->count() == 2);
0404     QCOMPARE(m_mainWindow->activeViewContainer()->url(), parentDirUrl);
0405     QVERIFY(m_mainWindow->isUrlOpen(parentDirUrl.toString()));
0406     QVERIFY(!m_mainWindow->actionCollection()->action(QStringLiteral("undo_close_tab"))->isEnabled());
0407 
0408     // Go forward to the child folder.
0409     m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::Forward))->trigger();
0410     QVERIFY(spyDirectoryLoadingCompleted.wait());
0411     QCOMPARE(m_mainWindow->activeViewContainer()->url(), childDirUrl);
0412     QCOMPARE(m_mainWindow->m_activeViewContainer->view()->selectedItems().count(), 0); // There was no action in this view yet that would warrant a selection.
0413 
0414     // Go back to the parent folder.
0415     m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::Back))->trigger();
0416     QVERIFY(spyDirectoryLoadingCompleted.wait());
0417     QTest::qWait(100); // Somehow the item we emerged from doesn't have keyboard focus yet if we don't wait a split second.
0418     QCOMPARE(m_mainWindow->activeViewContainer()->url(), parentDirUrl);
0419     QVERIFY(m_mainWindow->isUrlOpen(parentDirUrl.toString()));
0420 
0421     // Close current tab and see if the "go" actions are correctly disabled in the remaining tab that was never active until now and shows the "b" dir
0422     m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::Close))->trigger(); // Close current tab
0423     QVERIFY(m_mainWindow->m_tabWidget->count() == 1);
0424     QCOMPARE(m_mainWindow->activeViewContainer()->url(), childDirUrl);
0425     QCOMPARE(m_mainWindow->m_activeViewContainer->view()->selectedItems().count(), 0); // There was no action in this tab yet that would warrant a selection.
0426     QVERIFY(!m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::Back))->isEnabled());
0427     QVERIFY(!m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::Forward))->isEnabled());
0428     QVERIFY(m_mainWindow->actionCollection()->action(QStringLiteral("undo_close_tab"))->isEnabled());
0429 }
0430 
0431 void DolphinMainWindowTest::testOpenFiles()
0432 {
0433     QScopedPointer<TestDir> testDir{new TestDir()};
0434     QString testDirUrl(QDir::cleanPath(testDir->url().toString()));
0435     testDir->createDir("a");
0436     testDir->createDir("a/b");
0437     testDir->createDir("a/b/c");
0438     testDir->createDir("a/b/c/d");
0439     m_mainWindow->openDirectories({testDirUrl}, false);
0440     m_mainWindow->show();
0441 
0442     // We only see the unselected "a" folder in the test dir. There are no other tabs.
0443     QVERIFY(m_mainWindow->isUrlOpen(testDirUrl));
0444     QVERIFY(m_mainWindow->isItemVisibleInAnyView(testDirUrl + "/a"));
0445     QVERIFY(!m_mainWindow->isUrlOpen(testDirUrl + "/a"));
0446     QVERIFY(!m_mainWindow->isItemVisibleInAnyView(testDirUrl + "/a/b"));
0447     QCOMPARE(m_mainWindow->m_tabWidget->count(), 1);
0448     QCOMPARE(m_mainWindow->m_tabWidget->currentIndex(), 0);
0449     QCOMPARE(m_mainWindow->m_activeViewContainer->view()->selectedItems().count(), 0);
0450 
0451     // "a" is already in view, so "opening" "a" should simply select it without opening a new tab.
0452     m_mainWindow->openFiles({testDirUrl + "/a"}, false);
0453     QTRY_COMPARE(m_mainWindow->m_activeViewContainer->view()->selectedItems().count(), 1);
0454     QCOMPARE(m_mainWindow->m_tabWidget->count(), 1);
0455     QVERIFY(m_mainWindow->isItemVisibleInAnyView(testDirUrl + "/a"));
0456 
0457     // "b" is not in view, so "opening" "b" should open a new active tab of the parent folder "a" and select "b" there.
0458     m_mainWindow->openFiles({testDirUrl + "/a/b"}, false);
0459     QTRY_VERIFY(m_mainWindow->isUrlOpen(testDirUrl + "/a"));
0460     QCOMPARE(m_mainWindow->m_tabWidget->count(), 2);
0461     QCOMPARE(m_mainWindow->m_tabWidget->currentIndex(), 1);
0462     QTRY_VERIFY(m_mainWindow->isItemVisibleInAnyView(testDirUrl + "/a/b"));
0463     QVERIFY2(!m_mainWindow->isUrlOpen(testDirUrl + "/a/b"), "The directory b is supposed to be visible but not open in its own tab.");
0464     QTRY_COMPARE(m_mainWindow->m_activeViewContainer->view()->selectedItems().count(), 1);
0465 
0466     QVERIFY(m_mainWindow->isUrlOpen(testDirUrl));
0467     QVERIFY(m_mainWindow->isItemVisibleInAnyView(testDirUrl + "/a"));
0468     // "a" is still in view in the first tab, so "opening" "a" should switch to the first tab and select "a" there.
0469     m_mainWindow->openFiles({testDirUrl + "/a"}, false);
0470     QCOMPARE(m_mainWindow->m_tabWidget->count(), 2);
0471     QCOMPARE(m_mainWindow->m_tabWidget->currentIndex(), 0);
0472     QVERIFY(m_mainWindow->isUrlOpen(testDirUrl));
0473     QVERIFY(m_mainWindow->isUrlOpen(testDirUrl + "/a"));
0474 
0475     // Directory "a" is already open in the second tab in which "b" is selected, so opening the directory "a" should switch to that tab.
0476     m_mainWindow->openDirectories({testDirUrl + "/a"}, false);
0477     QCOMPARE(m_mainWindow->m_tabWidget->count(), 2);
0478     QCOMPARE(m_mainWindow->m_tabWidget->currentIndex(), 1);
0479 
0480     // In the details view mode directories can be expanded, which changes if openFiles() needs to open a new tab or not to open a file.
0481     m_mainWindow->actionCollection()->action(QStringLiteral("details"))->trigger();
0482     QTRY_VERIFY(m_mainWindow->activeViewContainer()->view()->itemsExpandable());
0483 
0484     // Expand the already selected "b" with the right arrow key. This should make "c" visible.
0485     QVERIFY2(!m_mainWindow->isItemVisibleInAnyView(testDirUrl + "/a/b/c"), "The parent folder wasn't expanded yet, so c shouldn't be visible.");
0486     QTest::keyClick(m_mainWindow->activeViewContainer()->view()->m_container, Qt::Key::Key_Right);
0487     QTRY_VERIFY(m_mainWindow->isItemVisibleInAnyView(testDirUrl + "/a/b/c"));
0488     QVERIFY2(!m_mainWindow->isUrlOpen(testDirUrl + "/a/b"), "b is supposed to be expanded, however it shouldn't be open in its own tab.");
0489     QVERIFY(m_mainWindow->isUrlOpen(testDirUrl + "/a"));
0490 
0491     // Switch to first tab by opening it even though it is already open.
0492     m_mainWindow->openDirectories({testDirUrl}, false);
0493     QCOMPARE(m_mainWindow->m_tabWidget->count(), 2);
0494     QCOMPARE(m_mainWindow->m_tabWidget->currentIndex(), 0);
0495 
0496     // "c" is in view in the second tab because "b" is expanded there, so "opening" "c" should switch to that tab and select "c" there.
0497     m_mainWindow->openFiles({testDirUrl + "/a/b/c"}, false);
0498     QCOMPARE(m_mainWindow->m_tabWidget->count(), 2);
0499     QCOMPARE(m_mainWindow->m_tabWidget->currentIndex(), 1);
0500     QTRY_COMPARE(m_mainWindow->m_activeViewContainer->view()->selectedItems().count(), 1);
0501     QVERIFY(m_mainWindow->isUrlOpen(testDirUrl));
0502     QVERIFY(m_mainWindow->isUrlOpen(testDirUrl + "/a"));
0503 
0504     // Opening the directory "c" on the other hand will open it in a new tab even though it is already visible in the view
0505     // because openDirecories() and openFiles() serve different purposes. One opens views at urls, the other selects files within views.
0506     m_mainWindow->openDirectories({testDirUrl + "/a/b/c/d", testDirUrl + "/a/b/c"}, true);
0507     QCOMPARE(m_mainWindow->m_tabWidget->count(), 3);
0508     QCOMPARE(m_mainWindow->m_tabWidget->currentIndex(), 2);
0509     QVERIFY(m_mainWindow->m_tabWidget->currentTabPage()->splitViewEnabled());
0510     QVERIFY(m_mainWindow->isItemVisibleInAnyView(testDirUrl + "/a/b/c")); // It should still be visible in the second tab.
0511     QTRY_COMPARE(m_mainWindow->m_activeViewContainer->view()->selectedItems().count(), 0);
0512     QVERIFY(m_mainWindow->isUrlOpen(testDirUrl + "/a/b/c/d"));
0513     QVERIFY(m_mainWindow->isUrlOpen(testDirUrl + "/a/b/c"));
0514 
0515     // "c" is in view in the second tab because "b" is expanded there,
0516     // so "opening" "c" should switch to that tab even though "c" as a directory is open in the current tab.
0517     m_mainWindow->openFiles({testDirUrl + "/a/b/c"}, false);
0518     QCOMPARE(m_mainWindow->m_tabWidget->count(), 3);
0519     QCOMPARE(m_mainWindow->m_tabWidget->currentIndex(), 1);
0520     QVERIFY2(m_mainWindow->isItemVisibleInAnyView(testDirUrl + "/a/b/c/d"), "It should be visible in the secondary view of the third tab.");
0521 
0522     // Select "b" and un-expand it with the left arrow key. This should make "c" invisible.
0523     m_mainWindow->openFiles({testDirUrl + "/a/b"}, false);
0524     QTest::keyClick(m_mainWindow->activeViewContainer()->view()->m_container, Qt::Key::Key_Left);
0525     QTRY_VERIFY(!m_mainWindow->isItemVisibleInAnyView(testDirUrl + "/a/b/c"));
0526 
0527     // "d" is in view in the third tab in the secondary view, so "opening" "d" should select that view.
0528     m_mainWindow->openFiles({testDirUrl + "/a/b/c/d"}, false);
0529     QCOMPARE(m_mainWindow->m_tabWidget->count(), 3);
0530     QCOMPARE(m_mainWindow->m_tabWidget->currentIndex(), 2);
0531     QVERIFY(m_mainWindow->m_tabWidget->currentTabPage()->secondaryViewContainer()->isActive());
0532     QTRY_COMPARE(m_mainWindow->m_activeViewContainer->view()->selectedItems().count(), 1);
0533 }
0534 
0535 void DolphinMainWindowTest::testAccessibilityAncestorTree()
0536 {
0537     m_mainWindow->openDirectories({QUrl::fromLocalFile(QDir::homePath())}, false);
0538     m_mainWindow->show();
0539     QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data()));
0540     QVERIFY(m_mainWindow->isVisible());
0541 
0542     QAccessibleInterface *accessibleInterfaceOfMainWindow = QAccessible::queryAccessibleInterface(m_mainWindow.get());
0543     Q_CHECK_PTR(accessibleInterfaceOfMainWindow);
0544 
0545     // We will test the accessibility of objects traversing forwards and backwards.
0546     int testedObjectsSizeAfterTraversingForwards = 0;
0547     for (int i = 0; i < 2; i++) {
0548         std::tuple<Qt::Key, Qt::KeyboardModifier> focusChainTraversalKeyCombination = {Qt::Key::Key_Tab, Qt::NoModifier};
0549         if (i) {
0550             focusChainTraversalKeyCombination = {Qt::Key::Key_Tab, Qt::ShiftModifier};
0551         }
0552 
0553         // We will do accessibility checks for every object that gets focus. Focus will be changed using the focusChainTraversalKeyCombination.
0554         std::set<const QObject *> testedObjects; // Makes sure we stop testing when we arrive at an item that was already tested.
0555         while (qApp->focusObject() && !testedObjects.count(qApp->focusObject())) {
0556             const auto currentlyFocusedObject = qApp->focusObject();
0557 
0558             QAccessibleInterface *accessibleInterface = QAccessible::queryAccessibleInterface(currentlyFocusedObject);
0559             // The accessibleInterfaces of focused objects might themselves have children.
0560             // We go down that hierarchy as far as possible and then test the ancestor tree from there.
0561             while (accessibleInterface->childCount() > 0) {
0562                 accessibleInterface = accessibleInterface->child(0);
0563             }
0564             while (accessibleInterface != accessibleInterfaceOfMainWindow) {
0565                 QVERIFY2(accessibleInterface,
0566                          qPrintable(QString("%1's accessibleInterface or one of its accessible children doesn't have the main window as an ancestor.")
0567                                         .arg(currentlyFocusedObject->metaObject()->className())));
0568                 accessibleInterface = accessibleInterface->parent();
0569             }
0570 
0571             testedObjects.insert(currentlyFocusedObject); // Add it to testedObjects so we won't test it again later.
0572             QTest::keyClick(m_mainWindow.get(), std::get<0>(focusChainTraversalKeyCombination), std::get<1>(focusChainTraversalKeyCombination));
0573             QVERIFY2(currentlyFocusedObject != qApp->focusObject(),
0574                      "The focus chain is broken. The focused object should have changed after pressing the focusChainTraversalKeyCombination.");
0575         }
0576 
0577         if (i == 0) {
0578             testedObjectsSizeAfterTraversingForwards = testedObjects.size();
0579         } else {
0580             QCOMPARE(testedObjects.size(), testedObjectsSizeAfterTraversingForwards); // The size after traversing backwards is different than
0581                                                                                       // after going forwards which is probably not intended.
0582         }
0583     }
0584 }
0585 
0586 void DolphinMainWindowTest::testAutoSaveSession()
0587 {
0588     m_mainWindow->openDirectories({QUrl::fromLocalFile(QDir::homePath())}, false);
0589     m_mainWindow->show();
0590     QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data()));
0591     QVERIFY(m_mainWindow->isVisible());
0592 
0593     // Create config file
0594     KConfigGui::setSessionConfig(QStringLiteral("dolphin"), QStringLiteral("dolphin"));
0595     KConfig *config = KConfigGui::sessionConfig();
0596     m_mainWindow->saveGlobalProperties(config);
0597     m_mainWindow->savePropertiesInternal(config, 1);
0598     config->sync();
0599 
0600     // Setup watcher for config file changes
0601     const QString configFileName = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/" + KConfigGui::sessionConfig()->name();
0602     QFileSystemWatcher *configWatcher = new QFileSystemWatcher({configFileName}, this);
0603     QSignalSpy spySessionSaved(configWatcher, &QFileSystemWatcher::fileChanged);
0604 
0605     // Enable session autosave.
0606     m_mainWindow->setSessionAutoSaveEnabled(true);
0607     m_mainWindow->m_sessionSaveTimer->setInterval(200); // Lower the interval to speed up the testing
0608 
0609     // Open a new tab
0610     auto tabWidget = m_mainWindow->findChild<DolphinTabWidget *>("tabWidget");
0611     QVERIFY(tabWidget);
0612     tabWidget->openNewActivatedTab(QUrl::fromLocalFile(QDir::tempPath()));
0613     QCOMPARE(tabWidget->count(), 2);
0614 
0615     // Wait till a session save occurs
0616     QVERIFY(spySessionSaved.wait(60000));
0617 
0618     // Disable session autosave.
0619     m_mainWindow->setSessionAutoSaveEnabled(false);
0620 }
0621 
0622 void DolphinMainWindowTest::cleanupTestCase()
0623 {
0624     m_mainWindow->showNormal();
0625     m_mainWindow->actionCollection()->action(QStringLiteral("split_view"))->setChecked(false); // disable split view (starts animation)
0626 
0627 #if HAVE_BALOO
0628     m_mainWindow->actionCollection()->action(QStringLiteral("show_information_panel"))->setChecked(false); // hide panel
0629 #endif
0630 
0631 #if HAVE_TERMINAL
0632     m_mainWindow->actionCollection()->action(QStringLiteral("show_terminal_panel"))->setChecked(false); // hide panel
0633 #endif
0634 
0635     // Quit Dolphin to save the hiding of panels and make sure that normal Quit doesn't crash.
0636     m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::Quit))->trigger();
0637 }
0638 
0639 QTEST_MAIN(DolphinMainWindowTest)
0640 
0641 #include "dolphinmainwindowtest.moc"