1 /****************************************************************************
3 ** Copyright (C) 2007 Trolltech ASA. All rights reserved.
5 ** This file is part of the Qt Concurrent project on Trolltech Labs.
7 ** This file may be used under the terms of the GNU General Public
8 ** License version 2.0 as published by the Free Software Foundation
9 ** and appearing in the file LICENSE.GPL included in the packaging of
10 ** this file. Please review the following information to ensure GNU
11 ** General Public Licensing requirements will be met:
12 ** http://www.trolltech.com/products/qt/opensource.html
14 ** If you are unsure which license is appropriate for your use, please
15 ** review the following information:
16 ** http://www.trolltech.com/products/qt/licensing.html or contact the
17 ** sales department at sales@trolltech.com.
19 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
20 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
22 ****************************************************************************/
24 #include <QtGui/QtGui>
26 #include "modeltest.h"
28 Q_DECLARE_METATYPE(QModelIndex)
31 Connect to all of the models signals. Whenever anything happens recheck everything.
33 ModelTest::ModelTest(QAbstractItemModel *_model, QObject *parent) : QObject(parent), model(_model), fetchingMore(false)
37 connect(model, SIGNAL(columnsAboutToBeInserted(const QModelIndex &, int, int)),
38 this, SLOT(runAllTests()));
39 connect(model, SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)),
40 this, SLOT(runAllTests()));
41 connect(model, SIGNAL(columnsInserted(const QModelIndex &, int, int)),
42 this, SLOT(runAllTests()));
43 connect(model, SIGNAL(columnsRemoved(const QModelIndex &, int, int)),
44 this, SLOT(runAllTests()));
45 connect(model, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),
46 this, SLOT(runAllTests()));
47 connect(model, SIGNAL(headerDataChanged(Qt::Orientation, int, int)),
48 this, SLOT(runAllTests()));
49 connect(model, SIGNAL(layoutAboutToBeChanged ()), this, SLOT(runAllTests()));
50 connect(model, SIGNAL(layoutChanged ()), this, SLOT(runAllTests()));
51 connect(model, SIGNAL(modelReset ()), this, SLOT(runAllTests()));
52 connect(model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)),
53 this, SLOT(runAllTests()));
54 connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
55 this, SLOT(runAllTests()));
56 connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
57 this, SLOT(runAllTests()));
58 connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
59 this, SLOT(runAllTests()));
61 // Special checks for inserting/removing
62 connect(model, SIGNAL(layoutAboutToBeChanged()),
63 this, SLOT(layoutAboutToBeChanged()));
64 connect(model, SIGNAL(layoutChanged()),
65 this, SLOT(layoutChanged()));
67 connect(model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)),
68 this, SLOT(rowsAboutToBeInserted(const QModelIndex &, int, int)));
69 connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
70 this, SLOT(rowsAboutToBeRemoved(const QModelIndex &, int, int)));
71 connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
72 this, SLOT(rowsInserted(const QModelIndex &, int, int)));
73 connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
74 this, SLOT(rowsRemoved(const QModelIndex &, int, int)));
79 void ModelTest::runAllTests()
83 nonDestructiveBasicTest();
93 nonDestructiveBasicTest tries to call a number of the basic functions (not all)
94 to make sure the model doesn't outright segfault, testing the functions that makes sense.
96 void ModelTest::nonDestructiveBasicTest()
98 Q_ASSERT(model->buddy(QModelIndex()) == QModelIndex());
99 model->canFetchMore(QModelIndex());
100 Q_ASSERT(model->columnCount(QModelIndex()) >= 0);
101 Q_ASSERT(model->data(QModelIndex()) == QVariant());
103 model->fetchMore(QModelIndex());
104 fetchingMore = false;
105 Qt::ItemFlags flags = model->flags(QModelIndex());
106 Q_ASSERT(flags == Qt::ItemIsDropEnabled || flags == 0);
107 model->hasChildren(QModelIndex());
108 model->hasIndex(0, 0);
109 model->headerData(0, Qt::Horizontal);
111 Q_ASSERT(model->index(-1, -1) == QModelIndex());
112 model->itemData(QModelIndex());
114 model->match(QModelIndex(), -1, cache);
116 Q_ASSERT(model->parent(QModelIndex()) == QModelIndex());
117 Q_ASSERT(model->rowCount() >= 0);
119 model->setData(QModelIndex(), variant, -1);
120 model->setHeaderData(-1, Qt::Horizontal, QVariant());
121 model->setHeaderData(0, Qt::Horizontal, QVariant());
122 model->setHeaderData(999999, Qt::Horizontal, QVariant());
123 QMap<int, QVariant> roles;
124 model->sibling(0, 0, QModelIndex());
125 model->span(QModelIndex());
126 model->supportedDropActions();
130 Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren()
132 Models that are dynamically populated are not as fully tested here.
134 void ModelTest::rowCount()
137 QModelIndex topIndex = model->index(0, 0, QModelIndex());
138 int rows = model->rowCount(topIndex);
141 Q_ASSERT(model->hasChildren(topIndex) == true);
143 QModelIndex secondLevelIndex = model->index(0, 0, topIndex);
144 if (secondLevelIndex.isValid()) { // not the top level
145 // check a row count where parent is valid
146 rows = model->rowCount(secondLevelIndex);
149 Q_ASSERT(model->hasChildren(secondLevelIndex) == true);
152 // The models rowCount() is tested more extensively in checkChildren(),
153 // but this catches the big mistakes
157 Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren()
159 void ModelTest::columnCount()
162 QModelIndex topIndex = model->index(0, 0, QModelIndex());
163 Q_ASSERT(model->columnCount(topIndex) >= 0);
165 // check a column count where parent is valid
166 QModelIndex childIndex = model->index(0, 0, topIndex);
167 if (childIndex.isValid())
168 Q_ASSERT(model->columnCount(childIndex) >= 0);
170 // columnCount() is tested more extensively in checkChildren(),
171 // but this catches the big mistakes
175 Tests model's implementation of QAbstractItemModel::hasIndex()
177 void ModelTest::hasIndex()
179 // Make sure that invalid values returns an invalid index
180 Q_ASSERT(model->hasIndex(-2, -2) == false);
181 Q_ASSERT(model->hasIndex(-2, 0) == false);
182 Q_ASSERT(model->hasIndex(0, -2) == false);
184 int rows = model->rowCount();
185 int columns = model->columnCount();
187 // check out of bounds
188 Q_ASSERT(model->hasIndex(rows, columns) == false);
189 Q_ASSERT(model->hasIndex(rows + 1, columns + 1) == false);
192 Q_ASSERT(model->hasIndex(0, 0) == true);
194 // hasIndex() is tested more extensively in checkChildren(),
195 // but this catches the big mistakes
199 Tests model's implementation of QAbstractItemModel::index()
201 void ModelTest::index()
203 // Make sure that invalid values returns an invalid index
204 Q_ASSERT(model->index(-2, -2) == QModelIndex());
205 Q_ASSERT(model->index(-2, 0) == QModelIndex());
206 Q_ASSERT(model->index(0, -2) == QModelIndex());
208 int rows = model->rowCount();
209 int columns = model->columnCount();
214 // Catch off by one errors
215 Q_ASSERT(model->index(rows, columns) == QModelIndex());
216 Q_ASSERT(model->index(0, 0).isValid() == true);
218 // Make sure that the same index is *always* returned
219 QModelIndex a = model->index(0, 0);
220 QModelIndex b = model->index(0, 0);
223 // index() is tested more extensively in checkChildren(),
224 // but this catches the big mistakes
228 Tests model's implementation of QAbstractItemModel::parent()
230 void ModelTest::parent()
232 // Make sure the model wont crash and will return an invalid QModelIndex
233 // when asked for the parent of an invalid index.
234 Q_ASSERT(model->parent(QModelIndex()) == QModelIndex());
236 if (model->rowCount() == 0)
239 // Column 0 | Column 1 |
241 // \- topIndex | topIndex1 |
242 // \- childIndex | childIndex1 |
244 // Common error test #1, make sure that a top level index has a parent
245 // that is a invalid QModelIndex.
246 QModelIndex topIndex = model->index(0, 0, QModelIndex());
247 Q_ASSERT(model->parent(topIndex) == QModelIndex());
249 // Common error test #2, make sure that a second level index has a parent
250 // that is the first level index.
251 if (model->rowCount(topIndex) > 0) {
252 QModelIndex childIndex = model->index(0, 0, topIndex);
253 Q_ASSERT(model->parent(childIndex) == topIndex);
256 // Common error test #3, the second column should NOT have the same children
257 // as the first column in a row.
258 // Usually the second column shouldn't have children.
259 QModelIndex topIndex1 = model->index(0, 1, QModelIndex());
260 if (model->rowCount(topIndex1) > 0) {
261 QModelIndex childIndex = model->index(0, 0, topIndex);
262 QModelIndex childIndex1 = model->index(0, 0, topIndex1);
263 Q_ASSERT(childIndex != childIndex1);
266 // Full test, walk n levels deep through the model making sure that all
267 // parent's children correctly specify their parent.
268 checkChildren(QModelIndex());
272 Called from the parent() test.
274 A model that returns an index of parent X should also return X when asking
275 for the parent of the index.
277 This recursive function does pretty extensive testing on the whole model in an
278 effort to catch edge cases.
280 This function assumes that rowCount(), columnCount() and index() already work.
281 If they have a bug it will point it out, but the above tests should have already
282 found the basic bugs because it is easier to figure out the problem in
283 those tests then this one.
285 void ModelTest::checkChildren(const QModelIndex &parent, int currentDepth)
287 // First just try walking back up the tree.
288 QModelIndex p = parent;
292 // For models that are dynamically populated
293 if (model->canFetchMore(parent)) {
295 model->fetchMore(parent);
296 fetchingMore = false;
299 int rows = model->rowCount(parent);
300 int columns = model->columnCount(parent);
303 Q_ASSERT(model->hasChildren(parent));
305 // Some further testing against rows(), columns(), and hasChildren()
307 Q_ASSERT(columns >= 0);
309 Q_ASSERT(model->hasChildren(parent) == true);
311 //qDebug() << "parent:" << model->data(parent).toString() << "rows:" << rows
312 // << "columns:" << columns << "parent column:" << parent.column();
314 Q_ASSERT(model->hasIndex(rows + 1, 0, parent) == false);
315 for (int r = 0; r < rows; ++r) {
316 if (model->canFetchMore(parent)) {
318 model->fetchMore(parent);
319 fetchingMore = false;
321 Q_ASSERT(model->hasIndex(r, columns + 1, parent) == false);
322 for (int c = 0; c < columns; ++c) {
323 Q_ASSERT(model->hasIndex(r, c, parent) == true);
324 QModelIndex index = model->index(r, c, parent);
325 // rowCount() and columnCount() said that it existed...
326 Q_ASSERT(index.isValid() == true);
328 // index() should always return the same index when called twice in a row
329 QModelIndex modifiedIndex = model->index(r, c, parent);
330 Q_ASSERT(index == modifiedIndex);
332 // Make sure we get the same index if we request it twice in a row
333 QModelIndex a = model->index(r, c, parent);
334 QModelIndex b = model->index(r, c, parent);
337 // Some basic checking on the index that is returned
338 Q_ASSERT(index.model() == model);
339 Q_ASSERT(index.row() == r);
340 Q_ASSERT(index.column() == c);
341 // While you can technically return a QVariant usually this is a sign
342 // of an bug in data() Disable if this really is ok in your model.
343 //Q_ASSERT(model->data(index, Qt::DisplayRole).isValid() == true);
345 // If the next test fails here is some somewhat useful debug you play with.
347 if (model->parent(index) != parent) {
348 qDebug() << r << c << currentDepth << model->data(index).toString()
349 << model->data(parent).toString();
350 qDebug() << index << parent << model->parent(index);
351 // And a view that you can even use to show the model.
353 //view.setModel(model);
357 // Check that we can get back our real parent.
358 QModelIndex p = model->parent(index);
359 //qDebug() << "child:" << index;
361 //qDebug() << parent;
362 Q_ASSERT(model->parent(index) == parent);
364 // recursively go down the children
365 if (model->hasChildren(index) && currentDepth < 10 ) {
366 //qDebug() << r << c << "has children" << model->rowCount(index);
367 checkChildren(index, ++currentDepth);
368 }/* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/
370 // make sure that after testing the children that the index doesn't change.
371 QModelIndex newerIndex = model->index(r, c, parent);
372 Q_ASSERT(index == newerIndex);
378 Tests model's implementation of QAbstractItemModel::data()
380 void ModelTest::data()
382 // Invalid index should return an invalid qvariant
383 Q_ASSERT(!model->data(QModelIndex()).isValid());
385 if (model->rowCount() == 0)
388 // A valid index should have a valid QVariant data
389 Q_ASSERT(model->index(0, 0).isValid());
391 // shouldn't be able to set data on an invalid index
392 Q_ASSERT(model->setData(QModelIndex(), QLatin1String("foo"), Qt::DisplayRole) == false);
394 // General Purpose roles that should return a QString
395 QVariant variant = model->data(model->index(0, 0), Qt::ToolTipRole);
396 if (variant.isValid()) {
397 Q_ASSERT(qVariantCanConvert<QString>(variant));
399 variant = model->data(model->index(0, 0), Qt::StatusTipRole);
400 if (variant.isValid()) {
401 Q_ASSERT(qVariantCanConvert<QString>(variant));
403 variant = model->data(model->index(0, 0), Qt::WhatsThisRole);
404 if (variant.isValid()) {
405 Q_ASSERT(qVariantCanConvert<QString>(variant));
408 // General Purpose roles that should return a QSize
409 variant = model->data(model->index(0, 0), Qt::SizeHintRole);
410 if (variant.isValid()) {
411 Q_ASSERT(qVariantCanConvert<QSize>(variant));
414 // General Purpose roles that should return a QFont
415 QVariant fontVariant = model->data(model->index(0, 0), Qt::FontRole);
416 if (fontVariant.isValid()) {
417 Q_ASSERT(qVariantCanConvert<QFont>(fontVariant));
420 // Check that the alignment is one we know about
421 QVariant textAlignmentVariant = model->data(model->index(0, 0), Qt::TextAlignmentRole);
422 if (textAlignmentVariant.isValid()) {
423 int alignment = textAlignmentVariant.toInt();
424 Q_ASSERT(alignment == Qt::AlignLeft ||
425 alignment == Qt::AlignRight ||
426 alignment == Qt::AlignHCenter ||
427 alignment == Qt::AlignJustify ||
428 alignment == Qt::AlignTop ||
429 alignment == Qt::AlignBottom ||
430 alignment == Qt::AlignVCenter ||
431 alignment == Qt::AlignCenter ||
432 alignment == Qt::AlignAbsolute ||
433 alignment == Qt::AlignLeading ||
434 alignment == Qt::AlignTrailing);
437 // General Purpose roles that should return a QColor
438 QVariant colorVariant = model->data(model->index(0, 0), Qt::BackgroundColorRole);
439 if (colorVariant.isValid()) {
440 Q_ASSERT(qVariantCanConvert<QColor>(colorVariant));
443 colorVariant = model->data(model->index(0, 0), Qt::TextColorRole);
444 if (colorVariant.isValid()) {
445 Q_ASSERT(qVariantCanConvert<QColor>(colorVariant));
448 // Check that the "check state" is one we know about.
449 QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole);
450 if (checkStateVariant.isValid()) {
451 int state = checkStateVariant.toInt();
452 Q_ASSERT(state == Qt::Unchecked ||
453 state == Qt::PartiallyChecked ||
454 state == Qt::Checked);
459 Store what is about to be inserted to make sure it actually happens
463 void ModelTest::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
468 c.oldSize = model->rowCount(parent);
469 c.last = model->data(model->index(start - 1, 0, parent));
470 c.next = model->data(model->index(start, 0, parent));
475 Confirm that what was said was going to happen actually did
477 \sa rowsAboutToBeInserted()
479 void ModelTest::rowsInserted(const QModelIndex & parent, int start, int end)
481 Changing c = insert.pop();
482 Q_ASSERT(c.parent == parent);
483 Q_ASSERT(c.oldSize + (end - start + 1) == model->rowCount(parent));
484 Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent)));
486 if (c.next != model->data(model->index(end + 1, 0, c.parent))) {
487 qDebug() << start << end;
488 for (int i=0; i < model->rowCount(); ++i)
489 qDebug() << model->index(i, 0).data().toString();
490 qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent));
493 Q_ASSERT(c.next == model->data(model->index(end + 1, 0, c.parent)));
496 void ModelTest::layoutAboutToBeChanged()
498 for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i)
499 changing.append(QPersistentModelIndex(model->index(i, 0)));
502 void ModelTest::layoutChanged()
504 for (int i = 0; i < changing.count(); ++i) {
505 QPersistentModelIndex p = changing[i];
506 Q_ASSERT(p == model->index(p.row(), p.column(), p.parent()));
512 Store what is about to be inserted to make sure it actually happens
516 void ModelTest::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
520 c.oldSize = model->rowCount(parent);
521 c.last = model->data(model->index(start - 1, 0, parent));
522 c.next = model->data(model->index(end + 1, 0, parent));
527 Confirm that what was said was going to happen actually did
529 \sa rowsAboutToBeRemoved()
531 void ModelTest::rowsRemoved(const QModelIndex & parent, int start, int end)
533 Changing c = remove.pop();
534 Q_ASSERT(c.parent == parent);
535 Q_ASSERT(c.oldSize - (end - start + 1) == model->rowCount(parent));
536 Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent)));
537 Q_ASSERT(c.next == model->data(model->index(start, 0, c.parent)));