]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/tests/dolphinmainwindowtest.cpp
Change three view buttons into one with menu arrow
[dolphin.git] / src / tests / dolphinmainwindowtest.cpp
index f5ece564d33e32f725c6c369746878fd2e3d4125..b22afa142f6022f692852cf305850624d19c6781 100644 (file)
@@ -9,14 +9,21 @@
 #include "dolphintabpage.h"
 #include "dolphintabwidget.h"
 #include "dolphinviewcontainer.h"
+#include "kitemviews/kfileitemmodel.h"
+#include "kitemviews/kfileitemmodelrolesupdater.h"
 #include "kitemviews/kitemlistcontainer.h"
+#include "kitemviews/kitemlistcontroller.h"
+#include "kitemviews/kitemlistselectionmanager.h"
+#include "kitemviews/kitemlistwidget.h"
 #include "testdir.h"
+#include "views/dolphinitemlistview.h"
 
 #include <KActionCollection>
 #include <KConfig>
 #include <KConfigGui>
 
 #include <QAccessible>
+#include <QDomDocument>
 #include <QFileSystemWatcher>
 #include <QScopedPointer>
 #include <QSignalSpy>
@@ -24,6 +31,7 @@
 #include <QTest>
 
 #include <set>
+#include <unordered_set>
 
 class DolphinMainWindowTest : public QObject
 {
@@ -32,6 +40,7 @@ class DolphinMainWindowTest : public QObject
 private Q_SLOTS:
     void initTestCase();
     void init();
+    void testSyncDesktopAndPhoneUi();
     void testClosingTabsWithSearchBoxVisible();
     void testActiveViewAfterClosingSplitView_data();
     void testActiveViewAfterClosingSplitView();
@@ -42,11 +51,15 @@ private Q_SLOTS:
     void testNewFileMenuEnabled();
     void testWindowTitle_data();
     void testWindowTitle();
+    void testFocusLocationBar();
+    void testFocusPlacesPanel();
     void testPlacesPanelWidthResistance();
     void testGoActions();
     void testOpenFiles();
-    void testAccessibilityAncestorTree();
+    void testAccessibilityTree();
     void testAutoSaveSession();
+    void testInlineRename();
+    void testThumbnailAfterRename();
     void cleanupTestCase();
 
 private:
@@ -63,6 +76,109 @@ void DolphinMainWindowTest::init()
     m_mainWindow.reset(new DolphinMainWindow());
 }
 
