1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
5** Contact: https://www.qt.io/licensing/
6**
7** This file is part of the QtTest module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial License Usage
11** Licensees holding valid commercial Qt licenses may use this file in
12** accordance with the commercial license agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and The Qt Company. For licensing terms
15** and conditions see https://www.qt.io/terms-conditions. For further
16** information use the contact form at https://www.qt.io/contact-us.
17**
18** GNU Lesser General Public License Usage
19** Alternatively, this file may be used under the terms of the GNU Lesser
20** General Public License version 3 as published by the Free Software
21** Foundation and appearing in the file LICENSE.LGPL3 included in the
22** packaging of this file. Please review the following information to
23** ensure the GNU Lesser General Public License version 3 requirements
24** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25**
26** GNU General Public License Usage
27** Alternatively, this file may be used under the terms of the GNU
28** General Public License version 2.0 or (at your option) the GNU General
29** Public license version 3 or any later version approved by the KDE Free
30** Qt Foundation. The licenses are as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32** included in the packaging of this file. Please review the following
33** information to ensure the GNU General Public License requirements will
34** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35** https://www.gnu.org/licenses/gpl-3.0.html.
36**
37** $QT_END_LICENSE$
38**
39****************************************************************************/
40
41#include "qabstractitemmodeltester.h"
42
43#include <private/qobject_p.h>
44#include <QtCore/QPointer>
45#include <QtCore/QAbstractItemModel>
46#include <QtCore/QStack>
47#include <QtTest/QtTest>
48
49QT_BEGIN_NAMESPACE
50
51Q_LOGGING_CATEGORY(lcModelTest, "qt.modeltest")
52
53#define MODELTESTER_VERIFY(statement) \
54do { \
55 if (!verify(static_cast<bool>(statement), #statement, "", __FILE__, __LINE__)) \
56 return; \
57} while (false)
58
59#define MODELTESTER_COMPARE(actual, expected) \
60do { \
61 if (!compare((actual), (expected), #actual, #expected, __FILE__, __LINE__)) \
62 return; \
63} while (false)
64
65class QAbstractItemModelTesterPrivate : public QObjectPrivate
66{
67 Q_DECLARE_PUBLIC(QAbstractItemModelTester)
68public:
69 QAbstractItemModelTesterPrivate(QAbstractItemModel *model, QAbstractItemModelTester::FailureReportingMode failureReportingMode);
70
71 void nonDestructiveBasicTest();
72 void rowAndColumnCount();
73 void hasIndex();
74 void index();
75 void parent();
76 void data();
77
78 void runAllTests();
79 void layoutAboutToBeChanged();
80 void layoutChanged();
81 void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end);
82 void rowsInserted(const QModelIndex &parent, int start, int end);
83 void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end);
84 void rowsRemoved(const QModelIndex &parent, int start, int end);
85 void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
86 void headerDataChanged(Qt::Orientation orientation, int start, int end);
87
88private:
89 void checkChildren(const QModelIndex &parent, int currentDepth = 0);
90
91 bool verify(bool statement, const char *statementStr, const char *description, const char *file, int line);
92
93 template<typename T1, typename T2>
94 bool compare(const T1 &t1, const T2 &t2,
95 const char *actual, const char *expected,
96 const char *file, int line);
97
98 QPointer<QAbstractItemModel> model;
99 QAbstractItemModelTester::FailureReportingMode failureReportingMode;
100
101 struct Changing {
102 QModelIndex parent;
103 int oldSize;
104 QVariant last;
105 QVariant next;
106 };
107 QStack<Changing> insert;
108 QStack<Changing> remove;
109
110 bool fetchingMore;
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 through
187 QtTest's logging mechanism.
188
189 \value Warning The failures will be reported as
190 warning messages in the \c{qt.modeltest} logging category.
191
192 \value Fatal A failure will cause immediate and
193 abnormal program termination. The reason for the failure will be reported
194 using \c{qFatal()}.
195*/
196
197/*!
198 Creates a model tester instance, with the given \a parent, that will test
199 the model \a model.
200*/
201QAbstractItemModelTester::QAbstractItemModelTester(QAbstractItemModel *model, QObject *parent)
202 : QAbstractItemModelTester(model, FailureReportingMode::QtTest, parent)
203{
204}
205
206/*!
207 Creates a model tester instance, with the given \a parent, that will test
208 the model \a model, using the specified \a mode to report test failures.
209
210 \sa QAbstractItemModelTester::FailureReportingMode
211*/
212QAbstractItemModelTester::QAbstractItemModelTester(QAbstractItemModel *model, FailureReportingMode mode, QObject *parent)
213 : QObject(*new QAbstractItemModelTesterPrivate(model, mode), parent)
214{
215 if (!model)
216 qFatal("%s: model must not be null", Q_FUNC_INFO);
217
218 Q_D(QAbstractItemModelTester);
219
220 auto runAllTests = [d] { d->runAllTests(); };
221
222 connect(model, &QAbstractItemModel::columnsAboutToBeInserted,
223 this, runAllTests);
224 connect(model, &QAbstractItemModel::columnsAboutToBeRemoved,
225 this, runAllTests);
226 connect(model, &QAbstractItemModel::columnsInserted,
227 this, runAllTests);
228 connect(model, &QAbstractItemModel::columnsRemoved,
229 this, runAllTests);
230 connect(model, &QAbstractItemModel::dataChanged,
231 this, runAllTests);
232 connect(model, &QAbstractItemModel::headerDataChanged,
233 this, runAllTests);
234 connect(model, &QAbstractItemModel::layoutAboutToBeChanged,
235 this, runAllTests);
236 connect(model, &QAbstractItemModel::layoutChanged,
237 this, runAllTests);
238 connect(model, &QAbstractItemModel::modelReset,
239 this, runAllTests);
240 connect(model, &QAbstractItemModel::rowsAboutToBeInserted,
241 this, runAllTests);
242 connect(model, &QAbstractItemModel::rowsAboutToBeRemoved,
243 this, runAllTests);
244 connect(model, &QAbstractItemModel::rowsInserted,
245 this, runAllTests);
246 connect(model, &QAbstractItemModel::rowsRemoved,
247 this, runAllTests);
248
249 // Special checks for changes
250 connect(model, &QAbstractItemModel::layoutAboutToBeChanged,
251 this, [d]{ d->layoutAboutToBeChanged(); });
252 connect(model, &QAbstractItemModel::layoutChanged,
253 this, [d]{ d->layoutChanged(); });
254
255 connect(model, &QAbstractItemModel::rowsAboutToBeInserted,
256 this, [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeInserted(parent, start, end); });
257 connect(model, &QAbstractItemModel::rowsAboutToBeRemoved,
258 this, [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeRemoved(parent, start, end); });
259 connect(model, &QAbstractItemModel::rowsInserted,
260 this, [d](const QModelIndex &parent, int start, int end) { d->rowsInserted(parent, start, end); });
261 connect(model, &QAbstractItemModel::rowsRemoved,
262 this, [d](const QModelIndex &parent, int start, int end) { d->rowsRemoved(parent, start, end); });
263 connect(model, &QAbstractItemModel::dataChanged,
264 this, [d](const QModelIndex &topLeft, const QModelIndex &bottomRight) { d->dataChanged(topLeft, bottomRight); });
265 connect(model, &QAbstractItemModel::headerDataChanged,
266 this, [d](Qt::Orientation orientation, int start, int end) { d->headerDataChanged(orientation, start, end); });
267
268 runAllTests();
269}
270
271/*!
272 Returns the model that this instance is testing.
273*/
274QAbstractItemModel *QAbstractItemModelTester::model() const
275{
276 Q_D(const QAbstractItemModelTester);
277 return d->model.data();
278}
279
280/*!
281 Returns the mode that this instancing is using to report test failures.
282
283 \sa QAbstractItemModelTester::FailureReportingMode
284*/
285QAbstractItemModelTester::FailureReportingMode QAbstractItemModelTester::failureReportingMode() const
286{
287 Q_D(const QAbstractItemModelTester);
288 return d->failureReportingMode;
289}
290
291QAbstractItemModelTesterPrivate::QAbstractItemModelTesterPrivate(QAbstractItemModel *model, QAbstractItemModelTester::FailureReportingMode failureReportingMode)
292 : model(model),
293 failureReportingMode(failureReportingMode),
294 fetchingMore(false)
295{
296}
297
298void QAbstractItemModelTesterPrivate::runAllTests()
299{
300 if (fetchingMore)
301 return;
302 nonDestructiveBasicTest();
303 rowAndColumnCount();
304 hasIndex();
305 index();
306 parent();
307 data();
308}
309
310/*
311 nonDestructiveBasicTest tries to call a number of the basic functions (not all)
312 to make sure the model doesn't outright segfault, testing the functions that makes sense.
313*/
314void QAbstractItemModelTesterPrivate::nonDestructiveBasicTest()
315{
316 MODELTESTER_VERIFY(!model->buddy(QModelIndex()).isValid());
317 model->canFetchMore(QModelIndex());
318 MODELTESTER_VERIFY(model->columnCount(QModelIndex()) >= 0);
319 fetchingMore = true;
320 model->fetchMore(QModelIndex());
321 fetchingMore = false;
322 Qt::ItemFlags flags = model->flags(QModelIndex());
323 MODELTESTER_VERIFY(flags == Qt::ItemIsDropEnabled || flags == 0);
324 model->hasChildren(QModelIndex());
325 const bool hasRow = model->hasIndex(0, 0);
326 QVariant cache;
327 if (hasRow)
328 model->match(model->index(0, 0), -1, cache);
329 model->mimeTypes();
330 MODELTESTER_VERIFY(!model->parent(QModelIndex()).isValid());
331 MODELTESTER_VERIFY(model->rowCount() >= 0);
332 model->span(QModelIndex());
333 model->supportedDropActions();
334 model->roleNames();
335}
336
337/*
338 Tests model's implementation of QAbstractItemModel::rowCount(),
339 columnCount() and hasChildren().
340
341 Models that are dynamically populated are not as fully tested here.
342 */
343void QAbstractItemModelTesterPrivate::rowAndColumnCount()
344{
345 if (!model->hasChildren())
346 return;
347
348 QModelIndex topIndex = model->index(0, 0, QModelIndex());
349
350 // check top row
351 int rows = model->rowCount(topIndex);
352 MODELTESTER_VERIFY(rows >= 0);
353
354 int columns = model->columnCount(topIndex);
355 MODELTESTER_VERIFY(columns >= 0);
356
357 if (rows == 0 || columns == 0)
358 return;
359
360 MODELTESTER_VERIFY(model->hasChildren(topIndex));
361
362 QModelIndex secondLevelIndex = model->index(0, 0, topIndex);
363 MODELTESTER_VERIFY(secondLevelIndex.isValid());
364
365 rows = model->rowCount(secondLevelIndex);
366 MODELTESTER_VERIFY(rows >= 0);
367
368 columns = model->columnCount(secondLevelIndex);
369 MODELTESTER_VERIFY(columns >= 0);
370
371 if (rows == 0 || columns == 0)
372 return;
373
374 MODELTESTER_VERIFY(model->hasChildren(secondLevelIndex));
375
376 // rowCount() / columnCount() are tested more extensively in checkChildren()
377}
378
379/*
380 Tests model's implementation of QAbstractItemModel::hasIndex()
381 */
382void QAbstractItemModelTesterPrivate::hasIndex()
383{
384 // Make sure that invalid values returns an invalid index
385 MODELTESTER_VERIFY(!model->hasIndex(-2, -2));
386 MODELTESTER_VERIFY(!model->hasIndex(-2, 0));
387 MODELTESTER_VERIFY(!model->hasIndex(0, -2));
388
389 const int rows = model->rowCount();
390 const int columns = model->columnCount();
391
392 // check out of bounds
393 MODELTESTER_VERIFY(!model->hasIndex(rows, columns));
394 MODELTESTER_VERIFY(!model->hasIndex(rows + 1, columns + 1));
395
396 if (rows > 0 && columns > 0)
397 MODELTESTER_VERIFY(model->hasIndex(0, 0));
398
399 // hasIndex() is tested more extensively in checkChildren(),
400 // but this catches the big mistakes
401}
402
403/*
404 Tests model's implementation of QAbstractItemModel::index()
405 */
406void QAbstractItemModelTesterPrivate::index()
407{
408 const int rows = model->rowCount();
409 const int columns = model->columnCount();
410
411 for (int row = 0; row < rows; ++row) {
412 for (int column = 0; column < columns; ++column) {
413 // Make sure that the same index is *always* returned
414 QModelIndex a = model->index(row, column);
415 QModelIndex b = model->index(row, column);
416 MODELTESTER_VERIFY(a.isValid());
417 MODELTESTER_VERIFY(b.isValid());
418 MODELTESTER_COMPARE(a, b);
419 }
420 }
421
422 // index() is tested more extensively in checkChildren(),
423 // but this catches the big mistakes
424}
425
426/*
427 Tests model's implementation of QAbstractItemModel::parent()
428 */
429void QAbstractItemModelTesterPrivate::parent()
430{
431 // Make sure the model won't crash and will return an invalid QModelIndex
432 // when asked for the parent of an invalid index.
433 MODELTESTER_VERIFY(!model->parent(QModelIndex()).isValid());
434
435 if (!model->hasChildren())
436 return;
437
438 // Column 0 | Column 1 |
439 // QModelIndex() | |
440 // \- topIndex | topIndex1 |
441 // \- childIndex | childIndex1 |
442
443 // Common error test #1, make sure that a top level index has a parent
444 // that is a invalid QModelIndex.
445 QModelIndex topIndex = model->index(0, 0, QModelIndex());
446 MODELTESTER_VERIFY(!model->parent(topIndex).isValid());
447
448 // Common error test #2, make sure that a second level index has a parent
449 // that is the first level index.
450 if (model->hasChildren(topIndex)) {
451 QModelIndex childIndex = model->index(0, 0, topIndex);
452 MODELTESTER_VERIFY(childIndex.isValid());
453 MODELTESTER_COMPARE(model->parent(childIndex), topIndex);
454 }
455
456 // Common error test #3, the second column should NOT have the same children
457 // as the first column in a row.
458 // Usually the second column shouldn't have children.
459 if (model->hasIndex(0, 1)) {
460 QModelIndex topIndex1 = model->index(0, 1, QModelIndex());
461 MODELTESTER_VERIFY(topIndex1.isValid());
462 if (model->hasChildren(topIndex) && model->hasChildren(topIndex1)) {
463 QModelIndex childIndex = model->index(0, 0, topIndex);
464 MODELTESTER_VERIFY(childIndex.isValid());
465 QModelIndex childIndex1 = model->index(0, 0, topIndex1);
466 MODELTESTER_VERIFY(childIndex1.isValid());
467 MODELTESTER_VERIFY(childIndex != childIndex1);
468 }
469 }
470
471 // Full test, walk n levels deep through the model making sure that all
472 // parent's children correctly specify their parent.
473 checkChildren(QModelIndex());
474}
475
476/*
477 Called from the parent() test.
478
479 A model that returns an index of parent X should also return X when asking
480 for the parent of the index.
481
482 This recursive function does pretty extensive testing on the whole model in an
483 effort to catch edge cases.
484
485 This function assumes that rowCount(), columnCount() and index() already work.
486 If they have a bug it will point it out, but the above tests should have already
487 found the basic bugs because it is easier to figure out the problem in
488 those tests then this one.
489 */
490void QAbstractItemModelTesterPrivate::checkChildren(const QModelIndex &parent, int currentDepth)
491{
492 // First just try walking back up the tree.
493 QModelIndex p = parent;
494 while (p.isValid())
495 p = p.parent();
496
497 // For models that are dynamically populated
498 if (model->canFetchMore(parent)) {
499 fetchingMore = true;
500 model->fetchMore(parent);
501 fetchingMore = false;
502 }
503
504 const int rows = model->rowCount(parent);
505 const int columns = model->columnCount(parent);
506
507 if (rows > 0)
508 MODELTESTER_VERIFY(model->hasChildren(parent));
509
510 // Some further testing against rows(), columns(), and hasChildren()
511 MODELTESTER_VERIFY(rows >= 0);
512 MODELTESTER_VERIFY(columns >= 0);
513 if (rows > 0 && columns > 0)
514 MODELTESTER_VERIFY(model->hasChildren(parent));
515
516 const QModelIndex topLeftChild = model->index(0, 0, parent);
517
518 MODELTESTER_VERIFY(!model->hasIndex(rows, 0, parent));
519 MODELTESTER_VERIFY(!model->hasIndex(rows + 1, 0, parent));
520
521 for (int r = 0; r < rows; ++r) {
522 MODELTESTER_VERIFY(!model->hasIndex(r, columns, parent));
523 MODELTESTER_VERIFY(!model->hasIndex(r, columns + 1, parent));
524 for (int c = 0; c < columns; ++c) {
525 MODELTESTER_VERIFY(model->hasIndex(r, c, parent));
526 QModelIndex index = model->index(r, c, parent);
527 // rowCount() and columnCount() said that it existed...
528 if (!index.isValid())
529 qCWarning(lcModelTest) << "Got invalid index at row=" << r << "col=" << c << "parent=" << parent;
530 MODELTESTER_VERIFY(index.isValid());
531
532 // index() should always return the same index when called twice in a row
533 QModelIndex modifiedIndex = model->index(r, c, parent);
534 MODELTESTER_COMPARE(index, modifiedIndex);
535
536 {
537 const QModelIndex sibling = model->sibling(r, c, topLeftChild);
538 MODELTESTER_COMPARE(index, sibling);
539 }
540 {
541 const QModelIndex sibling = topLeftChild.sibling(r, c);
542 MODELTESTER_COMPARE(index, sibling);
543 }
544
545 // Some basic checking on the index that is returned
546 MODELTESTER_COMPARE(index.model(), model);
547 MODELTESTER_COMPARE(index.row(), r);
548 MODELTESTER_COMPARE(index.column(), c);
549
550 // If the next test fails here is some somewhat useful debug you play with.
551 if (model->parent(index) != parent) {
552 qCWarning(lcModelTest) << "Inconsistent parent() implementation detected:";
553 qCWarning(lcModelTest) << " index=" << index << "exp. parent=" << parent << "act. parent=" << model->parent(index);
554 qCWarning(lcModelTest) << " row=" << r << "col=" << c << "depth=" << currentDepth;
555 qCWarning(lcModelTest) << " data for child" << model->data(index).toString();
556 qCWarning(lcModelTest) << " data for parent" << model->data(parent).toString();
557 }
558
559 // Check that we can get back our real parent.
560 MODELTESTER_COMPARE(model->parent(index), parent);
561
562 QPersistentModelIndex persistentIndex = index;
563
564 // recursively go down the children
565 if (model->hasChildren(index) && currentDepth < 10)
566 checkChildren(index, ++currentDepth);
567
568 // make sure that after testing the children that the index doesn't change.
569 QModelIndex newerIndex = model->index(r, c, parent);
570 MODELTESTER_COMPARE(persistentIndex, newerIndex);
571 }
572 }
573}
574
575/*
576 Tests model's implementation of QAbstractItemModel::data()
577 */
578void QAbstractItemModelTesterPrivate::data()
579{
580 if (!model->hasChildren())
581 return;
582
583 MODELTESTER_VERIFY(model->index(0, 0).isValid());
584
585 // General Purpose roles that should return a QString
586 QVariant variant;
587 variant = model->data(model->index(0, 0), Qt::DisplayRole);
588 if (variant.isValid())
589 MODELTESTER_VERIFY(variant.canConvert<QString>());
590 variant = model->data(model->index(0, 0), Qt::ToolTipRole);
591 if (variant.isValid())
592 MODELTESTER_VERIFY(variant.canConvert<QString>());
593 variant = model->data(model->index(0, 0), Qt::StatusTipRole);
594 if (variant.isValid())
595 MODELTESTER_VERIFY(variant.canConvert<QString>());
596 variant = model->data(model->index(0, 0), Qt::WhatsThisRole);
597 if (variant.isValid())
598 MODELTESTER_VERIFY(variant.canConvert<QString>());
599
600 // General Purpose roles that should return a QSize
601 variant = model->data(model->index(0, 0), Qt::SizeHintRole);
602 if (variant.isValid())
603 MODELTESTER_VERIFY(variant.canConvert<QSize>());
604
605 // Check that the alignment is one we know about
606 QVariant textAlignmentVariant = model->data(model->index(0, 0), Qt::TextAlignmentRole);
607 if (textAlignmentVariant.isValid()) {
608 Qt::Alignment alignment = textAlignmentVariant.value<Qt::Alignment>();
609 MODELTESTER_COMPARE(alignment, (alignment & (Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask)));
610 }
611
612 // Check that the "check state" is one we know about.
613 QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole);
614 if (checkStateVariant.isValid()) {
615 int state = checkStateVariant.toInt();
616 MODELTESTER_VERIFY(state == Qt::Unchecked
617 || state == Qt::PartiallyChecked
618 || state == Qt::Checked);
619 }
620
621 Q_Q(QAbstractItemModelTester);
622
623 if (!QTestPrivate::testDataGuiRoles(q))
624 return;
625}
626
627/*
628 Store what is about to be inserted to make sure it actually happens
629
630 \sa rowsInserted()
631 */
632void QAbstractItemModelTesterPrivate::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
633{
634 qCDebug(lcModelTest) << "rowsAboutToBeInserted"
635 << "start=" << start << "end=" << end << "parent=" << parent
636 << "parent data=" << model->data(parent).toString()
637 << "current count of parent=" << model->rowCount(parent)
638 << "last before insertion=" << model->index(start - 1, 0, parent) << model->data(model->index(start - 1, 0, parent));
639
640 Changing c;
641 c.parent = parent;
642 c.oldSize = model->rowCount(parent);
643 c.last = (start - 1 >= 0) ? model->index(start - 1, 0, parent).data() : QVariant();
644 c.next = (start < c.oldSize) ? model->index(start, 0, parent).data() : QVariant();
645 insert.push(c);
646}
647
648/*
649 Confirm that what was said was going to happen actually did
650
651 \sa rowsAboutToBeInserted()
652 */
653void QAbstractItemModelTesterPrivate::rowsInserted(const QModelIndex &parent, int start, int end)
654{
655 qCDebug(lcModelTest) << "rowsInserted"
656 << "start=" << start << "end=" << end << "parent=" << parent
657 << "parent data=" << model->data(parent).toString()
658 << "current count of parent=" << model->rowCount(parent);
659
660 for (int i = start; i <= end; ++i) {
661 qCDebug(lcModelTest) << " itemWasInserted:" << i
662 << model->index(i, 0, parent).data();
663 }
664
665
666 Changing c = insert.pop();
667 MODELTESTER_COMPARE(parent, c.parent);
668
669 MODELTESTER_COMPARE(model->rowCount(parent), c.oldSize + (end - start + 1));
670 if (start - 1 >= 0)
671 MODELTESTER_COMPARE(model->data(model->index(start - 1, 0, c.parent)), c.last);
672
673 if (end + 1 < model->rowCount(c.parent)) {
674 if (c.next != model->data(model->index(end + 1, 0, c.parent))) {
675 qDebug() << start << end;
676 for (int i = 0; i < model->rowCount(); ++i)
677 qDebug() << model->index(i, 0).data().toString();
678 qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent));
679 }
680
681 MODELTESTER_COMPARE(model->data(model->index(end + 1, 0, c.parent)), c.next);
682 }
683}
684
685void QAbstractItemModelTesterPrivate::layoutAboutToBeChanged()
686{
687 for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i)
688 changing.append(QPersistentModelIndex(model->index(i, 0)));
689}
690
691void QAbstractItemModelTesterPrivate::layoutChanged()
692{
693 for (int i = 0; i < changing.count(); ++i) {
694 QPersistentModelIndex p = changing[i];
695 MODELTESTER_COMPARE(model->index(p.row(), p.column(), p.parent()), QModelIndex(p));
696 }
697 changing.clear();
698}
699
700/*
701 Store what is about to be inserted to make sure it actually happens
702
703 \sa rowsRemoved()
704 */
705void QAbstractItemModelTesterPrivate::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
706{
707 qCDebug(lcModelTest) << "rowsAboutToBeRemoved"
708 << "start=" << start << "end=" << end << "parent=" << parent
709 << "parent data=" << model->data(parent).toString()
710 << "current count of parent=" << model->rowCount(parent)
711 << "last before removal=" << model->index(start - 1, 0, parent) << model->data(model->index(start - 1, 0, parent));
712
713 Changing c;
714 c.parent = parent;
715 c.oldSize = model->rowCount(parent);
716 if (start > 0) {
717 const QModelIndex startIndex = model->index(start - 1, 0, parent);
718 MODELTESTER_VERIFY(startIndex.isValid());
719 c.last = model->data(startIndex);
720 }
721 if (end < c.oldSize - 1) {
722 const QModelIndex endIndex = model->index(end + 1, 0, parent);
723 MODELTESTER_VERIFY(endIndex.isValid());
724 c.next = model->data(endIndex);
725 }
726
727 remove.push(c);
728}
729
730/*
731 Confirm that what was said was going to happen actually did
732
733 \sa rowsAboutToBeRemoved()
734 */
735void QAbstractItemModelTesterPrivate::rowsRemoved(const QModelIndex &parent, int start, int end)
736{
737 qCDebug(lcModelTest) << "rowsRemoved"
738 << "start=" << start << "end=" << end << "parent=" << parent
739 << "parent data=" << model->data(parent).toString()
740 << "current count of parent=" << model->rowCount(parent);
741
742 Changing c = remove.pop();
743 MODELTESTER_COMPARE(parent, c.parent);
744 MODELTESTER_COMPARE(model->rowCount(parent), c.oldSize - (end - start + 1));
745 if (start > 0)
746 MODELTESTER_COMPARE(model->data(model->index(start - 1, 0, c.parent)), c.last);
747 if (end < c.oldSize - 1)
748 MODELTESTER_COMPARE(model->data(model->index(start, 0, c.parent)), c.next);
749}
750
751void QAbstractItemModelTesterPrivate::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
752{
753 MODELTESTER_VERIFY(topLeft.isValid());
754 MODELTESTER_VERIFY(bottomRight.isValid());
755 QModelIndex commonParent = bottomRight.parent();
756 MODELTESTER_COMPARE(topLeft.parent(), commonParent);
757 MODELTESTER_VERIFY(topLeft.row() <= bottomRight.row());
758 MODELTESTER_VERIFY(topLeft.column() <= bottomRight.column());
759 int rowCount = model->rowCount(commonParent);
760 int columnCount = model->columnCount(commonParent);
761 MODELTESTER_VERIFY(bottomRight.row() < rowCount);
762 MODELTESTER_VERIFY(bottomRight.column() < columnCount);
763}
764
765void QAbstractItemModelTesterPrivate::headerDataChanged(Qt::Orientation orientation, int start, int end)
766{
767 MODELTESTER_VERIFY(start >= 0);
768 MODELTESTER_VERIFY(end >= 0);
769 MODELTESTER_VERIFY(start <= end);
770 int itemCount = orientation == Qt::Vertical ? model->rowCount() : model->columnCount();
771 MODELTESTER_VERIFY(start < itemCount);
772 MODELTESTER_VERIFY(end < itemCount);
773}
774
775bool QAbstractItemModelTesterPrivate::verify(bool statement,
776 const char *statementStr, const char *description,
777 const char *file, int line)
778{
779 static const char formatString[] = "FAIL! %s (%s) returned FALSE (%s:%d)";
780
781 switch (failureReportingMode) {
782 case QAbstractItemModelTester::FailureReportingMode::QtTest:
783 return QTest::qVerify(statement, statementStr, description, file, line);
784 break;
785
786 case QAbstractItemModelTester::FailureReportingMode::Warning:
787 if (!statement)
788 qCWarning(lcModelTest, formatString, statementStr, description, file, line);
789 break;
790
791 case QAbstractItemModelTester::FailureReportingMode::Fatal:
792 if (!statement)
793 qFatal(formatString, statementStr, description, file, line);
794 break;
795 }
796
797 return statement;
798}
799
800
801template<typename T1, typename T2>
802bool QAbstractItemModelTesterPrivate::compare(const T1 &t1, const T2 &t2,
803 const char *actual, const char *expected,
804 const char *file, int line)
805{
806 const bool result = static_cast<bool>(t1 == t2);
807
808 static const char formatString[] = "FAIL! Compared values are not the same:\n Actual (%s) %s\n Expected (%s) %s\n (%s:%d)";
809
810 switch (failureReportingMode) {
811 case QAbstractItemModelTester::FailureReportingMode::QtTest:
812 return QTest::qCompare(t1, t2, actual, expected, file, line);
813 break;
814
815 case QAbstractItemModelTester::FailureReportingMode::Warning:
816 if (!result)
817 qCWarning(lcModelTest, formatString, actual, QTest::toString(t1), expected, QTest::toString(t2), file, line);
818 break;
819
820 case QAbstractItemModelTester::FailureReportingMode::Fatal:
821 if (!result)
822 qFatal(formatString, actual, QTest::toString(t1), expected, QTest::toString(t2), file, line);
823 break;
824 }
825
826 return result;
827}
828
829
830QT_END_NAMESPACE
831