1/****************************************************************************
2**
3** Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, authors Filipe Azevedo <filipe.azevedo@kdab.com> and David Faure <david.faure@kdab.com>
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 <QTest>
30#include <QSignalSpy>
31#include <QSortFilterProxyModel>
32#include <QStandardItem>
33
34Q_DECLARE_METATYPE(QModelIndex)
35
36static const int s_filterRole = Qt::UserRole + 1;
37
38class ModelSignalSpy : public QObject {
39 Q_OBJECT
40public:
41 explicit ModelSignalSpy(QAbstractItemModel &model) {
42 connect(sender: &model, signal: &QAbstractItemModel::rowsInserted, receiver: this, slot: &ModelSignalSpy::onRowsInserted);
43 connect(sender: &model, signal: &QAbstractItemModel::rowsRemoved, receiver: this, slot: &ModelSignalSpy::onRowsRemoved);
44 connect(sender: &model, signal: &QAbstractItemModel::rowsAboutToBeInserted, receiver: this, slot: &ModelSignalSpy::onRowsAboutToBeInserted);
45 connect(sender: &model, signal: &QAbstractItemModel::rowsAboutToBeRemoved, receiver: this, slot: &ModelSignalSpy::onRowsAboutToBeRemoved);
46 connect(sender: &model, signal: &QAbstractItemModel::rowsMoved, receiver: this, slot: &ModelSignalSpy::onRowsMoved);
47 connect(sender: &model, signal: &QAbstractItemModel::dataChanged, receiver: this, slot: &ModelSignalSpy::onDataChanged);
48 connect(sender: &model, signal: &QAbstractItemModel::layoutChanged, receiver: this, slot: &ModelSignalSpy::onLayoutChanged);
49 connect(sender: &model, signal: &QAbstractItemModel::modelReset, receiver: this, slot: &ModelSignalSpy::onModelReset);
50 }
51
52 QStringList mSignals;
53
54private Q_SLOTS:
55 void onRowsInserted(QModelIndex p, int start, int end) {
56 mSignals << QLatin1String("rowsInserted(") + textForRowSpy(parent: p, start, end) + ')';
57 }
58 void onRowsRemoved(QModelIndex p, int start, int end) {
59 mSignals << QLatin1String("rowsRemoved(") + textForRowSpy(parent: p, start, end) + ')';
60 }
61 void onRowsAboutToBeInserted(QModelIndex p, int start, int end) {
62 mSignals << QLatin1String("rowsAboutToBeInserted(") + textForRowSpy(parent: p, start, end) + ')';
63 }
64 void onRowsAboutToBeRemoved(QModelIndex p, int start, int end) {
65 mSignals << QLatin1String("rowsAboutToBeRemoved(") + textForRowSpy(parent: p, start, end) + ')';
66 }
67 void onRowsMoved(QModelIndex,int,int,QModelIndex,int) {
68 mSignals << QStringLiteral("rowsMoved");
69 }
70 void onDataChanged(const QModelIndex &from, const QModelIndex& ) {
71 mSignals << QStringLiteral("dataChanged(%1)").arg(a: from.data(arole: Qt::DisplayRole).toString());
72 }
73 void onLayoutChanged() {
74 mSignals << QStringLiteral("layoutChanged");
75 }
76 void onModelReset() {
77 mSignals << QStringLiteral("modelReset");
78 }
79private:
80 QString textForRowSpy(const QModelIndex &parent, int start, int end)
81 {
82 QString txt = parent.data(arole: Qt::DisplayRole).toString();
83 if (!txt.isEmpty())
84 txt += QLatin1Char('.');
85 txt += QString::number(start+1);
86 if (start != end)
87 txt += QLatin1Char('-') + QString::number(end+1);
88 return txt;
89 }
90};
91
92class TestModel : public QSortFilterProxyModel
93{
94 Q_OBJECT
95public:
96 TestModel(QAbstractItemModel *sourceModel)
97 : QSortFilterProxyModel()
98 {
99 setFilterRole(s_filterRole);
100 setRecursiveFilteringEnabled(true);
101 setSourceModel(sourceModel);
102 }
103
104 virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
105 {
106 return sourceModel()->index(row: sourceRow, column: 0, parent: sourceParent).data(arole: s_filterRole).toBool()
107 && QSortFilterProxyModel::filterAcceptsRow(source_row: sourceRow, source_parent: sourceParent);
108 }
109};
110
111// Represents this tree
112// - A
113// - - B
114// - - - C
115// - - - D
116// - - E
117// as a single string, englobing children in brackets, like this:
118// [A[B[C D] E]]
119// In addition, items that match the filtering (data(s_filterRole) == true) have a * after their value.
120static QString treeAsString(const QAbstractItemModel &model, const QModelIndex &parent = QModelIndex())
121{
122 QString ret;
123 const int rowCount = model.rowCount(parent);
124 if (rowCount > 0) {
125 ret += QLatin1Char('[');
126 for (int row = 0 ; row < rowCount; ++row) {
127 if (row > 0) {
128 ret += ' ';
129 }
130 const QModelIndex child = model.index(row, column: 0, parent);
131 ret += child.data(arole: Qt::DisplayRole).toString();
132 if (child.data(arole: s_filterRole).toBool())
133 ret += QLatin1Char('*');
134 ret += treeAsString(model, parent: child);
135 }
136 ret += QLatin1Char(']');
137 }
138 return ret;
139}
140
141// Fill a tree model based on a string representation (see treeAsString)
142static void fillModel(QStandardItemModel &model, const QString &str)
143{
144 QCOMPARE(str.count('['), str.count(']'));
145 QStandardItem *item = 0;
146 QString data;
147 for ( int i = 0 ; i < str.length() ; ++i ) {
148 const QChar ch = str.at(i);
149 if ((ch == '[' || ch == ']' || ch == ' ') && !data.isEmpty()) {
150 if (data.endsWith(c: '*')) {
151 item->setData(value: true, role: s_filterRole);
152 data.chop(n: 1);
153 }
154 item->setText(data);
155 data.clear();
156 }
157 if (ch == '[') {
158 // Create new child
159 QStandardItem *child = new QStandardItem;
160 if (item)
161 item->appendRow(aitem: child);
162 else
163 model.appendRow(aitem: child);
164 item = child;
165 } else if (ch == ']') {
166 // Go up to parent
167 item = item->parent();
168 } else if (ch == ' ') {
169 // Create new sibling
170 QStandardItem *child = new QStandardItem;
171 QStandardItem *parent = item->parent();
172 if (parent)
173 parent->appendRow(aitem: child);
174 else
175 model.appendRow(aitem: child);
176 item = child;
177 } else {
178 data += ch;
179 }
180 }
181}
182
183class tst_QSortFilterProxyModel_Recursive : public QObject
184{
185 Q_OBJECT
186private:
187private Q_SLOTS:
188 void testInitialFiltering_data()
189 {
190 QTest::addColumn<QString>(name: "sourceStr");
191 QTest::addColumn<QString>(name: "proxyStr");
192
193 QTest::newRow(dataTag: "empty") << "[]" << "";
194 QTest::newRow(dataTag: "no") << "[1]" << "";
195 QTest::newRow(dataTag: "yes") << "[1*]" << "[1*]";
196 QTest::newRow(dataTag: "second") << "[1 2*]" << "[2*]";
197 QTest::newRow(dataTag: "child_yes") << "[1 2[2.1*]]" << "[2[2.1*]]";
198 QTest::newRow(dataTag: "grandchild_yes") << "[1 2[2.1[2.1.1*]]]" << "[2[2.1[2.1.1*]]]";
199 // 1, 3.1 and 4.2.1 match, so their parents are in the model
200 QTest::newRow(dataTag: "more") << "[1* 2[2.1] 3[3.1*] 4[4.1 4.2[4.2.1*]]]" << "[1* 3[3.1*] 4[4.2[4.2.1*]]]";
201 }
202
203 void testInitialFiltering()
204 {
205 QFETCH(QString, sourceStr);
206 QFETCH(QString, proxyStr);
207
208 QStandardItemModel model;
209 fillModel(model, str: sourceStr);
210 QCOMPARE(treeAsString(model), sourceStr);
211
212 TestModel proxy(&model);
213 QVERIFY(proxy.isRecursiveFilteringEnabled());
214 QCOMPARE(treeAsString(proxy), proxyStr);
215 }
216
217 // Test changing a role that is unrelated to the filtering.
218 void testUnrelatedDataChange()
219 {
220 QStandardItemModel model;
221 const QString sourceStr = QStringLiteral("[1[1.1[1.1.1*]]]");
222 fillModel(model, str: sourceStr);
223 QCOMPARE(treeAsString(model), sourceStr);
224
225 TestModel proxy(&model);
226 QCOMPARE(treeAsString(proxy), sourceStr);
227
228 ModelSignalSpy spy(proxy);
229 QStandardItem *item_1_1_1 = model.item(row: 0)->child(row: 0)->child(row: 0);
230
231 // When changing the text on the item
232 item_1_1_1->setText(QStringLiteral("ME"));
233
234 QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[ME*]]]"));
235
236 // filterRole is Qt::UserRole + 1, so parents are not checked and
237 // therefore no dataChanged for parents
238 QCOMPARE(spy.mSignals, QStringList()
239 << QStringLiteral("dataChanged(ME)"));
240 }
241
242 // Test changing a role that is unrelated to the filtering, in a hidden item.
243 void testHiddenDataChange()
244 {
245 QStandardItemModel model;
246 const QString sourceStr = QStringLiteral("[1[1.1[1.1.1]]]");
247 fillModel(model, str: sourceStr);
248 QCOMPARE(treeAsString(model), sourceStr);
249
250 TestModel proxy(&model);
251 QCOMPARE(treeAsString(proxy), QString());
252
253 ModelSignalSpy spy(proxy);
254 QStandardItem *item_1_1_1 = model.item(row: 0)->child(row: 0)->child(row: 0);
255
256 // When changing the text on a hidden item
257 item_1_1_1->setText(QStringLiteral("ME"));
258
259 QCOMPARE(treeAsString(proxy), QString());
260 QCOMPARE(spy.mSignals, QStringList());
261 }
262
263 // Test that we properly react to a data-changed signal in a descendant and include all required rows
264 void testDataChangeIn_data()
265 {
266 QTest::addColumn<QString>(name: "sourceStr");
267 QTest::addColumn<QString>(name: "initialProxyStr");
268 QTest::addColumn<QString>(name: "add"); // set the flag on this item
269 QTest::addColumn<QString>(name: "expectedProxyStr");
270 QTest::addColumn<QStringList>(name: "expectedSignals");
271
272 QTest::newRow(dataTag: "toplevel") << "[1]" << "" << "1" << "[1*]"
273 << (QStringList() << QStringLiteral("rowsAboutToBeInserted(1)") << QStringLiteral("rowsInserted(1)"));
274 QTest::newRow(dataTag: "show_parents") << "[1[1.1[1.1.1]]]" << "" << "1.1.1" << "[1[1.1[1.1.1*]]]"
275 << (QStringList() << QStringLiteral("rowsAboutToBeInserted(1)") << QStringLiteral("rowsInserted(1)"));
276
277 const QStringList insert_1_1_1 = QStringList()
278 << QStringLiteral("rowsAboutToBeInserted(1.1.1)")
279 << QStringLiteral("rowsInserted(1.1.1)")
280 << QStringLiteral("dataChanged(1.1)")
281 << QStringLiteral("dataChanged(1)")
282 ;
283 QTest::newRow(dataTag: "parent_visible") << "[1[1.1*[1.1.1]]]" << "[1[1.1*]]" << "1.1.1" << "[1[1.1*[1.1.1*]]]"
284 << insert_1_1_1;
285
286 QTest::newRow(dataTag: "sibling_visible") << "[1[1.1[1.1.1 1.1.2*]]]" << "[1[1.1[1.1.2*]]]" << "1.1.1" << "[1[1.1[1.1.1* 1.1.2*]]]"
287 << insert_1_1_1;
288
289 QTest::newRow(dataTag: "visible_cousin") << "[1[1.1[1.1.1 1.1.2[1.1.2.1*]]]]" << "[1[1.1[1.1.2[1.1.2.1*]]]]" << "1.1.1" << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]"
290 << insert_1_1_1;
291
292 QTest::newRow(dataTag: "show_parent") << "[1[1.1[1.1.1 1.1.2] 1.2*]]" << "[1[1.2*]]" << "1.1.1" << "[1[1.1[1.1.1*] 1.2*]]"
293 << (QStringList()
294 << QStringLiteral("rowsAboutToBeInserted(1.1)")
295 << QStringLiteral("rowsInserted(1.1)")
296 << QStringLiteral("dataChanged(1)"));
297
298 QTest::newRow(dataTag: "with_children") << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" << "1.1.1" << "[1[1.1[1.1.1*[1.1.1.1*]]] 2*]"
299 << (QStringList()
300 << QStringLiteral("dataChanged(1.1.1)")
301 << QStringLiteral("dataChanged(1.1)")
302 << QStringLiteral("dataChanged(1)"));
303
304 }
305
306 void testDataChangeIn()
307 {
308 QFETCH(QString, sourceStr);
309 QFETCH(QString, initialProxyStr);
310 QFETCH(QString, add);
311 QFETCH(QString, expectedProxyStr);
312 QFETCH(QStringList, expectedSignals);
313
314 QStandardItemModel model;
315 fillModel(model, str: sourceStr);
316 QCOMPARE(treeAsString(model), sourceStr);
317
318 TestModel proxy(&model);
319 QCOMPARE(treeAsString(proxy), initialProxyStr);
320
321 ModelSignalSpy spy(proxy);
322 // When changing the data on the designated item to show this row
323 QStandardItem *itemToChange = itemByText(model, text: add);
324 QVERIFY(!itemToChange->data(s_filterRole).toBool());
325 itemToChange->setData(value: true, role: s_filterRole);
326
327 // The proxy should update as expected
328 QCOMPARE(treeAsString(proxy), expectedProxyStr);
329
330 //qDebug() << spy.mSignals;
331 QCOMPARE(spy.mSignals, expectedSignals);
332 }
333
334 void testDataChangeOut_data()
335 {
336 QTest::addColumn<QString>(name: "sourceStr");
337 QTest::addColumn<QString>(name: "initialProxyStr");
338 QTest::addColumn<QString>(name: "remove"); // unset the flag on this item
339 QTest::addColumn<QString>(name: "expectedProxyStr");
340 QTest::addColumn<QStringList>(name: "expectedSignals");
341
342 const QStringList remove1_1_1 = (QStringList()
343 << QStringLiteral("rowsAboutToBeRemoved(1.1.1)")
344 << QStringLiteral("rowsRemoved(1.1.1)")
345 << QStringLiteral("dataChanged(1.1)")
346 << QStringLiteral("dataChanged(1)"));
347
348 QTest::newRow(dataTag: "toplevel") << "[1*]" << "[1*]" << "1" << ""
349 << (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1)") << QStringLiteral("rowsRemoved(1)"));
350
351 QTest::newRow(dataTag: "hide_parent") << "[1[1.1[1.1.1*]]]" << "[1[1.1[1.1.1*]]]" << "1.1.1" << "" <<
352 (QStringList()
353 << QStringLiteral("rowsAboutToBeRemoved(1.1.1)")
354 << QStringLiteral("rowsRemoved(1.1.1)")
355 << QStringLiteral("rowsAboutToBeRemoved(1.1)")
356 << QStringLiteral("rowsRemoved(1.1)")
357 << QStringLiteral("rowsAboutToBeRemoved(1)")
358 << QStringLiteral("rowsRemoved(1)"));
359
360 QTest::newRow(dataTag: "parent_visible") << "[1[1.1*[1.1.1*]]]" << "[1[1.1*[1.1.1*]]]" << "1.1.1" << "[1[1.1*]]"
361 << remove1_1_1;
362
363 QTest::newRow(dataTag: "visible") << "[1[1.1[1.1.1* 1.1.2*]]]" << "[1[1.1[1.1.1* 1.1.2*]]]" << "1.1.1" << "[1[1.1[1.1.2*]]]"
364 << remove1_1_1;
365 QTest::newRow(dataTag: "visible_cousin") << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" << "1.1.1" << "[1[1.1[1.1.2[1.1.2.1*]]]]"
366 << remove1_1_1;
367
368 // The following tests trigger the removal of an ascendant.
369 QTest::newRow(dataTag: "remove_parent") << "[1[1.1[1.1.1* 1.1.2] 1.2*]]" << "[1[1.1[1.1.1*] 1.2*]]" << "1.1.1" << "[1[1.2*]]"
370 << (QStringList()
371 << QStringLiteral("rowsAboutToBeRemoved(1.1.1)")
372 << QStringLiteral("rowsRemoved(1.1.1)")
373 << QStringLiteral("rowsAboutToBeRemoved(1.1)")
374 << QStringLiteral("rowsRemoved(1.1)")
375 << QStringLiteral("dataChanged(1)"));
376
377 QTest::newRow(dataTag: "with_children") << "[1[1.1[1.1.1*[1.1.1.1*]]] 2*]" << "[1[1.1[1.1.1*[1.1.1.1*]]] 2*]" << "1.1.1" << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]"
378 << (QStringList()
379 << QStringLiteral("dataChanged(1.1.1)")
380 << QStringLiteral("dataChanged(1.1)")
381 << QStringLiteral("dataChanged(1)"));
382
383 QTest::newRow(dataTag: "last_visible") << "[1[1.1[1.1.1* 1.1.2]]]" << "[1[1.1[1.1.1*]]]" << "1.1.1" << ""
384 << (QStringList()
385 << QStringLiteral("rowsAboutToBeRemoved(1.1.1)")
386 << QStringLiteral("rowsRemoved(1.1.1)")
387 << QStringLiteral("rowsAboutToBeRemoved(1.1)")
388 << QStringLiteral("rowsRemoved(1.1)")
389 << QStringLiteral("rowsAboutToBeRemoved(1)")
390 << QStringLiteral("rowsRemoved(1)"));
391
392 }
393
394 void testDataChangeOut()
395 {
396 QFETCH(QString, sourceStr);
397 QFETCH(QString, initialProxyStr);
398 QFETCH(QString, remove);
399 QFETCH(QString, expectedProxyStr);
400 QFETCH(QStringList, expectedSignals);
401
402 QStandardItemModel model;
403 fillModel(model, str: sourceStr);
404 QCOMPARE(treeAsString(model), sourceStr);
405
406 TestModel proxy(&model);
407 QCOMPARE(treeAsString(proxy), initialProxyStr);
408
409 ModelSignalSpy spy(proxy);
410
411 // When changing the data on the designated item to exclude this row again
412 QStandardItem *itemToChange = itemByText(model, text: remove);
413 QVERIFY(itemToChange->data(s_filterRole).toBool());
414 itemToChange->setData(value: false, role: s_filterRole);
415
416 // The proxy should update as expected
417 QCOMPARE(treeAsString(proxy), expectedProxyStr);
418
419 //qDebug() << spy.mSignals;
420 QCOMPARE(spy.mSignals, expectedSignals);
421 }
422
423 void testInsert()
424 {
425 QStandardItemModel model;
426 const QString sourceStr = QStringLiteral("[1[1.1[1.1.1]]]");
427 fillModel(model, str: sourceStr);
428 QCOMPARE(treeAsString(model), sourceStr);
429
430 TestModel proxy(&model);
431 QCOMPARE(treeAsString(proxy), QString());
432
433 ModelSignalSpy spy(proxy);
434 QStandardItem *item_1_1_1 = model.item(row: 0)->child(row: 0)->child(row: 0);
435 QStandardItem *item_1_1_1_1 = new QStandardItem(QStringLiteral("1.1.1.1"));
436 item_1_1_1_1->setData(value: true, role: s_filterRole);
437 item_1_1_1->appendRow(aitem: item_1_1_1_1);
438 QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.1[1.1.1.1*]]]]"));
439
440 QCOMPARE(spy.mSignals, QStringList() << QStringLiteral("rowsAboutToBeInserted(1)")
441 << QStringLiteral("rowsInserted(1)"));
442 }
443
444 // Start from [1[1.1[1.1.1 1.1.2[1.1.2.1*]]]]
445 // where 1.1.1 is hidden but 1.1 is shown, we want to insert a shown child in 1.1.1.
446 // The proxy ensures dataChanged is called on 1.1,
447 // so that 1.1.1 and 1.1.1.1 are included in the model.
448 void testInsertCousin()
449 {
450 QStandardItemModel model;
451 const QString sourceStr = QStringLiteral("[1[1.1[1.1.1 1.1.2[1.1.2.1*]]]]");
452 fillModel(model, str: sourceStr);
453 QCOMPARE(treeAsString(model), sourceStr);
454
455 TestModel proxy(&model);
456 QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.2[1.1.2.1*]]]]"));
457
458 ModelSignalSpy spy(proxy);
459 {
460 QStandardItem *item_1_1_1_1 = new QStandardItem(QStringLiteral("1.1.1.1"));
461 item_1_1_1_1->setData(value: true, role: s_filterRole);
462 QStandardItem *item_1_1_1 = model.item(row: 0)->child(row: 0)->child(row: 0);
463 item_1_1_1->appendRow(aitem: item_1_1_1_1);
464 }
465
466 QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.1[1.1.1.1*] 1.1.2[1.1.2.1*]]]]"));
467 //qDebug() << spy.mSignals;
468 QCOMPARE(spy.mSignals, QStringList()
469 << QStringLiteral("rowsAboutToBeInserted(1.1.1)")
470 << QStringLiteral("rowsInserted(1.1.1)")
471 << QStringLiteral("dataChanged(1.1)")
472 << QStringLiteral("dataChanged(1)"));
473 }
474
475 void testInsertWithChildren()
476 {
477 QStandardItemModel model;
478 const QString sourceStr = QStringLiteral("[1[1.1]]");
479 fillModel(model, str: sourceStr);
480 QCOMPARE(treeAsString(model), sourceStr);
481
482 TestModel proxy(&model);
483 QCOMPARE(treeAsString(proxy), QString());
484
485 ModelSignalSpy spy(proxy);
486 {
487 QStandardItem *item_1_1_1 = new QStandardItem(QStringLiteral("1.1.1"));
488 QStandardItem *item_1_1_1_1 = new QStandardItem(QStringLiteral("1.1.1.1"));
489 item_1_1_1_1->setData(value: true, role: s_filterRole);
490 item_1_1_1->appendRow(aitem: item_1_1_1_1);
491
492 QStandardItem *item_1_1 = model.item(row: 0)->child(row: 0);
493 item_1_1->appendRow(aitem: item_1_1_1);
494 }
495
496 QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.1[1.1.1.1*]]]]"));
497 QCOMPARE(spy.mSignals, QStringList()
498 << QStringLiteral("rowsAboutToBeInserted(1)")
499 << QStringLiteral("rowsInserted(1)"));
500 }
501
502 void testInsertIntoVisibleWithChildren()
503 {
504 QStandardItemModel model;
505 const QString sourceStr = QStringLiteral("[1[1.1[1.1.1*]]]");
506 fillModel(model, str: sourceStr);
507 QCOMPARE(treeAsString(model), sourceStr);
508
509 TestModel proxy(&model);
510 QCOMPARE(treeAsString(proxy), sourceStr);
511
512 ModelSignalSpy spy(proxy);
513 {
514 QStandardItem *item_1_1_2 = new QStandardItem(QStringLiteral("1.1.2"));
515 QStandardItem *item_1_1_2_1 = new QStandardItem(QStringLiteral("1.1.2.1"));
516 item_1_1_2_1->setData(value: true, role: s_filterRole);
517 item_1_1_2->appendRow(aitem: item_1_1_2_1);
518
519 QStandardItem *item_1_1 = model.item(row: 0)->child(row: 0);
520 item_1_1->appendRow(aitem: item_1_1_2);
521 }
522
523 QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]"));
524 QCOMPARE(spy.mSignals, QStringList()
525 << QStringLiteral("rowsAboutToBeInserted(1.1.2)")
526 << QStringLiteral("rowsInserted(1.1.2)"));
527 }
528
529 void testInsertBefore()
530 {
531 QStandardItemModel model;
532 const QString sourceStr = "[1[1.1[1.1.2*]]]";
533 fillModel(model, str: sourceStr);
534 QCOMPARE(treeAsString(model), sourceStr);
535
536 TestModel proxy(&model);
537 QCOMPARE(treeAsString(proxy), sourceStr);
538
539 ModelSignalSpy spy(proxy);
540 {
541 QStandardItem *item_1_1_1 = new QStandardItem("1.1.1");
542
543 QStandardItem *item_1_1 = model.item(row: 0)->child(row: 0);
544 item_1_1->insertRow(arow: 0, aitem: item_1_1_1);
545 }
546
547 QCOMPARE(treeAsString(proxy), QString("[1[1.1[1.1.2*]]]"));
548 QCOMPARE(spy.mSignals, QStringList());
549 }
550
551 void testInsertHidden() // inserting filtered-out rows shouldn't emit anything
552 {
553 QStandardItemModel model;
554 const QString sourceStr = QStringLiteral("[1[1.1]]");
555 fillModel(model, str: sourceStr);
556 QCOMPARE(treeAsString(model), sourceStr);
557
558 TestModel proxy(&model);
559 QCOMPARE(treeAsString(proxy), QString());
560
561 ModelSignalSpy spy(proxy);
562 {
563 QStandardItem *item_1_1_1 = new QStandardItem(QStringLiteral("1.1.1"));
564 QStandardItem *item_1_1_1_1 = new QStandardItem(QStringLiteral("1.1.1.1"));
565 item_1_1_1->appendRow(aitem: item_1_1_1_1);
566
567 QStandardItem *item_1_1 = model.item(row: 0)->child(row: 0);
568 item_1_1->appendRow(aitem: item_1_1_1);
569 }
570
571 QCOMPARE(treeAsString(proxy), QString());
572 QCOMPARE(spy.mSignals, QStringList());
573 }
574
575 void testConsecutiveInserts_data()
576 {
577 testInitialFiltering_data();
578 }
579
580 void testConsecutiveInserts()
581 {
582 QFETCH(QString, sourceStr);
583 QFETCH(QString, proxyStr);
584
585 QStandardItemModel model;
586 TestModel proxy(&model); // this time the proxy listens to the model while we fill it
587
588 fillModel(model, str: sourceStr);
589 QCOMPARE(treeAsString(model), sourceStr);
590 QCOMPARE(treeAsString(proxy), proxyStr);
591 }
592
593 void testRemove_data()
594 {
595 QTest::addColumn<QString>(name: "sourceStr");
596 QTest::addColumn<QString>(name: "initialProxyStr");
597 QTest::addColumn<QString>(name: "remove"); // remove this item
598 QTest::addColumn<QString>(name: "expectedProxyStr");
599 QTest::addColumn<QStringList>(name: "expectedSignals");
600
601 const QStringList remove1_1_1 = (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") << QStringLiteral("rowsRemoved(1.1.1)"));
602
603 QTest::newRow(dataTag: "toplevel") << "[1* 2* 3*]" << "[1* 2* 3*]" << "1" << "[2* 3*]"
604 << (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1)") << QStringLiteral("rowsRemoved(1)"));
605
606 QTest::newRow(dataTag: "remove_hidden") << "[1 2* 3*]" << "[2* 3*]" << "1" << "[2* 3*]" << QStringList();
607
608 QTest::newRow(dataTag: "parent_hidden") << "[1[1.1[1.1.1]]]" << "" << "1.1.1" << "" << QStringList();
609
610 QTest::newRow(dataTag: "child_hidden") << "[1[1.1*[1.1.1]]]" << "[1[1.1*]]" << "1.1.1" << "[1[1.1*]]" << QStringList();
611
612 QTest::newRow(dataTag: "parent_visible") << "[1[1.1*[1.1.1*]]]" << "[1[1.1*[1.1.1*]]]" << "1.1.1" << "[1[1.1*]]"
613 << remove1_1_1;
614
615 QTest::newRow(dataTag: "visible") << "[1[1.1[1.1.1* 1.1.2*]]]" << "[1[1.1[1.1.1* 1.1.2*]]]" << "1.1.1" << "[1[1.1[1.1.2*]]]"
616 << remove1_1_1;
617 QTest::newRow(dataTag: "visible_cousin") << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" << "1.1.1" << "[1[1.1[1.1.2[1.1.2.1*]]]]"
618 << remove1_1_1;
619
620 // The following tests trigger the removal of an ascendant.
621 // We could optimize the rows{AboutToBe,}Removed(1.1.1) away...
622
623 QTest::newRow(dataTag: "remove_parent") << "[1[1.1[1.1.1* 1.1.2] 1.2*]]" << "[1[1.1[1.1.1*] 1.2*]]" << "1.1.1" << "[1[1.2*]]"
624 << (QStringList()
625 << QStringLiteral("rowsAboutToBeRemoved(1.1.1)")
626 << QStringLiteral("rowsRemoved(1.1.1)")
627 << QStringLiteral("rowsAboutToBeRemoved(1.1)")
628 << QStringLiteral("rowsRemoved(1.1)")
629 << QStringLiteral("dataChanged(1)"));
630
631 QTest::newRow(dataTag: "with_children") << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" << "1.1.1" << "[2*]"
632 << (QStringList()
633 << QStringLiteral("rowsAboutToBeRemoved(1.1.1)")
634 << QStringLiteral("rowsRemoved(1.1.1)")
635 << QStringLiteral("rowsAboutToBeRemoved(1)")
636 << QStringLiteral("rowsRemoved(1)"));
637
638 QTest::newRow(dataTag: "last_visible") << "[1[1.1[1.1.1* 1.1.2]]]" << "[1[1.1[1.1.1*]]]" << "1.1.1" << ""
639 << (QStringList()
640 << QStringLiteral("rowsAboutToBeRemoved(1.1.1)")
641 << QStringLiteral("rowsRemoved(1.1.1)")
642 << QStringLiteral("rowsAboutToBeRemoved(1)")
643 << QStringLiteral("rowsRemoved(1)"));
644
645
646 }
647
648 void testRemove()
649 {
650 QFETCH(QString, sourceStr);
651 QFETCH(QString, initialProxyStr);
652 QFETCH(QString, remove);
653 QFETCH(QString, expectedProxyStr);
654 QFETCH(QStringList, expectedSignals);
655
656 QStandardItemModel model;
657 fillModel(model, str: sourceStr);
658 QCOMPARE(treeAsString(model), sourceStr);
659
660 TestModel proxy(&model);
661 QCOMPARE(treeAsString(proxy), initialProxyStr);
662
663 ModelSignalSpy spy(proxy);
664 QStandardItem *itemToRemove = itemByText(model, text: remove);
665 QVERIFY(itemToRemove);
666 if (itemToRemove->parent())
667 itemToRemove->parent()->removeRow(row: itemToRemove->row());
668 else
669 model.removeRow(arow: itemToRemove->row());
670 QCOMPARE(treeAsString(proxy), expectedProxyStr);
671
672 //qDebug() << spy.mSignals;
673 QCOMPARE(spy.mSignals, expectedSignals);
674 }
675
676 void testStandardFiltering_data()
677 {
678 QTest::addColumn<QString>(name: "sourceStr");
679 QTest::addColumn<QString>(name: "initialProxyStr");
680 QTest::addColumn<QString>(name: "filter");
681 QTest::addColumn<QString>(name: "expectedProxyStr");
682
683 QTest::newRow(dataTag: "select_child") << "[1[1.1[1.1.1* 1.1.2*]]]" << "[1[1.1[1.1.1* 1.1.2*]]]"
684 << "1.1.2" << "[1[1.1[1.1.2*]]]";
685
686 QTest::newRow(dataTag: "filter_all_out") << "[1[1.1[1.1.1*]]]" << "[1[1.1[1.1.1*]]]"
687 << "test" << "";
688
689 QTest::newRow(dataTag: "select_parent") << "[1[1.1[1.1.1*[child*] 1.1.2*]]]" << "[1[1.1[1.1.1*[child*] 1.1.2*]]]"
690 << "1.1.1" << "[1[1.1[1.1.1*]]]";
691
692 }
693
694 void testStandardFiltering()
695 {
696 QFETCH(QString, sourceStr);
697 QFETCH(QString, initialProxyStr);
698 QFETCH(QString, filter);
699 QFETCH(QString, expectedProxyStr);
700
701 QStandardItemModel model;
702 fillModel(model, str: sourceStr);
703 QCOMPARE(treeAsString(model), sourceStr);
704
705 TestModel proxy(&model);
706 QCOMPARE(treeAsString(proxy), initialProxyStr);
707
708 ModelSignalSpy spy(proxy);
709
710 //qDebug() << "setFilterFixedString";
711 proxy.setFilterRole(Qt::DisplayRole);
712 proxy.setFilterFixedString(filter);
713
714 QCOMPARE(treeAsString(proxy), expectedProxyStr);
715
716 }
717
718private:
719 QStandardItem *itemByText(const QStandardItemModel& model, const QString &text) const {
720 QModelIndexList list = model.match(start: model.index(row: 0, column: 0), role: Qt::DisplayRole, value: text, hits: 1, flags: Qt::MatchRecursive);
721 return list.isEmpty() ? 0 : model.itemFromIndex(index: list.first());
722 }
723};
724
725QTEST_GUILESS_MAIN(tst_QSortFilterProxyModel_Recursive)
726#include "tst_qsortfilterproxymodel_recursive.moc"
727

source code of qtbase/tests/auto/corelib/itemmodels/qsortfilterproxymodel_recursive/tst_qsortfilterproxymodel_recursive.cpp