+/**
+ * It is too easy to forget that most changes in dolphinui.rc should be mirrored in dolphinuiforphones.rc. This test makes sure that these two files stay
+ * mostly identical. Differences between those files need to be explicitly added as exceptions to this test. So if you land here after changing either
+ * dolphinui.rc or dolphinuiforphones.rc, then resolve this test failure either by making the exact same change to the other ui.rc file, or by adding the
+ * changed object to the `exceptions` variable below.
+ */
+void DolphinMainWindowTest::testSyncDesktopAndPhoneUi()
+{
+    std::unordered_set<QString> exceptions{{QStringLiteral("version"), QStringLiteral("ToolBar")}};
+
+    QDomDocument desktopUi;
+    QFile desktopUiXmlFile(":/kxmlgui5/dolphin/dolphinui.rc");
+    desktopUiXmlFile.open(QIODevice::ReadOnly);
+    desktopUi.setContent(&desktopUiXmlFile);
+    desktopUiXmlFile.close();
+
+    QDomDocument phoneUi;
+    QFile phoneUiXmlFile(":/kxmlgui5/dolphin/dolphinuiforphones.rc");
+    phoneUiXmlFile.open(QIODevice::ReadOnly);
+    phoneUi.setContent(&phoneUiXmlFile);
+    phoneUiXmlFile.close();
+
+    QDomElement desktopUiElement = desktopUi.documentElement();
+    QDomElement phoneUiElement = phoneUi.documentElement();
+
+    auto nextUiElement = [&exceptions](QDomElement uiElement) -> QDomElement {
+        QDomNode nextUiNode{uiElement};
+        do {
+            // If the current node is an exception, we skip its children as well.
+            if (exceptions.count(nextUiNode.nodeName()) == 0) {
+                auto firstChild{nextUiNode.firstChild()};
+                if (!firstChild.isNull()) {
+                    nextUiNode = firstChild;
+                    continue;
+                }
+            }
+            auto nextSibling{nextUiNode.nextSibling()};
+            if (!nextSibling.isNull()) {
+                nextUiNode = nextSibling;
+                continue;
+            }
+            auto parent{nextUiNode.parentNode()};
+            while (true) {
+                if (parent.isNull()) {
+                    return QDomElement();
+                }
+                auto nextParentSibling{parent.nextSibling()};
+                if (!nextParentSibling.isNull()) {
+                    nextUiNode = nextParentSibling;
+                    break;
+                }
+                parent = parent.parentNode();
+            }
+        } while (
+            !nextUiNode.isNull()
+            && (nextUiNode.toElement().isNull() || exceptions.count(nextUiNode.nodeName()))); // We loop until we either give up finding an element or find one.
+        if (nextUiNode.isNull()) {
+            return QDomElement();
+        }
+        return nextUiNode.toElement();
+    };
+
+    int totalComparisonsCount{0};
+    do {
+        QVERIFY2(desktopUiElement.tagName() == phoneUiElement.tagName(),
+                 qPrintable(QStringLiteral("Node mismatch: dolphinui.rc/%1::%2 and dolphinuiforphones.rc/%3::%4")
+                                .arg(desktopUiElement.parentNode().toElement().tagName())
+                                .arg(desktopUiElement.tagName())
+                                .arg(phoneUiElement.parentNode().toElement().tagName())
+                                .arg(phoneUiElement.tagName())));
+        QCOMPARE(desktopUiElement.text(), phoneUiElement.text());
+        const auto desktopUiElementAttributes = desktopUiElement.attributes();
+        const auto phoneUiElementAttributes = phoneUiElement.attributes();
+        for (int i = 0; i < desktopUiElementAttributes.count(); i++) {
+            QVERIFY2(phoneUiElementAttributes.count() >= i,
+                     qPrintable(QStringLiteral("Attribute mismatch: dolphinui.rc/%1::%2 has more attributes than dolphinuiforphones.rc/%3::%4")
+                                    .arg(desktopUiElement.parentNode().toElement().tagName())
+                                    .arg(desktopUiElement.tagName())
+                                    .arg(phoneUiElement.parentNode().toElement().tagName())
+                                    .arg(phoneUiElement.tagName())));
+            if (exceptions.count(desktopUiElementAttributes.item(i).nodeName())) {
+                continue;
+            }
+            QCOMPARE(desktopUiElementAttributes.item(i).nodeName(), phoneUiElementAttributes.item(i).nodeName());
+            QCOMPARE(desktopUiElementAttributes.item(i).nodeValue(), phoneUiElementAttributes.item(i).nodeValue());
+            totalComparisonsCount++;
+        }
+        QVERIFY2(desktopUiElementAttributes.count() == phoneUiElementAttributes.count(),
+                 qPrintable(QStringLiteral("Attribute mismatch: dolphinui.rc/%1::%2 has fewer attributes than dolphinuiforphones.rc/%3::%4. %5 < %6")
+                                .arg(desktopUiElement.parentNode().toElement().tagName())
+                                .arg(desktopUiElement.tagName())
+                                .arg(phoneUiElement.parentNode().toElement().tagName())
+                                .arg(phoneUiElement.tagName())
+                                .arg(phoneUiElementAttributes.count())
+                                .arg(desktopUiElementAttributes.count())));
+
+        desktopUiElement = nextUiElement(desktopUiElement);
+        phoneUiElement = nextUiElement(phoneUiElement);
+        totalComparisonsCount++;
+    } while (!desktopUiElement.isNull() || !phoneUiElement.isNull());
+    QVERIFY2(totalComparisonsCount > 200, qPrintable(QStringLiteral("There were only %1 comparisons. Did the test run correctly?").arg(totalComparisonsCount)));
+}
+
 // See https://bugs.kde.org/show_bug.cgi?id=379135
 void DolphinMainWindowTest::testClosingTabsWithSearchBoxVisible()
 {
@@ -279,6 +395,86 @@ void DolphinMainWindowTest::testWindowTitle()
     QCOMPARE(m_mainWindow->windowTitle(), expectedWindowTitle);
 }
 
