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