]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/tests/dolphindetailsviewtest.cpp
Improve stability of unit tests when running them with Valgrind
[dolphin.git] / src / tests / dolphindetailsviewtest.cpp
index ab3006d9d7b7250dd93c907b533a58ba44a51071..31b4a57cdde64e630b0decad2e1de0b1b6f5cf3e 100644 (file)
 
 #include <qtest_kde.h>
 
-#include "testhelper.h"
+#include "testbase.h"
+#include "testdir.h"
 
 #include "views/dolphindetailsview.h"
 #include "views/dolphinview.h"
 #include "views/dolphinmodel.h"
-#include "views/dolphindirlister.h"
 #include "views/dolphinsortfilterproxymodel.h"
 #include "views/zoomlevelinfo.h"
 
-#include <KTempDir>
+#include <qtestmouse.h>
+#include <qtestkeyboard.h>
 
-#include <QtCore/QDir>
-
-#include "kdebug.h"
-
-class DolphinDetailsViewTest : public QObject
+class DolphinDetailsViewTest : public TestBase
 {
     Q_OBJECT
 
 private slots:
 
-    void initTestCase();
-    void cleanupTestCase();
+    void testExpandedUrls();
 
+    void bug217447_shiftArrowSelection();
     void bug234600_overlappingIconsWhenZooming();
+    void bug257401_longFilenamesKeyboardNavigation();
 
 private:
 
-    TestHelper* m_helper;
-    DolphinView* m_view;
+    /**
+     * initView(DolphinView*) sets the correct view mode, shows the view on the screen, and waits
+     * until loading the folder in the view is finished.
+     *
+     * Many unit tests need access to the internal DolphinDetailsView in DolphinView.
+     * Therefore, a pointer to the details view is returned by initView(DolphinView*).
+     */
+    DolphinDetailsView* initView(DolphinView* view) const {
+        QSignalSpy spyFinishedPathLoading(view, SIGNAL(finishedPathLoading(const KUrl&)));
+        view->setMode(DolphinView::DetailsView);
+        DolphinDetailsView* detailsView = qobject_cast<DolphinDetailsView*>(itemView(view));
+        Q_ASSERT(detailsView);
+        detailsView->setFoldersExpandable(true);
+        view->resize(400, 400);
+        view->show();
+        QTest::qWaitForWindowShown(view);
+
+        // If the DolphinView's finishedPathLoading(const KUrl&) signal has not been received yet,
+        // we have to wait a bit more.
+        // The reason why the if-statement is needed here is that the signal might have been emitted
+        // while we were waiting in QTest::qWaitForWindowShown(view)
+        // -> waitForFinishedPathLoading(view) would fail in that case.
+        if (spyFinishedPathLoading.isEmpty()) {
+            waitForFinishedPathLoading(view);
+        }
+
+        return detailsView;
+    }
+
+    QModelIndex proxyModelIndexForUrl(const DolphinView* view, const KUrl& url) const {
+        const QModelIndex index = view->m_viewAccessor.m_dolphinModel->indexForUrl(url);
+        return view->m_viewAccessor.m_proxyModel->mapFromSource(index);
+    }
 };
 
-void DolphinDetailsViewTest::initTestCase()
+/**
+ * This test verifies that DolphinDetailsView::expandedUrls() returns the right set of URLs.
+ * The test creates a folder hierarchy: 3 folders (a, b, c) contain 3 subfolders (also named a, b, c) each.
+ * Each of those contains 3 further subfolders of the same name.
+ */
+
+void DolphinDetailsViewTest::testExpandedUrls()
 {
-    // add time stamps to find origin of test failures due to timeout at
-    // http://my.cdash.org/index.php?project=kdebase&date=
-    qputenv("KDE_DEBUG_TIMESTAMP", QByteArray("1"));
+    QStringList files;
+    QStringList subFolderNames;
+    subFolderNames << "a" << "b" << "c";
+
+    foreach(const QString& level1, subFolderNames) {
+        foreach(const QString& level2, subFolderNames) {
+            foreach(const QString& level3, subFolderNames) {
+                files << level1 + "/" + level2 + "/" + level3 + "/testfile";
+            }
+        }
+    }
+
+    TestDir dir;
+    dir.createFiles(files);
+    DolphinView view(dir.url(), 0);
+    DolphinDetailsView* detailsView = initView(&view);
+
+    // We start with an empty set of expanded URLs.
+    QSet<KUrl> expectedExpandedUrls;
+    QCOMPARE(detailsView->expandedUrls(), expectedExpandedUrls);
 
-    m_helper = new TestHelper;
-    m_view = m_helper->view();
+    // Expand URLs one by one and verify the result of DolphinDetailsView::expandedUrls()
+    QStringList itemsToExpand;
+    itemsToExpand << "b" << "b/a" << "b/a/c" << "b/c" << "c";
+
+    foreach(const QString& item, itemsToExpand) {
+        KUrl url(dir.name() + item);
+        detailsView->expand(proxyModelIndexForUrl(&view, url));
+        expectedExpandedUrls += url;
+        QCOMPARE(detailsView->expandedUrls(), expectedExpandedUrls);
+
+        // Before we proceed, we have to make sure that the view has finished
+        // loading the contents of the expanded folder.
+        waitForFinishedPathLoading(&view);
+    }
+
+    // Collapse URLs one by one and verify the result of DolphinDetailsView::expandedUrls()
+    QStringList itemsToCollapse;
+    itemsToCollapse << "b/c" << "b/a/c" << "c" << "b/a" << "b";
+
+    foreach(const QString& item, itemsToCollapse) {
+        KUrl url(dir.name() + item);
+        detailsView->collapse(proxyModelIndexForUrl(&view, url));
+        expectedExpandedUrls -= url;
+        QCOMPARE(detailsView->expandedUrls(), expectedExpandedUrls);
+    }
 }
 