+void DolphinMainWindowTest::testFocusLocationBar()
+{
+    const QUrl homePathUrl{QUrl::fromLocalFile(QDir::homePath())};
+    m_mainWindow->openDirectories({homePathUrl}, false);
+    m_mainWindow->show();
+    QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data()));
+    QVERIFY(m_mainWindow->isVisible());
+
+    QAction *replaceLocationAction = m_mainWindow->actionCollection()->action(QStringLiteral("replace_location"));
+    replaceLocationAction->trigger();
+    QVERIFY(m_mainWindow->activeViewContainer()->urlNavigator()->isAncestorOf(QApplication::focusWidget()));
+    replaceLocationAction->trigger();
+    QVERIFY(m_mainWindow->activeViewContainer()->view()->hasFocus());
+
+    QAction *editableLocationAction = m_mainWindow->actionCollection()->action(QStringLiteral("editable_location"));
+    editableLocationAction->trigger();
+    QVERIFY(m_mainWindow->activeViewContainer()->urlNavigator()->isAncestorOf(QApplication::focusWidget()));
+    QVERIFY(m_mainWindow->activeViewContainer()->urlNavigator()->isUrlEditable());
+    editableLocationAction->trigger();
+    QVERIFY(!m_mainWindow->activeViewContainer()->urlNavigator()->isUrlEditable());
+
+    replaceLocationAction->trigger();
+    QVERIFY(m_mainWindow->activeViewContainer()->urlNavigator()->isAncestorOf(QApplication::focusWidget()));
+
+    // Pressing Escape multiple times should eventually move the focus back to the active view.
+    QTest::keyClick(QApplication::focusWidget(), Qt::Key_Escape); // Focus might not go the view yet because it toggles the editable state of the location bar.
+    QTest::keyClick(QApplication::focusWidget(), Qt::Key_Escape);
+    QVERIFY(m_mainWindow->activeViewContainer()->view()->hasFocus());
+}
+
+void DolphinMainWindowTest::testFocusPlacesPanel()
+{
+    m_mainWindow->openDirectories({QUrl::fromLocalFile(QDir::homePath())}, false);
+    m_mainWindow->show();
+    QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data()));
+    QVERIFY(m_mainWindow->isVisible());
+
+    QWidget *placesPanel = reinterpret_cast<QWidget *>(m_mainWindow->m_placesPanel);
+    QVERIFY2(QTest::qWaitFor(
+                 [&]() {
+                     return placesPanel && placesPanel->isVisible() && placesPanel->width() > 0 && placesPanel->height() > 0;
+                 },
+                 5000),
+             "The test couldn't be initialised properly. The places panel should be visible.");
+
+    QAction *focusPlacesPanelAction = m_mainWindow->actionCollection()->action(QStringLiteral("focus_places_panel"));
+    QAction *showPlacesPanelAction = m_mainWindow->actionCollection()->action(QStringLiteral("show_places_panel"));
+
+    focusPlacesPanelAction->trigger();
+    QVERIFY(placesPanel->hasFocus());
+
+    focusPlacesPanelAction->trigger();
+    QVERIFY2(m_mainWindow->activeViewContainer()->isAncestorOf(QApplication::focusWidget()),
+             "Triggering focus_places_panel while the panel already has focus should return the focus to the view.");
+
+    focusPlacesPanelAction->trigger();
+    QVERIFY(placesPanel->hasFocus());
+
+    showPlacesPanelAction->trigger();
+    QVERIFY(!placesPanel->isVisible());
+    QVERIFY2(m_mainWindow->activeViewContainer()->isAncestorOf(QApplication::focusWidget()),
+             "Hiding the Places panel while it has focus should return the focus to the view.");
+
+    showPlacesPanelAction->trigger();
+    QVERIFY(placesPanel->isVisible());
+    QVERIFY2(placesPanel->hasFocus(), "Enabling the Places panel should move keyboard focus there.");
+
+    /// Test that activating a place always moves focus to the view.
+    QTest::keyClick(QApplication::focusWidget(), Qt::Key::Key_Enter);
+    QVERIFY2(m_mainWindow->activeViewContainer()->isAncestorOf(QApplication::focusWidget()),
+             "Activating a place should move focus to the view that loads that place.");
+
+    focusPlacesPanelAction->trigger();
+    QVERIFY(placesPanel->hasFocus());
+
+    QTest::keyClick(QApplication::focusWidget(), Qt::Key::Key_Enter);
+    QVERIFY2(m_mainWindow->activeViewContainer()->isAncestorOf(QApplication::focusWidget()),
+             "Activating a place should move focus to the view even if the view already has that place loaded.");
+}
+
 /**
  * The places panel will resize itself if any of the other widgets requires too much horizontal space
  * but a user never wants the size of the places panel to change unless they resized it themselves explicitly.
@@ -360,7 +556,7 @@ void DolphinMainWindowTest::testGoActions()
     testDir->createDir("b/b-1");
     testDir->createFile("b/b-2");
     testDir->createDir("c");
-    QUrl childDirUrl(QDir::cleanPath(testDir->url().toString() + "/b"));
+    const QUrl childDirUrl(QDir::cleanPath(testDir->url().toString() + "/b"));
     m_mainWindow->openDirectories({childDirUrl}, false); // Open "b" dir
     m_mainWindow->show();
     QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data()));
@@ -381,24 +577,40 @@ void DolphinMainWindowTest::testGoActions()
     const QUrl parentDirUrl = m_mainWindow->activeViewContainer()->url();
     QVERIFY(parentDirUrl != childDirUrl);
 
-    // The item we just emerged from should now have keyboard focus but this doesn't necessarily mean that it is selected.
-    // 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.
+    auto currentItemUrl = [this]() {
+        const int currentIndex = m_mainWindow->m_activeViewContainer->view()->m_container->controller()->selectionManager()->currentItem();
+        const KFileItem currentItem = m_mainWindow->m_activeViewContainer->view()->m_model->fileItem(currentIndex);
+        return currentItem.url();
+    };
+
+    QCOMPARE(currentItemUrl(), childDirUrl); // The item we just emerged from should now have keyboard focus.
+    QCOMPARE(m_mainWindow->m_activeViewContainer->view()->selectedItems().count(), 0); // The item we just emerged from should not be selected. BUG: 424723
+    // Pressing arrow keys should not only move the keyboard focus but also select the item.
+    // We press "Down" to select "c" below and then "Up" so the folder "b" we just emerged from is selected for the first time.
     m_mainWindow->actionCollection()->action(QStringLiteral("compact"))->trigger();
     QTest::keyClick(m_mainWindow->activeViewContainer()->view()->m_container, Qt::Key::Key_Down, Qt::NoModifier);
     QCOMPARE(m_mainWindow->m_activeViewContainer->view()->selectedItems().count(), 1);
+    QVERIFY2(currentItemUrl() != childDirUrl, "The current item didn't change after pressing the 'Down' key.");
     QTest::keyClick(m_mainWindow->activeViewContainer()->view()->m_container, Qt::Key::Key_Up, Qt::NoModifier);
     QCOMPARE(m_mainWindow->m_activeViewContainer->view()->selectedItems().count(), 1);
+    QCOMPARE(currentItemUrl(), childDirUrl); // After pressing 'Down' and then 'Up' we should be back where we were.
+
+    // Enter the child folder "b".
     QTest::keyClick(m_mainWindow->activeViewContainer()->view()->m_container, Qt::Key::Key_Enter, Qt::NoModifier);
     QVERIFY(spyDirectoryLoadingCompleted.wait());
     QCOMPARE(m_mainWindow->activeViewContainer()->url(), childDirUrl);
     QVERIFY(m_mainWindow->isUrlOpen(childDirUrl.toString()));
 
-    // Go back to the parent folder
+    // Go back to the parent folder.
     m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::Back))->trigger();
     QVERIFY(spyDirectoryLoadingCompleted.wait());
     QTest::qWait(100); // Somehow the item we emerged from doesn't have keyboard focus yet if we don't wait a split second.
     QCOMPARE(m_mainWindow->activeViewContainer()->url(), parentDirUrl);
     QVERIFY(m_mainWindow->isUrlOpen(parentDirUrl.toString()));
+    // Going 'Back' means that the view should be in the same state it was in when we left.
+    QCOMPARE(currentItemUrl(), childDirUrl); // The item we last interacted with in this location should still have keyboard focus.
+    QCOMPARE(m_mainWindow->m_activeViewContainer->view()->selectedItems().count(), 1);
+    QCOMPARE(m_mainWindow->m_activeViewContainer->view()->selectedItems().constFirst().url(), childDirUrl); // It should still be selected.
 
     // Open a new tab for the "b" child dir and verify that this doesn't interfere with anything.
     QTest::keyClick(m_mainWindow->activeViewContainer()->view()->m_container, Qt::Key::Key_Enter, Qt::ControlModifier); // Open new inactive tab
@@ -412,6 +624,26 @@ void DolphinMainWindowTest::testGoActions()
     QVERIFY(spyDirectoryLoadingCompleted.wait());
     QCOMPARE(m_mainWindow->activeViewContainer()->url(), childDirUrl);
     QCOMPARE(m_mainWindow->m_activeViewContainer->view()->selectedItems().count(), 0); // There was no action in this view yet that would warrant a selection.
+    QCOMPARE(currentItemUrl(), QUrl(QDir::cleanPath(testDir->url().toString() + "/b/b-1"))); // The first item in the view should have keyboard focus.
+
+    // Press the 'Down' key in the child folder.
+    QTest::keyClick(m_mainWindow->activeViewContainer()->view()->m_container, Qt::Key::Key_Down, Qt::NoModifier);
+    // The second item in the view should have keyboard focus and be selected.
+    const QUrl secondItemInChildFolderUrl{QDir::cleanPath(testDir->url().toString() + "/b/b-2")};
+    QCOMPARE(currentItemUrl(), secondItemInChildFolderUrl);
+    QCOMPARE(m_mainWindow->m_activeViewContainer->view()->selectedItems().count(), 1);
+    QCOMPARE(m_mainWindow->m_activeViewContainer->view()->selectedItems().constFirst().url(), secondItemInChildFolderUrl);
+
+    // Go back to the parent folder and then re-enter the child folder.
+    m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::Back))->trigger();
+    QVERIFY(spyDirectoryLoadingCompleted.wait());
+    m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::Forward))->trigger();
+    QVERIFY(spyDirectoryLoadingCompleted.wait());
+    QCOMPARE(m_mainWindow->activeViewContainer()->url(), childDirUrl);
+    // The state of the view should be identical to how it was before we triggered "Back" and then "Forward".
+    QTRY_COMPARE(currentItemUrl(), secondItemInChildFolderUrl);
+    QCOMPARE(m_mainWindow->m_activeViewContainer->view()->selectedItems().count(), 1);
+    QCOMPARE(m_mainWindow->m_activeViewContainer->view()->selectedItems().constFirst().url(), secondItemInChildFolderUrl);
 
     // Go back to the parent folder.
     m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::Back))->trigger();
@@ -534,7 +766,7 @@ void DolphinMainWindowTest::testOpenFiles()
     QTRY_COMPARE(m_mainWindow->m_activeViewContainer->view()->selectedItems().count(), 1);
 }
 
-void DolphinMainWindowTest::testAccessibilityAncestorTree()
+void DolphinMainWindowTest::testAccessibilityTree()
 {
     m_mainWindow->openDirectories({QUrl::fromLocalFile(QDir::homePath())}, false);
     m_mainWindow->show();
@@ -544,7 +776,7 @@ void DolphinMainWindowTest::testAccessibilityAncestorTree()
     QAccessibleInterface *accessibleInterfaceOfMainWindow = QAccessible::queryAccessibleInterface(m_mainWindow.get());
     Q_CHECK_PTR(accessibleInterfaceOfMainWindow);
 
-    // We will test the accessibility of objects traversing forwards and backwards.
+    /// Test the accessibility of objects while traversing forwards (Tab key) and backwards (Shift+Tab).
     int testedObjectsSizeAfterTraversingForwards = 0;
     for (int i = 0; i < 2; i++) {
         std::tuple<Qt::Key, Qt::KeyboardModifier> focusChainTraversalKeyCombination = {Qt::Key::Key_Tab, Qt::NoModifier};
@@ -552,11 +784,42 @@ void DolphinMainWindowTest::testAccessibilityAncestorTree()
             focusChainTraversalKeyCombination = {Qt::Key::Key_Tab, Qt::ShiftModifier};
         }
 
-        // We will do accessibility checks for every object that gets focus. Focus will be changed using the focusChainTraversalKeyCombination.
+        /// @see firstNamedAncestor below.
+        QAccessibleInterface *firstNamedAncestorOfPreviousIteration = nullptr;
+
+        /// Perform accessibility checks for every object that gets focus. Focus will be changed using the focusChainTraversalKeyCombination.
         std::set<const QObject *> testedObjects; // Makes sure we stop testing when we arrive at an item that was already tested.
         while (qApp->focusObject() && !testedObjects.count(qApp->focusObject())) {
             const auto currentlyFocusedObject = qApp->focusObject();
+            const QAccessibleInterface *accessibleIntefaceOfCurrentlyFocusedObject = QAccessible::queryAccessibleInterface(currentlyFocusedObject);
+            QVERIFY(accessibleIntefaceOfCurrentlyFocusedObject);
+
+            /// Test that each object reachable by Tab or Shift+Tab has at least some accessible information. Objects without any accessible information
+            /// are even less useful to accessibility software users than unlabeled buttons are e.g. to sighted users, because unlabeled buttons at least
+            /// convey some information through their placement and icon.
+            if (currentlyFocusedObject != m_mainWindow->m_activeViewContainer->view()->m_container) { // Skip the custom container widget which has no
+                                                                                                      // accessible name on purpose.
+                /**
+                 * The first ancestor with an accessible name is interesting because it is sometimes used to identify an object if the object itself has no
+                 * name. We keep it in mind to check if two subsequent objects without a name can at least be told apart by their first named ancestor.
+                 */
+                QAccessibleInterface *firstNamedAncestor = accessibleIntefaceOfCurrentlyFocusedObject->parent();
+                while (firstNamedAncestor) {
+                    if (!firstNamedAncestor->text(QAccessible::Name).isEmpty()) {
+                        break;
+                    }
+                    firstNamedAncestor = firstNamedAncestor->parent();
+                }
+                QTRY_VERIFY2(!accessibleIntefaceOfCurrentlyFocusedObject->text(QAccessible::Name).isEmpty()
+                                 || (firstNamedAncestor && firstNamedAncestor != firstNamedAncestorOfPreviousIteration),
+                             qPrintable(QStringLiteral("%1's accessibleInterface does not have an accessible name and can not be distinguished from the object"
+                                                       " that had focus previously. Please fix this. You can find this %1 within its parent %2.")
+                                            .arg(currentlyFocusedObject->metaObject()->className())
+                                            .arg(currentlyFocusedObject->parent()->metaObject()->className())));
+                firstNamedAncestorOfPreviousIteration = firstNamedAncestor;
+            }
 
