1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QtTest/QtTest>
30#include <QtCore>
31#include <qquicktreemodeladaptor_p.h>
32#include "../shared/testmodel.h"
33
34class tst_QQuickTreeModelAdaptor : public QObject
35{
36 Q_OBJECT
37
38public:
39 void compareData(int row, QQuickTreeModelAdaptor1 &tma, const QModelIndex &idx, TestModel &model, bool expanded = false);
40 void compareModels(QQuickTreeModelAdaptor1 &tma, TestModel &model);
41 void expandAndTest(const QModelIndex &idx, QQuickTreeModelAdaptor1 &tma, bool expandable, int expectedRowCountDifference);
42 void collapseAndTest(const QModelIndex &idx, QQuickTreeModelAdaptor1 &tma, bool expandable, int expectedRowCountDifference);
43 bool rowRoleWasChanged(const QSignalSpy &captured, int row, int role);
44
45private slots:
46 void initTestCase();
47 void cleanup();
48
49 void setModel();
50 void modelDestroyed();
51 void modelReset();
52
53 void rootIndex();
54
55 void dataAccess();
56 void dataChange();
57 void groupedDataChange();
58
59 void expandAndCollapse_data();
60 void expandAndCollapse();
61 void expandAndCollapse2ndLevel();
62
63 void layoutChange();
64
65 void removeRows_data();
66 void removeRows();
67
68 void removeRowsChildrenAndParent();
69 void removeChildrenMoveParent();
70 void removeChildrenRelayoutParent();
71
72 void insertRows_data();
73 void insertRows();
74
75 void moveRows_data();
76 void moveRows();
77 void moveRowsDataChanged_data();
78 void moveRowsDataChanged();
79 void reparentOnSameRow();
80 void moveAllChildren();
81
82 void selectionForRowRange();
83
84 void hasChildrenEmit();
85};
86
87void tst_QQuickTreeModelAdaptor::initTestCase()
88{
89}
90
91void tst_QQuickTreeModelAdaptor::cleanup()
92{
93}
94
95void tst_QQuickTreeModelAdaptor::compareData(int row, QQuickTreeModelAdaptor1 &tma, const QModelIndex &modelIdx, TestModel &model, bool expanded)
96{
97 const QModelIndex &tmaIdx = tma.index(row);
98 const int indexDepth = model.level(index: modelIdx) - model.level(index: tma.rootIndex()) - 1;
99 QCOMPARE(tma.mapToModel(tmaIdx), modelIdx);
100 QCOMPARE(tma.data(tmaIdx, Qt::DisplayRole).toString(), model.displayData(modelIdx));
101 QCOMPARE(tma.data(tmaIdx, QQuickTreeModelAdaptor1::DepthRole).toInt(), indexDepth);
102 QCOMPARE(tma.data(tmaIdx, QQuickTreeModelAdaptor1::ExpandedRole).toBool(), expanded);
103 QCOMPARE(tma.data(tmaIdx, QQuickTreeModelAdaptor1::HasChildrenRole).toBool(), model.hasChildren(modelIdx));
104}
105
106void tst_QQuickTreeModelAdaptor::expandAndTest(const QModelIndex &idx, QQuickTreeModelAdaptor1 &tma, bool expandable,
107 int expectedRowCountDifference)
108{
109 QSignalSpy rowsAboutToBeInsertedSpy(&tma, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)));
110 QSignalSpy rowsInsertedSpy(&tma, SIGNAL(rowsInserted(QModelIndex,int,int)));
111 QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
112
113 int oldRowCount = tma.rowCount();
114 tma.expand(idx);
115 QCOMPARE(tma.isExpanded(idx), expandable);
116
117 const QModelIndex &tmaIdx = tma.index(row: tma.itemIndex(index: idx));
118 QCOMPARE(tma.data(tmaIdx, QQuickTreeModelAdaptor1::ExpandedRole).toBool(), expandable);
119
120 if (expandable && expectedRowCountDifference != 0) {
121 // Rows were added below the parent
122 QCOMPARE(tma.rowCount(), oldRowCount + expectedRowCountDifference);
123 QCOMPARE(rowsAboutToBeInsertedSpy.count(), rowsInsertedSpy.count());
124 QVERIFY(rowsInsertedSpy.count() > 0);
125 if (rowsInsertedSpy.count() == 1) {
126 const QVariantList &rowsAboutToBeInsertedArgs = rowsAboutToBeInsertedSpy.takeFirst();
127 const QVariantList &rowsInsertedArgs = rowsInsertedSpy.takeFirst();
128 for (int i = 0; i < rowsInsertedArgs.count(); i++)
129 QCOMPARE(rowsAboutToBeInsertedArgs.at(i), rowsInsertedArgs.at(i));
130 QCOMPARE(rowsInsertedArgs.at(0).toModelIndex(), QModelIndex());
131 QCOMPARE(rowsInsertedArgs.at(1).toInt(), tma.itemIndex(idx) + 1);
132 QCOMPARE(rowsInsertedArgs.at(2).toInt(), tma.itemIndex(idx) + expectedRowCountDifference);
133 }
134
135 // Data changed for the parent's ExpandedRole (value checked above)
136 QCOMPARE(dataChangedSpy.count(), 1);
137 const QVariantList &dataChangedArgs = dataChangedSpy.first();
138 QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaIdx);
139 QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaIdx);
140 QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), QVector<int>(1, QQuickTreeModelAdaptor1::ExpandedRole));
141 } else {
142 QCOMPARE(tma.rowCount(), oldRowCount);
143 QCOMPARE(rowsAboutToBeInsertedSpy.count(), 0);
144 QCOMPARE(rowsInsertedSpy.count(), 0);
145 }
146}
147
148void tst_QQuickTreeModelAdaptor::collapseAndTest(const QModelIndex &idx, QQuickTreeModelAdaptor1 &tma,
149 bool expandable, int expectedRowCountDifference)
150{
151 QSignalSpy rowsAboutToBeRemovedSpy(&tma, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)));
152 QSignalSpy rowsRemovedSpy(&tma, SIGNAL(rowsRemoved(QModelIndex,int,int)));
153 QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
154
155 int oldRowCount = tma.rowCount();
156 tma.collapse(idx);
157 QVERIFY(!tma.isExpanded(idx));
158
159 const QModelIndex &tmaIdx = tma.index(row: tma.itemIndex(index: idx));
160 if (tmaIdx.isValid())
161 QCOMPARE(tma.data(tmaIdx, QQuickTreeModelAdaptor1::ExpandedRole).toBool(), false);
162
163 if (expandable && expectedRowCountDifference != 0) {
164 // Rows were removed below the parent
165 QCOMPARE(tma.rowCount(), oldRowCount - expectedRowCountDifference);
166 QCOMPARE(rowsAboutToBeRemovedSpy.count(), 1);
167 QCOMPARE(rowsRemovedSpy.count(), 1);
168 const QVariantList &rowsAboutToBeRemovedArgs = rowsAboutToBeRemovedSpy.takeFirst();
169 const QVariantList &rowsRemovedArgs = rowsRemovedSpy.takeFirst();
170 for (int i = 0; i < rowsRemovedArgs.count(); i++)
171 QCOMPARE(rowsAboutToBeRemovedArgs.at(i), rowsRemovedArgs.at(i));
172 QCOMPARE(rowsRemovedArgs.at(0).toModelIndex(), QModelIndex());
173 QCOMPARE(rowsRemovedArgs.at(1).toInt(), tma.itemIndex(idx) + 1);
174 QCOMPARE(rowsRemovedArgs.at(2).toInt(), tma.itemIndex(idx) + expectedRowCountDifference);
175
176 // Data changed for the parent's ExpandedRole (value checked above)
177 QVERIFY(dataChangedSpy.count() >= 1);
178 const QVariantList &dataChangedArgs = dataChangedSpy.first();
179 QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaIdx);
180 QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaIdx);
181 QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), QVector<int>(1, QQuickTreeModelAdaptor1::ExpandedRole));
182 } else {
183 QCOMPARE(tma.rowCount(), oldRowCount);
184 QCOMPARE(rowsAboutToBeRemovedSpy.count(), 0);
185 QCOMPARE(rowsRemovedSpy.count(), 0);
186 }
187}
188
189bool tst_QQuickTreeModelAdaptor::rowRoleWasChanged(const QSignalSpy &captured, int row, int role)
190{
191 for (const QVariantList &args : captured) {
192 const int startRow = args.at(i: 0).toModelIndex().row();
193 const int endRow = args.at(i: 1).toModelIndex().row();
194 if (row >= startRow && row <= endRow) {
195 const QVector<int> roles = args.at(i: 2).value<QVector<int>>();
196 if (roles.contains(t: role))
197 return true;
198 }
199 }
200 return false;
201}
202
203void tst_QQuickTreeModelAdaptor::compareModels(QQuickTreeModelAdaptor1 &tma, TestModel &model)
204{
205 QModelIndex parent = tma.rootIndex();
206 QStack<QModelIndex> parents;
207 QModelIndex idx = model.index(row: 0, column: 0, parent);
208 int modelVisibleRows = model.rowCount(parent);
209 for (int i = 0; i < tma.rowCount(); i++) {
210 bool expanded = tma.isExpanded(row: i);
211 compareData(row: i, tma, modelIdx: idx, model, expanded);
212 if (expanded) {
213 parents.push(t: parent);
214 parent = idx;
215 modelVisibleRows += model.rowCount(parent);
216 idx = model.index(row: 0, column: 0, parent);
217 } else {
218 while (idx.row() == model.rowCount(parent) - 1) {
219 if (parents.isEmpty())
220 break;
221 idx = parent;
222 parent = parents.pop();
223 }
224 idx = model.index(row: idx.row() + 1, column: 0, parent);
225 }
226 }
227 QCOMPARE(tma.rowCount(), modelVisibleRows);
228
229 // Duplicates the model inspection above, but provides extra tests
230 QVERIFY(tma.testConsistency());
231}
232
233void tst_QQuickTreeModelAdaptor::setModel()
234{
235 TestModel model(5, 1);
236 QQuickTreeModelAdaptor1 tma;
237
238 QSignalSpy modelChangedSpy(&tma, SIGNAL(modelChanged(QAbstractItemModel*)));
239 tma.setModel(&model);
240 QCOMPARE(modelChangedSpy.count(), 1);
241 QCOMPARE(tma.model(), &model);
242
243 // Set same model twice
244 tma.setModel(&model);
245 QCOMPARE(modelChangedSpy.count(), 1);
246
247 modelChangedSpy.clear();
248 tma.setModel(0);
249 QCOMPARE(modelChangedSpy.count(), 1);
250 QCOMPARE(tma.model(), static_cast<QAbstractItemModel *>(0));
251}
252
253void tst_QQuickTreeModelAdaptor::modelDestroyed()
254{
255 TestModel *model = new TestModel(5, 1);
256 QQuickTreeModelAdaptor1 tma;
257
258 QSignalSpy modelChangedSpy(&tma, SIGNAL(modelChanged(QAbstractItemModel*)));
259 tma.setModel(model);
260 QCOMPARE(modelChangedSpy.count(), 1);
261 QCOMPARE(tma.model(), model);
262
263 QModelIndex idx = model->index(row: 0, column: 0);
264 modelChangedSpy.clear();
265 delete model;
266 QCOMPARE(modelChangedSpy.count(), 1);
267 QCOMPARE(tma.model(), (QAbstractItemModel *)0);
268 tma.expand(idx); // No crash, all fine
269}
270
271void tst_QQuickTreeModelAdaptor::modelReset()
272{
273 TestModel model(5, 1);
274 QQuickTreeModelAdaptor1 tma;
275 tma.setModel(&model);
276
277 QSignalSpy modelAboutToBeResetSpy(&tma, SIGNAL(modelAboutToBeReset()));
278 QSignalSpy modelResetSpy(&tma, SIGNAL(modelReset()));
279
280 // Nothing expanded
281 model.resetModel();
282 QCOMPARE(modelAboutToBeResetSpy.count(), 1);
283 QCOMPARE(modelResetSpy.count(), 1);
284 QCOMPARE(tma.rowCount(), model.rowCount());
285 compareModels(tma, model);
286
287 // Expanded items should not be anymore
288 tma.expand(model.index(row: 0, column: 0));
289 tma.expand(model.index(row: 2, column: 0));
290 tma.expand(model.index(row: 2, column: 0, parent: model.index(row: 2, column: 0)));
291 modelAboutToBeResetSpy.clear();
292 modelResetSpy.clear();
293 model.resetModel();
294 QCOMPARE(modelAboutToBeResetSpy.count(), 1);
295 QCOMPARE(modelResetSpy.count(), 1);
296 QCOMPARE(tma.rowCount(), model.rowCount());
297 compareModels(tma, model);
298}
299
300void tst_QQuickTreeModelAdaptor::rootIndex()
301{
302 TestModel model(5, 1);
303
304 QQuickTreeModelAdaptor1 tma;
305 tma.setModel(&model);
306
307 QVERIFY(!tma.rootIndex().isValid());
308 compareModels(tma, model);
309
310 QSignalSpy rootIndexSpy(&tma, SIGNAL(rootIndexChanged()));
311 QModelIndex rootIndex = model.index(row: 0, column: 0);
312 tma.setRootIndex(rootIndex);
313 QCOMPARE(tma.rootIndex(), rootIndex);
314 QCOMPARE(rootIndexSpy.count(), 1);
315 compareModels(tma, model);
316
317 rootIndexSpy.clear();
318 rootIndex = model.index(row: 2, column: 2, parent: tma.rootIndex());
319 tma.setRootIndex(rootIndex);
320 QCOMPARE(tma.rootIndex(), rootIndex);
321 QCOMPARE(rootIndexSpy.count(), 1);
322 compareModels(tma, model);
323
324 // Expand 1st visible item, business as usual
325 expandAndTest(idx: model.index(row: 0, column: 0, parent: rootIndex), tma, expandable: true /*expandable*/, expectedRowCountDifference: 5);
326 // Expand non root item descendant item, nothing should happen
327 expandAndTest(idx: model.index(row: 0, column: 0), tma, expandable: true /*expandable*/, expectedRowCountDifference: 0);
328 // Collapse 1st visible item, business as usual
329 collapseAndTest(idx: model.index(row: 0, column: 0, parent: rootIndex), tma, expandable: true /*expandable*/, expectedRowCountDifference: 5);
330 // Collapse non root item descendant item, nothing should happen
331 collapseAndTest(idx: model.index(row: 0, column: 0), tma, expandable: true /*expandable*/, expectedRowCountDifference: 0);
332}
333
334void tst_QQuickTreeModelAdaptor::dataAccess()
335{
336 TestModel model(5, 1);
337
338 QQuickTreeModelAdaptor1 tma;
339 tma.setModel(&model);
340
341 QCOMPARE(tma.rowCount(), model.rowCount());
342 compareModels(tma, model);
343
344 QModelIndex parentIdx = model.index(row: 2, column: 0);
345 QVERIFY(model.hasChildren(parentIdx));
346 tma.expand(parentIdx);
347 QVERIFY(tma.isExpanded(parentIdx));
348 QCOMPARE(tma.rowCount(), model.rowCount() + model.rowCount(parentIdx));
349 compareModels(tma, model);
350
351 tma.collapse(parentIdx);
352 QCOMPARE(tma.rowCount(), model.rowCount());
353 compareModels(tma, model);
354}
355
356void tst_QQuickTreeModelAdaptor::dataChange()
357{
358 TestModel model(5, 1);
359
360 QQuickTreeModelAdaptor1 tma;
361 tma.setModel(&model);
362
363 QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
364 const QModelIndex &idx = model.index(row: 2, column: 0);
365 model.setData(index: idx, value: QVariant(), role: Qt::DisplayRole);
366 QCOMPARE(dataChangedSpy.count(), 1);
367 const QVariantList &dataChangedArgs = dataChangedSpy.first();
368 const QModelIndex &tmaIdx = tma.index(row: tma.itemIndex(index: idx));
369 QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaIdx);
370 QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaIdx);
371 QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), QVector<int>(1, Qt::DisplayRole));
372 compareModels(tma, model);
373
374 {
375 // Non expanded children shouldn't emit any signal
376 dataChangedSpy.clear();
377 const QModelIndex &childIdx = model.index(row: 4, column: 0, parent: idx);
378 model.setData(index: childIdx, value: QVariant(), role: Qt::DisplayRole);
379 QCOMPARE(dataChangedSpy.count(), 0);
380 compareModels(tma, model);
381
382 // But expanded children should
383 tma.expand(idx);
384 QVERIFY(tma.isExpanded(idx));
385 dataChangedSpy.clear(); // expand() emits dataChanged() with ExpandedRole
386 model.setData(index: childIdx, value: QVariant(), role: Qt::DisplayRole);
387 QCOMPARE(dataChangedSpy.count(), 1);
388 const QVariantList &dataChangedArgs = dataChangedSpy.first();
389 const QModelIndex &tmaIdx = tma.index(row: tma.itemIndex(index: childIdx));
390 QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaIdx);
391 QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaIdx);
392 QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), QVector<int>(1, Qt::DisplayRole));
393 compareModels(tma, model);
394 }
395}
396
397void tst_QQuickTreeModelAdaptor::groupedDataChange()
398{
399 TestModel model(10, 1);
400 const QModelIndex &topLeftIdx = model.index(row: 1, column: 0);
401 const QModelIndex &bottomRightIdx = model.index(row: 7, column: 0);
402
403 QQuickTreeModelAdaptor1 tma;
404 tma.setModel(&model);
405
406 QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
407 const QVector<int> roles(1, Qt::DisplayRole);
408
409 {
410 // No expanded items
411 model.groupedSetData(topLeft: topLeftIdx, bottomRight: bottomRightIdx, roles);
412 QCOMPARE(dataChangedSpy.count(), 1);
413 compareModels(tma, model);
414
415 const QModelIndex &tmaTLIdx = tma.index(row: tma.itemIndex(index: topLeftIdx));
416 const QModelIndex &tmaBRIdx = tma.index(row: tma.itemIndex(index: bottomRightIdx));
417 const QVariantList &dataChangedArgs = dataChangedSpy.first();
418 QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaTLIdx);
419 QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaBRIdx);
420 QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), roles);
421 }
422
423 // One item expanded in the group range
424 const QModelIndex &expandedIdx = model.index(row: 4, column: 0);
425 tma.expand(expandedIdx);
426 QVERIFY(tma.isExpanded(expandedIdx));
427
428 for (int i = 0; i < 2; i++) {
429 const QModelIndex &tmaTLIdx = tma.index(row: tma.itemIndex(index: topLeftIdx));
430 const QModelIndex &tmaExpandedIdx = tma.index(row: tma.itemIndex(index: expandedIdx));
431 const QModelIndex &tmaExpandedSiblingIdx = tma.index(row: tma.itemIndex(index: expandedIdx.sibling(arow: expandedIdx.row() + 1, acolumn: 0)));
432 const QModelIndex &tmaBRIdx = tma.index(row: tma.itemIndex(index: bottomRightIdx));
433
434 dataChangedSpy.clear(); // expand() sends a dataChaned() signal
435 model.groupedSetData(topLeft: topLeftIdx, bottomRight: bottomRightIdx, roles);
436 QCOMPARE(dataChangedSpy.count(), 2);
437 compareModels(tma, model);
438
439 QVariantList dataChangedArgs = dataChangedSpy.takeFirst();
440 QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaTLIdx);
441 QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaExpandedIdx);
442 QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), roles);
443
444 dataChangedArgs = dataChangedSpy.takeFirst();
445 QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaExpandedSiblingIdx);
446 QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaBRIdx);
447 QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), roles);
448
449 // Further expanded descendants should not change grouping
450 tma.expand(model.index(row: 0, column: 0, parent: expandedIdx));
451 QVERIFY(tma.isExpanded(expandedIdx));
452 }
453 tma.collapse(model.index(row: 0, column: 0, parent: expandedIdx));
454
455 // Let's expand one more and see what happens...
456 const QModelIndex &otherExpandedIdx = model.index(row: 6, column: 0);
457 tma.expand(otherExpandedIdx);
458 QVERIFY(tma.isExpanded(otherExpandedIdx));
459
460 for (int i = 0; i < 3; i++) {
461 const QModelIndex &tmaTLIdx = tma.index(row: tma.itemIndex(index: topLeftIdx));
462 const QModelIndex &tmaExpandedIdx = tma.index(row: tma.itemIndex(index: expandedIdx));
463 const QModelIndex &tmaExpandedSiblingIdx = tma.index(row: tma.itemIndex(index: expandedIdx.sibling(arow: expandedIdx.row() + 1, acolumn: 0)));
464 const QModelIndex &tmaOtherExpandedIdx = tma.index(row: tma.itemIndex(index: otherExpandedIdx));
465 const QModelIndex &tmaOtherExpandedSiblingIdx = tma.index(row: tma.itemIndex(index: otherExpandedIdx.sibling(arow: otherExpandedIdx.row() + 1, acolumn: 0)));
466 const QModelIndex &tmaBRIdx = tma.index(row: tma.itemIndex(index: bottomRightIdx));
467
468 dataChangedSpy.clear(); // expand() sends a dataChaned() signal
469 model.groupedSetData(topLeft: topLeftIdx, bottomRight: bottomRightIdx, roles);
470 QCOMPARE(dataChangedSpy.count(), 3);
471 compareModels(tma, model);
472
473 QVariantList dataChangedArgs = dataChangedSpy.takeFirst();
474 QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaTLIdx);
475 QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaExpandedIdx);
476 QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), roles);
477
478 dataChangedArgs = dataChangedSpy.takeFirst();
479 QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaExpandedSiblingIdx);
480 QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaOtherExpandedIdx);
481 QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), roles);
482
483 dataChangedArgs = dataChangedSpy.takeFirst();
484 QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaOtherExpandedSiblingIdx);
485 QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaBRIdx);
486 QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), roles);
487
488 // Further expanded descendants should not change grouping
489 if (i == 0) {
490 tma.expand(model.index(row: 0, column: 0, parent: expandedIdx));
491 QVERIFY(tma.isExpanded(expandedIdx));
492 } else {
493 tma.expand(model.index(row: 0, column: 0, parent: otherExpandedIdx));
494 QVERIFY(tma.isExpanded(expandedIdx));
495 }
496 }
497}
498
499void tst_QQuickTreeModelAdaptor::expandAndCollapse_data()
500{
501 QTest::addColumn<int>(name: "parentRow");
502 QTest::newRow(dataTag: "First") << 0;
503 QTest::newRow(dataTag: "Middle") << 2;
504 QTest::newRow(dataTag: "Last") << 4;
505 QTest::newRow(dataTag: "Non expandable") << 3;
506}
507
508void tst_QQuickTreeModelAdaptor::expandAndCollapse()
509{
510 QFETCH(int, parentRow);
511 TestModel model(5, 1);
512 const QModelIndex &parentIdx = model.index(row: parentRow, column: 0);
513 bool expandable = model.hasChildren(parent: parentIdx);
514
515 QQuickTreeModelAdaptor1 tma;
516 tma.setModel(&model);
517
518 expandAndTest(idx: parentIdx, tma, expandable, expectedRowCountDifference: model.rowCount(parent: parentIdx));
519 compareModels(tma, model);
520
521 collapseAndTest(idx: parentIdx, tma, expandable, expectedRowCountDifference: model.rowCount(parent: parentIdx));
522 compareModels(tma, model);
523}
524
525void tst_QQuickTreeModelAdaptor::expandAndCollapse2ndLevel()
526{
527 const int expandRows[] = { 0, 2, 4, 3 };
528 const int expandRowsCount = sizeof(expandRows) / sizeof(expandRows[0]);
529 for (int i = 0; i < expandRowsCount - 1; i++) { // Skip last non-expandable row
530 TestModel model(5, 1);
531 const QModelIndex &parentIdx = model.index(row: expandRows[i], column: 0);
532 QVERIFY(model.hasChildren(parentIdx));
533
534 QQuickTreeModelAdaptor1 tma;
535 tma.setModel(&model);
536
537 tma.expand(parentIdx);
538 QVERIFY(tma.isExpanded(parentIdx));
539 QCOMPARE(tma.rowCount(), model.rowCount() + model.rowCount(parentIdx));
540
541 for (int j = 0; j < expandRowsCount; j++) {
542 const QModelIndex &childIdx = model.index(row: expandRows[j], column: 0, parent: parentIdx);
543 bool expandable = model.hasChildren(parent: childIdx);
544
545 // Expand child
546 expandAndTest(idx: childIdx, tma, expandable, expectedRowCountDifference: model.rowCount(parent: childIdx));
547 compareModels(tma, model);
548 // Collapse child
549 collapseAndTest(idx: childIdx, tma, expandable, expectedRowCountDifference: model.rowCount(parent: childIdx));
550 compareModels(tma, model);
551
552 // Expand child again
553 expandAndTest(idx: childIdx, tma, expandable, expectedRowCountDifference: model.rowCount(parent: childIdx));
554 compareModels(tma, model);
555 // Collapse parent -> child node invisible, but expanded
556 collapseAndTest(idx: parentIdx, tma, expandable: true, expectedRowCountDifference: model.rowCount(parent: parentIdx) + model.rowCount(parent: childIdx));
557 compareModels(tma, model);
558 QCOMPARE(tma.isExpanded(childIdx), expandable);
559 // Expand parent again
560 expandAndTest(idx: parentIdx, tma, expandable: true, expectedRowCountDifference: model.rowCount(parent: parentIdx) + model.rowCount(parent: childIdx));
561 compareModels(tma, model);
562
563 // Collapse parent -> child node invisible, but expanded
564 collapseAndTest(idx: parentIdx, tma, expandable: true, expectedRowCountDifference: model.rowCount(parent: parentIdx) + model.rowCount(parent: childIdx));
565 compareModels(tma, model);
566 QCOMPARE(tma.isExpanded(childIdx), expandable);
567 // Collapse child -> nothing should change
568 collapseAndTest(idx: childIdx, tma, expandable: false, expectedRowCountDifference: 0);
569 compareModels(tma, model);
570 // Expand parent again
571 expandAndTest(idx: parentIdx, tma, expandable: true, expectedRowCountDifference: model.rowCount(parent: parentIdx));
572 compareModels(tma, model);
573
574 // Expand child, one last time
575 expandAndTest(idx: childIdx, tma, expandable, expectedRowCountDifference: model.rowCount(parent: childIdx));
576 compareModels(tma, model);
577 // Collapse child, and done
578 collapseAndTest(idx: childIdx, tma, expandable, expectedRowCountDifference: model.rowCount(parent: childIdx));
579 compareModels(tma, model);
580 }
581 }
582}
583
584void tst_QQuickTreeModelAdaptor::layoutChange()
585{
586 TestModel model(5, 1);
587 const QModelIndex &idx = model.index(row: 0, column: 0);
588 const QModelIndex &idx2 = model.index(row: 2, column: 0);
589
590 QQuickTreeModelAdaptor1 tma;
591 tma.setModel(&model);
592
593 // Nothing expanded
594 QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
595 model.changeLayout();
596 QCOMPARE(dataChangedSpy.count(), 1);
597 QVariantList dataChangedArgs = dataChangedSpy.takeFirst();
598 QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tma.index(0));
599 QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tma.index(tma.rowCount() - 1));
600 QVERIFY(dataChangedArgs.at(2).value<QVector<int> >().isEmpty());
601 compareModels(tma, model);
602
603 // One item expanded
604 tma.expand(idx);
605 QVERIFY(tma.isExpanded(idx));
606 dataChangedSpy.clear();
607 model.changeLayout();
608 QCOMPARE(dataChangedSpy.count(), 1);
609 dataChangedArgs = dataChangedSpy.takeFirst();
610 QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tma.index(0));
611 QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tma.index(tma.rowCount() - 1));
612 QVERIFY(dataChangedArgs.at(2).value<QVector<int> >().isEmpty());
613 compareModels(tma, model);
614
615 // One parent layout change, expanded
616 dataChangedSpy.clear();
617 QList<QPersistentModelIndex> parents;
618 parents << idx;
619 model.changeLayout(parents);
620 QCOMPARE(dataChangedSpy.count(), 1);
621 dataChangedArgs = dataChangedSpy.takeFirst();
622 QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tma.index(tma.itemIndex(model.index(0, 0, idx))));
623 QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tma.index(tma.itemIndex(model.index(model.rowCount(idx) - 1, 0, idx))));
624 QVERIFY(dataChangedArgs.at(2).value<QVector<int> >().isEmpty());
625 compareModels(tma, model);
626
627 // One parent layout change, collapsed
628 tma.collapse(idx);
629 dataChangedSpy.clear();
630 model.changeLayout(parents);
631 QCOMPARE(dataChangedSpy.count(), 0);
632 compareModels(tma, model);
633
634 // Two-parent layout change, both collapsed
635 parents << idx2;
636 dataChangedSpy.clear();
637 model.changeLayout(parents);
638 QCOMPARE(dataChangedSpy.count(), 0);
639 compareModels(tma, model);
640
641 // Two-parent layout change, only one expanded
642 tma.expand(idx2);
643 QVERIFY(tma.isExpanded(idx2));
644 dataChangedSpy.clear();
645 model.changeLayout(parents);
646 QCOMPARE(dataChangedSpy.count(), 1);
647 dataChangedArgs = dataChangedSpy.takeFirst();
648 QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tma.index(tma.itemIndex(model.index(0, 0, idx2))));
649 QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tma.index(tma.itemIndex(model.index(model.rowCount(idx2) - 1, 0, idx2))));
650 QVERIFY(dataChangedArgs.at(2).value<QVector<int> >().isEmpty());
651 compareModels(tma, model);
652
653 // Two-parent layout change, both expanded
654 tma.expand(idx);
655 QVERIFY(tma.isExpanded(idx));
656 dataChangedSpy.clear();
657 model.changeLayout(parents);
658 QCOMPARE(dataChangedSpy.count(), 2);
659 dataChangedArgs = dataChangedSpy.takeFirst();
660 QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tma.index(tma.itemIndex(model.index(0, 0, idx))));
661 QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tma.index(tma.itemIndex(model.index(model.rowCount(idx) - 1, 0, idx))));
662 QVERIFY(dataChangedArgs.at(2).value<QVector<int> >().isEmpty());
663 dataChangedArgs = dataChangedSpy.takeFirst();
664 QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tma.index(tma.itemIndex(model.index(0, 0, idx2))));
665 QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tma.index(tma.itemIndex(model.index(model.rowCount(idx2) - 1, 0, idx2))));
666 QVERIFY(dataChangedArgs.at(2).value<QVector<int> >().isEmpty());
667 compareModels(tma, model);
668}
669
670static const int ModelRowCount = 9;
671
672void tst_QQuickTreeModelAdaptor::removeRows_data()
673{
674 QTest::addColumn<int>(name: "removeFromRow");
675 QTest::addColumn<int>(name: "removeCount");
676 QTest::addColumn<int>(name: "removeParentRow");
677 QTest::addColumn<int>(name: "expandRow");
678 QTest::addColumn<int>(name: "expandParentRow");
679 QTest::addColumn<int>(name: "expectedRemovedCount");
680
681 QTest::newRow(dataTag: "Nothing expanded, remove 1st row") << 0 << 1 << -1 << -1 << -1 << 1;
682 QTest::newRow(dataTag: "Expand 1st row, remove 1st row") << 0 << 1 << -1 << 0 << -1 << 1 + ModelRowCount;
683 QTest::newRow(dataTag: "Expand last row, remove 1st row") << 0 << 1 << -1 << ModelRowCount - 1 << -1 << 1;
684 QTest::newRow(dataTag: "Nothing expanded, remove last row") << ModelRowCount - 1 << 1 << -1 << -1 << -1 << 1;
685 QTest::newRow(dataTag: "Expand 1st row, remove last row") << ModelRowCount - 1 << 1 << -1 << 0 << -1 << 1;
686 QTest::newRow(dataTag: "Expand last row, remove last row") << ModelRowCount - 1 << 1 << -1 << ModelRowCount - 1 << -1 << 1 + ModelRowCount;
687 QTest::newRow(dataTag: "Remove child row, parent collapsed") << 2 << 1 << 0 << -1 << -1 << 0;
688 QTest::newRow(dataTag: "Remove child row, parent expanded") << 2 << 1 << 0 << 0 << -1 << 1;
689 QTest::newRow(dataTag: "Remove several rows, nothing expanded") << 2 << 5 << -1 << -1 << -1 << 5;
690 QTest::newRow(dataTag: "Remove several rows, 1st row expanded") << 2 << 5 << -1 << 0 << -1 << 5;
691 QTest::newRow(dataTag: "Remove several rows, last row expanded") << 2 << 5 << -1 << ModelRowCount - 1 << -1 << 5;
692 QTest::newRow(dataTag: "Remove several rows, one of them expanded") << 2 << 5 << -1 << 4 << -1 << 5 + ModelRowCount;
693 QTest::newRow(dataTag: "Remove all rows, nothing expanded") << 0 << ModelRowCount << -1 << -1 << -1 << ModelRowCount;
694 QTest::newRow(dataTag: "Remove all rows, 1st row expanded") << 0 << ModelRowCount << -1 << 0 << -1 << ModelRowCount * 2;
695 QTest::newRow(dataTag: "Remove all rows, last row expanded") << 0 << ModelRowCount << -1 << ModelRowCount - 1 << -1 << ModelRowCount * 2;
696 QTest::newRow(dataTag: "Remove all rows, random one expanded") << 0 << ModelRowCount << -1 << 4 << -1 << ModelRowCount * 2;
697}
698
699void tst_QQuickTreeModelAdaptor::removeRows()
700{
701 QFETCH(int, removeFromRow);
702 QFETCH(int, removeCount);
703 QFETCH(int, removeParentRow);
704 QFETCH(int, expandRow);
705 QFETCH(int, expandParentRow);
706 QFETCH(int, expectedRemovedCount);
707
708 TestModel model(ModelRowCount, 1);
709 QQuickTreeModelAdaptor1 tma;
710 tma.setModel(&model);
711
712 const QModelIndex &expandParentIdx = expandParentRow == -1 ? QModelIndex() : model.index(row: expandParentRow, column: 0);
713 if (expandParentIdx.isValid()) {
714 tma.expand(expandParentIdx);
715 QVERIFY(tma.isExpanded(expandParentIdx));
716 }
717 const QModelIndex &expandIdx = model.index(row: expandRow, column: 0, parent: expandParentIdx);
718 if (expandIdx.isValid()) {
719 tma.expand(expandIdx);
720 QVERIFY(tma.isExpanded(expandIdx));
721 }
722
723 const QModelIndex &removeParentIdx = removeParentRow == -1 ? QModelIndex() : model.index(row: removeParentRow, column: 0);
724 const QModelIndex &removeIdx = model.index(row: removeFromRow, column: 0, parent: removeParentIdx);
725 int tmaItemIdx = tma.itemIndex(index: removeIdx);
726
727 QSignalSpy rowsAboutToBeRemovedSpy(&tma, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)));
728 QSignalSpy rowsRemovedSpy(&tma, SIGNAL(rowsRemoved(const QModelIndex&, int, int)));
729 model.removeRows(row: removeFromRow, count: removeCount, parent: removeParentIdx);
730 if (expectedRemovedCount == 0) {
731 QCOMPARE(rowsAboutToBeRemovedSpy.count(), 0);
732 QCOMPARE(rowsRemovedSpy.count(), 0);
733 } else {
734 QCOMPARE(rowsAboutToBeRemovedSpy.count(), 1);
735 QCOMPARE(rowsRemovedSpy.count(), 1);
736 QVariantList rowsAboutToBeRemovedArgs = rowsAboutToBeRemovedSpy.first();
737 QVariantList rowsRemovedArgs = rowsRemovedSpy.first();
738 QCOMPARE(rowsAboutToBeRemovedArgs, rowsRemovedArgs);
739 QCOMPARE(rowsAboutToBeRemovedArgs.at(0).toModelIndex(), QModelIndex());
740 QCOMPARE(rowsAboutToBeRemovedArgs.at(1).toInt(), tmaItemIdx);
741 QCOMPARE(rowsAboutToBeRemovedArgs.at(2).toInt(), tmaItemIdx + expectedRemovedCount - 1);
742 }
743}
744
745void tst_QQuickTreeModelAdaptor::removeRowsChildrenAndParent()
746{
747 TestModel model(ModelRowCount, 1);
748 QQuickTreeModelAdaptor1 tma;
749 tma.setModel(&model);
750
751 // Expand the first node
752 const QModelIndex &parent = model.index(row: 0, column: 0);
753 tma.expand(parent);
754 QVERIFY(tma.isExpanded(parent));
755
756 QSignalSpy rowsAboutToBeRemovedSpy(&tma, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)));
757 QSignalSpy rowsRemovedSpy(&tma, SIGNAL(rowsRemoved(const QModelIndex&, int, int)));
758
759 // Remove the first node children
760 int expectedRemovedCount = model.rowCount(parent);
761 int tmaItemIdx = tma.itemIndex(index: model.index(row: 0, column: 0, parent));
762 QCOMPARE(tmaItemIdx, tma.itemIndex(parent) + 1);
763 model.removeRows(row: 0, count: expectedRemovedCount, parent);
764 QCOMPARE(rowsAboutToBeRemovedSpy.count(), 1);
765 QCOMPARE(rowsRemovedSpy.count(), 1);
766 QVariantList rowsAboutToBeRemovedArgs = rowsAboutToBeRemovedSpy.first();
767 QVariantList rowsRemovedArgs = rowsRemovedSpy.first();
768 QCOMPARE(rowsAboutToBeRemovedArgs, rowsRemovedArgs);
769 QCOMPARE(rowsAboutToBeRemovedArgs.at(0).toModelIndex(), QModelIndex());
770 QCOMPARE(rowsAboutToBeRemovedArgs.at(1).toInt(), tmaItemIdx);
771 QCOMPARE(rowsAboutToBeRemovedArgs.at(2).toInt(), tmaItemIdx + expectedRemovedCount - 1);
772
773 // Remove the first node
774 rowsAboutToBeRemovedSpy.clear();
775 rowsRemovedSpy.clear();
776 model.removeRows(row: 0, count: 1, parent: QModelIndex());
777 QCOMPARE(rowsAboutToBeRemovedSpy.count(), 1);
778 QCOMPARE(rowsRemovedSpy.count(), 1);
779 rowsAboutToBeRemovedArgs = rowsAboutToBeRemovedSpy.first();
780 rowsRemovedArgs = rowsRemovedSpy.first();
781 QCOMPARE(rowsAboutToBeRemovedArgs, rowsRemovedArgs);
782 QCOMPARE(rowsAboutToBeRemovedArgs.at(0).toModelIndex(), QModelIndex());
783 QCOMPARE(rowsAboutToBeRemovedArgs.at(1).toInt(), 0);
784 QCOMPARE(rowsAboutToBeRemovedArgs.at(2).toInt(), 0);
785}
786
787void tst_QQuickTreeModelAdaptor::removeChildrenMoveParent()
788{
789 TestModel model(ModelRowCount, 1);
790 QQuickTreeModelAdaptor1 tma;
791 tma.setModel(&model);
792
793 // Expand the first node
794 const QModelIndex &parent = model.index(row: 0, column: 0);
795 tma.expand(parent);
796 QVERIFY(tma.isExpanded(parent));
797
798 // Remove the first node children
799 QSignalSpy rowsAboutToBeRemovedSpy(&tma, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)));
800 QSignalSpy rowsRemovedSpy(&tma, SIGNAL(rowsRemoved(const QModelIndex&, int, int)));
801 int expectedRemovedCount = model.rowCount(parent);
802 int tmaItemIdx = tma.itemIndex(index: model.index(row: 0, column: 0, parent));
803 QCOMPARE(tmaItemIdx, tma.itemIndex(parent) + 1);
804 model.removeRows(row: 0, count: expectedRemovedCount, parent);
805 QCOMPARE(rowsAboutToBeRemovedSpy.count(), 1);
806 QCOMPARE(rowsRemovedSpy.count(), 1);
807 QVariantList rowsAboutToBeRemovedArgs = rowsAboutToBeRemovedSpy.first();
808 QVariantList rowsRemovedArgs = rowsRemovedSpy.first();
809 QCOMPARE(rowsAboutToBeRemovedArgs, rowsRemovedArgs);
810 QCOMPARE(rowsAboutToBeRemovedArgs.at(0).toModelIndex(), QModelIndex());
811 QCOMPARE(rowsAboutToBeRemovedArgs.at(1).toInt(), tmaItemIdx);
812 QCOMPARE(rowsAboutToBeRemovedArgs.at(2).toInt(), tmaItemIdx + expectedRemovedCount - 1);
813
814 // Move the first node
815 QSignalSpy rowsAboutToBeMovedSpy(&tma, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)));
816 QSignalSpy rowsMovedSpy(&tma, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)));
817 model.moveRows(sourceParent: QModelIndex(), sourceRow: 0, count: 1, destinationParent: QModelIndex(), destinationChild: 3);
818 QCOMPARE(rowsAboutToBeMovedSpy.count(), 1);
819 QCOMPARE(rowsRemovedSpy.count(), 1);
820 QVariantList rowsAboutToBeMovedArgs = rowsAboutToBeMovedSpy.first();
821 QVariantList rowsMovedArgs = rowsMovedSpy.first();
822 QCOMPARE(rowsAboutToBeMovedArgs, rowsMovedArgs);
823 QCOMPARE(rowsAboutToBeMovedArgs.at(0).toModelIndex(), QModelIndex());
824 QCOMPARE(rowsAboutToBeMovedArgs.at(1).toInt(), 0);
825 QCOMPARE(rowsAboutToBeMovedArgs.at(2).toInt(), 0);
826 QCOMPARE(rowsAboutToBeMovedArgs.at(3).toModelIndex(), QModelIndex());
827 QCOMPARE(rowsAboutToBeMovedArgs.at(4).toInt(), 3);
828}
829
830void tst_QQuickTreeModelAdaptor::removeChildrenRelayoutParent()
831{
832 TestModel model(ModelRowCount, 1);
833 QQuickTreeModelAdaptor1 tma;
834 tma.setModel(&model);
835
836 // Expand the first node
837 const QModelIndex &parent = model.index(row: 0, column: 0);
838 tma.expand(parent);
839 QVERIFY(tma.isExpanded(parent));
840
841 QSignalSpy rowsAboutToBeRemovedSpy(&tma, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)));
842 QSignalSpy rowsRemovedSpy(&tma, SIGNAL(rowsRemoved(const QModelIndex&, int, int)));
843
844 // Remove the first node children
845 int expectedRemovedCount = model.rowCount(parent);
846 int tmaItemIdx = tma.itemIndex(index: model.index(row: 0, column: 0, parent));
847 QCOMPARE(tmaItemIdx, tma.itemIndex(parent) + 1);
848 model.removeRows(row: 0, count: expectedRemovedCount, parent);
849 QCOMPARE(rowsAboutToBeRemovedSpy.count(), 1);
850 QCOMPARE(rowsRemovedSpy.count(), 1);
851 QVariantList rowsAboutToBeRemovedArgs = rowsAboutToBeRemovedSpy.first();
852 QVariantList rowsRemovedArgs = rowsRemovedSpy.first();
853 QCOMPARE(rowsAboutToBeRemovedArgs, rowsRemovedArgs);
854 QCOMPARE(rowsAboutToBeRemovedArgs.at(0).toModelIndex(), QModelIndex());
855 QCOMPARE(rowsAboutToBeRemovedArgs.at(1).toInt(), tmaItemIdx);
856 QCOMPARE(rowsAboutToBeRemovedArgs.at(2).toInt(), tmaItemIdx + expectedRemovedCount - 1);
857
858 // Relayout the first node
859 QSignalSpy dataChanged(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
860 QList<QPersistentModelIndex> parents;
861 parents << parent;
862 model.changeLayout(parents);
863 QCOMPARE(dataChanged.count(), 0);
864}
865
866void tst_QQuickTreeModelAdaptor::insertRows_data()
867{
868 QTest::addColumn<int>(name: "insertFromRow");
869 QTest::addColumn<int>(name: "insertCount");
870 QTest::addColumn<int>(name: "insertParentRow");
871 QTest::addColumn<int>(name: "expandRow");
872 QTest::addColumn<int>(name: "expandParentRow");
873 QTest::addColumn<int>(name: "expectedInsertedCount");
874
875 QTest::newRow(dataTag: "Nothing expanded, insert 1st row") << 0 << 1 << -1 << -1 << -1 << 1;
876 QTest::newRow(dataTag: "Expand 1st row, insert 1st row") << 0 << 1 << -1 << 0 << -1 << 1;
877 QTest::newRow(dataTag: "Expand last row, insert 1st row") << 0 << 1 << -1 << ModelRowCount - 1 << -1 << 1;
878 QTest::newRow(dataTag: "Nothing expanded, insert before the last row") << ModelRowCount - 1 << 1 << -1 << -1 << -1 << 1;
879 QTest::newRow(dataTag: "Nothing expanded, insert after the last row") << ModelRowCount << 1 << -1 << -1 << -1 << 1;
880 QTest::newRow(dataTag: "Expand 1st row, insert before the last row") << ModelRowCount - 1 << 1 << -1 << 0 << -1 << 1;
881 QTest::newRow(dataTag: "Expand 1st row, insert after the last row") << ModelRowCount << 1 << -1 << 0 << -1 << 1;
882 QTest::newRow(dataTag: "Expand last row, insert before the last row") << ModelRowCount - 1 << 1 << -1 << ModelRowCount - 1 << -1 << 1;
883 QTest::newRow(dataTag: "Expand last row, insert after the last row") << ModelRowCount << 1 << -1 << ModelRowCount - 1 << -1 << 1;
884 QTest::newRow(dataTag: "Insert child row, parent collapsed") << 2 << 1 << 0 << -1 << -1 << 0;
885 QTest::newRow(dataTag: "Insert child row, parent expanded") << 2 << 1 << 0 << 0 << -1 << 1;
886 QTest::newRow(dataTag: "Insert several rows, nothing expanded") << 2 << 5 << -1 << -1 << -1 << 5;
887 QTest::newRow(dataTag: "Insert several rows, 1st row expanded") << 2 << 5 << -1 << 0 << -1 << 5;
888 QTest::newRow(dataTag: "Insert several rows, last row expanded") << 2 << 5 << -1 << ModelRowCount - 1 << -1 << 5;
889}
890
891void tst_QQuickTreeModelAdaptor::insertRows()
892{
893 QFETCH(int, insertFromRow);
894 QFETCH(int, insertCount);
895 QFETCH(int, insertParentRow);
896 QFETCH(int, expandRow);
897 QFETCH(int, expandParentRow);
898 QFETCH(int, expectedInsertedCount);
899
900 TestModel model(ModelRowCount, 1);
901 QQuickTreeModelAdaptor1 tma;
902 tma.setModel(&model);
903
904 const QModelIndex &expandParentIdx = expandParentRow == -1 ? QModelIndex() : model.index(row: expandParentRow, column: 0);
905 if (expandParentIdx.isValid()) {
906 tma.expand(expandParentIdx);
907 QVERIFY(tma.isExpanded(expandParentIdx));
908 }
909 const QModelIndex &expandIdx = model.index(row: expandRow, column: 0, parent: expandParentIdx);
910 if (expandIdx.isValid()) {
911 tma.expand(expandIdx);
912 QVERIFY(tma.isExpanded(expandIdx));
913 }
914
915 const QModelIndex &insertParentIdx = insertParentRow == -1 ? QModelIndex() : model.index(row: insertParentRow, column: 0);
916 const QModelIndex &insertIdx = model.index(row: insertFromRow, column: 0, parent: insertParentIdx);
917 int tmaItemIdx = insertFromRow == model.rowCount(parent: insertParentIdx) ? tma.rowCount() : tma.itemIndex(index: insertIdx);
918
919 QSignalSpy rowsAboutToBeInsertedSpy(&tma, SIGNAL(rowsAboutToBeInserted(const QModelIndex&, int, int)));
920 QSignalSpy rowsInsertedSpy(&tma, SIGNAL(rowsInserted(const QModelIndex&, int, int)));
921 model.insertRows(row: insertFromRow, count: insertCount, parent: insertParentIdx);
922 if (expectedInsertedCount == 0) {
923 QCOMPARE(rowsAboutToBeInsertedSpy.count(), 0);
924 QCOMPARE(rowsInsertedSpy.count(), 0);
925 } else {
926 QCOMPARE(rowsAboutToBeInsertedSpy.count(), 1);
927 QCOMPARE(rowsInsertedSpy.count(), 1);
928 QVariantList rowsAboutToBeInsertedArgs = rowsAboutToBeInsertedSpy.first();
929 QVariantList rowsInsertedArgs = rowsInsertedSpy.first();
930 QCOMPARE(rowsAboutToBeInsertedArgs, rowsInsertedArgs);
931 QCOMPARE(rowsAboutToBeInsertedArgs.at(0).toModelIndex(), QModelIndex());
932 QCOMPARE(rowsAboutToBeInsertedArgs.at(1).toInt(), tmaItemIdx);
933 QCOMPARE(rowsAboutToBeInsertedArgs.at(2).toInt(), tmaItemIdx + expectedInsertedCount - 1);
934 QCOMPARE(tma.itemIndex(model.index(insertFromRow, 0, insertParentIdx)), tmaItemIdx);
935 }
936}
937
938enum MoveSignalType {
939 RowsMoved = 0, RowsInserted, RowsRemoved
940};
941
942void tst_QQuickTreeModelAdaptor::moveRows_data()
943{
944 QTest::addColumn<int>(name: "sourceRow");
945 QTest::addColumn<bool>(name: "expandSource");
946 QTest::addColumn<int>(name: "moveCount");
947 QTest::addColumn<int>(name: "sourceParentRow");
948 QTest::addColumn<bool>(name: "expandSourceParent");
949 QTest::addColumn<int>(name: "destRow");
950 QTest::addColumn<bool>(name: "expandDest");
951 QTest::addColumn<int>(name: "destParentRow");
952 QTest::addColumn<bool>(name: "expandDestParent");
953 QTest::addColumn<int>(name: "expandRow");
954 QTest::addColumn<int>(name: "expandParentRow");
955 QTest::addColumn<int>(name: "signalType");
956 QTest::addColumn<int>(name: "expectedMovedCount");
957
958 QTest::newRow(dataTag: "From and to top-level parent")
959 << 0 << false << 1 << -1 << false
960 << 3 << false << -1 << false
961 << -1 << -1 << (int)RowsMoved << 1;
962 QTest::newRow(dataTag: "From and to top-level parent, expanded")
963 << 0 << true << 1 << -1 << false
964 << 3 << false << -1 << false
965 << -1 << -1 << (int)RowsMoved << ModelRowCount + 1;
966 QTest::newRow(dataTag: "From and to top-level parent, backwards")
967 << 4 << false << 1 << -1 << false
968 << 0 << false << -1 << false
969 << -1 << -1 << (int)RowsMoved << 1;
970 QTest::newRow(dataTag: "From and to top-level parent, expanded, backwards")
971 << 4 << true << 1 << -1 << false
972 << 0 << false << -1 << false
973 << -1 << -1 << (int)RowsMoved << ModelRowCount + 1;
974 QTest::newRow(dataTag: "Moving between collapsed parents")
975 << 0 << false << 1 << 0 << false
976 << 0 << false << 2 << false
977 << -1 << -1 << (int)RowsMoved << 0;
978 QTest::newRow(dataTag: "From expanded parent to collapsed parent")
979 << 0 << false << 1 << 0 << true
980 << 0 << false << 2 << false
981 << -1 << -1 << (int)RowsRemoved << 1;
982 QTest::newRow(dataTag: "From collapsed parent to expanded parent")
983 << 0 << false << 1 << 0 << false
984 << 0 << false << 2 << true
985 << -1 << -1 << (int)RowsInserted << 1;
986 QTest::newRow(dataTag: "From and to same expanded parent")
987 << 0 << false << 1 << 0 << true
988 << 2 << false << 0 << false
989 << -1 << -1 << (int)RowsMoved << 1;
990 QTest::newRow(dataTag: "From expanded parent to collapsed parent, expanded row")
991 << 0 << true << 1 << 0 << true
992 << 0 << false << 2 << false
993 << -1 << -1 << (int)RowsRemoved << ModelRowCount + 1;
994 QTest::newRow(dataTag: "From collapsed parent to expanded parent, expanded row")
995 << 0 << true << 1 << 0 << false
996 << 0 << false << 2 << true
997 << -1 << -1 << (int)RowsInserted << ModelRowCount + 1;
998 QTest::newRow(dataTag: "From and to same expanded parent, expanded row, forward")
999 << 0 << true << 1 << 0 << true
1000 << 5 << false << 0 << false
1001 << -1 << -1 << (int)RowsMoved << ModelRowCount + 1;
1002 QTest::newRow(dataTag: "From and to same expanded parent, expanded row, last row")
1003 << 0 << true << 1 << 0 << true
1004 << ModelRowCount << false << 0 << false
1005 << -1 << -1 << (int)RowsMoved << ModelRowCount + 1;
1006 QTest::newRow(dataTag: "From and to same expanded parent, expanded row, several")
1007 << 0 << true << 3 << 0 << true
1008 << 5 << false << 0 << false
1009 << -1 << -1 << (int)RowsMoved << ModelRowCount + 3;
1010 QTest::newRow(dataTag: "From and to same expanded parent, expanded row, backward")
1011 << 6 << true << 1 << 0 << true
1012 << 0 << false << 0 << false
1013 << -1 << -1 << (int)RowsMoved << ModelRowCount + 1;
1014 QTest::newRow(dataTag: "From and to same expanded parent, expanded row, several, backward")
1015 << 6 << true << 2 << 0 << true
1016 << 0 << false << 0 << false
1017 << -1 << -1 << (int)RowsMoved << ModelRowCount + 2;
1018 QTest::newRow(dataTag: "From and to different expanded parents")
1019 << 0 << false << 1 << 0 << true
1020 << 1 << false << 4 << true
1021 << -1 << -1 << (int)RowsMoved << 1;
1022 QTest::newRow(dataTag: "From and to different expanded parents, backward")
1023 << 0 << false << 1 << 4 << true
1024 << 2 << false << 0 << true
1025 << -1 << -1 << (int)RowsMoved << 1;
1026 QTest::newRow(dataTag: "From and to different expanded parents, up in level")
1027 << 0 << false << 1 << 0 << true
1028 << 5 << true << -1 << true
1029 << -1 << -1 << (int)RowsMoved << 1;
1030 QTest::newRow(dataTag: "From and to different expanded parents, up in level, backwards")
1031 << 0 << false << 1 << 4 << true
1032 << 1 << false << -1 << true
1033 << -1 << -1 << (int)RowsMoved << 1;
1034 QTest::newRow(dataTag: "From and to different expanded parents, up in level, as 1st item")
1035 << 0 << false << 1 << 0 << true
1036 << 0 << false << -1 << true
1037 << -1 << -1 << (int)RowsMoved << 1;
1038 QTest::newRow(dataTag: "From and to different expanded parents, backward, up in level")
1039 << 0 << false << 1 << 4 << true
1040 << 2 << false << 0 << true
1041 << -1 << -1 << (int)RowsMoved << 1;
1042}
1043
1044void tst_QQuickTreeModelAdaptor::moveRows()
1045{
1046 QFETCH(int, sourceRow);
1047 QFETCH(bool, expandSource);
1048 QFETCH(int, moveCount);
1049 QFETCH(int, sourceParentRow);
1050 QFETCH(bool, expandSourceParent);
1051 QFETCH(int, destRow);
1052 QFETCH(bool, expandDest);
1053 QFETCH(int, destParentRow);
1054 QFETCH(bool, expandDestParent);
1055 QFETCH(int, expandRow);
1056 QFETCH(int, expandParentRow);
1057 QFETCH(int, signalType);
1058 QFETCH(int, expectedMovedCount);
1059
1060 TestModel model(ModelRowCount, 1);
1061 model.alternateChildlessRows = false;
1062 QQuickTreeModelAdaptor1 tma;
1063 tma.setModel(&model);
1064
1065 const QModelIndex &expandParentIdx = expandParentRow == -1 ? QModelIndex() : model.index(row: expandParentRow, column: 0);
1066 if (expandParentIdx.isValid()) {
1067 tma.expand(expandParentIdx);
1068 QVERIFY(tma.isExpanded(expandParentIdx));
1069 }
1070 const QModelIndex &expandIdx = model.index(row: expandRow, column: 0, parent: expandParentIdx);
1071 if (expandIdx.isValid()) {
1072 tma.expand(expandIdx);
1073 QVERIFY(tma.isExpanded(expandIdx));
1074 }
1075
1076 const QModelIndex &sourceParentIdx = sourceParentRow == -1 ? QModelIndex() : model.index(row: sourceParentRow, column: 0);
1077 if (expandSourceParent && sourceParentIdx.isValid()) {
1078 tma.expand(sourceParentIdx);
1079 QVERIFY(tma.isExpanded(sourceParentIdx));
1080 }
1081 const QModelIndex &sourceIdx = model.index(row: sourceRow, column: 0, parent: sourceParentIdx);
1082 if (expandSource) {
1083 tma.expand(sourceIdx);
1084 QVERIFY(tma.isExpanded(sourceIdx));
1085 }
1086
1087 const QModelIndex &destParentIdx = destParentRow == -1 ? QModelIndex() : model.index(row: destParentRow, column: 0);
1088 if (expandDestParent && destParentIdx.isValid()) {
1089 tma.expand(destParentIdx);
1090 QVERIFY(tma.isExpanded(destParentIdx));
1091 }
1092 const QModelIndex &destIdx = model.index(row: destRow, column: 0, parent: destParentIdx);
1093 if (expandDest) {
1094 tma.expand(destIdx);
1095 QVERIFY(tma.isExpanded(destIdx));
1096 }
1097
1098 int tmaSourceItemIdx = signalType == RowsInserted ? -1 // Not tested if RowsInserted
1099 : tma.itemIndex(index: sourceIdx);
1100 int tmaDestItemIdx = signalType == RowsRemoved ? -1 : // Not tested if RowsRemoved
1101 destRow == model.rowCount(parent: destParentIdx) ? -1 /* FIXME */ : tma.itemIndex(index: destIdx);
1102
1103 QSignalSpy rowsAboutToBeMovedSpy(&tma, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)));
1104 QSignalSpy rowsMovedSpy(&tma, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)));
1105 QSignalSpy rowsAboutToBeInsertedSpy(&tma, SIGNAL(rowsAboutToBeInserted(const QModelIndex&, int, int)));
1106 QSignalSpy rowsInsertedSpy(&tma, SIGNAL(rowsInserted(const QModelIndex&, int, int)));
1107 QSignalSpy rowsAboutToBeRemovedSpy(&tma, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)));
1108 QSignalSpy rowsRemovedSpy(&tma, SIGNAL(rowsRemoved(const QModelIndex&, int, int)));
1109
1110 QVERIFY(model.moveRows(sourceParentIdx, sourceRow, moveCount, destParentIdx, destRow));
1111
1112 if (signalType != RowsMoved || expectedMovedCount == 0) {
1113 QCOMPARE(rowsAboutToBeMovedSpy.count(), 0);
1114 QCOMPARE(rowsMovedSpy.count(), 0);
1115 }
1116 if (signalType != RowsInserted || expectedMovedCount == 0) {
1117 QCOMPARE(rowsAboutToBeInsertedSpy.count(), 0);
1118 QCOMPARE(rowsInsertedSpy.count(), 0);
1119 }
1120 if (signalType != RowsRemoved || expectedMovedCount == 0) {
1121 QCOMPARE(rowsAboutToBeRemovedSpy.count(), 0);
1122 QCOMPARE(rowsRemovedSpy.count(), 0);
1123 }
1124
1125 if (expectedMovedCount != 0) {
1126 if (signalType == RowsMoved) {
1127 QCOMPARE(rowsAboutToBeMovedSpy.count(), 1);
1128 QCOMPARE(rowsMovedSpy.count(), 1);
1129 QVariantList rowsAboutToBeMovedArgs = rowsAboutToBeMovedSpy.first();
1130 QVariantList rowsMovedArgs = rowsMovedSpy.first();
1131 QCOMPARE(rowsAboutToBeMovedArgs, rowsMovedArgs);
1132 QCOMPARE(rowsAboutToBeMovedArgs.at(0).toModelIndex(), QModelIndex());
1133 QCOMPARE(rowsAboutToBeMovedArgs.at(1).toInt(), tmaSourceItemIdx);
1134 QCOMPARE(rowsAboutToBeMovedArgs.at(2).toInt(), tmaSourceItemIdx + expectedMovedCount - 1);
1135 QCOMPARE(rowsAboutToBeMovedArgs.at(3).toModelIndex(), QModelIndex());
1136 if (tmaDestItemIdx != -1)
1137 QCOMPARE(rowsAboutToBeMovedArgs.at(4).toInt(), tmaDestItemIdx);
1138 } else if (signalType == RowsInserted) {
1139 // We only test with one level of expanded children here, so we can do
1140 // exhaustive testing depending on whether the moved row is expanded.
1141 int signalCount = expandSource ? 2 : 1;
1142 QCOMPARE(rowsAboutToBeInsertedSpy.count(), signalCount);
1143 QCOMPARE(rowsInsertedSpy.count(), signalCount);
1144 QVariantList rowsAboutToBeInsertedArgs = rowsAboutToBeInsertedSpy.takeFirst();
1145 QVariantList rowsInsertedArgs = rowsInsertedSpy.takeFirst();
1146 QCOMPARE(rowsAboutToBeInsertedArgs, rowsInsertedArgs);
1147 QCOMPARE(rowsAboutToBeInsertedArgs.at(0).toModelIndex(), QModelIndex());
1148 QCOMPARE(rowsAboutToBeInsertedArgs.at(1).toInt(), tmaDestItemIdx);
1149 if (expandSource) {
1150 QCOMPARE(rowsAboutToBeInsertedArgs.at(2).toInt(), tmaDestItemIdx);
1151 rowsAboutToBeInsertedArgs = rowsAboutToBeInsertedSpy.first();
1152 rowsInsertedArgs = rowsInsertedSpy.first();
1153 QCOMPARE(rowsAboutToBeInsertedArgs, rowsInsertedArgs);
1154 QCOMPARE(rowsAboutToBeInsertedArgs.at(0).toModelIndex(), QModelIndex());
1155 QCOMPARE(rowsAboutToBeInsertedArgs.at(1).toInt(), tmaDestItemIdx + 1);
1156 }
1157 QCOMPARE(rowsAboutToBeInsertedArgs.at(2).toInt(), tmaDestItemIdx + expectedMovedCount - 1);
1158 QCOMPARE(tma.itemIndex(model.index(destRow, 0, destParentIdx)), tmaDestItemIdx);
1159 } else if (signalType == RowsRemoved) {
1160 QCOMPARE(rowsAboutToBeRemovedSpy.count(), 1);
1161 QCOMPARE(rowsRemovedSpy.count(), 1);
1162 QVariantList rowsAboutToBeRemovedArgs = rowsAboutToBeRemovedSpy.first();
1163 QVariantList rowsRemovedArgs = rowsRemovedSpy.first();
1164 QCOMPARE(rowsAboutToBeRemovedArgs, rowsRemovedArgs);
1165 QCOMPARE(rowsAboutToBeRemovedArgs.at(0).toModelIndex(), QModelIndex());
1166 QCOMPARE(rowsAboutToBeRemovedArgs.at(1).toInt(), tmaSourceItemIdx);
1167 QCOMPARE(rowsAboutToBeRemovedArgs.at(2).toInt(), tmaSourceItemIdx + expectedMovedCount - 1);
1168 }
1169 }
1170 QVERIFY(tma.testConsistency());
1171 compareModels(tma, model);
1172}
1173
1174void tst_QQuickTreeModelAdaptor::moveRowsDataChanged_data()
1175{
1176 /* This is the list of rows in the *list* model which are expanded. */
1177 QTest::addColumn<QVector<int>>(name: "expandedRows");
1178
1179 /* Here's the row to be moved (always just one) and the destination
1180 * position. We use a QVector<int> to identify a single row of the tree
1181 * model: the array value at index n represents the row number at the depth
1182 * n.
1183 */
1184 QTest::addColumn<QVector<int>>(name: "sourcePath");
1185 QTest::addColumn<QVector<int>>(name: "destPath");
1186
1187 /* This is the list of expected changed rows in the *list* model. */
1188 QTest::addColumn<QSet<int>>(name: "expectedRowChanges");
1189
1190 QTest::newRow(dataTag: "From and to top-level parent")
1191 << QVector<int> {}
1192 << QVector<int> { 4 }
1193 << QVector<int> { 3 }
1194 << QSet<int> { 3, 4 };
1195
1196 QTest::newRow(dataTag: "From and to top-level parent, expanded")
1197 << QVector<int> { 3, 5 }
1198 << QVector<int> { 4 }
1199 << QVector<int> { 3 }
1200 << QSet<int> { 3, 4, 5, 6, 7, 8, 9 };
1201
1202 QTest::newRow(dataTag: "From and to top-level parent, expanded, down")
1203 << QVector<int> { 3, 5 }
1204 << QVector<int> { 3 }
1205 << QVector<int> { 5 }
1206 << QSet<int> { 3, 4, 5, 6, 7, 8, 9 };
1207
1208 /* Expected visible result:
1209 * A A
1210 * D D
1211 * `-E |-E
1212 * F `-H
1213 * G -> F
1214 * |-H G
1215 * |-I |-I
1216 * `-L `-L
1217 * M M
1218 */
1219 QTest::newRow(dataTag: "Visible move, different parents")
1220 << QVector<int> { 3, 1 }
1221 << QVector<int> { 3, 0 }
1222 << QVector<int> { 1, 1 }
1223 << QSet<int> { 3, 4, 5, 6, 7 };
1224
1225 QTest::newRow(dataTag: "Move to non expanded parent")
1226 << QVector<int> {}
1227 << QVector<int> { 3 }
1228 << QVector<int> { 1, 0 }
1229 << QSet<int> { 3 };
1230}
1231
1232void tst_QQuickTreeModelAdaptor::moveRowsDataChanged()
1233{
1234 QFETCH(QVector<int>, expandedRows);
1235 QFETCH(QVector<int>, sourcePath);
1236 QFETCH(QVector<int>, destPath);
1237 QFETCH(QSet<int>, expectedRowChanges);
1238
1239 TestModel model(0, 1);
1240 model.alternateChildlessRows = false;
1241 model.fetchMore(QModelIndex());
1242 QQuickTreeModelAdaptor1 tma;
1243 tma.setModel(&model);
1244
1245 /* Build this model:
1246 * A
1247 * |-B
1248 * `-C
1249 * D
1250 * `-E
1251 * F
1252 * G
1253 * |-H
1254 * |-I
1255 * | |-J
1256 * | `-K
1257 * `-L
1258 * M
1259 */
1260 model.insertRows(row: 0, count: 5, parent: QModelIndex());
1261 QModelIndex index = model.index(row: 0, column: 0);
1262 model.insertRows(row: 0, count: 2, parent: index);
1263 index = model.index(row: 1, column: 0);
1264 model.insertRows(row: 0, count: 1, parent: index);
1265 index = model.index(row: 3, column: 0);
1266 model.insertRows(row: 0, count: 3, parent: index);
1267 index = model.index(row: 1, column: 0, parent: index);
1268 model.insertRows(row: 0, count: 2, parent: index);
1269
1270 for (int row : expandedRows) {
1271 tma.expandRow(n: row);
1272 }
1273
1274 /* Find the source index */
1275 int sourceRow = sourcePath.takeLast();
1276 QModelIndex sourceIndex;
1277 for (int row : sourcePath) {
1278 sourceIndex = model.index(row, column: 0, parent: sourceIndex);
1279 }
1280
1281 /* Find the destination index */
1282 int destRow = destPath.takeLast();
1283 QModelIndex destIndex;
1284 for (int row : destPath) {
1285 destIndex = model.index(row, column: 0, parent: destIndex);
1286 }
1287
1288 QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
1289 QVERIFY(dataChangedSpy.isValid());
1290
1291 QVERIFY(model.moveRows(sourceIndex, sourceRow, 1, destIndex, destRow));
1292
1293 QSet<int> emittedChanges;
1294 for (int i = 0; i < dataChangedSpy.count(); i++) {
1295 QVariantList args = dataChangedSpy.at(i);
1296 QVector<int> roles = args.at(i: 2).value<QVector<int>>();
1297 if (!roles.isEmpty() &&
1298 !roles.contains(t: QQuickTreeModelAdaptor1::ModelIndexRole))
1299 continue;
1300
1301 const QModelIndex topLeft = args.at(i: 0).value<QModelIndex>();
1302 const QModelIndex bottomRight = args.at(i: 1).value<QModelIndex>();
1303 for (int row = topLeft.row(); row <= bottomRight.row(); row++) {
1304 emittedChanges.insert(value: row);
1305 }
1306 }
1307
1308 QCOMPARE(emittedChanges, expectedRowChanges);
1309}
1310
1311void tst_QQuickTreeModelAdaptor::reparentOnSameRow()
1312{
1313 TestModel model(2, 1);
1314 model.alternateChildlessRows = false;
1315 QQuickTreeModelAdaptor1 tma;
1316 tma.setModel(&model);
1317
1318 const QModelIndex &destParent = model.index(row: 0, column: 0);
1319 const QModelIndex &sourceParent = QModelIndex();
1320 QVERIFY(destParent.isValid());
1321 tma.expand(destParent);
1322 QVERIFY(tma.isExpanded(destParent));
1323
1324 QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
1325 QSignalSpy rowsMovedSpy(&tma, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)));
1326 QVERIFY(rowsMovedSpy.isValid());
1327 QVERIFY(dataChangedSpy.isValid());
1328
1329 QVERIFY(model.moveRows(sourceParent, 1, 1, destParent, 2));
1330
1331 QModelIndex movedIndex = tma.index(row: 3, column: 0, parent: QModelIndex());
1332 QVERIFY(movedIndex.isValid());
1333 QCOMPARE(movedIndex.data(QQuickTreeModelAdaptor1::DepthRole).toInt(), 1);
1334 QCOMPARE(tma.data(movedIndex, QQuickTreeModelAdaptor1::ModelIndexRole).toModelIndex(), model.index(2, 0, destParent));
1335
1336 // at least DepthRole and ModeIndexRole changes should have happened for the affected row
1337 bool depthChanged = false;
1338 bool modelIndexChanged = false;
1339 const QList<QList<QVariant> > &changes = dataChangedSpy;
1340 for (const QList<QVariant> &change : changes) {
1341 if (change.at(i: 0) == movedIndex) {
1342 if (change.at(i: 2).value<QVector<int> >().contains(t: QQuickTreeModelAdaptor1::DepthRole))
1343 depthChanged = true;
1344 if (change.at(i: 2).value<QVector<int> >().contains(t: QQuickTreeModelAdaptor1::ModelIndexRole))
1345 modelIndexChanged = true;
1346 }
1347 }
1348
1349 QCOMPARE(depthChanged, true);
1350 QCOMPARE(modelIndexChanged, true);
1351
1352 QCOMPARE(rowsMovedSpy.count(), 0);
1353
1354 model.moveRow(sourceParent: destParent, sourceRow: 2, destinationParent: QModelIndex(), destinationChild: 1);
1355
1356 QCOMPARE(rowsMovedSpy.count(), 0);
1357 QVERIFY(tma.testConsistency());
1358 compareModels(tma, model);
1359}
1360
1361void tst_QQuickTreeModelAdaptor::moveAllChildren()
1362{
1363 TestModel model(0, 1);
1364 model.alternateChildlessRows = false;
1365 model.fetchMore(QModelIndex());
1366 QQuickTreeModelAdaptor1 tma;
1367 tma.setModel(&model);
1368
1369 /* Build this model:
1370 * A
1371 * B
1372 * `-C
1373 */
1374 model.insertRows(row: 0, count: 2, parent: QModelIndex());
1375 QPersistentModelIndex aIndex(model.index(row: 0, column: 0));
1376 QPersistentModelIndex bIndex(model.index(row: 1, column: 0));
1377 model.insertRows(row: 0, count: 1, parent: bIndex);
1378
1379 QCOMPARE(model.rowCount(QModelIndex()), 2);
1380 /* Expand B, then move C under A */
1381 tma.expandRow(n: 1);
1382
1383
1384 QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
1385 QVERIFY(dataChangedSpy.isValid());
1386
1387 QVERIFY(model.moveRows(bIndex, 0, 1, aIndex, 0));
1388
1389 /* Check the outcome */
1390 QCOMPARE(tma.data(aIndex, QQuickTreeModelAdaptor1::HasChildrenRole).toBool(), true);
1391 QCOMPARE(tma.data(bIndex, QQuickTreeModelAdaptor1::HasChildrenRole).toBool(), false);
1392 QVERIFY(rowRoleWasChanged(dataChangedSpy, 0, QQuickTreeModelAdaptor1::HasChildrenRole));
1393 QVERIFY(rowRoleWasChanged(dataChangedSpy, 1, QQuickTreeModelAdaptor1::HasChildrenRole));
1394 dataChangedSpy.clear();
1395
1396 /* Move C back into under B */
1397 QVERIFY(model.moveRows(aIndex, 0, 1, bIndex, 0));
1398
1399 QCOMPARE(tma.data(aIndex, QQuickTreeModelAdaptor1::HasChildrenRole).toBool(), false);
1400 QCOMPARE(tma.data(bIndex, QQuickTreeModelAdaptor1::HasChildrenRole).toBool(), true);
1401 QVERIFY(rowRoleWasChanged(dataChangedSpy, 0, QQuickTreeModelAdaptor1::HasChildrenRole));
1402 QVERIFY(rowRoleWasChanged(dataChangedSpy, 1, QQuickTreeModelAdaptor1::HasChildrenRole));
1403 /* B must not be in expanded state, since it previously lost all its children */
1404 QCOMPARE(tma.data(bIndex, QQuickTreeModelAdaptor1::ExpandedRole).toBool(), false);
1405
1406 dataChangedSpy.clear();
1407}
1408
1409void tst_QQuickTreeModelAdaptor::selectionForRowRange()
1410{
1411 const int ModelRowCount = 9;
1412 const int ModelRowCountLoopStep = 4;
1413
1414 TestModel model(ModelRowCount, 1);
1415 model.alternateChildlessRows = false;
1416 QQuickTreeModelAdaptor1 tma;
1417 tma.setModel(&model);
1418
1419 // NOTE: Some selections may look a bit cryptic. Insert a call to
1420 // tma.dump() before each block if you need to see what's going on.
1421
1422 for (int i = 0; i < ModelRowCount; i += ModelRowCountLoopStep) {
1423 // Single row selection
1424 const QModelIndex &idx = model.index(row: i, column: 0);
1425 const QItemSelection &sel = tma.selectionForRowRange(fromIndex: idx, toIndex: idx);
1426 QCOMPARE(sel.count(), 1);
1427 const QItemSelectionRange &range = sel.first();
1428 QCOMPARE(QModelIndex(range.topLeft()), model.index(i, 0));
1429 QCOMPARE(QModelIndex(range.bottomRight()), model.index(i, 0));
1430 }
1431
1432 for (int i = 0; i < ModelRowCount - ModelRowCountLoopStep; i += ModelRowCountLoopStep) {
1433 // Single range selection
1434 const QModelIndex &from = model.index(row: i, column: 0);
1435 const QModelIndex &to = model.index(row: i + ModelRowCountLoopStep, column: 0);
1436 const QItemSelection &sel = tma.selectionForRowRange(fromIndex: from, toIndex: to);
1437 QCOMPARE(sel.count(), 1);
1438 const QItemSelectionRange &range = sel.first();
1439 QCOMPARE(QModelIndex(range.topLeft()), model.index(i, 0));
1440 QCOMPARE(QModelIndex(range.bottomRight()), model.index(i + ModelRowCountLoopStep, 0));
1441 }
1442
1443 { // Select all, no branch expanded
1444 const QModelIndex &from = model.index(row: 0, column: 0);
1445 const QModelIndex &to = model.index(row: ModelRowCount - 1, column: 0);
1446 const QItemSelection &sel = tma.selectionForRowRange(fromIndex: from, toIndex: to);
1447 QCOMPARE(sel.count(), 1);
1448 const QItemSelectionRange &range = sel.first();
1449 QCOMPARE(QModelIndex(range.topLeft()), model.index(0, 0));
1450 QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0));
1451 }
1452
1453 // Expand 1st top-level item
1454 const QModelIndex &parent = model.index(row: 0, column: 0);
1455 tma.expand(parent);
1456
1457 { // 1st item expanded, select first 5 rows
1458 const QModelIndex &from = tma.mapRowToModelIndex(row: 0);
1459 const QModelIndex &to = tma.mapRowToModelIndex(row: 4);
1460 const QItemSelection &sel = tma.selectionForRowRange(fromIndex: from, toIndex: to);
1461 QCOMPARE(sel.count(), 2);
1462 // We don't know in which order the selection ranges are
1463 // being added, so we iterate and try to find what we expect.
1464 for (const QItemSelectionRange &range : sel) {
1465 if (range.topLeft() == model.index(row: 0, column: 0))
1466 QCOMPARE(QModelIndex(range.bottomRight()), model.index(0, 0));
1467 else if (range.topLeft() == model.index(row: 0, column: 0, parent))
1468 QCOMPARE(QModelIndex(range.bottomRight()), model.index(3, 0, parent));
1469 else
1470 QFAIL("Unexpected selection range");
1471 }
1472 }
1473
1474 { // 1st item expanded, select first 5 top-level items
1475 const QModelIndex &from = tma.mapRowToModelIndex(row: 0);
1476 const QModelIndex &to = tma.mapRowToModelIndex(row: 4 + ModelRowCount);
1477 const QItemSelection &sel = tma.selectionForRowRange(fromIndex: from, toIndex: to);
1478 QCOMPARE(sel.count(), 2);
1479 // We don't know in which order the selection ranges are
1480 // being added, so we iterate and try to find what we expect.
1481 for (const QItemSelectionRange &range : sel) {
1482 if (range.topLeft() == model.index(row: 0, column: 0))
1483 QCOMPARE(QModelIndex(range.bottomRight()), model.index(4, 0));
1484 else if (range.topLeft() == model.index(row: 0, column: 0, parent))
1485 QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent));
1486 else
1487 QFAIL("Unexpected selection range");
1488 }
1489 }
1490
1491 // Expand 2nd top-level item
1492 const QModelIndex &parent2 = model.index(row: 1, column: 0);
1493 tma.expand(parent2);
1494
1495 { // 1st two items expanded, select first 5 top-level items
1496 const QModelIndex &from = tma.mapRowToModelIndex(row: 0);
1497 const QModelIndex &to = tma.mapRowToModelIndex(row: 4 + 2 * ModelRowCount);
1498 const QItemSelection &sel = tma.selectionForRowRange(fromIndex: from, toIndex: to);
1499 QCOMPARE(sel.count(), 3);
1500 // We don't know in which order the selection ranges are
1501 // being added, so we iterate and try to find what we expect.
1502 for (const QItemSelectionRange &range : sel) {
1503 if (range.topLeft() == model.index(row: 0, column: 0))
1504 QCOMPARE(QModelIndex(range.bottomRight()), model.index(4, 0));
1505 else if (range.topLeft() == model.index(row: 0, column: 0, parent))
1506 QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent));
1507 else if (range.topLeft() == model.index(row: 0, column: 0, parent: parent2))
1508 QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent2));
1509 else
1510 QFAIL("Unexpected selection range");
1511 }
1512 }
1513
1514 // Expand 1st child of 1st top-level item
1515 const QModelIndex &parent3 = model.index(row: 0, column: 0, parent);
1516 tma.expand(parent3);
1517
1518 { // 1st two items, and 1st child of 1st item expanded, select first 5 rows
1519 const QModelIndex &from = tma.mapRowToModelIndex(row: 0);
1520 const QModelIndex &to = tma.mapRowToModelIndex(row: 4);
1521 const QItemSelection &sel = tma.selectionForRowRange(fromIndex: from, toIndex: to);
1522 QCOMPARE(sel.count(), 3);
1523 // We don't know in which order the selection ranges are
1524 // being added, so we iterate and try to find what we expect.
1525 for (const QItemSelectionRange &range : sel) {
1526 if (range.topLeft() == model.index(row: 0, column: 0))
1527 QCOMPARE(QModelIndex(range.bottomRight()), model.index(0, 0));
1528 else if (range.topLeft() == model.index(row: 0, column: 0, parent))
1529 QCOMPARE(QModelIndex(range.bottomRight()), model.index(0, 0, parent));
1530 else if (range.topLeft() == model.index(row: 0, column: 0, parent: parent3))
1531 QCOMPARE(QModelIndex(range.bottomRight()), model.index(2, 0, parent3));
1532 else
1533 QFAIL("Unexpected selection range");
1534 }
1535 }
1536
1537 { // 1st two items, and 1st child of 1st item expanded, select all
1538 const QModelIndex &from = tma.mapRowToModelIndex(row: 0);
1539 const QModelIndex &to = tma.mapRowToModelIndex(row: 4 * ModelRowCount - 1);
1540 const QItemSelection &sel = tma.selectionForRowRange(fromIndex: from, toIndex: to);
1541 QCOMPARE(sel.count(), 4);
1542 // We don't know in which order the selection ranges are
1543 // being added, so we iterate and try to find what we expect.
1544 for (const QItemSelectionRange &range : sel) {
1545 if (range.topLeft() == model.index(row: 0, column: 0))
1546 QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0));
1547 else if (range.topLeft() == model.index(row: 0, column: 0, parent))
1548 QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent));
1549 else if (range.topLeft() == model.index(row: 0, column: 0, parent: parent2))
1550 QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent2));
1551 else if (range.topLeft() == model.index(row: 0, column: 0, parent: parent3))
1552 QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent3));
1553 else
1554 QFAIL("Unexpected selection range");
1555 }
1556 }
1557
1558 { // 1st two items, and 1st child of 1st item expanded, select rows across branches
1559 const QModelIndex &from = tma.mapRowToModelIndex(row: 8);
1560 const QModelIndex &to = tma.mapRowToModelIndex(row: 23);
1561 const QItemSelection &sel = tma.selectionForRowRange(fromIndex: from, toIndex: to);
1562 QCOMPARE(sel.count(), 4);
1563 // We don't know in which order the selection ranges are
1564 // being added, so we iterate and try to find what we expect.
1565 for (const QItemSelectionRange &range : sel) {
1566 if (range.topLeft() == model.index(row: 1, column: 0))
1567 QCOMPARE(QModelIndex(range.bottomRight()), model.index(1, 0));
1568 else if (range.topLeft() == model.index(row: 1, column: 0, parent))
1569 QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent));
1570 else if (range.topLeft() == model.index(row: 0, column: 0, parent: parent2))
1571 QCOMPARE(QModelIndex(range.bottomRight()), model.index(3, 0, parent2));
1572 else if (range.topLeft() == model.index(row: 6, column: 0, parent: parent3))
1573 QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent3));
1574 else
1575 QFAIL("Unexpected selection range");
1576 }
1577 }
1578}
1579
1580void tst_QQuickTreeModelAdaptor::hasChildrenEmit()
1581{
1582 TestModel model(1, 1);
1583 model.alternateChildlessRows = false;
1584 QQuickTreeModelAdaptor1 tma;
1585 tma.setModel(&model);
1586
1587 QModelIndex root = model.index(row: 0,column: 0);
1588 QVERIFY(root.isValid());
1589
1590 QModelIndex child = model.index(row: 0, column: 0, parent: root);
1591 QVERIFY(child.isValid());
1592
1593 // Root not expanded , child not expanded, insert in child, expect no datachanged
1594 {
1595 QVERIFY(!tma.isExpanded(root));
1596 QVERIFY(!tma.isExpanded(child));
1597 QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
1598 QCOMPARE(dataChangedSpy.count(), 0);
1599 QCOMPARE(model.rowCount(child), 1);
1600 model.insertRow(arow: 1, aparent: child);
1601 QCOMPARE(model.rowCount(child), 2);
1602 QCOMPARE(dataChangedSpy.count(), 0);
1603 }
1604
1605 // Root not expanded, insert in root, expect datachanged
1606 {
1607 QVERIFY(!tma.isExpanded(root));
1608 QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
1609 QCOMPARE(dataChangedSpy.count(), 0);
1610 QCOMPARE(model.rowCount(root), 1);
1611 model.insertRow(arow: 1, aparent: root);
1612 QCOMPARE(model.rowCount(root), 2);
1613 QCOMPARE(dataChangedSpy.count(), 1);
1614 }
1615
1616 // Root not expanded, child not expanded, remove in child, expect no datachanged
1617 {
1618 QVERIFY(!tma.isExpanded(root));
1619 QVERIFY(!tma.isExpanded(child));
1620 QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
1621 QCOMPARE(dataChangedSpy.count(), 0);
1622 QCOMPARE(model.rowCount(child), 2);
1623 model.removeRow(arow: 1, aparent: child);
1624 QCOMPARE(model.rowCount(child), 1);
1625 QCOMPARE(dataChangedSpy.count(), 0);
1626 }
1627
1628 // Root not expanded, remove in root, expected datachanged on hasChildren
1629 {
1630 QVERIFY(!tma.isExpanded(root));
1631 QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
1632 QCOMPARE(dataChangedSpy.count(), 0);
1633 QCOMPARE(model.rowCount(root), 2);
1634 model.removeRow(arow: 1, aparent: root);
1635 QCOMPARE(model.rowCount(root), 1);
1636 QCOMPARE(dataChangedSpy.count(), 1);
1637 }
1638}
1639
1640QTEST_MAIN(tst_QQuickTreeModelAdaptor)
1641#include "tst_qquicktreemodeladaptor.moc"
1642

source code of qtquickcontrols/tests/auto/qquicktreemodeladaptor/tst_qquicktreemodeladaptor.cpp