-void DolphinDetailsViewTest::cleanupTestCase()
+/**
+ * When the first item in the view is active and Shift is held while the "arrow down"
+ * key is pressed repeatedly, the selection should grow by one item for each key press.
+ * A change in Qt 4.6 revealed a bug in DolphinDetailsView which broke this, see
+ *
+ * https://bugs.kde.org/show_bug.cgi?id=217447
+ *
+ * The problem was that DolphinDetailsView, which uses not the full width of the "Name"
+ * column for an item, but only the width of the actual file name, did not reimplement
+ * QTreeView::visualRect(). This caused item selection to fail because QAbstractItemView
+ * uses the center of the visualRect of an item internally. If the width of the file name
+ * is less than half the width of the "Name" column, the center of an item's visualRect
+ * was therefore outside the space that DolphinDetailsView actually assigned to the
+ * item, and this led to unexpected deselection of items.
+ *
+ * TODO: To make the test more reliable, one could adjust the width of the "Name"
+ * column before the test in order to really make sure that the column is more than twice
+ * as wide as the space actually occupied by the file names (this triggers the bug).
+ */
+
+void DolphinDetailsViewTest::bug217447_shiftArrowSelection()
 {
-    delete m_helper;
+    TestDir dir;
+    for (int i = 0; i < 100; i++) {
+        dir.createFile(QString("%1").arg(i));
+    }
+    DolphinView view(dir.url(), 0);
+    DolphinDetailsView* detailsView = initView(&view);
+
+    // Select the first item
+    QModelIndex index0 = detailsView->model()->index(0, 0);
+    detailsView->setCurrentIndex(index0);
+    QCOMPARE(detailsView->currentIndex(), index0);
+
+    // Before we test Shift-selection, we verify that the root cause is fixed a bit more
+    // directly: we check that passing the corners or the center of an item's visualRect
+    // to itemAt() returns the item (and not an invalid model index).
+    QRect rect = detailsView->visualRect(index0);
+    QCOMPARE(detailsView->indexAt(rect.center()), index0);
+    QCOMPARE(detailsView->indexAt(rect.topLeft()), index0);
+    QCOMPARE(detailsView->indexAt(rect.topRight()), index0);
+    QCOMPARE(detailsView->indexAt(rect.bottomLeft()), index0);
+    QCOMPARE(detailsView->indexAt(rect.bottomRight()), index0);
+
+    // Another way to test this is to Ctrl-click the center of the visualRect.
+    // The selection state of the item should be toggled.
+    detailsView->clearSelection();
+    QItemSelectionModel* selectionModel = detailsView->selectionModel();
+    QCOMPARE(selectionModel->selectedIndexes().count(), 0);
+
+    QTest::mouseClick(detailsView->viewport(), Qt::LeftButton, Qt::ControlModifier, rect.center());
+    QModelIndexList selectedIndexes = selectionModel->selectedIndexes();
+    QCOMPARE(selectedIndexes.count(), 1);
+    QVERIFY(selectedIndexes.contains(index0));
+
+    // Now we go down item by item using Shift+Down. In each step, we check that the current item
+    // is added to the selection and that the size of the selection grows by one.
+
+    int current = 1;
+
+    while (current < 100) {
+        QTest::keyClick(detailsView->viewport(), Qt::Key_Down, Qt::ShiftModifier);
+        QModelIndex currentIndex = detailsView->model()->index(current, 0);
+        QCOMPARE(detailsView->currentIndex(), currentIndex);
+
+        selectedIndexes = selectionModel->selectedIndexes();
+        QCOMPARE(selectedIndexes.count(), current + 1);
+        QVERIFY(selectedIndexes.contains(currentIndex));
+
+        current++;
+    }
 }
 
 /**
@@ -79,24 +222,11 @@ void DolphinDetailsViewTest::bug234600_overlappingIconsWhenZooming()
 {
     QStringList files;
     files << "a" << "b" << "c" << "d";
-    m_helper->createFiles(files);
-
-    m_view->setMode(DolphinView::DetailsView);
-    DolphinDetailsView* detailsView = qobject_cast<DolphinDetailsView*>(m_helper->itemView());
-    QVERIFY(detailsView);
-    m_view->resize(400, 400);
-    m_view->show();
-    QTest::qWaitForWindowShown(m_view);
-
-    // We have to make sure that the view has loaded the directory before we start the test.
-    // TODO: This will be needed frequently. Maybe move to TestHelper.
-    kDebug() << "Reloading view and waiting for the finishedPathLoading(const KUrl&) signal...";
-    QSignalSpy finished(m_view, SIGNAL(finishedPathLoading(const KUrl&)));
-    m_view->reload();
-    while (finished.count() != 1) {
-        QTest::qWait(50);
-    }
-    kDebug() << "...signal received, continuing";
+
+    TestDir dir;
+    dir.createFiles(files);
+    DolphinView view(dir.url(), 0);
+    DolphinDetailsView* detailsView = initView(&view);
 
     QModelIndex index0 = detailsView->model()->index(0, 0);
     detailsView->setCurrentIndex(index0);
@@ -104,32 +234,71 @@ void DolphinDetailsViewTest::bug234600_overlappingIconsWhenZooming()
 
     // Setting the zoom level to the minimum value and triggering DolphinDetailsView::currentChanged(...)
     // should make sure that the bug is triggered.
+    int zoomLevelBackup = view.zoomLevel();
     int zoomLevel = ZoomLevelInfo::minimumLevel();
-    m_view->setZoomLevel(zoomLevel);
+    view.setZoomLevel(zoomLevel);
 
     QModelIndex index1 = detailsView->model()->index(1, 0);
     detailsView->setCurrentIndex(index1);
     QCOMPARE(detailsView->currentIndex(), index1);
 
-    kDebug() << "Now checking zoom levels...";
-
     // Increase the zoom level successively to the maximum.
     while(zoomLevel < ZoomLevelInfo::maximumLevel()) {
         zoomLevel++;
-        kDebug() << "Testing zoom level" << zoomLevel;
-        m_view->setZoomLevel(zoomLevel);
+        view.setZoomLevel(zoomLevel);
+        QCOMPARE(view.zoomLevel(), zoomLevel);
 
         //Check for each zoom level that the height of each item is at least the icon size.
         QVERIFY(detailsView->visualRect(index1).height() >= ZoomLevelInfo::iconSizeForZoomLevel(zoomLevel));
     }
 
-    m_view->hide();
+    view.setZoomLevel(zoomLevelBackup);
+}
+
+/**
+ * The width of the visualRect of an item is usually replaced by the width of the file name.
+ * However, if the file name is wider then the view's name column, this leads to problems with
+ * keyboard navigation if files with very long names are present in the current folder, see
+ *
+ * https://bugs.kde.org/show_bug.cgi?id=257401
+ *
+ * This test checks that the visualRect of an item is never wider than the "Name" column.
+ */
+
+void DolphinDetailsViewTest::bug257401_longFilenamesKeyboardNavigation() {
+    TestDir dir;
+    QString name;
+    for (int i = 0; i < 20; i++) {
+        name += "mmmmmmmmmm";
+        dir.createFile(name);
+    }
+    DolphinView view(dir.url(), 0);
+    DolphinDetailsView* detailsView = initView(&view);
 
-    kDebug() << "Cleaning up test directory...";
-    m_helper->cleanupTestDir();
-    kDebug() << "Done.";
+    // Select the first item
+    QModelIndex index0 = detailsView->model()->index(0, 0);
+    detailsView->setCurrentIndex(index0);
+    QCOMPARE(detailsView->currentIndex(), index0);
+    QVERIFY(detailsView->visualRect(index0).width() < detailsView->columnWidth(DolphinModel::Name));
+
+    QItemSelectionModel* selectionModel = detailsView->selectionModel();
+    QModelIndexList selectedIndexes = selectionModel->selectedIndexes();
+    QCOMPARE(selectedIndexes.count(), 1);
+    QVERIFY(selectedIndexes.contains(index0));
+
+    // Move down successively using the "Down" key and check that current item
+    // and selection are as expected.
+    for (int i = 0; i < 19; i++) {
+        QTest::keyClick(detailsView->viewport(), Qt::Key_Down, Qt::NoModifier);
+        QModelIndex currentIndex = detailsView->model()->index(i + 1, 0);
+        QCOMPARE(detailsView->currentIndex(), currentIndex);
+        QVERIFY(detailsView->visualRect(currentIndex).width() <= detailsView->columnWidth(DolphinModel::Name));
+        selectedIndexes = selectionModel->selectedIndexes();
+        QCOMPARE(selectedIndexes.count(), 1);
+        QVERIFY(selectedIndexes.contains(currentIndex));
+    }
 }
 
 QTEST_KDEMAIN(DolphinDetailsViewTest, GUI)
 
-#include "dolphindetailsviewtest.moc"
\ No newline at end of file
+#include "dolphindetailsviewtest.moc"