+            /// Test that each accessible interface has the main window as its parent.
             QAccessibleInterface *accessibleInterface = QAccessible::queryAccessibleInterface(currentlyFocusedObject);
             // The accessibleInterfaces of focused objects might themselves have children.
             // We go down that hierarchy as far as possible and then test the ancestor tree from there.
@@ -565,7 +828,7 @@ void DolphinMainWindowTest::testAccessibilityAncestorTree()
             }
             while (accessibleInterface != accessibleInterfaceOfMainWindow) {
                 QVERIFY2(accessibleInterface,
-                         qPrintable(QString("%1's accessibleInterface or one of its accessible children doesn't have the main window as an ancestor.")
+                         qPrintable(QStringLiteral("%1's accessibleInterface or one of its accessible children doesn't have the main window as an ancestor.")
                                         .arg(currentlyFocusedObject->metaObject()->className())));
                 accessibleInterface = accessibleInterface->parent();
             }
@@ -583,6 +846,8 @@ void DolphinMainWindowTest::testAccessibilityAncestorTree()
                                                                                       // after going forwards which is probably not intended.
         }
     }
+    QCOMPARE_GE(testedObjectsSizeAfterTraversingForwards, 11); // The test did not reach many objects while using the Tab key to move through Dolphin. Did the
+                                                               // test run correctly?
 }
 
 void DolphinMainWindowTest::testAutoSaveSession()
