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