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 <QTest>
48#include <QLoggingCategory>
49
50QT_BEGIN_NAMESPACE
51
52Q_LOGGING_CATEGORY(lcModelTest, "qt.modeltest")
53
54#define MODELTESTER_VERIFY(statement) \
55do { \
56 if (!verify(static_cast<bool>(statement), #statement, "", __FILE__, __LINE__)) \
57 return; \
58} while (false)
59
60#define MODELTESTER_COMPARE(actual, expected) \
61do { \
62 if (!compare((actual), (expected), #actual, #expected, __FILE__, __LINE__)) \
63 return; \
64} while (false)
65
66class QAbstractItemModelTesterPrivate : public QObjectPrivate
67{
68 Q_DECLARE_PUBLIC(QAbstractItemModelTester)
69public:
70 QAbstractItemModelTesterPrivate(QAbstractItemModel *model, QAbstractItemModelTester::FailureReportingMode failureReportingMode);
71
72 void nonDestructiveBasicTest();
73 void rowAndColumnCount();
74 void hasIndex();
75 void index();
76 void parent();
77 void data();
78
79 void runAllTests();
80 void layoutAboutToBeChanged();
81 void layoutChanged();
82 void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end);
83 void rowsInserted(const QModelIndex &parent, int start, int end);
84 void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end);
85 void rowsRemoved(const QModelIndex &parent, int start, int end);
86 void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
87 void headerDataChanged(Qt::Orientation orientation, int start, int end);
88
89private:
90 void checkChildren(const QModelIndex &parent, int currentDepth = 0);
91
92 bool verify(bool statement, const char *statementStr, const char *description, const char *file, int line);
93
94 template<typename T1, typename T2>
95 bool compare(const T1 &t1, const T2 &t2,
96 const char *actual, const char *expected,
97 const char *file, int line);
98
99 QPointer<QAbstractItemModel> model;
100 QAbstractItemModelTester::FailureReportingMode failureReportingMode;
101
102 struct Changing {
103 QModelIndex parent;
104 int oldSize;
105 QVariant last;
106 QVariant next;
107 };
108 QStack<Changing> insert;
109 QStack<Changing> remove;
110
111 bool fetchingMore;
112
113 QList<QPersistentModelIndex> changing;
114};
115
116/*!
117 \class QAbstractItemModelTester
118 \since 5.11
119 \inmodule QtTest
120
121 \brief The QAbstractItemModelTester class helps testing QAbstractItemModel subclasses.
122
123 The QAbstractItemModelTester class is a utility class to test item models.
124
125 When implementing an item model (that is, a concrete QAbstractItemModel
126 subclass) one must abide to a very strict set of rules that ensure
127 consistency for users of the model (views, proxy models, and so on).
128
129 For instance, for a given index, a model's reimplementation of
130 \l{QAbstractItemModel::hasChildren()}{hasChildren()} must be consistent
131 with the values returned by \l{QAbstractItemModel::rowCount()}{rowCount()}
132 and \l{QAbstractItemModel::columnCount()}{columnCount()}.
133
134 QAbstractItemModelTester helps catching the most common errors in custom
135 item model classes. By performing a series of tests, it
136 will try to check that the model status is consistent at all times. The
137 tests will be repeated automatically every time the model is modified.
138
139 QAbstractItemModelTester employs non-destructive tests, which typically
140 consist in reading data and metadata out of a given item model.
141 QAbstractItemModelTester will also attempt illegal modifications of
142 the model. In models which are properly implemented, such attempts
143 should be rejected, and no data should be changed as a consequence.
144
145 \section1 Usage
146
147 Using QAbstractItemModelTester is straightforward. In a \l{Qt Test Overview}{test case}
148 it is sufficient to create an instance, passing the model that
149 needs to be tested to the constructor:
150
151 \code
152 MyModel *modelToBeTested = ...;
153 auto tester = new QAbstractItemModelTester(modelToBeTested);
154 \endcode
155
156 QAbstractItemModelTester will report testing failures through the
157 Qt Test logging mechanisms.
158
159 It is also possible to use QAbstractItemModelTester outside of a test case.
160 For instance, it may be useful to test an item model used by an application
161 without the need of building an explicit unit test for such a model (which
162 might be challenging). In order to use QAbstractItemModelTester outside of
163 a test case, pass one of the \c QAbstractItemModelTester::FailureReportingMode
164 enumerators to its constructor, therefore specifying how failures should
165 be logged.
166
167 QAbstractItemModelTester may also report additional debugging information
168 as logging messages under the \c qt.modeltest logging category. Such
169 debug logging is disabled by default; refer to the
170 QLoggingCategory documentation to learn how to enable it.
171
172 \note While QAbstractItemModelTester is a valid help for development and
173 testing of custom item models, it does not (and cannot) catch all possible
174 problems in QAbstractItemModel subclasses. Notably, it will never perform
175 meaningful destructive testing of a model, which must be therefore tested
176 separately.
177
178 \sa {Model/View Programming}, QAbstractItemModel
179*/
180
181/*!
182 \enum QAbstractItemModelTester::FailureReportingMode
183
184 This enumeration specifies how QAbstractItemModelTester should report
185 a failure when it tests a QAbstractItemModel subclass.
186
187 \value QtTest The failures will be reported through
188 QtTest's logging mechanism.
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*/
202QAbstractItemModelTester::QAbstractItemModelTester(QAbstractItemModel *model, QObject *parent)
203 : QAbstractItemModelTester(model, FailureReportingMode::QtTest, parent)
204{
205}
206
207/*!
208 Creates a model tester instance, with the given \a parent, that will test
209 the model \a model, using the specified \a mode to report test failures.
210
211 \sa QAbstractItemModelTester::FailureReportingMode
212*/
213QAbstractItemModelTester::QAbstractItemModelTester(QAbstractItemModel *model, FailureReportingMode mode, QObject *parent)
214 : QObject(*new QAbstractItemModelTesterPrivate(model, mode), parent)
215{
216 if (!model)
217 qFatal("%s: model must not be null", Q_FUNC_INFO);
218
219 Q_D(QAbstractItemModelTester);
220
221 auto runAllTests = [d] { d->runAllTests(); };
222
223 connect(model, &QAbstractItemModel::columnsAboutToBeInserted,
224 this, runAllTests);
225 connect(model, &QAbstractItemModel::columnsAboutToBeRemoved,
226 this, runAllTests);
227 connect(model, &QAbstractItemModel::columnsInserted,
228 this, runAllTests);
229 connect(model, &QAbstractItemModel::columnsRemoved,
230 this, runAllTests);
231 connect(model, &QAbstractItemModel::dataChanged,
232 this, runAllTests);
233 connect(model, &QAbstractItemModel::headerDataChanged,
234 this, runAllTests);
235 connect(model, &QAbstractItemModel::layoutAboutToBeChanged,
236 this, runAllTests);
237 connect(model, &QAbstractItemModel::layoutChanged,
238 this, runAllTests);
239 connect(model, &QAbstractItemModel::modelReset,
240 this, runAllTests);
241 connect(model, &QAbstractItemModel::rowsAboutToBeInserted,
242 this, runAllTests);
243 connect(model, &QAbstractItemModel::rowsAboutToBeRemoved,
244 this, runAllTests);
245 connect(model, &QAbstractItemModel::rowsInserted,
246 this, runAllTests);
247 connect(model, &QAbstractItemModel::rowsRemoved,
248 this, runAllTests);
249
250 // Special checks for changes
251 connect(model, &QAbstractItemModel::layoutAboutToBeChanged,
252 this, [d]{ d->layoutAboutToBeChanged(); });
253 connect(model, &QAbstractItemModel::layoutChanged,
254 this, [d]{ d->layoutChanged(); });
255
256 connect(model, &QAbstractItemModel::rowsAboutToBeInserted,
257 this, [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeInserted(parent, start, end); });
258 connect(model, &QAbstractItemModel::rowsAboutToBeRemoved,
259 this, [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeRemoved(parent, start, end); });
260 connect(model, &QAbstractItemModel::rowsInserted,
261 this, [d](const QModelIndex &parent, int start, int end) { d->rowsInserted(parent, start, end); });
262 connect(model, &QAbstractItemModel::rowsRemoved,
263 this, [d](const QModelIndex &parent, int start, int end) { d->rowsRemoved(parent, start, end); });
264 connect(model, &QAbstractItemModel::dataChanged,
265 this, [d](const QModelIndex &topLeft, const QModelIndex &bottomRight) { d->dataChanged(topLeft, bottomRight); });
266 connect(model, &QAbstractItemModel::headerDataChanged,
267 this, [d](Qt::Orientation orientation, int start, int end) { d->headerDataChanged(orientation, start, end); });
268
269 runAllTests();
270}
271
272/*!
273 Returns the model that this instance is testing.
274*/
275QAbstractItemModel *QAbstractItemModelTester::model() const
276{
277 Q_D(const QAbstractItemModelTester);
278 return d->model.data();
279}
280
281/*!
282 Returns the mode that this instancing is using to report test failures.
283
284 \sa QAbstractItemModelTester::FailureReportingMode
285*/
286QAbstractItemModelTester::FailureReportingMode QAbstractItemModelTester::failureReportingMode() const
287{
288 Q_D(const QAbstractItemModelTester);
289 return d->failureReportingMode;
290}
291
292bool QAbstractItemModelTester::verify(bool statement, const char *statementStr, const char *description, const char *file, int line)
293{
294 Q_D(QAbstractItemModelTester);
295 return d->verify(statement, statementStr, description, file, line);
296}
297
298QAbstractItemModelTesterPrivate::QAbstractItemModelTesterPrivate(QAbstractItemModel *model, QAbstractItemModelTester::FailureReportingMode failureReportingMode)
299 : model(model),
300 failureReportingMode(failureReportingMode),
301 fetchingMore(false)
302{
303}
304
305void QAbstractItemModelTesterPrivate::runAllTests()
306{
307 if (fetchingMore)
308 return;
309 nonDestructiveBasicTest();
310 rowAndColumnCount();
311 hasIndex();
312 index();
313 parent();
314 data();
315}
316
317/*
318 nonDestructiveBasicTest tries to call a number of the basic functions (not all)
319 to make sure the model doesn't outright segfault, testing the functions that makes sense.
320*/
321void QAbstractItemModelTesterPrivate::nonDestructiveBasicTest()
322{
323 MODELTESTER_VERIFY(!model->buddy(QModelIndex()).isValid());
324 model->canFetchMore(QModelIndex());
325 MODELTESTER_VERIFY(model->columnCount(QModelIndex()) >= 0);
326 fetchingMore = true;
327 model->fetchMore(QModelIndex());
328 fetchingMore = false;
329 Qt::ItemFlags flags = model->flags(QModelIndex());
330 MODELTESTER_VERIFY(flags == Qt::ItemIsDropEnabled || flags == 0);
331 model->hasChildren(QModelIndex());
332 const bool hasRow = model->hasIndex(0, 0);
333 QVariant cache;
334 if (hasRow)
335 model->match(model->index(0, 0), -1, cache);
336 model->mimeTypes();
337 MODELTESTER_VERIFY(!model->parent(QModelIndex()).isValid());
338 MODELTESTER_VERIFY(model->rowCount() >= 0);
339 model->span(QModelIndex());
340 model->supportedDropActions();
341 model->roleNames();
342}
343
344/*
345 Tests model's implementation of QAbstractItemModel::rowCount(),
346 columnCount() and hasChildren().
347
348 Models that are dynamically populated are not as fully tested here.
349 */
350void QAbstractItemModelTesterPrivate::rowAndColumnCount()
351{
352 if (!model->hasChildren())
353 return;
354
355 QModelIndex topIndex = model->index(0, 0, QModelIndex());
356
357 // check top row
358 int rows = model->rowCount(topIndex);
359 MODELTESTER_VERIFY(rows >= 0);
360
361 int columns = model->columnCount(topIndex);
362 MODELTESTER_VERIFY(columns >= 0);
363
364 if (rows == 0 || columns == 0)
365 return;
366
367 MODELTESTER_VERIFY(model->hasChildren(topIndex));
368
369 QModelIndex secondLevelIndex = model->index(0, 0, topIndex);
370 MODELTESTER_VERIFY(secondLevelIndex.isValid());
371
372 rows = model->rowCount(secondLevelIndex);
373 MODELTESTER_VERIFY(rows >= 0);
374
375 columns = model->columnCount(secondLevelIndex);
376 MODELTESTER_VERIFY(columns >= 0);
377
378 if (rows == 0 || columns == 0)
379 return;
380
381 MODELTESTER_VERIFY(model->hasChildren(secondLevelIndex));
382
383 // rowCount() / columnCount() are tested more extensively in checkChildren()
384}
385
386/*
387 Tests model's implementation of QAbstractItemModel::hasIndex()
388 */
389void QAbstractItemModelTesterPrivate::hasIndex()
390{
391 // Make sure that invalid values returns an invalid index
392 MODELTESTER_VERIFY(!model->hasIndex(-2, -2));
393 MODELTESTER_VERIFY(!model->hasIndex(-2, 0));
394 MODELTESTER_VERIFY(!model->hasIndex(0, -2));
395
396 const int rows = model->rowCount();
397 const int columns = model->columnCount();
398
399 // check out of bounds
400 MODELTESTER_VERIFY(!model->hasIndex(rows, columns));
401 MODELTESTER_VERIFY(!model->hasIndex(rows + 1, columns + 1));
402
403 if (rows > 0 && columns > 0)
404 MODELTESTER_VERIFY(model->hasIndex(0, 0));
405
406 // hasIndex() is tested more extensively in checkChildren(),
407 // but this catches the big mistakes
408}
409
410/*
411 Tests model's implementation of QAbstractItemModel::index()
412 */
413void QAbstractItemModelTesterPrivate::index()
414{
415 const int rows = model->rowCount();
416 const int columns = model->columnCount();
417
418 for (int row = 0; row < rows; ++row) {
419 for (int column = 0; column < columns; ++column) {
420 // Make sure that the same index is *always* returned
421 QModelIndex a = model->index(row, column);
422 QModelIndex b = model->index(row, column);
423 MODELTESTER_VERIFY(a.isValid());
424 MODELTESTER_VERIFY(b.isValid());
425 MODELTESTER_COMPARE(a, b);
426 }
427 }
428
429 // index() is tested more extensively in checkChildren(),
430 // but this catches the big mistakes
431}
432
433/*
434 Tests model's implementation of QAbstractItemModel::parent()
435 */
436void QAbstractItemModelTesterPrivate::parent()
437{
438 // Make sure the model won't crash and will return an invalid QModelIndex
439 // when asked for the parent of an invalid index.
440 MODELTESTER_VERIFY(!model->parent(QModelIndex()).isValid());
441
442 if (model->rowCount() == 0 || model->columnCount() == 0)
443 return;
444
445 // Column 0 | Column 1 |
446 // QModelIndex() | |
447 // \- topIndex | topIndex1 |
448 // \- childIndex | childIndex1 |
449
450 // Common error test #1, make sure that a top level index has a parent
451 // that is a invalid QModelIndex.
452 QModelIndex topIndex = model->index(0, 0, QModelIndex());
453 MODELTESTER_VERIFY(topIndex.isValid());
454 MODELTESTER_VERIFY(!model->parent(topIndex).isValid());
455
456 // Common error test #2, make sure that a second level index has a parent
457 // that is the first level index.
458 if (model->rowCount(topIndex) > 0) {
459 QModelIndex childIndex = model->index(0, 0, topIndex);
460 MODELTESTER_VERIFY(childIndex.isValid());
461 MODELTESTER_COMPARE(model->parent(childIndex), topIndex);
462 }
463
464 // Common error test #3, the second column should NOT have the same children
465 // as the first column in a row.
466 // Usually the second column shouldn't have children.
467 if (model->hasIndex(0, 1)) {
468 QModelIndex topIndex1 = model->index(0, 1, QModelIndex());
469 MODELTESTER_VERIFY(topIndex1.isValid());
470 if (model->rowCount(topIndex) > 0 && model->rowCount(topIndex1) > 0) {
471 QModelIndex childIndex = model->index(0, 0, topIndex);
472 MODELTESTER_VERIFY(childIndex.isValid());
473 QModelIndex childIndex1 = model->index(0, 0, topIndex1);
474 MODELTESTER_VERIFY(childIndex1.isValid());
475 MODELTESTER_VERIFY(childIndex != childIndex1);
476 }
477 }
478
479 // Full test, walk n levels deep through the model making sure that all
480 // parent's children correctly specify their parent.
481 checkChildren(QModelIndex());
482}
483
484/*
485 Called from the parent() test.
486
487 A model that returns an index of parent X should also return X when asking
488 for the parent of the index.
489
490 This recursive function does pretty extensive testing on the whole model in an
491 effort to catch edge cases.
492
493 This function assumes that rowCount(), columnCount() and index() already work.
494 If they have a bug it will point it out, but the above tests should have already
495 found the basic bugs because it is easier to figure out the problem in
496 those tests then this one.
497 */
498void QAbstractItemModelTesterPrivate::checkChildren(const QModelIndex &parent, int currentDepth)
499{
500 // First just try walking back up the tree.
501 QModelIndex p = parent;
502 while (p.isValid())
503 p = p.parent();
504
505 // For models that are dynamically populated
506 if (model->canFetchMore(parent)) {
507 fetchingMore = true;
508 model->fetchMore(parent);
509 fetchingMore = false;
510 }
511
512 const int rows = model->rowCount(parent);
513 const int columns = model->columnCount(parent);
514
515 if (rows > 0)
516 MODELTESTER_VERIFY(model->hasChildren(parent));
517
518 // Some further testing against rows(), columns(), and hasChildren()
519 MODELTESTER_VERIFY(rows >= 0);
520 MODELTESTER_VERIFY(columns >= 0);
521 if (rows > 0 && columns > 0)
522 MODELTESTER_VERIFY(model->hasChildren(parent));
523
524 const QModelIndex topLeftChild = model->index(0, 0, parent);
525
526 MODELTESTER_VERIFY(!model->hasIndex(rows, 0, parent));
527 MODELTESTER_VERIFY(!model->hasIndex(rows + 1, 0, parent));
528
529 for (int r = 0; r < rows; ++r) {
530 MODELTESTER_VERIFY(!model->hasIndex(r, columns, parent));
531 MODELTESTER_VERIFY(!model->hasIndex(r, columns + 1, parent));
532 for (int c = 0; c < columns; ++c) {
533 MODELTESTER_VERIFY(model->hasIndex(r, c, parent));
534 QModelIndex index = model->index(r, c, parent);
535 // rowCount() and columnCount() said that it existed...
536 if (!index.isValid())
537 qCWarning(lcModelTest) << "Got invalid index at row=" << r << "col=" << c << "parent=" << parent;
538 MODELTESTER_VERIFY(index.isValid());
539
540 // index() should always return the same index when called twice in a row
541 QModelIndex modifiedIndex = model->index(r, c, parent);
542 MODELTESTER_COMPARE(index, modifiedIndex);
543
544 {
545 const QModelIndex sibling = model->sibling(r, c, topLeftChild);
546 MODELTESTER_COMPARE(index, sibling);
547 }
548 {
549 const QModelIndex sibling = topLeftChild.sibling(r, c);
550 MODELTESTER_COMPARE(index, sibling);
551 }
552
553 // Some basic checking on the index that is returned
554 MODELTESTER_COMPARE(index.model(), model);
555 MODELTESTER_COMPARE(index.row(), r);
556 MODELTESTER_COMPARE(index.column(), c);
557
558 // If the next test fails here is some somewhat useful debug you play with.
559 if (model->parent(index) != parent) {
560 qCWarning(lcModelTest) << "Inconsistent parent() implementation detected:";
561 qCWarning(lcModelTest) << " index=" << index << "exp. parent=" << parent << "act. parent=" << model->parent(index);
562 qCWarning(lcModelTest) << " row=" << r << "col=" << c << "depth=" << currentDepth;
563 qCWarning(lcModelTest) << " data for child" << model->data(index).toString();
564 qCWarning(lcModelTest) << " data for parent" << model->data(parent).toString();
565 }
566
567 // Check that we can get back our real parent.
568 MODELTESTER_COMPARE(model->parent(index), parent);
569
570 QPersistentModelIndex persistentIndex = index;
571
572 // recursively go down the children
573 if (model->hasChildren(index) && currentDepth < 10)
574 checkChildren(index, ++currentDepth);
575
576 // make sure that after testing the children that the index doesn't change.
577 QModelIndex newerIndex = model->index(r, c, parent);
578 MODELTESTER_COMPARE(persistentIndex, newerIndex);
579 }
580 }
581}
582
583/*
584 Tests model's implementation of QAbstractItemModel::data()
585 */
586void QAbstractItemModelTesterPrivate::data()
587{
588 if (model->rowCount() == 0 || model->columnCount() == 0)
589 return;
590
591 MODELTESTER_VERIFY(model->index(0, 0).isValid());
592
593 // General Purpose roles that should return a QString
594 QVariant variant;
595 variant = model->data(model->index(0, 0), Qt::DisplayRole);
596 if (variant.isValid())
597 MODELTESTER_VERIFY(variant.canConvert<QString>());
598 variant = model->data(model->index(0, 0), Qt::ToolTipRole);
599 if (variant.isValid())
600 MODELTESTER_VERIFY(variant.canConvert<QString>());
601 variant = model->data(model->index(0, 0), Qt::StatusTipRole);
602 if (variant.isValid())
603 MODELTESTER_VERIFY(variant.canConvert<QString>());
604 variant = model->data(model->index(0, 0), Qt::WhatsThisRole);
605 if (variant.isValid())
606 MODELTESTER_VERIFY(variant.canConvert<QString>());
607
608 // General Purpose roles that should return a QSize
609 variant = model->data(model->index(0, 0), Qt::SizeHintRole);
610 if (variant.isValid())
611 MODELTESTER_VERIFY(variant.canConvert<QSize>());
612
613 // Check that the alignment is one we know about
614 QVariant textAlignmentVariant = model->data(model->index(0, 0), Qt::TextAlignmentRole);
615 if (textAlignmentVariant.isValid()) {
616 Qt::Alignment alignment = qvariant_cast<Qt::Alignment>(textAlignmentVariant);
617 MODELTESTER_COMPARE(alignment, (alignment & (Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask)));
618 }
619
620 // Check that the "check state" is one we know about.
621 QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole);
622 if (checkStateVariant.isValid()) {
623 int state = checkStateVariant.toInt();
624 MODELTESTER_VERIFY(state == Qt::Unchecked
625 || state == Qt::PartiallyChecked
626 || state == Qt::Checked);
627 }
628
629 Q_Q(QAbstractItemModelTester);
630
631 if (!QTestPrivate::testDataGuiRoles(q))
632 return;
633}
634
635/*
636 Store what is about to be inserted to make sure it actually happens
637
638 \sa rowsInserted()
639 */
640void QAbstractItemModelTesterPrivate::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
641{
642 qCDebug(lcModelTest) << "rowsAboutToBeInserted"
643 << "start=" << start << "end=" << end << "parent=" << parent
644 << "parent data=" << model->data(parent).toString()
645 << "current count of parent=" << model->rowCount(parent)
646 << "last before insertion=" << model->index(start - 1, 0, parent) << model->data(model->index(start - 1, 0, parent));
647
648 Changing c;
649 c.parent = parent;
650 c.oldSize = model->rowCount(parent);
651 c.last = (start - 1 >= 0) ? model->index(start - 1, 0, parent).data() : QVariant();
652 c.next = (start < c.oldSize) ? model->index(start, 0, parent).data() : QVariant();
653 insert.push(c);
654}
655
656/*
657 Confirm that what was said was going to happen actually did
658
659 \sa rowsAboutToBeInserted()
660 */
661void QAbstractItemModelTesterPrivate::rowsInserted(const QModelIndex &parent, int start, int end)
662{
663 qCDebug(lcModelTest) << "rowsInserted"
664 << "start=" << start << "end=" << end << "parent=" << parent
665 << "parent data=" << model->data(parent).toString()
666 << "current count of parent=" << model->rowCount(parent);
667
668 for (int i = start; i <= end; ++i) {
669 qCDebug(lcModelTest) << " itemWasInserted:" << i
670 << model->index(i, 0, parent).data();
671 }
672
673
674 Changing c = insert.pop();
675 MODELTESTER_COMPARE(parent, c.parent);
676
677 MODELTESTER_COMPARE(model->rowCount(parent), c.oldSize + (end - start + 1));
678 if (start - 1 >= 0)
679 MODELTESTER_COMPARE(model->data(model->index(start - 1, 0, c.parent)), c.last);
680
681 if (end + 1 < model->rowCount(c.parent)) {
682 if (c.next != model->data(model->index(end + 1, 0, c.parent))) {
683 qDebug() << start << end;
684 for (int i = 0; i < model->rowCount(); ++i)
685 qDebug() << model->index(i, 0).data().toString();
686 qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent));
687 }
688
689 MODELTESTER_COMPARE(model->data(model->index(end + 1, 0, c.parent)), c.next);
690 }
691}
692
693void QAbstractItemModelTesterPrivate::layoutAboutToBeChanged()
694{
695 for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i)
696 changing.append(QPersistentModelIndex(model->index(i, 0)));
697}
698
699void QAbstractItemModelTesterPrivate::layoutChanged()
700{
701 for (int i = 0; i < changing.count(); ++i) {
702 QPersistentModelIndex p = changing[i];
703 MODELTESTER_COMPARE(model->index(p.row(), p.column(), p.parent()), QModelIndex(p));
704 }
705 changing.clear();
706}
707
708/*
709 Store what is about to be inserted to make sure it actually happens
710
711 \sa rowsRemoved()
712 */
713void QAbstractItemModelTesterPrivate::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
714{
715 qCDebug(lcModelTest) << "rowsAboutToBeRemoved"
716 << "start=" << start << "end=" << end << "parent=" << parent
717 << "parent data=" << model->data(parent).toString()
718 << "current count of parent=" << model->rowCount(parent)
719 << "last before removal=" << model->index(start - 1, 0, parent) << model->data(model->index(start - 1, 0, parent));
720
721 Changing c;
722 c.parent = parent;
723 c.oldSize = model->rowCount(parent);
724 if (start > 0 && model->columnCount(parent) > 0) {
725 const QModelIndex startIndex = model->index(start - 1, 0, parent);
726 MODELTESTER_VERIFY(startIndex.isValid());
727 c.last = model->data(startIndex);
728 }
729 if (end < c.oldSize - 1 && model->columnCount(parent) > 0) {
730 const QModelIndex endIndex = model->index(end + 1, 0, parent);
731 MODELTESTER_VERIFY(endIndex.isValid());
732 c.next = model->data(endIndex);
733 }
734
735 remove.push(c);
736}
737
738/*
739 Confirm that what was said was going to happen actually did
740
741 \sa rowsAboutToBeRemoved()
742 */
743void QAbstractItemModelTesterPrivate::rowsRemoved(const QModelIndex &parent, int start, int end)
744{
745 qCDebug(lcModelTest) << "rowsRemoved"
746 << "start=" << start << "end=" << end << "parent=" << parent
747 << "parent data=" << model->data(parent).toString()
748 << "current count of parent=" << model->rowCount(parent);
749
750 Changing c = remove.pop();
751 MODELTESTER_COMPARE(parent, c.parent);
752 MODELTESTER_COMPARE(model->rowCount(parent), c.oldSize - (end - start + 1));
753 if (start > 0)
754 MODELTESTER_COMPARE(model->data(model->index(start - 1, 0, c.parent)), c.last);
755 if (end < c.oldSize - 1)
756 MODELTESTER_COMPARE(model->data(model->index(start, 0, c.parent)), c.next);
757}
758
759void QAbstractItemModelTesterPrivate::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
760{
761 MODELTESTER_VERIFY(topLeft.isValid());
762 MODELTESTER_VERIFY(bottomRight.isValid());
763 QModelIndex commonParent = bottomRight.parent();
764 MODELTESTER_COMPARE(topLeft.parent(), commonParent);
765 MODELTESTER_VERIFY(topLeft.row() <= bottomRight.row());
766 MODELTESTER_VERIFY(topLeft.column() <= bottomRight.column());
767 int rowCount = model->rowCount(commonParent);
768 int columnCount = model->columnCount(commonParent);
769 MODELTESTER_VERIFY(bottomRight.row() < rowCount);
770 MODELTESTER_VERIFY(bottomRight.column() < columnCount);
771}
772
773void QAbstractItemModelTesterPrivate::headerDataChanged(Qt::Orientation orientation, int start, int end)
774{
775 MODELTESTER_VERIFY(start >= 0);
776 MODELTESTER_VERIFY(end >= 0);
777 MODELTESTER_VERIFY(start <= end);
778 int itemCount = orientation == Qt::Vertical ? model->rowCount() : model->columnCount();
779 MODELTESTER_VERIFY(start < itemCount);
780 MODELTESTER_VERIFY(end < itemCount);
781}
782
783bool QAbstractItemModelTesterPrivate::verify(bool statement,
784 const char *statementStr, const char *description,
785 const char *file, int line)
786{
787 static const char formatString[] = "FAIL! %s (%s) returned FALSE (%s:%d)";
788
789 switch (failureReportingMode) {
790 case QAbstractItemModelTester::FailureReportingMode::QtTest:
791 return QTest::qVerify(statement, statementStr, description, file, line);
792 break;
793
794 case QAbstractItemModelTester::FailureReportingMode::Warning:
795 if (!statement)
796 qCWarning(lcModelTest, formatString, statementStr, description, file, line);
797 break;
798
799 case QAbstractItemModelTester::FailureReportingMode::Fatal:
800 if (!statement)
801 qFatal(formatString, statementStr, description, file, line);
802 break;
803 }
804
805 return statement;
806}
807
808
809template<typename T1, typename T2>
810bool QAbstractItemModelTesterPrivate::compare(const T1 &t1, const T2 &t2,
811 const char *actual, const char *expected,
812 const char *file, int line)
813{
814 const bool result = static_cast<bool>(t1 == t2);
815
816 static const char formatString[] = "FAIL! Compared values are not the same:\n Actual (%s) %s\n Expected (%s) %s\n (%s:%d)";
817
818 switch (failureReportingMode) {
819 case QAbstractItemModelTester::FailureReportingMode::QtTest:
820 return QTest::qCompare(t1, t2, actual, expected, file, line);
821 break;
822
823 case QAbstractItemModelTester::FailureReportingMode::Warning:
824 if (!result) {
825 auto t1string = QTest::toString(t1);
826 auto t2string = QTest::toString(t2);
827 qCWarning(lcModelTest, formatString, actual, t1string ? t1string : "(nullptr)",
828 expected, t2string ? t2string : "(nullptr)",
829 file, line);
830 delete [] t1string;
831 delete [] t2string;
832 }
833 break;
834
835 case QAbstractItemModelTester::FailureReportingMode::Fatal:
836 if (!result) {
837 auto t1string = QTest::toString(t1);
838 auto t2string = QTest::toString(t2);
839 qFatal(formatString, actual, t1string ? t1string : "(nullptr)",
840 expected, t2string ? t2string : "(nullptr)",
841 file, line);
842 delete [] t1string;
843 delete [] t2string;
844 }
845 break;
846 }
847
848 return result;
849}
850
851
852QT_END_NAMESPACE
853