@@ -621,6 +886,105 @@ void DolphinMainWindowTest::testAutoSaveSession()
     m_mainWindow->setSessionAutoSaveEnabled(false);
 }
 
+void DolphinMainWindowTest::testInlineRename()
+{
+    QScopedPointer<TestDir> testDir{new TestDir()};
+    testDir->createFiles({"aaaa", "bbbb", "cccc", "dddd"});
+    m_mainWindow->openDirectories({testDir->url()}, false);
+    m_mainWindow->show();
+    QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data()));
+    QVERIFY(m_mainWindow->isVisible());
+
+    DolphinView *view = m_mainWindow->activeViewContainer()->view();
+    QSignalSpy viewDirectoryLoadingCompletedSpy(view, &DolphinView::directoryLoadingCompleted);
+    QSignalSpy itemsReorderedSpy(view->m_model, &KFileItemModel::itemsMoved);
+    QSignalSpy modelDirectoryLoadingCompletedSpy(view->m_model, &KFileItemModel::directoryLoadingCompleted);
+
+    QVERIFY(viewDirectoryLoadingCompletedSpy.wait());
+    QTest::qWait(500); // we need to wait for the file widgets to become visible
+    view->markUrlsAsSelected({QUrl(testDir->url().toString() + "/aaaa")});
+    view->updateViewState();
+    view->renameSelectedItems();
+    QTest::keyClick(QApplication::focusWidget(), Qt::Key_Left);
+    QTest::keyClick(QApplication::focusWidget(), Qt::Key_E);
+    QTest::keyClick(QApplication::focusWidget(), Qt::Key_Down);
+
+    QVERIFY(itemsReorderedSpy.wait());
+    QVERIFY(view->m_view->m_editingRole);
+    KItemListWidget *widget = view->m_view->m_visibleItems.value(view->m_view->firstVisibleIndex());
+    QVERIFY(!widget->editedRole().isEmpty());
+
+    QTest::keyClick(QApplication::focusWidget(), Qt::Key_Left);
+    QTest::keyClick(QApplication::focusWidget(), Qt::Key_A);
+    QTest::keyClick(QApplication::focusWidget(), Qt::Key_Down);
+    QTest::keyClick(QApplication::focusWidget(), Qt::Key_Down);
+    QTest::keyClick(QApplication::focusWidget(), Qt::Key_Left);
+    QTest::keyClick(QApplication::focusWidget(), Qt::Key_A);
+    QTest::keyClick(QApplication::focusWidget(), Qt::Key_Down);
+
+    QVERIFY(itemsReorderedSpy.wait());
+    QVERIFY(view->m_view->m_editingRole);
+    widget = view->m_view->m_visibleItems.value(view->m_view->lastVisibleIndex());
+    QVERIFY(!widget->editedRole().isEmpty());
+
+    QTest::keyClick(QApplication::focusWidget(), Qt::Key_Escape);
+    QVERIFY(widget->isCurrent());
+    view->m_model->refreshDirectory(testDir->url());
+    QVERIFY(modelDirectoryLoadingCompletedSpy.wait());
+
+    QCOMPARE(view->m_model->fileItem(0).name(), "abbbb");
+    QCOMPARE(view->m_model->fileItem(1).name(), "adddd");
+    QCOMPARE(view->m_model->fileItem(2).name(), "cccc");
+    QCOMPARE(view->m_model->fileItem(3).name(), "eaaaa");
+    QCOMPARE(view->m_model->count(), 4);
+}
+
+void DolphinMainWindowTest::testThumbnailAfterRename()
+{
+    // Create testdir and red square jpg for testing
+    QScopedPointer<TestDir> testDir{new TestDir()};
+    QImage testImage(256, 256, QImage::Format_Mono);
+    testImage.setColorCount(1);
+    testImage.setColor(0, qRgba(255, 0, 0, 255)); // Index #0 = Red
+    for (short x = 0; x < 256; ++x) {
+        for (short y = 0; y < 256; ++y) {
+            testImage.setPixel(x, y, 0);
+        }
+    }
+    testImage.save(testDir.data()->path() + "/a.jpg");
+
+    // Open dir and show it
+    m_mainWindow->openDirectories({testDir->url()}, false);
+    DolphinView *view = m_mainWindow->activeViewContainer()->view();
+    // Prepare signal spies
+    QSignalSpy viewDirectoryLoadingCompletedSpy(view, &DolphinView::directoryLoadingCompleted);
+    QSignalSpy itemsChangedSpy(view->m_model, &KFileItemModel::itemsChanged);
+    QSignalSpy modelDirectoryLoadingCompletedSpy(view->m_model, &KFileItemModel::directoryLoadingCompleted);
+    QSignalSpy previewUpdatedSpy(view->m_view->m_modelRolesUpdater, &KFileItemModelRolesUpdater::previewJobFinished);
+    // Show window and check that our preview has been updated, then wait for it to appear
+    m_mainWindow->show();
+    QVERIFY(viewDirectoryLoadingCompletedSpy.wait());
+    QVERIFY(previewUpdatedSpy.wait());
+    QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data()));
+    QVERIFY(m_mainWindow->isVisible());
+    QTest::qWait(500); // we need to wait for the file widgets to become visible
+
+    // Set image selected and rename it to b.jpg, make sure editing role is working
+    view->markUrlsAsSelected({QUrl(testDir->url().toString() + "/a.jpg")});
+    view->updateViewState();
+    view->renameSelectedItems();
+    QVERIFY(view->m_view->m_editingRole);
+    QTest::keyClick(QApplication::focusWidget(), Qt::Key_B);
+    QTest::keyClick(QApplication::focusWidget(), Qt::Key_Enter);
+    QVERIFY(itemsChangedSpy.wait()); // Make sure that rename worked
+
+    // Check that preview gets updated and filename is correct
+    QVERIFY(previewUpdatedSpy.wait());
+    QVERIFY(!view->m_view->m_editingRole);
+    QCOMPARE(view->m_model->fileItem(0).name(), "b.jpg");
+    QCOMPARE(view->m_model->count(), 1);
+}
+
 void DolphinMainWindowTest::cleanupTestCase()
 {
     m_mainWindow->showNormal();