1 /*
2     SPDX-FileCopyrightText: 2015 Stephen Kelly <steveire@gmail.com>
3     SPDX-FileCopyrightText: 2015 David Faure <faure@kde.org>
4     SPDX-FileCopyrightText: 2016 Ableton AG <info@ableton.com>
5     SPDX-FileContributor: Stephen Kelly <stephen.kelly@ableton.com>
6 
7     SPDX-License-Identifier: LGPL-2.0-or-later
8 */
9 
10 #include "dynamictreemodel.h"
11 #include "modeltest.h"
12 #include "test_model_helpers.h"
13 
14 #include <kselectionproxymodel.h>
15 
16 #include <QIdentityProxyModel>
17 #include <QSignalSpy>
18 #include <QStringListModel>
19 #include <QTest>
20 
21 using namespace TestModelHelpers;
22 
23 class KSelectionProxyModelTest : public QObject
24 {
25     Q_OBJECT
26 public:
KSelectionProxyModelTest(QObject * parent=nullptr)27     KSelectionProxyModelTest(QObject *parent = nullptr)
28         : QObject(parent)
29         , days({QStringLiteral("Monday"), QStringLiteral("Tuesday"), QStringLiteral("Wednesday"), QStringLiteral("Thursday")})
30     {
31     }
32 
33 private Q_SLOTS:
34     void columnCountShouldBeStable();
35     void selectOnSourceReset();
36     void selectionMapping();
37     void removeRows_data();
38     void removeRows();
39 
40     void selectionModelModelChange();
41     void deselection_data();
42     void deselection();
43 
44 private:
45     const QStringList days;
46 };
47 
columnCountShouldBeStable()48 void KSelectionProxyModelTest::columnCountShouldBeStable()
49 {
50     // Given a KSelectionProxy on top of a stringlist model
51     QStringListModel strings(days);
52     QItemSelectionModel selectionModel(&strings);
53     KSelectionProxyModel proxy(&selectionModel);
54     proxy.setSourceModel(&strings);
55 
56     QSignalSpy rowATBISpy(&proxy, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)));
57     QSignalSpy rowInsertedSpy(&proxy, SIGNAL(rowsInserted(QModelIndex, int, int)));
58 
59     // No selection => the proxy should have 0 rows, 1 column
60     // (if it had 0 columns, it would have to emit column insertions, too much trouble)
61     QCOMPARE(proxy.rowCount(), 0);
62     QCOMPARE(proxy.columnCount(), 1);
63     QCOMPARE(rowATBISpy.count(), 0);
64     QCOMPARE(rowInsertedSpy.count(), 0);
65 
66     // Select second entry -> the proxy should have 1 rows, 1 column
67     selectionModel.select(QItemSelection(strings.index(1, 0), strings.index(1, 0)), QItemSelectionModel::Select);
68     QCOMPARE(proxy.rowCount(), 1);
69     QCOMPARE(proxy.columnCount(), 1);
70     QCOMPARE(rowSpyToText(rowATBISpy), QStringLiteral("0,0"));
71     QCOMPARE(rowSpyToText(rowInsertedSpy), QStringLiteral("0,0"));
72 }
73 
selectOnSourceReset()74 void KSelectionProxyModelTest::selectOnSourceReset()
75 {
76     QStringListModel strings(days);
77     QItemSelectionModel selectionModel(&strings);
78 
79     connect(&strings, &QAbstractItemModel::modelReset, [&] {
80         selectionModel.select(QItemSelection(strings.index(0, 0), strings.index(2, 0)), QItemSelectionModel::Select);
81     });
82 
83     KSelectionProxyModel proxy(&selectionModel);
84     proxy.setSourceModel(&strings);
85 
86     selectionModel.select(QItemSelection(strings.index(0, 0), strings.index(2, 0)), QItemSelectionModel::Select);
87 
88     QCOMPARE(proxy.rowCount(), 3);
89     for (int i = 0; i < 3; ++i) {
90         QCOMPARE(proxy.index(i, 0).data().toString(), days.at(i));
91     }
92 
93     QStringList numbers = {QStringLiteral("One"), QStringLiteral("Two"), QStringLiteral("Three"), QStringLiteral("Four")};
94     strings.setStringList(numbers);
95 
96     QCOMPARE(proxy.rowCount(), 3);
97     for (int i = 0; i < 3; ++i) {
98         QCOMPARE(proxy.index(i, 0).data().toString(), numbers.at(i));
99     }
100 
101     QVERIFY(selectionModel.selection().contains(strings.index(0, 0)));
102     QVERIFY(selectionModel.selection().contains(strings.index(1, 0)));
103     QVERIFY(selectionModel.selection().contains(strings.index(2, 0)));
104 }
105 
selectionModelModelChange()106 void KSelectionProxyModelTest::selectionModelModelChange()
107 {
108     QStringListModel strings(days);
109     QItemSelectionModel selectionModel(&strings);
110 
111     QIdentityProxyModel identity;
112     identity.setSourceModel(&strings);
113 
114     KSelectionProxyModel proxy(&selectionModel);
115     proxy.setSourceModel(&identity);
116     selectionModel.select(strings.index(0, 0), QItemSelectionModel::Select);
117 
118     QCOMPARE(proxy.rowCount(), 1);
119     QCOMPARE(proxy.index(0, 0).data().toString(), QStringLiteral("Monday"));
120 
121     QStringListModel strings2({QStringLiteral("Today"), QStringLiteral("Tomorrow")});
122 
123     QSignalSpy resetSpy(&proxy, &QAbstractItemModel::modelReset);
124 
125     selectionModel.setModel(&strings2);
126 
127     QCOMPARE(resetSpy.size(), 1);
128     QCOMPARE(proxy.rowCount(), 0);
129 
130     proxy.setSourceModel(&strings2);
131     selectionModel.select(strings2.index(0, 0), QItemSelectionModel::Select);
132 
133     QCOMPARE(proxy.rowCount(), 1);
134     QCOMPARE(proxy.index(0, 0).data().toString(), QStringLiteral("Today"));
135 
136     QSignalSpy spy(&proxy, SIGNAL(modelReset()));
137 
138     QStringList numbers = {QStringLiteral("One"), QStringLiteral("Two"), QStringLiteral("Three"), QStringLiteral("Four")};
139     strings.setStringList(numbers);
140 
141     QCOMPARE(spy.count(), 0);
142 
143     strings2.setStringList(numbers);
144 
145     QCOMPARE(spy.count(), 1);
146 
147     QCOMPARE(proxy.rowCount(), 0);
148     QVERIFY(!selectionModel.hasSelection());
149 
150     selectionModel.select(strings2.index(0, 0), QItemSelectionModel::Select);
151 
152     QCOMPARE(proxy.rowCount(), 1);
153     QCOMPARE(proxy.index(0, 0).data().toString(), numbers.at(0));
154 }
155 
deselection_data()156 void KSelectionProxyModelTest::deselection_data()
157 {
158     QTest::addColumn<int>("kspm_mode");
159     QTest::addColumn<QStringList>("selection");
160     QTest::addColumn<int>("expectedRowCountBefore");
161     QTest::addColumn<int>("spyCount");
162     QTest::addColumn<QStringList>("toDeselect");
163     QTest::addColumn<int>("expectedRowCountAfter");
164 
165     auto testNumber = 1;
166 
167     QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
168         << static_cast<int>(KSelectionProxyModel::SubTreesWithoutRoots) << QStringList{"2"} << 2 << 1 << QStringList{"2"} << 0;
169     ++testNumber;
170 
171     QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
172         << static_cast<int>(KSelectionProxyModel::SubTreesWithoutRoots) << QStringList{"3", "9"} << 4 << 1 << QStringList{"3"} << 2;
173     ++testNumber;
174 
175     QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
176         << static_cast<int>(KSelectionProxyModel::SubTreesWithoutRoots) << QStringList{"3", "9"} << 4 << 1 << QStringList{"3", "9"} << 0;
177     ++testNumber;
178 
179     QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
180         << static_cast<int>(KSelectionProxyModel::SubTreesWithoutRoots) << QStringList{"3", "9"} << 4 << 1 << QStringList{"9"} << 2;
181     ++testNumber;
182 
183     QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
184         << static_cast<int>(KSelectionProxyModel::SubTreesWithoutRoots) << QStringList{"3", "9", "11", "15"} << 6 << 1 << QStringList{"9"} << 7;
185     ++testNumber;
186 
187     QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
188         << static_cast<int>(KSelectionProxyModel::SubTreesWithoutRoots) << QStringList{"3", "9", "11", "15"} << 6 << 1 << QStringList{"9", "15"} << 5;
189     ++testNumber;
190 
191     QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
192         << static_cast<int>(KSelectionProxyModel::SubTreesWithoutRoots) << QStringList{"3", "9", "11", "15"} << 6 << 1 << QStringList{"3", "9", "15"} << 3;
193     ++testNumber;
194 
195     QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
196         << static_cast<int>(KSelectionProxyModel::SubTreesWithoutRoots) << QStringList{"3", "9", "11", "15"} << 6 << 1 << QStringList{"3", "9", "11", "15"}
197         << 0;
198     ++testNumber;
199 
200     QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
201         << static_cast<int>(KSelectionProxyModel::SubTreesWithoutRoots) << QStringList{"3", "9", "11", "15"} << 6 << 0 << QStringList{"11"} << 6;
202     ++testNumber;
203 
204     QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
205         << static_cast<int>(KSelectionProxyModel::SubTreesWithoutRoots) << QStringList{"3", "9", "11", "15"} << 6 << 2 << QStringList{"3", "15"} << 2;
206     ++testNumber;
207 
208     QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
209         << static_cast<int>(KSelectionProxyModel::ExactSelection) << QStringList{"3", "9", "11", "15"} << 4 << 1 << QStringList{"11"} << 3;
210     ++testNumber;
211 
212     QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
213         << static_cast<int>(KSelectionProxyModel::ExactSelection) << QStringList{"3", "9", "11", "15"} << 4 << 2 << QStringList{"3", "11"} << 2;
214     ++testNumber;
215 
216     QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
217         << static_cast<int>(KSelectionProxyModel::ExactSelection) << QStringList{"3", "9", "11", "15"} << 4 << 1 << QStringList{"3", "9", "11"} << 1;
218     ++testNumber;
219 
220     QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
221         << static_cast<int>(KSelectionProxyModel::ChildrenOfExactSelection) << QStringList{"3"} << 2 << 1 << QStringList{"3"} << 0;
222     ++testNumber;
223 
224     QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
225         << static_cast<int>(KSelectionProxyModel::ChildrenOfExactSelection) << QStringList{"3", "9", "11", "15"} << 9 << 1 << QStringList{"3", "9", "11"} << 2;
226     ++testNumber;
227 
228     QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
229         << static_cast<int>(KSelectionProxyModel::ChildrenOfExactSelection) << QStringList{"3", "9", "11", "15"} << 9 << 1 << QStringList{"9"} << 7;
230     ++testNumber;
231 
232     QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
233         << static_cast<int>(KSelectionProxyModel::ChildrenOfExactSelection) << QStringList{"3", "9", "11", "15"} << 9 << 2 << QStringList{"3", "11"} << 4;
234     ++testNumber;
235 
236     QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
237         << static_cast<int>(KSelectionProxyModel::ChildrenOfExactSelection) << QStringList{"4", "6", "9", "15"} << 7 << 1 << QStringList{"4"} << 5;
238     ++testNumber;
239 
240     QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
241         << static_cast<int>(KSelectionProxyModel::ChildrenOfExactSelection) << QStringList{"6", "7"} << 1 << 0 << QStringList{"7"} << 1;
242     ++testNumber;
243 }
244 
deselection()245 void KSelectionProxyModelTest::deselection()
246 {
247     QFETCH(int, kspm_mode);
248     QFETCH(QStringList, selection);
249     QFETCH(int, expectedRowCountBefore);
250     QFETCH(int, spyCount);
251     QFETCH(QStringList, toDeselect);
252     QFETCH(int, expectedRowCountAfter);
253 
254     DynamicTreeModel tree;
255     new ModelTest(&tree, &tree);
256     ModelResetCommand resetCommand(&tree);
257     resetCommand.setInitialTree(
258         " - 1"
259         " - - 2"
260         " - - - 3"
261         " - - - - 4"
262         " - - - - - 5"
263         " - - - - - 6"
264         " - - - - - - 7"
265         " - - - - 8"
266         " - - - 9"
267         " - - - - 10"
268         " - - - - 11"
269         " - - - - - 12"
270         " - - - - - 13"
271         " - - - - - 14"
272         " - - 15"
273         " - - - 16"
274         " - - - 17");
275     resetCommand.doCommand();
276 
277     QItemSelectionModel selectionModel(&tree);
278 
279     KSelectionProxyModel proxy(&selectionModel);
280     new ModelTest(&proxy, &proxy);
281     proxy.setFilterBehavior(static_cast<KSelectionProxyModel::FilterBehavior>(kspm_mode));
282     proxy.setSourceModel(&tree);
283 
284     QSignalSpy beforeSpy(&proxy, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)));
285     QSignalSpy afterSpy(&proxy, SIGNAL(rowsRemoved(QModelIndex, int, int)));
286 
287     QItemSelection sel;
288     for (auto item : selection) {
289         QModelIndexList idxs = tree.match(tree.index(0, 0), Qt::DisplayRole, item, 1, Qt::MatchRecursive);
290         QCOMPARE(idxs.size(), 1);
291         sel << QItemSelectionRange(idxs.at(0), idxs.at(0));
292     }
293     selectionModel.select(sel, QItemSelectionModel::Select);
294 
295     QCOMPARE(proxy.rowCount(), expectedRowCountBefore);
296 
297     QItemSelection desel;
298     for (auto item : toDeselect) {
299         QModelIndexList idxs = tree.match(tree.index(0, 0), Qt::DisplayRole, item, 1, Qt::MatchRecursive);
300         QCOMPARE(idxs.size(), 1);
301         desel << QItemSelectionRange(idxs.at(0), idxs.at(0));
302     }
303     selectionModel.select(desel, QItemSelectionModel::Deselect);
304 
305     QCOMPARE(beforeSpy.count(), spyCount);
306     QCOMPARE(afterSpy.count(), spyCount);
307 
308     QCOMPARE(proxy.rowCount(), expectedRowCountAfter);
309 }
310 
removeRows_data()311 void KSelectionProxyModelTest::removeRows_data()
312 {
313     QTest::addColumn<int>("kspm_mode");
314     QTest::addColumn<bool>("connectSelectionModelFirst");
315     QTest::addColumn<bool>("emulateSingleSelectionMode");
316     QTest::addColumn<QStringList>("selection");
317     QTest::addColumn<int>("expectedRowCountBefore");
318     QTest::addColumn<int>("spyCount");
319     QTest::addColumn<QStringList>("toRemove");
320     QTest::addColumn<int>("expectedRowCountAfter");
321 
322     // Because the KSelectionProxyModel has two dependencies - a QItemSelectionModel
323     // and a QAbstractItemModel, whichever one signals first determines the internal
324     // code path is used to perform removal.  That order is determined by order
325     // of signal slot connections, and these tests use connectSelectionModelFirst
326     // to test both.
327 
328     // When using a QAbstractItemView, the SelectionMode can determine how the
329     // selection changes when a selected row is removed.  When the row is
330     // AboutToBeRemoved, the view might change the selection to a row which is
331     // not to be removed.  This means that depending on signal-slot connection
332     // order, the KSelectionProxyModel::sourceRowsAboutToBeRemoved method
333     // might be executed, but then the selection can be changed before
334     // executing the KSelectionProxyModel::sourceRowsRemoved method.  These tests
335     // are run with and without similar emulated behavior.
336 
337     auto testNumber = 1;
338 
339     for (auto emulateSingleSelectionMode : {true, false}) {
340         for (auto kspm_mode : {KSelectionProxyModel::SubTreesWithoutRoots, KSelectionProxyModel::ChildrenOfExactSelection}) {
341             for (auto connectSelectionModelFirst : {true, false}) {
342                 QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
343                     << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"2"} << 2 << 1
344                     << QStringList{"2", "2"} << (emulateSingleSelectionMode ? 2 : 0);
345                 ++testNumber;
346 
347                 QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
348                     << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"2"} << 2
349                     << (kspm_mode == KSelectionProxyModel::ChildrenOfExactSelection ? 0 : 1) << QStringList{"4", "4"} << 2;
350                 ++testNumber;
351 
352                 QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
353                     << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"2"} << 2
354                     << (kspm_mode == KSelectionProxyModel::ChildrenOfExactSelection ? 0 : 1) << QStringList{"5", "5"} << 2;
355                 ++testNumber;
356 
357                 QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
358                     << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"2"} << 2
359                     << (kspm_mode == KSelectionProxyModel::ChildrenOfExactSelection ? 0 : 1) << QStringList{"6", "6"} << 2;
360                 ++testNumber;
361 
362                 QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
363                     << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"2"} << 2
364                     << (kspm_mode == KSelectionProxyModel::ChildrenOfExactSelection ? 0 : 1) << QStringList{"7", "7"} << 2;
365                 ++testNumber;
366 
367                 QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
368                     << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"2"} << 2 << 1
369                     << QStringList{"1", "1"} << 0;
370                 ++testNumber;
371 
372                 QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
373                     << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"2"} << 2 << 1
374                     << QStringList{"9", "9"} << 1;
375                 ++testNumber;
376 
377                 QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
378                     << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"2"} << 2 << 0
379                     << QStringList{"15", "15"} << 2;
380                 ++testNumber;
381 
382                 QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
383                     << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"5"} << 0 << 0
384                     << QStringList{"5", "5"} << (emulateSingleSelectionMode ? 1 : 0);
385                 ++testNumber;
386 
387                 QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
388                     << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"5"} << 0 << 0
389                     << QStringList{"4", "4"} << 0;
390                 ++testNumber;
391 
392                 QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
393                     << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"5"} << 0 << 0
394                     << QStringList{"3", "3"} << 0;
395                 ++testNumber;
396 
397                 QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
398                     << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"5"} << 0 << 0
399                     << QStringList{"2", "2"} << 0;
400                 ++testNumber;
401 
402                 QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
403                     << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"6"} << 1 << 1
404                     << QStringList{"4", "4"} << 0;
405                 ++testNumber;
406             }
407         }
408     }
409 
410     for (auto connectSelectionModelFirst : {true, false}) {
411         for (auto kspm_mode : {KSelectionProxyModel::SubTreesWithoutRoots, KSelectionProxyModel::ChildrenOfExactSelection}) {
412             QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
413                 << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"3", "15"} << 4 << 1 << QStringList{"3", "3"} << 2;
414             ++testNumber;
415 
416             QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
417                 << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "15"} << 4 << 1 << QStringList{"2", "2"} << 2;
418             ++testNumber;
419 
420             QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
421                 << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "11"} << 5 << 1 << QStringList{"2", "2"} << 0;
422             ++testNumber;
423 
424             QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
425                 << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "11"} << 5 << 1 << QStringList{"3", "3"} << 3;
426             ++testNumber;
427 
428             QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
429                 << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "11"} << 5 << 1 << QStringList{"11", "11"} << 2;
430             ++testNumber;
431 
432             QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
433                 << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "11"} << 5 << 1 << QStringList{"3", "9"} << 0;
434             ++testNumber;
435 
436             QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
437                 << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "11", "15"} << 7 << 1 << QStringList{"3", "9"} << 2;
438             ++testNumber;
439 
440             QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
441                 << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "8", "10", "11", "16"} << 5 << 1
442                 << QStringList{"3", "9"} << 0;
443             ++testNumber;
444 
445             QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
446                 << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "8", "10", "11", "16"} << 5 << 1
447                 << QStringList{"3", "3"} << 3;
448             ++testNumber;
449 
450             QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
451                 << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "8", "10", "11", "16"} << 5 << 1
452                 << QStringList{"9", "9"} << 2;
453             ++testNumber;
454 
455             QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
456                 << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "11"} << 5 << 1 << QStringList{"9", "9"} << 2;
457             ++testNumber;
458 
459             QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
460                 << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "6", "11"}
461                 << (kspm_mode == KSelectionProxyModel::ChildrenOfExactSelection ? 6 : 5) << 1 << QStringList{"9", "9"}
462                 << (kspm_mode == KSelectionProxyModel::ChildrenOfExactSelection ? 3 : 2);
463             ++testNumber;
464 
465             QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
466                 << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "8", "11"} << 5 << 1 << QStringList{"9", "9"} << 2;
467             ++testNumber;
468 
469             QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
470                 << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"6", "8", "11"} << 4 << 0 << QStringList{"8", "8"} << 4;
471             ++testNumber;
472         }
473     }
474 
475     for (auto connectSelectionModelFirst : {true, false}) {
476         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
477             << static_cast<int>(KSelectionProxyModel::ExactSelection) << connectSelectionModelFirst << false << QStringList{"2"} << 1 << 1
478             << QStringList{"2", "2"} << 0;
479         ++testNumber;
480 
481         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
482             << static_cast<int>(KSelectionProxyModel::ExactSelection) << connectSelectionModelFirst << false << QStringList{"2", "3", "4"} << 3 << 1
483             << QStringList{"2", "2"} << 0;
484         ++testNumber;
485 
486         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
487             << static_cast<int>(KSelectionProxyModel::ExactSelection) << connectSelectionModelFirst << false << QStringList{"6", "9"} << 2 << 1
488             << QStringList{"2", "2"} << 0;
489         ++testNumber;
490 
491         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
492             << static_cast<int>(KSelectionProxyModel::ExactSelection) << connectSelectionModelFirst << false << QStringList{"6", "9"} << 2 << 1
493             << QStringList{"4", "4"} << 1;
494         ++testNumber;
495 
496         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
497             << static_cast<int>(KSelectionProxyModel::ExactSelection) << connectSelectionModelFirst << false << QStringList{"4", "10", "11"} << 3 << 1
498             << QStringList{"3", "9"} << 0;
499         ++testNumber;
500 
501         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
502             << static_cast<int>(KSelectionProxyModel::ExactSelection) << connectSelectionModelFirst << false << QStringList{"4", "6", "7", "10", "11"} << 5 << 1
503             << QStringList{"10", "11"} << 3;
504         ++testNumber;
505 
506         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
507             << static_cast<int>(KSelectionProxyModel::ExactSelection) << connectSelectionModelFirst << false << QStringList{"4", "5", "6", "7", "8"} << 5 << 1
508             << QStringList{"4", "8"} << 0;
509         ++testNumber;
510 
511         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
512             << static_cast<int>(KSelectionProxyModel::ExactSelection) << connectSelectionModelFirst << false << QStringList{"4", "5", "6", "7", "8"} << 5 << 1
513             << QStringList{"4", "4"} << 1;
514 
515         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
516             << static_cast<int>(KSelectionProxyModel::ExactSelection) << connectSelectionModelFirst << false << QStringList{"4", "5", "6", "7", "8"} << 5 << 1
517             << QStringList{"6", "6"} << 3;
518         ++testNumber;
519     }
520 
521     for (auto connectSelectionModelFirst : {true, false}) {
522         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
523             << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"2"} << 1 << 1 << QStringList{"2", "2"}
524             << 0;
525         ++testNumber;
526 
527         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
528             << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"2"} << 1 << 1 << QStringList{"4", "4"}
529             << 1;
530         ++testNumber;
531 
532         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
533             << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"2", "4"} << 1 << 1
534             << QStringList{"4", "4"} << 1;
535         ++testNumber;
536 
537         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
538             << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"2", "4"} << 1 << 1
539             << QStringList{"2", "2"} << 0;
540         ++testNumber;
541 
542         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
543             << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"4", "9"} << 2 << 1
544             << QStringList{"2", "2"} << 0;
545         ++testNumber;
546 
547         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
548             << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"4", "9"} << 2 << 1
549             << QStringList{"4", "4"} << 1;
550         ++testNumber;
551 
552         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
553             << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"4", "9"} << 2 << 1
554             << QStringList{"9", "9"} << 1;
555         ++testNumber;
556 
557         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
558             << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"4", "9"} << 2 << 1
559             << QStringList{"5", "6"} << 2;
560         ++testNumber;
561 
562         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
563             << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"4", "9"} << 2 << 1
564             << QStringList{"4", "8"} << 1;
565         ++testNumber;
566 
567         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
568             << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"6", "11", "15"} << 3 << 1
569             << QStringList{"9", "9"} << 2;
570         ++testNumber;
571 
572         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
573             << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"6", "11", "15"} << 3 << 1
574             << QStringList{"11", "11"} << 2;
575         ++testNumber;
576 
577         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
578             << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"6", "8", "10", "11"} << 4 << 1
579             << QStringList{"3", "3"} << 2;
580         ++testNumber;
581 
582         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
583             << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"6", "8", "10", "11"} << 4 << 1
584             << QStringList{"2", "2"} << 0;
585         ++testNumber;
586 
587         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
588             << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"6", "8", "10", "11"} << 4 << 1
589             << QStringList{"9", "9"} << 2;
590         ++testNumber;
591 
592         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
593             << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"10", "11"} << 2 << 1
594             << QStringList{"9", "9"} << 0;
595         ++testNumber;
596 
597         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
598             << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"4", "8"} << 2 << 1
599             << QStringList{"3", "3"} << 0;
600         ++testNumber;
601 
602         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
603             << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"12", "13", "14"} << 3 << 1
604             << QStringList{"11", "11"} << 0;
605         ++testNumber;
606 
607         QTest::newRow(QByteArray("test" + QByteArray::number(testNumber)).data())
608             << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"12", "13", "14"} << 3 << 1
609             << QStringList{"10", "11"} << 0;
610         ++testNumber;
611     }
612 }
613 
removeRows()614 void KSelectionProxyModelTest::removeRows()
615 {
616     QFETCH(int, kspm_mode);
617     QFETCH(bool, connectSelectionModelFirst);
618     QFETCH(bool, emulateSingleSelectionMode);
619     QFETCH(QStringList, selection);
620     QFETCH(int, expectedRowCountBefore);
621     QFETCH(int, spyCount);
622     QFETCH(QStringList, toRemove);
623     QFETCH(int, expectedRowCountAfter);
624 
625     DynamicTreeModel tree;
626     new ModelTest(&tree, &tree);
627     ModelResetCommand resetCommand(&tree);
628     resetCommand.setInitialTree(
629         " - 1"
630         " - - 2"
631         " - - - 3"
632         " - - - - 4"
633         " - - - - - 5"
634         " - - - - - 6"
635         " - - - - - - 7"
636         " - - - - 8"
637         " - - - 9"
638         " - - - - 10"
639         " - - - - 11"
640         " - - - - - 12"
641         " - - - - - 13"
642         " - - - - - 14"
643         " - - 15"
644         " - - - 16"
645         " - - - 17");
646     resetCommand.doCommand();
647 
648     QItemSelectionModel selectionModel;
649 
650     if (emulateSingleSelectionMode) {
651         QObject::connect(&tree, &QAbstractItemModel::rowsAboutToBeRemoved, &tree, [&tree, &selectionModel](QModelIndex const &p, int s, int e) {
652             auto rmIdx = tree.index(s, 0, p);
653             if (s == e && selectionModel.selectedIndexes().contains(rmIdx)) {
654                 auto nextIdx = tree.index(e + 1, 0, rmIdx.parent());
655                 selectionModel.select(nextIdx, QItemSelectionModel::ClearAndSelect);
656             }
657         });
658     }
659 
660     KSelectionProxyModel proxy;
661     new ModelTest(&proxy, &proxy);
662     proxy.setFilterBehavior(static_cast<KSelectionProxyModel::FilterBehavior>(kspm_mode));
663 
664     if (connectSelectionModelFirst) {
665         selectionModel.setModel(&tree);
666         proxy.setSourceModel(&tree);
667         proxy.setSelectionModel(&selectionModel);
668     } else {
669         proxy.setSourceModel(&tree);
670         proxy.setSelectionModel(&selectionModel);
671         selectionModel.setModel(&tree);
672     }
673 
674     QSignalSpy beforeSpy(&proxy, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)));
675     QSignalSpy afterSpy(&proxy, SIGNAL(rowsRemoved(QModelIndex, int, int)));
676 
677     QItemSelection sel;
678     for (auto item : selection) {
679         QModelIndexList idxs = tree.match(tree.index(0, 0), Qt::DisplayRole, item, 1, Qt::MatchRecursive);
680         QCOMPARE(idxs.size(), 1);
681         sel << QItemSelectionRange(idxs.at(0), idxs.at(0));
682     }
683     selectionModel.select(sel, QItemSelectionModel::Select);
684 
685     QCOMPARE(proxy.rowCount(), expectedRowCountBefore);
686 
687     for (auto removePairIndex = 0; removePairIndex < toRemove.size(); removePairIndex += 2) {
688         QModelIndexList idxs = tree.match(tree.index(0, 0), Qt::DisplayRole, toRemove[removePairIndex], 1, Qt::MatchRecursive);
689         QCOMPARE(idxs.size(), 1);
690 
691         auto startIdx = idxs.at(0);
692 
693         idxs = tree.match(tree.index(0, 0), Qt::DisplayRole, toRemove[removePairIndex + 1], 1, Qt::MatchRecursive);
694         QCOMPARE(idxs.size(), 1);
695 
696         auto endIdx = idxs.at(0);
697 
698         ModelRemoveCommand remove(&tree);
699         remove.setAncestorRowNumbers(tree.indexToPath(startIdx.parent()));
700         remove.setStartRow(startIdx.row());
701         remove.setEndRow(endIdx.row());
702         remove.doCommand();
703     }
704 
705     QCOMPARE(beforeSpy.count(), spyCount);
706     QCOMPARE(afterSpy.count(), spyCount);
707 
708     QCOMPARE(proxy.rowCount(), expectedRowCountAfter);
709 }
710 
selectionMapping()711 void KSelectionProxyModelTest::selectionMapping()
712 {
713     QStringListModel strings(days);
714     QItemSelectionModel selectionModel(&strings);
715     KSelectionProxyModel proxy(&selectionModel);
716     proxy.setFilterBehavior(KSelectionProxyModel::SubTrees);
717     proxy.setSourceModel(&strings);
718     auto idx1 = strings.index(0, 0);
719     auto idx2 = strings.index(2, 0);
720     QItemSelection sourceSel;
721     sourceSel << QItemSelectionRange(idx1, idx2);
722     selectionModel.select(sourceSel, QItemSelectionModel::Select);
723 
724     QItemSelection proxySel;
725     proxySel << QItemSelectionRange(proxy.index(0, 0), proxy.index(2, 0));
726 
727     QCOMPARE(proxy.mapSelectionToSource(proxySel), sourceSel);
728 }
729 
730 QTEST_MAIN(KSelectionProxyModelTest)
731 
732 #include "kselectionproxymodeltest.moc"
733