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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtbase/src/testlib/qabstractitemmodeltester.cpp