]> cloud.milkyroute.net Git - dolphin.git/blob - src/tests/dolphintreeviewtest.cpp
DolphinTreeViewTest: Add unit test for bug 259656 (multiple file
[dolphin.git] / src / tests / dolphintreeviewtest.cpp
1 /*****************************************************************************
2 * Copyright (C) 2010-2011 by Frank Reininghaus (frank78ac@googlemail.com) *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 *****************************************************************************/
19
20 #include <qtest_kde.h>
21 #include <kdebug.h>
22 #include <kaction.h>
23
24 #include "views/dolphintreeview.h"
25
26 #include <qtestkeyboard.h>
27 #include <qtestmouse.h>
28 #include <QtGui/QStringListModel>
29
30 class DolphinTreeViewTest : public QObject
31 {
32 Q_OBJECT
33
34 private slots:
35
36 void testKeyboardNavigationSelectionUpdate();
37
38 void bug218114_visualRegionForSelection();
39
40 private:
41
42 /** A method that simplifies checking a view's current item and selection */
43 static void verifyCurrentItemAndSelection(const QAbstractItemView& view, const QModelIndex& expectedCurrent, const QModelIndexList& expectedSelection) {
44 QCOMPARE(view.currentIndex(), expectedCurrent);
45 const QModelIndexList selectedIndexes = view.selectionModel()->selectedIndexes();
46 QCOMPARE(selectedIndexes.count(), expectedSelection.count());
47 foreach(const QModelIndex& index, expectedSelection) {
48 QVERIFY(selectedIndexes.contains(index));
49 }
50 }
51
52 /** Use this method if only one item is selected */
53 static void verifyCurrentItemAndSelection(const QAbstractItemView& view, const QModelIndex& current, const QModelIndex& selected) {
54 QModelIndexList list;
55 list << selected;
56 verifyCurrentItemAndSelection(view, current, list);
57 }
58
59 /** Use this method if the only selected item is the current item */
60 static void verifyCurrentItemAndSelection(const QAbstractItemView& view, const QModelIndex& current) {
61 verifyCurrentItemAndSelection(view, current, current);
62 }
63
64 };
65
66 /**
67 * TestView is a simple view class derived from DolphinTreeView.
68 * It makes sure that the visualRect for each index contains only the item text as
69 * returned by QAbstractItemModel::data(...) for the role Qt::DisplayRole.
70 *
71 * We have to check that DolphinTreeView handles the case of visualRects with different widths
72 * correctly because this is the case in DolphinDetailsView which is derived from DolphinTreeView.
73 */
74
75 class TestView : public DolphinTreeView
76 {
77 Q_OBJECT
78
79 public:
80
81 TestView(QWidget* parent = 0) : DolphinTreeView(parent) {};
82 ~TestView() {};
83
84 QRect visualRect(const QModelIndex& index) const {
85 QRect rect = DolphinTreeView::visualRect(index);
86
87 const QStyleOptionViewItem option = viewOptions();
88 const QFontMetrics fontMetrics(option.font);
89 int width = option.decorationSize.width() + fontMetrics.width(model()->data(index).toString());
90
91 rect.setWidth(width);
92 return rect;
93 }
94
95 };
96
97 /**
98 * This test checks that updating the selection after key presses works as expected.
99 * Qt does not handle this internally if the first letter of an item is pressed, which
100 * is why DolphinTreeView has some custom code for this. The test verifies that this
101 * works without unwanted side effects.
102 *
103 * The test uses the class TreeViewWithDeleteShortcut which deletes the selected items
104 * when Shift-Delete is pressed. This is needed to test the fix for bug 259656 (see below).
105 */
106
107 class TreeViewWithDeleteShortcut : public DolphinTreeView {
108
109 Q_OBJECT
110
111 public:
112
113 TreeViewWithDeleteShortcut(QWidget* parent = 0) : DolphinTreeView(parent) {
114 // To test the fix for bug 259656, we need a delete shortcut.
115 KAction* deleteAction = new KAction(this);
116 deleteAction->setShortcut(Qt::SHIFT | Qt::Key_Delete);
117 addAction(deleteAction);
118 connect(deleteAction, SIGNAL(triggered()), this, SLOT(deleteSelectedItems()));
119 };
120
121 ~TreeViewWithDeleteShortcut() {};
122
123 public slots:
124
125 void deleteSelectedItems() {
126 // We have to delete the items one by one and update the list of selected items after
127 // each step because every removal will invalidate the model indexes in the list.
128 QModelIndexList selectedItems = selectionModel()->selectedIndexes();
129 while (!selectedItems.isEmpty()) {
130 const QModelIndex index = selectedItems.takeFirst();
131 model()->removeRow(index.row());
132 selectedItems = selectionModel()->selectedIndexes();
133 }
134 }
135 };
136
137 void DolphinTreeViewTest::testKeyboardNavigationSelectionUpdate() {
138 QStringList items;
139 items << "a" << "b" << "c" << "d" << "e";
140 QStringListModel model(items);
141
142 QModelIndex index[5];
143 for (int i = 0; i < 5; i++) {
144 index[i] = model.index(i, 0);
145 }
146
147 TreeViewWithDeleteShortcut view;
148 view.setModel(&model);
149 view.setSelectionMode(QAbstractItemView::ExtendedSelection);
150 view.resize(400, 400);
151 view.show();
152 QTest::qWaitForWindowShown(&view);
153
154 view.clearSelection();
155 QVERIFY(view.selectionModel()->selectedIndexes().isEmpty());
156
157 /**
158 * Check that basic keyboard navigation with arrow keys works.
159 */
160
161 view.setCurrentIndex(index[0]);
162 verifyCurrentItemAndSelection(view, index[0]);
163
164 // Go down -> item 1 ("b") should be selected
165 kDebug() << "Down";
166 QTest::keyClick(view.viewport(), Qt::Key_Down);
167 verifyCurrentItemAndSelection(view, index[1]);
168
169 // Go down -> item 2 ("c") should be selected
170 kDebug() << "Down";
171 QTest::keyClick(view.viewport(), Qt::Key_Down);
172 verifyCurrentItemAndSelection(view, index[2]);
173
174 // Ctrl-Up -> item 2 ("c") remains selected
175 kDebug() << "Ctrl-Up";
176 QTest::keyClick(view.viewport(), Qt::Key_Up, Qt::ControlModifier);
177 verifyCurrentItemAndSelection(view, index[1], index[2]);
178
179 // Go up -> item 0 ("a") should be selected
180 kDebug() << "Up";
181 QTest::keyClick(view.viewport(), Qt::Key_Up);
182 verifyCurrentItemAndSelection(view, index[0]);
183
184 // Shift-Down -> items 0 and 1 ("a" and "b") should be selected
185 kDebug() << "Shift-Down";
186 QTest::keyClick(view.viewport(), Qt::Key_Down, Qt::ShiftModifier);
187 QModelIndexList expectedSelection;
188 expectedSelection << index[0] << index[1];
189 verifyCurrentItemAndSelection(view, index[1], expectedSelection);
190
191 /**
192 * When the first letter of a file name is pressed, this file becomes the current item
193 * and gets selected. If the user then Shift-clicks another item, it is expected that
194 * all items between these two items get selected. Before the bug
195 *
196 * https://bugs.kde.org/show_bug.cgi?id=201459
197 *
198 * was fixed, this was not the case: the starting point for the Shift-selection was not
199 * updated if an item was selected by pressing the first letter of the file name.
200 */
201
202 view.clearSelection();
203 QVERIFY(view.selectionModel()->selectedIndexes().isEmpty());
204
205 // Control-click item 0 ("a")
206 kDebug() << "Ctrl-click on \"a\"";
207 QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::ControlModifier, view.visualRect(index[0]).center());
208 verifyCurrentItemAndSelection(view, index[0]);
209
210 // Press "c", such that item 2 ("c") should be the current one.
211 kDebug() << "Press \"c\"";
212 QTest::keyClick(view.viewport(), Qt::Key_C);
213 verifyCurrentItemAndSelection(view, index[2]);
214
215 // Now Shift-Click the last item ("e"). We expect that 3 items ("c", "d", "e") are selected.
216 kDebug() << "Shift-click on \"e\"";
217 QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::ShiftModifier, view.visualRect(index[4]).center());
218 expectedSelection.clear();
219 expectedSelection << index[2] << index[3] << index[4];
220 verifyCurrentItemAndSelection(view, index[4], expectedSelection);
221
222 /**
223 * Starting a drag&drop operation should not clear the selection, see
224 *
225 * https://bugs.kde.org/show_bug.cgi?id=158649
226 */
227
228 view.clearSelection();
229 QVERIFY(view.selectionModel()->selectedIndexes().isEmpty());
230
231 // Click item 0 ("a")
232 kDebug() << "Click on \"a\"";
233 QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, view.visualRect(index[0]).center());
234 verifyCurrentItemAndSelection(view, index[0]);
235
236 // Shift-Down -> "a" and "b" should be selected
237 kDebug() << "Shift-Down";
238 QTest::keyClick(view.viewport(), Qt::Key_Down, Qt::ShiftModifier);
239 expectedSelection.clear();
240 expectedSelection << index[0] << index[1];
241 verifyCurrentItemAndSelection(view, index[1], expectedSelection);
242
243 // Press mouse button on item 0 ("a"), but do not release it. Check that the selection is unchanged
244 kDebug() << "Mouse press on \"a\"";
245 QTest::mousePress(view.viewport(), Qt::LeftButton, Qt::NoModifier, view.visualRect(index[0]).center());
246 verifyCurrentItemAndSelection(view, index[0], expectedSelection);
247
248 // Move mouse to item 1 ("b"), check that selection is unchanged
249 kDebug() << "Move mouse to \"b\"";
250 QMouseEvent moveEvent(QEvent::MouseMove, view.visualRect(index[1]).center(), Qt::NoButton, Qt::LeftButton, Qt::NoModifier);
251 bool moveEventReceived = qApp->notify(view.viewport(), &moveEvent);
252 QVERIFY(moveEventReceived);
253 verifyCurrentItemAndSelection(view, index[0], expectedSelection);
254
255 // Release mouse button on item 1 ("b"), check that selection is unchanged
256 kDebug() << "Mouse release on \"b\"";
257 QTest::mouseRelease(view.viewport(), Qt::LeftButton, Qt::NoModifier, view.visualRect(index[1]).center());
258 verifyCurrentItemAndSelection(view, index[0], expectedSelection);
259
260 /**
261 * Keeping Shift+Delete pressed for some time should delete only one item, see
262 *
263 * https://bugs.kde.org/show_bug.cgi?id=259656
264 */
265
266 view.clearSelection();
267 QVERIFY(view.selectionModel()->selectedIndexes().isEmpty());
268
269 // Click item 0 ("a")
270 kDebug() << "Click on \"a\"";
271 QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, view.visualRect(index[0]).center());
272 verifyCurrentItemAndSelection(view, index[0]);
273
274 // Press Shift-Delete and keep the keys pressed for some time
275 kDebug() << "Press Shift-Delete";
276 QTest::keyPress(view.viewport(), Qt::Key_Delete, Qt::ShiftModifier);
277 QTest::qWait(200);
278 QTest::keyRelease(view.viewport(), Qt::Key_Delete, Qt::ShiftModifier);
279
280 // Verify that only one item has been deleted
281 QCOMPARE(view.model()->rowCount(), 4);
282 }
283
284 /**
285 * QTreeView assumes implicitly that the width of each item's visualRect is the same. This leads to painting
286 * problems in Dolphin if items with different widths are in one QItemSelectionRange, see
287 *
288 * https://bugs.kde.org/show_bug.cgi?id=218114
289 *
290 * To fix this, DolphinTreeView has a custom implementation of visualRegionForSelection(). The following
291 * unit test checks that.
292 */
293
294 void DolphinTreeViewTest::bug218114_visualRegionForSelection()
295 {
296 QStringList items;
297 items << "a" << "an item with a long name" << "a";
298 QStringListModel model(items);
299
300 QModelIndex index0 = model.index(0, 0);
301 QModelIndex index1 = model.index(1, 0);
302 QModelIndex index2 = model.index(2, 0);
303
304 TestView view;
305 view.setModel(&model);
306 view.setSelectionMode(QAbstractItemView::ExtendedSelection);
307 view.resize(400, 400);
308 view.show();
309 QTest::qWaitForWindowShown(&view);
310
311 // First check that the width of index1 is larger than that of index0 and index2 (this triggers the bug).
312
313 QVERIFY(view.visualRect(index0).width() < view.visualRect(index1).width());
314 QVERIFY(view.visualRect(index2).width() < view.visualRect(index1).width());
315
316 // Select all items in one go.
317
318 view.selectAll();
319 const QItemSelection selection = view.selectionModel()->selection();
320 QCOMPARE(selection.count(), 1);
321 QCOMPARE(selection.indexes().count(), 3);
322
323 // Verify that the visualRegionForSelection contains all visualRects.
324 // We do this indirectly using QRegion::boundingRect() because
325 // QRegion::contains(const QRect&) returns true even if the QRect is not
326 // entirely inside the QRegion.
327
328 const QRegion region = view.visualRegionForSelection(selection);
329 const QRect boundingRect = region.boundingRect();
330
331 QVERIFY(boundingRect.contains(view.visualRect(index0)));
332 QVERIFY(boundingRect.contains(view.visualRect(index1)));
333 QVERIFY(boundingRect.contains(view.visualRect(index2)));
334 }
335
336 QTEST_KDEMAIN(DolphinTreeViewTest, GUI)
337
338 #include "dolphintreeviewtest.moc"