Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qabstractitemmodeltester.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
6
7#include <private/qobject_p.h>
8#include <private/qabstractitemmodel_p.h>
9#include <QtCore/qmetatype.h>
10#include <QtCore/QPointer>
11#include <QtCore/QAbstractItemModel>
12#include <QtCore/QStack>
13#include <QTest>
14#include <QLoggingCategory>
15
17
18Q_STATIC_LOGGING_CATEGORY(lcModelTest, "qt.modeltest")
19
20#define MODELTESTER_VERIFY(statement) do
21 {
22 if (!verify(static_cast<bool>(statement), #statement, "", __FILE__, __LINE__))
23 return; \
24}while (false)
25
26#define MODELTESTER_COMPARE(actual, expected) do
27 {
28 if (!compare((actual), (expected), #actual, #expected, __FILE__, __LINE__))
29 return; \
30}while (false)
31
32class QAbstractItemModelTesterPrivate : public QObjectPrivate
33{
34 Q_DECLARE_PUBLIC(QAbstractItemModelTester)
35public:
36 QAbstractItemModelTesterPrivate(QAbstractItemModel *model, QAbstractItemModelTester::FailureReportingMode failureReportingMode);
37
38 void nonDestructiveBasicTest();
39 void rowAndColumnCount();
40 void hasIndex();
41 void index();
42 void parent();
43 void data();
44
45 void runAllTests();
46
47 void layoutAboutToBeChanged();
48 void layoutChanged();
49
50 void modelAboutToBeReset();
51 void modelReset();
52
53 void columnsAboutToBeInserted(const QModelIndex &parent, int first, int last);
54 void columnsInserted(const QModelIndex &parent, int first, int last);
55 void columnsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd,
56 const QModelIndex &destinationParent, int destinationColumn);
57 void columnsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination,
58 int column);
59 void columnsAboutToBeRemoved(const QModelIndex &parent, int first, int last);
60 void columnsRemoved(const QModelIndex &parent, int first, int last);
61
62 void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end);
63 void rowsInserted(const QModelIndex &parent, int start, int end);
64 void rowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd,
65 const QModelIndex &destinationParent, int destinationRow);
66 void rowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination,
67 int row);
68 void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end);
69 void rowsRemoved(const QModelIndex &parent, int start, int end);
70 void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
71 void headerDataChanged(Qt::Orientation orientation, int start, int end);
72
73private:
74 void checkChildren(const QModelIndex &parent, int currentDepth = 0);
75
76 bool verify(bool statement, const char *statementStr, const char *description, const char *file, int line);
77 void testDataGuiRoles(QAbstractItemModelTester *tester);
78
79 template<typename T1, typename T2>
80 bool compare(const T1 &t1, const T2 &t2,
81 const char *actual, const char *expected,
82 const char *file, int line);
83
84 QPointer<QAbstractItemModel> model;
85 QAbstractItemModelTester::FailureReportingMode failureReportingMode;
86
87 struct Changing {
88 QModelIndex parent;
89 int oldSize;
90 QVariant last;
91 QVariant next;
92 };
93 QStack<Changing> insert;
94 QStack<Changing> remove;
95
96 bool useFetchMore = true;
97 bool fetchingMore;
98
99 enum class ChangeInFlight {
100 None,
101 ColumnsInserted,
102 ColumnsMoved,
103 ColumnsRemoved,
104 LayoutChanged,
105 ModelReset,
106 RowsInserted,
107 RowsMoved,
108 RowsRemoved
109 };
110 ChangeInFlight changeInFlight = ChangeInFlight::None;
111
112 QList<QPersistentModelIndex> changing;
113};
114
115/*!
116 \class QAbstractItemModelTester
117 \since 5.11
118 \inmodule QtTest
119
120 \brief The QAbstractItemModelTester class helps testing QAbstractItemModel subclasses.
121
122 The QAbstractItemModelTester class is a utility class to test item models.
123
124 When implementing an item model (that is, a concrete QAbstractItemModel
125 subclass) one must abide to a very strict set of rules that ensure
126 consistency for users of the model (views, proxy models, and so on).
127
128 For instance, for a given index, a model's reimplementation of
129 \l{QAbstractItemModel::hasChildren()}{hasChildren()} must be consistent
130 with the values returned by \l{QAbstractItemModel::rowCount()}{rowCount()}
131 and \l{QAbstractItemModel::columnCount()}{columnCount()}.
132
133 QAbstractItemModelTester helps catching the most common errors in custom
134 item model classes. By performing a series of tests, it
135 will try to check that the model status is consistent at all times. The
136 tests will be repeated automatically every time the model is modified.
137
138 QAbstractItemModelTester employs non-destructive tests, which typically
139 consist in reading data and metadata out of a given item model.
140 QAbstractItemModelTester will also attempt illegal modifications of
141 the model. In models which are properly implemented, such attempts
142 should be rejected, and no data should be changed as a consequence.
143
144 \section1 Usage
145
146 Using QAbstractItemModelTester is straightforward. In a \l{Qt Test Overview}{test case}
147 it is sufficient to create an instance, passing the model that
148 needs to be tested to the constructor:
149
150 \code
151 MyModel *modelToBeTested = ...;
152 auto tester = new QAbstractItemModelTester(modelToBeTested);
153 \endcode
154
155 QAbstractItemModelTester will report testing failures through the
156 Qt Test logging mechanisms.
157
158 It is also possible to use QAbstractItemModelTester outside of a test case.
159 For instance, it may be useful to test an item model used by an application
160 without the need of building an explicit unit test for such a model (which
161 might be challenging). In order to use QAbstractItemModelTester outside of
162 a test case, pass one of the \c QAbstractItemModelTester::FailureReportingMode
163 enumerators to its constructor, therefore specifying how failures should
164 be logged.
165
166 QAbstractItemModelTester may also report additional debugging information
167 as logging messages under the \c qt.modeltest logging category. Such
168 debug logging is disabled by default; refer to the
169 QLoggingCategory documentation to learn how to enable it.
170
171 \note While QAbstractItemModelTester is a valid help for development and
172 testing of custom item models, it does not (and cannot) catch all possible
173 problems in QAbstractItemModel subclasses. Notably, it will never perform
174 meaningful destructive testing of a model, which must be therefore tested
175 separately.
176
177 \sa {Model/View Programming}, QAbstractItemModel
178*/
179
180/*!
181 \enum QAbstractItemModelTester::FailureReportingMode
182
183 This enumeration specifies how QAbstractItemModelTester should report
184 a failure when it tests a QAbstractItemModel subclass.
185
186 \value QtTest The failures will be reported as QtTest test failures.
187
188 \value Warning The failures will be reported as
189 warning messages in the \c{qt.modeltest} logging category.
190
191 \value Fatal A failure will cause immediate and
192 abnormal program termination. The reason for the failure will be reported
193 using \c{qFatal()}.
194*/
195
196/*!
197 Creates a model tester instance, with the given \a parent, that will test
198 the model \a model.
199
200 The failure reporting mode is set to FailureReportingMode::QtTest.
201*/
202QAbstractItemModelTester::QAbstractItemModelTester(QAbstractItemModel *model, QObject *parent)
203 : QAbstractItemModelTester(model, FailureReportingMode::QtTest, parent)
204{
205}
206
207/*!
208 Creates a model tester instance, with the given \a parent, that will test
209 the model \a model, using the specified \a mode to report test failures.
210
211 \sa QAbstractItemModelTester::FailureReportingMode
212*/
213QAbstractItemModelTester::QAbstractItemModelTester(QAbstractItemModel *model, FailureReportingMode mode, QObject *parent)
214 : QObject(*new QAbstractItemModelTesterPrivate(model, mode), parent)
215{
216 if (!model)
217 qFatal("%s: model must not be null", Q_FUNC_INFO);
218
219 Q_D(QAbstractItemModelTester);
220
221 auto runAllTests = [d] { d->runAllTests(); };
222
223 connect(model, &QAbstractItemModel::columnsAboutToBeInserted,
224 this, runAllTests);
225 connect(model, &QAbstractItemModel::columnsAboutToBeRemoved,
226 this, runAllTests);
227 connect(model, &QAbstractItemModel::columnsInserted,
228 this, runAllTests);
229 connect(model, &QAbstractItemModel::columnsRemoved,
230 this, runAllTests);
231 connect(model, &QAbstractItemModel::dataChanged,
232 this, runAllTests);
233 connect(model, &QAbstractItemModel::headerDataChanged,
234 this, runAllTests);
235 connect(model, &QAbstractItemModel::layoutAboutToBeChanged,
236 this, runAllTests);
237 connect(model, &QAbstractItemModel::layoutChanged,
238 this, runAllTests);
239 connect(model, &QAbstractItemModel::modelReset,
240 this, runAllTests);
241 connect(model, &QAbstractItemModel::rowsAboutToBeInserted,
242 this, runAllTests);
243 connect(model, &QAbstractItemModel::rowsAboutToBeRemoved,
244 this, runAllTests);
245 connect(model, &QAbstractItemModel::rowsInserted,
246 this, runAllTests);
247 connect(model, &QAbstractItemModel::rowsRemoved,
248 this, runAllTests);
249
250 // Special checks for changes
251 connect(model, &QAbstractItemModel::layoutAboutToBeChanged,
252 this, [d]{ d->layoutAboutToBeChanged(); });
253 connect(model, &QAbstractItemModel::layoutChanged,
254 this, [d]{ d->layoutChanged(); });
255
256 // column operations
257 connect(model, &QAbstractItemModel::columnsAboutToBeInserted,
258 this, [d](const QModelIndex &parent, int start, int end) { d->columnsAboutToBeInserted(parent, start, end); });
259 connect(model, &QAbstractItemModel::columnsAboutToBeMoved,
260 this, [d](const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationColumn) {
261 d->columnsAboutToBeMoved(sourceParent, sourceStart, sourceEnd, destinationParent, destinationColumn); });
262 connect(model, &QAbstractItemModel::columnsAboutToBeRemoved,
263 this, [d](const QModelIndex &parent, int start, int end) { d->columnsAboutToBeRemoved(parent, start, end); });
264 connect(model, &QAbstractItemModel::columnsInserted,
265 this, [d](const QModelIndex &parent, int start, int end) { d->columnsInserted(parent, start, end); });
266 connect(model, &QAbstractItemModel::columnsMoved,
267 this, [d](const QModelIndex &parent, int start, int end, const QModelIndex &destination, int col) {
268 d->columnsMoved(parent, start, end, destination, col); });
269 connect(model, &QAbstractItemModel::columnsRemoved,
270 this, [d](const QModelIndex &parent, int start, int end) { d->columnsRemoved(parent, start, end); });
271
272 // row operations
273 connect(model, &QAbstractItemModel::rowsAboutToBeInserted,
274 this, [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeInserted(parent, start, end); });
275 connect(model, &QAbstractItemModel::rowsAboutToBeMoved,
276 this, [d](const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow) {
277 d->rowsAboutToBeMoved(sourceParent, sourceStart, sourceEnd, destinationParent, destinationRow); });
278 connect(model, &QAbstractItemModel::rowsAboutToBeRemoved,
279 this, [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeRemoved(parent, start, end); });
280 connect(model, &QAbstractItemModel::rowsInserted,
281 this, [d](const QModelIndex &parent, int start, int end) { d->rowsInserted(parent, start, end); });
282 connect(model, &QAbstractItemModel::rowsMoved,
283 this, [d](const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row) {
284 d->rowsMoved(parent, start, end, destination, row); });
285 connect(model, &QAbstractItemModel::rowsRemoved,
286 this, [d](const QModelIndex &parent, int start, int end) { d->rowsRemoved(parent, start, end); });
287
288 // reset
289 connect(model, &QAbstractItemModel::modelAboutToBeReset,
290 this, [d]() { d->modelAboutToBeReset(); });
291 connect(model, &QAbstractItemModel::modelReset,
292 this, [d]() { d->modelReset(); });
293
294 // data
295 connect(model, &QAbstractItemModel::dataChanged,
296 this, [d](const QModelIndex &topLeft, const QModelIndex &bottomRight) { d->dataChanged(topLeft, bottomRight); });
297 connect(model, &QAbstractItemModel::headerDataChanged,
298 this, [d](Qt::Orientation orientation, int start, int end) { d->headerDataChanged(orientation, start, end); });
299
300 runAllTests();
301}
302
303/*!
304 Returns the model that this instance is testing.
305*/
306QAbstractItemModel *QAbstractItemModelTester::model() const
307{
308 Q_D(const QAbstractItemModelTester);
309 return d->model.data();
310}
311
312/*!
313 Returns the mode that this instancing is using to report test failures.
314
315 \sa QAbstractItemModelTester::FailureReportingMode
316*/
317QAbstractItemModelTester::FailureReportingMode QAbstractItemModelTester::failureReportingMode() const
318{
319 Q_D(const QAbstractItemModelTester);
320 return d->failureReportingMode;
321}
322
323/*!
324 If \a value is true, enables dynamic population of the
325 tested model, which is the default.
326 If \a value is false, it disables it.
327
328 \since 6.4
329 \sa QAbstractItemModel::fetchMore()
330*/
331void QAbstractItemModelTester::setUseFetchMore(bool value)
332{
333 Q_D(QAbstractItemModelTester);
334 d->useFetchMore = value;
335}
336
337bool QAbstractItemModelTester::verify(bool statement, const char *statementStr, const char *description, const char *file, int line)
338{
339 Q_D(QAbstractItemModelTester);
340 return d->verify(statement, statementStr, description, file, line);
341}
342
343QAbstractItemModelTesterPrivate::QAbstractItemModelTesterPrivate(QAbstractItemModel *model, QAbstractItemModelTester::FailureReportingMode failureReportingMode)
344 : model(model),
345 failureReportingMode(failureReportingMode),
346 fetchingMore(false)
347{
348}
349
350void QAbstractItemModelTesterPrivate::runAllTests()
351{
352 if (fetchingMore)
353 return;
354 nonDestructiveBasicTest();
355 rowAndColumnCount();
356 hasIndex();
357 index();
358 parent();
359 data();
360}
361
362/*
363 nonDestructiveBasicTest tries to call a number of the basic functions (not all)
364 to make sure the model doesn't outright segfault, testing the functions that makes sense.
365*/
366void QAbstractItemModelTesterPrivate::nonDestructiveBasicTest()
367{
368 MODELTESTER_VERIFY(!model->buddy(QModelIndex()).isValid());
369 model->canFetchMore(QModelIndex());
370 MODELTESTER_VERIFY(model->columnCount(QModelIndex()) >= 0);
371 if (useFetchMore) {
372 fetchingMore = true;
373 model->fetchMore(QModelIndex());
374 fetchingMore = false;
375 }
376 Qt::ItemFlags flags = model->flags(QModelIndex());
377 MODELTESTER_VERIFY(flags == Qt::ItemIsDropEnabled || flags == 0);
378 model->hasChildren(QModelIndex());
379 const bool hasRow = model->hasIndex(0, 0);
380 QVariant cache;
381 if (hasRow)
382 model->match(model->index(0, 0), -1, cache);
383 model->mimeTypes();
384 MODELTESTER_VERIFY(!model->parent(QModelIndex()).isValid());
385 MODELTESTER_VERIFY(model->rowCount() >= 0);
386 model->span(QModelIndex());
387 model->supportedDropActions();
388 model->roleNames();
389}
390
391/*
392 Tests model's implementation of QAbstractItemModel::rowCount(),
393 columnCount() and hasChildren().
394
395 Models that are dynamically populated are not as fully tested here.
396 */
397void QAbstractItemModelTesterPrivate::rowAndColumnCount()
398{
399 if (!model->hasChildren())
400 return;
401
402 QModelIndex topIndex = model->index(0, 0, QModelIndex());
403
404 // check top row
405 int rows = model->rowCount(topIndex);
406 MODELTESTER_VERIFY(rows >= 0);
407
408 int columns = model->columnCount(topIndex);
409 MODELTESTER_VERIFY(columns >= 0);
410
411 if (rows == 0 || columns == 0)
412 return;
413
414 MODELTESTER_VERIFY(model->hasChildren(topIndex));
415
416 QModelIndex secondLevelIndex = model->index(0, 0, topIndex);
417 MODELTESTER_VERIFY(secondLevelIndex.isValid());
418
419 rows = model->rowCount(secondLevelIndex);
420 MODELTESTER_VERIFY(rows >= 0);
421
422 columns = model->columnCount(secondLevelIndex);
423 MODELTESTER_VERIFY(columns >= 0);
424
425 if (rows == 0 || columns == 0)
426 return;
427
428 MODELTESTER_VERIFY(model->hasChildren(secondLevelIndex));
429
430 // rowCount() / columnCount() are tested more extensively in checkChildren()
431}
432
433/*
434 Tests model's implementation of QAbstractItemModel::hasIndex()
435 */
436void QAbstractItemModelTesterPrivate::hasIndex()
437{
438 // Make sure that invalid values returns an invalid index
439 MODELTESTER_VERIFY(!model->hasIndex(-2, -2));
440 MODELTESTER_VERIFY(!model->hasIndex(-2, 0));
441 MODELTESTER_VERIFY(!model->hasIndex(0, -2));
442
443 const int rows = model->rowCount();
444 const int columns = model->columnCount();
445
446 // check out of bounds
447 MODELTESTER_VERIFY(!model->hasIndex(rows, columns));
448 MODELTESTER_VERIFY(!model->hasIndex(rows + 1, columns + 1));
449
450 if (rows > 0 && columns > 0)
451 MODELTESTER_VERIFY(model->hasIndex(0, 0));
452
453 // hasIndex() is tested more extensively in checkChildren(),
454 // but this catches the big mistakes
455}
456
457/*
458 Tests model's implementation of QAbstractItemModel::index()
459 */
460void QAbstractItemModelTesterPrivate::index()
461{
462 const int rows = model->rowCount();
463 const int columns = model->columnCount();
464
465 for (int row = 0; row < rows; ++row) {
466 for (int column = 0; column < columns; ++column) {
467 // Make sure that the same index is *always* returned
468 QModelIndex a = model->index(row, column);
469 QModelIndex b = model->index(row, column);
470 MODELTESTER_VERIFY(a.isValid());
471 MODELTESTER_VERIFY(b.isValid());
473 }
474 }
475
476 // index() is tested more extensively in checkChildren(),
477 // but this catches the big mistakes
478}
479
480/*
481 Tests model's implementation of QAbstractItemModel::parent()
482 */
483void QAbstractItemModelTesterPrivate::parent()
484{
485 // Make sure the model won't crash and will return an invalid QModelIndex
486 // when asked for the parent of an invalid index.
487 MODELTESTER_VERIFY(!model->parent(QModelIndex()).isValid());
488
489 if (model->rowCount() == 0 || model->columnCount() == 0)
490 return;
491
492 // Column 0 | Column 1 |
493 // QModelIndex() | |
494 // \- topIndex | topIndex1 |
495 // \- childIndex | childIndex1 |
496
497 // Common error test #1, make sure that a top level index has a parent
498 // that is a invalid QModelIndex.
499 QModelIndex topIndex = model->index(0, 0, QModelIndex());
500 MODELTESTER_VERIFY(topIndex.isValid());
501 MODELTESTER_VERIFY(!model->parent(topIndex).isValid());
502
503 // Common error test #2, make sure that a second level index has a parent
504 // that is the first level index.
505 if (model->rowCount(topIndex) > 0 && model->columnCount(topIndex) > 0) {
506 QModelIndex childIndex = model->index(0, 0, topIndex);
507 MODELTESTER_VERIFY(childIndex.isValid());
508 MODELTESTER_COMPARE(model->parent(childIndex), topIndex);
509 }
510
511 // Common error test #3, the second column should NOT have the same children
512 // as the first column in a row.
513 // Usually the second column shouldn't have children.
514 if (model->hasIndex(0, 1)) {
515 QModelIndex topIndex1 = model->index(0, 1, QModelIndex());
516 MODELTESTER_VERIFY(topIndex1.isValid());
517 if (model->rowCount(topIndex) > 0 && model->rowCount(topIndex1) > 0) {
518 QModelIndex childIndex = model->index(0, 0, topIndex);
519 MODELTESTER_VERIFY(childIndex.isValid());
520 QModelIndex childIndex1 = model->index(0, 0, topIndex1);
521 MODELTESTER_VERIFY(childIndex1.isValid());
522 MODELTESTER_VERIFY(childIndex != childIndex1);
523 }
524 }
525
526 // Full test, walk n levels deep through the model making sure that all
527 // parent's children correctly specify their parent.
528 checkChildren(QModelIndex());
529}
530
531/*
532 Called from the parent() test.
533
534 A model that returns an index of parent X should also return X when asking
535 for the parent of the index.
536
537 This recursive function does pretty extensive testing on the whole model in an
538 effort to catch edge cases.
539
540 This function assumes that rowCount(), columnCount() and index() already work.
541 If they have a bug it will point it out, but the above tests should have already
542 found the basic bugs because it is easier to figure out the problem in
543 those tests then this one.
544 */
545void QAbstractItemModelTesterPrivate::checkChildren(const QModelIndex &parent, int currentDepth)
546{
547 // First just try walking back up the tree.
548 QModelIndex p = parent;
549 while (p.isValid())
550 p = p.parent();
551
552 // For models that are dynamically populated
553 if (model->canFetchMore(parent) && useFetchMore) {
554 fetchingMore = true;
555 model->fetchMore(parent);
556 fetchingMore = false;
557 }
558
559 const int rows = model->rowCount(parent);
560 const int columns = model->columnCount(parent);
561
562 if (rows > 0)
563 MODELTESTER_VERIFY(model->hasChildren(parent));
564
565 // Some further testing against rows(), columns(), and hasChildren()
566 MODELTESTER_VERIFY(rows >= 0);
567 MODELTESTER_VERIFY(columns >= 0);
568 if (rows > 0 && columns > 0)
569 MODELTESTER_VERIFY(model->hasChildren(parent));
570
571 const QModelIndex topLeftChild = model->index(0, 0, parent);
572
573 MODELTESTER_VERIFY(!model->hasIndex(rows, 0, parent));
574 MODELTESTER_VERIFY(!model->hasIndex(rows + 1, 0, parent));
575
576 for (int r = 0; r < rows; ++r) {
577 MODELTESTER_VERIFY(!model->hasIndex(r, columns, parent));
578 MODELTESTER_VERIFY(!model->hasIndex(r, columns + 1, parent));
579 for (int c = 0; c < columns; ++c) {
580 MODELTESTER_VERIFY(model->hasIndex(r, c, parent));
581 QModelIndex index = model->index(r, c, parent);
582 // rowCount() and columnCount() said that it existed...
583 if (!index.isValid())
584 qCWarning(lcModelTest) << "Got invalid index at row=" << r << "col=" << c << "parent=" << parent;
585 MODELTESTER_VERIFY(index.isValid());
586
587 // index() should always return the same index when called twice in a row
588 QModelIndex modifiedIndex = model->index(r, c, parent);
589 MODELTESTER_COMPARE(index, modifiedIndex);
590
591 {
592 const QModelIndex sibling = model->sibling(r, c, topLeftChild);
593 MODELTESTER_COMPARE(index, sibling);
594 }
595 {
596 const QModelIndex sibling = topLeftChild.sibling(r, c);
597 MODELTESTER_COMPARE(index, sibling);
598 }
599
600 // Some basic checking on the index that is returned
601 MODELTESTER_COMPARE(index.model(), model);
602 MODELTESTER_COMPARE(index.row(), r);
603 MODELTESTER_COMPARE(index.column(), c);
604
605 // If the next test fails here is some somewhat useful debug you play with.
606 if (model->parent(index) != parent) {
607 qCWarning(lcModelTest) << "Inconsistent parent() implementation detected:";
608 qCWarning(lcModelTest) << " index=" << index << "exp. parent=" << parent << "act. parent=" << model->parent(index);
609 qCWarning(lcModelTest) << " row=" << r << "col=" << c << "depth=" << currentDepth;
610 qCWarning(lcModelTest) << " data for child" << model->data(index).toString();
611 qCWarning(lcModelTest) << " data for parent" << model->data(parent).toString();
612 }
613
614 // Check that we can get back our real parent.
615 MODELTESTER_COMPARE(model->parent(index), parent);
616
617 QPersistentModelIndex persistentIndex = index;
618
619 // recursively go down the children
620 if (model->hasChildren(index) && currentDepth < 10)
621 checkChildren(index, currentDepth + 1);
622
623 // make sure that after testing the children that the index doesn't change.
624 QModelIndex newerIndex = model->index(r, c, parent);
625 MODELTESTER_COMPARE(persistentIndex, newerIndex);
626 }
627 }
628}
629
630void QAbstractItemModelTesterPrivate::testDataGuiRoles(QAbstractItemModelTester *tester)
631{
632 const auto model = tester->model();
633 Q_ASSERT(model);
634
635 if (!model->hasChildren())
636 return;
637
638 static const QMetaType pixmapType = QMetaType(QMetaType::QPixmap);
639 if (!pixmapType.isValid())
640 return;
641
642 static const QMetaType imageType = QMetaType(QMetaType::QImage);
643 static const QMetaType iconType = QMetaType(QMetaType::QIcon);
644 static const QMetaType colorType = QMetaType(QMetaType::QColor);
645 static const QMetaType brushType = QMetaType(QMetaType::QBrush);
646 static const QMetaType fontType = QMetaType(QMetaType::QFont);
647
648 QVariant variant = model->data(model->index(0, 0), Qt::DecorationRole);
649 if (variant.isValid()) {
650 MODELTESTER_VERIFY(variant.canConvert(pixmapType)
651 || variant.canConvert(imageType)
652 || variant.canConvert(iconType)
653 || variant.canConvert(colorType)
654 || variant.canConvert(brushType));
655 }
656
657 // General Purpose roles that should return a QFont
658 variant = model->data(model->index(0, 0), Qt::FontRole);
659 if (variant.isValid())
660 MODELTESTER_VERIFY(variant.canConvert(fontType));
661
662 // General Purpose roles that should return a QColor or a QBrush
663 variant = model->data(model->index(0, 0), Qt::BackgroundRole);
664 if (variant.isValid())
665 MODELTESTER_VERIFY(variant.canConvert(colorType) || variant.canConvert(brushType));
666
667 variant = model->data(model->index(0, 0), Qt::ForegroundRole);
668 if (variant.isValid())
669 MODELTESTER_VERIFY(variant.canConvert(colorType) || variant.canConvert(brushType));
670}
671
672/*
673 Tests model's implementation of QAbstractItemModel::data()
674 */
675void QAbstractItemModelTesterPrivate::data()
676{
677 if (model->rowCount() == 0 || model->columnCount() == 0)
678 return;
679
680 MODELTESTER_VERIFY(model->index(0, 0).isValid());
681
682 // General Purpose roles that should return a QString
683 QVariant variant;
684 variant = model->data(model->index(0, 0), Qt::DisplayRole);
685 if (variant.isValid())
686 MODELTESTER_VERIFY(variant.canConvert<QString>());
687 variant = model->data(model->index(0, 0), Qt::ToolTipRole);
688 if (variant.isValid())
689 MODELTESTER_VERIFY(variant.canConvert<QString>());
690 variant = model->data(model->index(0, 0), Qt::StatusTipRole);
691 if (variant.isValid())
692 MODELTESTER_VERIFY(variant.canConvert<QString>());
693 variant = model->data(model->index(0, 0), Qt::WhatsThisRole);
694 if (variant.isValid())
695 MODELTESTER_VERIFY(variant.canConvert<QString>());
696
697 // General Purpose roles that should return a QSize
698 variant = model->data(model->index(0, 0), Qt::SizeHintRole);
699 if (variant.isValid())
700 MODELTESTER_VERIFY(variant.canConvert<QSize>());
701
702 // Check that the alignment is one we know about
703 QVariant textAlignmentVariant = model->data(model->index(0, 0), Qt::TextAlignmentRole);
704 if (textAlignmentVariant.isValid()) {
705 Qt::Alignment alignment = QtPrivate::legacyFlagValueFromModelData<Qt::Alignment>(textAlignmentVariant);
706 MODELTESTER_COMPARE(alignment, (alignment & (Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask)));
707 }
708
709 // Check that the "check state" is one we know about.
710 QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole);
711 if (checkStateVariant.isValid()) {
712 Qt::CheckState state = QtPrivate::legacyEnumValueFromModelData<Qt::CheckState>(checkStateVariant);
713 MODELTESTER_VERIFY(state == Qt::Unchecked
714 || state == Qt::PartiallyChecked
715 || state == Qt::Checked);
716 }
717
718 QVariant sizeHintVariant = model->data(model->index(0, 0), Qt::SizeHintRole);
719 if (sizeHintVariant.isValid())
720 MODELTESTER_VERIFY(sizeHintVariant.canConvert<QSize>());
721
722 Q_Q(QAbstractItemModelTester);
723 testDataGuiRoles(q);
724}
725
726void QAbstractItemModelTesterPrivate::columnsAboutToBeInserted(const QModelIndex &parent, int start,
727 int end)
728{
729 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None);
730 changeInFlight = ChangeInFlight::ColumnsInserted;
731
732 qCDebug(lcModelTest) << "columnsAboutToBeInserted"
733 << "start=" << start << "end=" << end << "parent=" << parent
734 << "parent data=" << model->data(parent).toString()
735 << "current count of parent=" << model->rowCount(parent)
736 << "last before insertion=" << model->index(start - 1, 0, parent)
737 << model->data(model->index(start - 1, 0, parent));
738}
739
740void QAbstractItemModelTesterPrivate::columnsInserted(const QModelIndex &parent, int first,
741 int last)
742{
743 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::ColumnsInserted);
744 changeInFlight = ChangeInFlight::None;
745
746 qCDebug(lcModelTest) << "columnsInserted"
747 << "start=" << first << "end=" << last << "parent=" << parent
748 << "parent data=" << model->data(parent).toString()
749 << "current count of parent=" << model->rowCount(parent);
750}
751
752void QAbstractItemModelTesterPrivate::columnsAboutToBeMoved(const QModelIndex &sourceParent,
753 int sourceStart, int sourceEnd,
754 const QModelIndex &destinationParent,
755 int destinationColumn)
756{
757 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None);
758 changeInFlight = ChangeInFlight::ColumnsMoved;
759
760 qCDebug(lcModelTest) << "columnsAboutToBeMoved"
761 << "sourceStart=" << sourceStart << "sourceEnd=" << sourceEnd
762 << "sourceParent=" << sourceParent
763 << "sourceParent data=" << model->data(sourceParent).toString()
764 << "destinationParent=" << destinationParent
765 << "destinationColumn=" << destinationColumn;
766}
767
768void QAbstractItemModelTesterPrivate::columnsMoved(const QModelIndex &sourceParent, int sourceStart,
769 int sourceEnd,
770 const QModelIndex &destinationParent,
771 int destinationColumn)
772{
773 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::ColumnsMoved);
774 changeInFlight = ChangeInFlight::None;
775
776 qCDebug(lcModelTest) << "columnsMoved"
777 << "sourceStart=" << sourceStart << "sourceEnd=" << sourceEnd
778 << "sourceParent=" << sourceParent
779 << "sourceParent data=" << model->data(sourceParent).toString()
780 << "destinationParent=" << destinationParent
781 << "destinationColumn=" << destinationColumn;
782}
783
784void QAbstractItemModelTesterPrivate::columnsAboutToBeRemoved(const QModelIndex &parent, int first,
785 int last)
786{
787 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None);
788 changeInFlight = ChangeInFlight::ColumnsRemoved;
789
790 qCDebug(lcModelTest) << "columnsAboutToBeRemoved"
791 << "start=" << first << "end=" << last << "parent=" << parent
792 << "parent data=" << model->data(parent).toString()
793 << "current count of parent=" << model->rowCount(parent)
794 << "last before removal=" << model->index(first - 1, 0, parent)
795 << model->data(model->index(first - 1, 0, parent));
796}
797
798void QAbstractItemModelTesterPrivate::columnsRemoved(const QModelIndex &parent, int first, int last)
799{
800 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::ColumnsRemoved);
801 changeInFlight = ChangeInFlight::None;
802
803 qCDebug(lcModelTest) << "columnsRemoved"
804 << "start=" << first << "end=" << last << "parent=" << parent
805 << "parent data=" << model->data(parent).toString()
806 << "current count of parent=" << model->rowCount(parent);
807}
808
809/*
810 Store what is about to be inserted to make sure it actually happens
811
812 \sa rowsInserted()
813 */
814void QAbstractItemModelTesterPrivate::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
815{
816 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None);
817 changeInFlight = ChangeInFlight::RowsInserted;
818
819 qCDebug(lcModelTest) << "rowsAboutToBeInserted"
820 << "start=" << start << "end=" << end << "parent=" << parent
821 << "parent data=" << model->data(parent).toString()
822 << "current count of parent=" << model->rowCount(parent)
823 << "last before insertion=" << model->index(start - 1, 0, parent) << model->data(model->index(start - 1, 0, parent));
824
825 Changing c;
826 c.parent = parent;
827 c.oldSize = model->rowCount(parent);
828 c.last = (start - 1 >= 0) ? model->index(start - 1, 0, parent).data() : QVariant();
829 c.next = (start < c.oldSize) ? model->index(start, 0, parent).data() : QVariant();
830 insert.push(c);
831}
832
833/*
834 Confirm that what was said was going to happen actually did
835
836 \sa rowsAboutToBeInserted()
837 */
838void QAbstractItemModelTesterPrivate::rowsInserted(const QModelIndex &parent, int start, int end)
839{
840 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::RowsInserted);
841 changeInFlight = ChangeInFlight::None;
842
843 qCDebug(lcModelTest) << "rowsInserted"
844 << "start=" << start << "end=" << end << "parent=" << parent
845 << "parent data=" << model->data(parent).toString()
846 << "current count of parent=" << model->rowCount(parent);
847
848 for (int i = start; i <= end; ++i) {
849 qCDebug(lcModelTest) << " itemWasInserted:" << i
850 << model->index(i, 0, parent).data();
851 }
852
853
854 Changing c = insert.pop();
855 MODELTESTER_COMPARE(parent, c.parent);
856
857 MODELTESTER_COMPARE(model->rowCount(parent), c.oldSize + (end - start + 1));
858 if (start - 1 >= 0)
859 MODELTESTER_COMPARE(model->data(model->index(start - 1, 0, c.parent)), c.last);
860
861 if (end + 1 < model->rowCount(c.parent)) {
862 if (c.next != model->data(model->index(end + 1, 0, c.parent))) {
863 qDebug() << start << end;
864 for (int i = 0; i < model->rowCount(); ++i)
865 qDebug() << model->index(i, 0).data().toString();
866 qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent));
867 }
868
869 MODELTESTER_COMPARE(model->data(model->index(end + 1, 0, c.parent)), c.next);
870 }
871}
872
873void QAbstractItemModelTesterPrivate::rowsAboutToBeMoved(const QModelIndex &sourceParent,
874 int sourceStart, int sourceEnd,
875 const QModelIndex &destinationParent,
876 int destinationRow)
877{
878 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None);
879 changeInFlight = ChangeInFlight::RowsMoved;
880
881 qCDebug(lcModelTest) << "rowsAboutToBeMoved"
882 << "sourceStart=" << sourceStart << "sourceEnd=" << sourceEnd
883 << "sourceParent=" << sourceParent
884 << "sourceParent data=" << model->data(sourceParent).toString()
885 << "destinationParent=" << destinationParent
886 << "destinationRow=" << destinationRow;
887}
888
889void QAbstractItemModelTesterPrivate::rowsMoved(const QModelIndex &sourceParent, int sourceStart,
890 int sourceEnd, const QModelIndex &destinationParent,
891 int destinationRow)
892{
893 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::RowsMoved);
894 changeInFlight = ChangeInFlight::None;
895
896 qCDebug(lcModelTest) << "rowsMoved"
897 << "sourceStart=" << sourceStart << "sourceEnd=" << sourceEnd
898 << "sourceParent=" << sourceParent
899 << "sourceParent data=" << model->data(sourceParent).toString()
900 << "destinationParent=" << destinationParent
901 << "destinationRow=" << destinationRow;
902}
903
904void QAbstractItemModelTesterPrivate::layoutAboutToBeChanged()
905{
906 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None);
907 changeInFlight = ChangeInFlight::LayoutChanged;
908
909 for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i)
910 changing.append(QPersistentModelIndex(model->index(i, 0)));
911}
912
913void QAbstractItemModelTesterPrivate::layoutChanged()
914{
915 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::LayoutChanged);
916 changeInFlight = ChangeInFlight::None;
917
918 for (int i = 0; i < changing.size(); ++i) {
919 QPersistentModelIndex p = changing[i];
920 MODELTESTER_COMPARE(model->index(p.row(), p.column(), p.parent()), QModelIndex(p));
921 }
922 changing.clear();
923}
924
925void QAbstractItemModelTesterPrivate::modelAboutToBeReset()
926{
927 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None);
928 changeInFlight = ChangeInFlight::ModelReset;
929}
930
931void QAbstractItemModelTesterPrivate::modelReset()
932{
933 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::ModelReset);
934 changeInFlight = ChangeInFlight::None;
935}
936
937/*
938 Store what is about to be inserted to make sure it actually happens
939
940 \sa rowsRemoved()
941 */
942void QAbstractItemModelTesterPrivate::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
943{
944 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None);
945 changeInFlight = ChangeInFlight::RowsRemoved;
946
947 qCDebug(lcModelTest) << "rowsAboutToBeRemoved"
948 << "start=" << start << "end=" << end << "parent=" << parent
949 << "parent data=" << model->data(parent).toString()
950 << "current count of parent=" << model->rowCount(parent)
951 << "last before removal=" << model->index(start - 1, 0, parent) << model->data(model->index(start - 1, 0, parent));
952
953 Changing c;
954 c.parent = parent;
955 c.oldSize = model->rowCount(parent);
956 if (start > 0 && model->columnCount(parent) > 0) {
957 const QModelIndex startIndex = model->index(start - 1, 0, parent);
958 MODELTESTER_VERIFY(startIndex.isValid());
959 c.last = model->data(startIndex);
960 }
961 if (end < c.oldSize - 1 && model->columnCount(parent) > 0) {
962 const QModelIndex endIndex = model->index(end + 1, 0, parent);
963 MODELTESTER_VERIFY(endIndex.isValid());
964 c.next = model->data(endIndex);
965 }
966
967 remove.push(c);
968}
969
970/*
971 Confirm that what was said was going to happen actually did
972
973 \sa rowsAboutToBeRemoved()
974 */
975void QAbstractItemModelTesterPrivate::rowsRemoved(const QModelIndex &parent, int start, int end)
976{
977 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::RowsRemoved);
978 changeInFlight = ChangeInFlight::None;
979
980 qCDebug(lcModelTest) << "rowsRemoved"
981 << "start=" << start << "end=" << end << "parent=" << parent
982 << "parent data=" << model->data(parent).toString()
983 << "current count of parent=" << model->rowCount(parent);
984
985 Changing c = remove.pop();
986 MODELTESTER_COMPARE(parent, c.parent);
987 MODELTESTER_COMPARE(model->rowCount(parent), c.oldSize - (end - start + 1));
988 if (start > 0)
989 MODELTESTER_COMPARE(model->data(model->index(start - 1, 0, c.parent)), c.last);
990 if (end < c.oldSize - 1)
991 MODELTESTER_COMPARE(model->data(model->index(start, 0, c.parent)), c.next);
992}
993
994void QAbstractItemModelTesterPrivate::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
995{
996 MODELTESTER_VERIFY(topLeft.isValid());
997 MODELTESTER_VERIFY(bottomRight.isValid());
998 QModelIndex commonParent = bottomRight.parent();
999 MODELTESTER_COMPARE(topLeft.parent(), commonParent);
1000 MODELTESTER_VERIFY(topLeft.row() <= bottomRight.row());
1001 MODELTESTER_VERIFY(topLeft.column() <= bottomRight.column());
1002 int rowCount = model->rowCount(commonParent);
1003 int columnCount = model->columnCount(commonParent);
1004 MODELTESTER_VERIFY(bottomRight.row() < rowCount);
1005 MODELTESTER_VERIFY(bottomRight.column() < columnCount);
1006}
1007
1008void QAbstractItemModelTesterPrivate::headerDataChanged(Qt::Orientation orientation, int start, int end)
1009{
1010 MODELTESTER_VERIFY(start >= 0);
1011 MODELTESTER_VERIFY(end >= 0);
1012 MODELTESTER_VERIFY(start <= end);
1013 int itemCount = orientation == Qt::Vertical ? model->rowCount() : model->columnCount();
1014 MODELTESTER_VERIFY(start < itemCount);
1015 MODELTESTER_VERIFY(end < itemCount);
1016}
1017
1018bool QAbstractItemModelTesterPrivate::verify(bool statement,
1019 const char *statementStr, const char *description,
1020 const char *file, int line)
1021{
1022 static const char formatString[] = "FAIL! %s (%s) returned FALSE (%s:%d)";
1023
1024 switch (failureReportingMode) {
1025 case QAbstractItemModelTester::FailureReportingMode::QtTest:
1026 return QTest::qVerify(statement, statementStr, description, file, line);
1027 break;
1028
1029 case QAbstractItemModelTester::FailureReportingMode::Warning:
1030 if (!statement)
1031 qCWarning(lcModelTest, formatString, statementStr, description, file, line);
1032 break;
1033
1034 case QAbstractItemModelTester::FailureReportingMode::Fatal:
1035 if (!statement)
1036 qFatal(formatString, statementStr, description, file, line);
1037 break;
1038 }
1039
1040 return statement;
1041}
1042
1043
1044template<typename T1, typename T2>
1045bool QAbstractItemModelTesterPrivate::compare(const T1 &t1, const T2 &t2,
1046 const char *actual, const char *expected,
1047 const char *file, int line)
1048{
1049 const bool result = static_cast<bool>(t1 == t2);
1050
1051 static const char formatString[] = "FAIL! Compared values are not the same:\n Actual (%s) %s\n Expected (%s) %s\n (%s:%d)";
1052
1053 switch (failureReportingMode) {
1054 case QAbstractItemModelTester::FailureReportingMode::QtTest:
1055 return QTest::qCompare(t1, t2, actual, expected, file, line);
1056 break;
1057
1058 case QAbstractItemModelTester::FailureReportingMode::Warning:
1059 if (!result) {
1060 auto t1string = QTest::toString(t1);
1061 auto t2string = QTest::toString(t2);
1062 qCWarning(lcModelTest, formatString, actual, t1string ? t1string : "(nullptr)",
1063 expected, t2string ? t2string : "(nullptr)",
1064 file, line);
1065 delete [] t1string;
1066 delete [] t2string;
1067 }
1068 break;
1069
1070 case QAbstractItemModelTester::FailureReportingMode::Fatal:
1071 if (!result) {
1072 auto t1string = QTest::toString(t1);
1073 auto t2string = QTest::toString(t2);
1074 qFatal(formatString, actual, t1string ? t1string : "(nullptr)",
1075 expected, t2string ? t2string : "(nullptr)",
1076 file, line);
1077 delete [] t1string;
1078 delete [] t2string;
1079 }
1080 break;
1081 }
1082
1083 return result;
1084}
1085
1086
1087QT_END_NAMESPACE
1088
1089#include "moc_qabstractitemmodeltester.cpp"
#define MODELTESTER_COMPARE(actual, expected)
#define MODELTESTER_VERIFY(statement)