File indexing completed on 2024-11-24 04:53:08
0001 /**************************************************************************** 0002 ** 0003 ** Copyright (C) 2007 Trolltech ASA. All rights reserved. 0004 ** 0005 ** This file is part of the Qt Concurrent project on Trolltech Labs. 0006 ** 0007 ** This file may be used under the terms of the GNU General Public 0008 ** License version 2.0 as published by the Free Software Foundation 0009 ** and appearing in the file LICENSE.GPL included in the packaging of 0010 ** this file. Please review the following information to ensure GNU 0011 ** General Public Licensing requirements will be met: 0012 ** http://www.trolltech.com/products/qt/opensource.html 0013 ** 0014 ** If you are unsure which license is appropriate for your use, please 0015 ** review the following information: 0016 ** http://www.trolltech.com/products/qt/licensing.html or contact the 0017 ** sales department at sales@trolltech.com. 0018 ** 0019 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE 0020 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 0021 ** 0022 ****************************************************************************/ 0023 0024 #include <QtGui/QtGui> 0025 #include "Imap/Model/ItemRoles.h" 0026 0027 #include "modeltest.h" 0028 0029 Q_DECLARE_METATYPE(QModelIndex) 0030 0031 /*! 0032 Connect to all of the models signals. Whenever anything happens recheck everything. 0033 */ 0034 ModelTest::ModelTest(QAbstractItemModel *_model, QObject *parent) : QObject(parent), model(_model), fetchingMore(false) 0035 { 0036 Q_ASSERT(model); 0037 0038 connect(model, SIGNAL(columnsAboutToBeInserted(const QModelIndex &, int, int)), 0039 this, SLOT(runAllTests())); 0040 connect(model, SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)), 0041 this, SLOT(runAllTests())); 0042 connect(model, SIGNAL(columnsInserted(const QModelIndex &, int, int)), 0043 this, SLOT(runAllTests())); 0044 connect(model, SIGNAL(columnsRemoved(const QModelIndex &, int, int)), 0045 this, SLOT(runAllTests())); 0046 connect(model, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), 0047 this, SLOT(runAllTests())); 0048 connect(model, SIGNAL(headerDataChanged(Qt::Orientation, int, int)), 0049 this, SLOT(runAllTests())); 0050 connect(model, SIGNAL(layoutAboutToBeChanged ()), this, SLOT(runAllTests())); 0051 connect(model, SIGNAL(layoutChanged ()), this, SLOT(runAllTests())); 0052 connect(model, SIGNAL(modelReset ()), this, SLOT(runAllTests())); 0053 connect(model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)), 0054 this, SLOT(runAllTests())); 0055 connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)), 0056 this, SLOT(runAllTests())); 0057 connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)), 0058 this, SLOT(runAllTests())); 0059 connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), 0060 this, SLOT(runAllTests())); 0061 0062 // Special checks for inserting/removing 0063 connect(model, SIGNAL(layoutAboutToBeChanged()), 0064 this, SLOT(layoutAboutToBeChanged())); 0065 connect(model, SIGNAL(layoutChanged()), 0066 this, SLOT(layoutChanged())); 0067 0068 connect(model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)), 0069 this, SLOT(rowsAboutToBeInserted(const QModelIndex &, int, int))); 0070 connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)), 0071 this, SLOT(rowsAboutToBeRemoved(const QModelIndex &, int, int))); 0072 connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)), 0073 this, SLOT(rowsInserted(const QModelIndex &, int, int))); 0074 connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), 0075 this, SLOT(rowsRemoved(const QModelIndex &, int, int))); 0076 0077 runAllTests(); 0078 } 0079 0080 void ModelTest::runAllTests() 0081 { 0082 if (fetchingMore) 0083 return; 0084 nonDestructiveBasicTest(); 0085 rowCount(); 0086 columnCount(); 0087 hasIndex(); 0088 index(); 0089 parent(); 0090 data(); 0091 } 0092 0093 /*! 0094 nonDestructiveBasicTest tries to call a number of the basic functions (not all) 0095 to make sure the model doesn't outright segfault, testing the functions that makes sense. 0096 */ 0097 void ModelTest::nonDestructiveBasicTest() 0098 { 0099 Q_ASSERT(model->buddy(QModelIndex()) == QModelIndex()); 0100 model->canFetchMore(QModelIndex()); 0101 Q_ASSERT(model->columnCount(QModelIndex()) >= 0); 0102 Q_ASSERT(model->data(QModelIndex()) == QVariant()); 0103 fetchingMore = true; 0104 model->fetchMore(QModelIndex()); 0105 fetchingMore = false; 0106 Qt::ItemFlags flags = model->flags(QModelIndex()); 0107 Q_ASSERT(flags == Qt::ItemIsDropEnabled || flags == 0); 0108 model->hasChildren(QModelIndex()); 0109 model->hasIndex(0, 0); 0110 model->headerData(0, Qt::Horizontal); 0111 model->index(0, 0); 0112 model->itemData(QModelIndex()); 0113 QVariant cache; 0114 model->match(QModelIndex(), -1, cache); 0115 model->mimeTypes(); 0116 Q_ASSERT(model->parent(QModelIndex()) == QModelIndex()); 0117 Q_ASSERT(model->rowCount() >= 0); 0118 QVariant variant; 0119 model->setData(QModelIndex(), variant, -1); 0120 model->setHeaderData(-1, Qt::Horizontal, QVariant()); 0121 model->setHeaderData(999999, Qt::Horizontal, QVariant()); 0122 QMap<int, QVariant> roles; 0123 model->sibling(0, 0, QModelIndex()); 0124 model->span(QModelIndex()); 0125 model->supportedDropActions(); 0126 } 0127 0128 /*! 0129 Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren() 0130 0131 Models that are dynamically populated are not as fully tested here. 0132 */ 0133 void ModelTest::rowCount() 0134 { 0135 // check top row 0136 QModelIndex topIndex = model->index(0, 0, QModelIndex()); 0137 int rows = model->rowCount(topIndex); 0138 Q_ASSERT(rows >= 0); 0139 if (rows > 0) 0140 Q_ASSERT(model->hasChildren(topIndex) == true); 0141 0142 QModelIndex secondLevelIndex = model->index(0, 0, topIndex); 0143 if (secondLevelIndex.isValid()) { // not the top level 0144 // check a row count where parent is valid 0145 rows = model->rowCount(secondLevelIndex); 0146 Q_ASSERT(rows >= 0); 0147 if (rows > 0) 0148 Q_ASSERT(model->hasChildren(secondLevelIndex) == true); 0149 } 0150 0151 // The models rowCount() is tested more extensively in checkChildren(), 0152 // but this catches the big mistakes 0153 } 0154 0155 /*! 0156 Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren() 0157 */ 0158 void ModelTest::columnCount() 0159 { 0160 // check top row 0161 QModelIndex topIndex = model->index(0, 0, QModelIndex()); 0162 Q_ASSERT(model->columnCount(topIndex) >= 0); 0163 0164 // check a column count where parent is valid 0165 QModelIndex childIndex = model->index(0, 0, topIndex); 0166 if (childIndex.isValid()) 0167 Q_ASSERT(model->columnCount(childIndex) >= 0); 0168 0169 // columnCount() is tested more extensively in checkChildren(), 0170 // but this catches the big mistakes 0171 } 0172 0173 /*! 0174 Tests model's implementation of QAbstractItemModel::hasIndex() 0175 */ 0176 void ModelTest::hasIndex() 0177 { 0178 // Make sure that invalid values returns an invalid index 0179 Q_ASSERT(model->hasIndex(-2, -2) == false); 0180 Q_ASSERT(model->hasIndex(-2, 0) == false); 0181 Q_ASSERT(model->hasIndex(0, -2) == false); 0182 0183 int rows = model->rowCount(); 0184 int columns = model->columnCount(); 0185 0186 // check out of bounds 0187 Q_ASSERT(model->hasIndex(rows, columns) == false); 0188 Q_ASSERT(model->hasIndex(rows + 1, columns + 1) == false); 0189 0190 if (rows > 0) 0191 Q_ASSERT(model->hasIndex(0, 0) == true); 0192 0193 // hasIndex() is tested more extensively in checkChildren(), 0194 // but this catches the big mistakes 0195 } 0196 0197 /*! 0198 Tests model's implementation of QAbstractItemModel::index() 0199 */ 0200 void ModelTest::index() 0201 { 0202 // Make sure that invalid values returns an invalid index 0203 Q_ASSERT(model->index(-2, -2) == QModelIndex()); 0204 Q_ASSERT(model->index(-2, 0) == QModelIndex()); 0205 Q_ASSERT(model->index(0, -2) == QModelIndex()); 0206 0207 int rows = model->rowCount(); 0208 int columns = model->columnCount(); 0209 0210 if (rows == 0) 0211 return; 0212 0213 // Catch off by one errors 0214 Q_ASSERT(model->index(rows, columns) == QModelIndex()); 0215 Q_ASSERT(model->index(0, 0).isValid() == true); 0216 0217 // Make sure that the same index is *always* returned 0218 QModelIndex a = model->index(0, 0); 0219 QModelIndex b = model->index(0, 0); 0220 Q_ASSERT(a == b); 0221 0222 // index() is tested more extensively in checkChildren(), 0223 // but this catches the big mistakes 0224 } 0225 0226 /*! 0227 Tests model's implementation of QAbstractItemModel::parent() 0228 */ 0229 void ModelTest::parent() 0230 { 0231 // Make sure the model wont crash and will return an invalid QModelIndex 0232 // when asked for the parent of an invalid index. 0233 Q_ASSERT(model->parent(QModelIndex()) == QModelIndex()); 0234 0235 if (model->rowCount() == 0) 0236 return; 0237 0238 // Column 0 | Column 1 | 0239 // QModelIndex() | | 0240 // \- topIndex | topIndex1 | 0241 // \- childIndex | childIndex1 | 0242 0243 // Common error test #1, make sure that a top level index has a parent 0244 // that is a invalid QModelIndex. 0245 QModelIndex topIndex = model->index(0, 0, QModelIndex()); 0246 Q_ASSERT(model->parent(topIndex) == QModelIndex()); 0247 0248 // Common error test #2, make sure that a second level index has a parent 0249 // that is the first level index. 0250 if (model->rowCount(topIndex) > 0) { 0251 QModelIndex childIndex = model->index(0, 0, topIndex); 0252 Q_ASSERT(model->parent(childIndex) == topIndex); 0253 } 0254 0255 // Common error test #3, the second column should NOT have the same children 0256 // as the first column in a row. 0257 // Usually the second column shouldn't have children. 0258 QModelIndex topIndex1 = model->index(0, 1, QModelIndex()); 0259 if (model->rowCount(topIndex1) > 0) { 0260 QModelIndex childIndex = model->index(0, 0, topIndex); 0261 QModelIndex childIndex1 = model->index(0, 0, topIndex1); 0262 Q_ASSERT(childIndex != childIndex1); 0263 } 0264 0265 // Full test, walk n levels deep through the model making sure that all 0266 // parent's children correctly specify their parent. 0267 checkChildren(QModelIndex()); 0268 } 0269 0270 /*! 0271 Called from the parent() test. 0272 0273 A model that returns an index of parent X should also return X when asking 0274 for the parent of the index. 0275 0276 This recursive function does pretty extensive testing on the whole model in an 0277 effort to catch edge cases. 0278 0279 This function assumes that rowCount(), columnCount() and index() already work. 0280 If they have a bug it will point it out, but the above tests should have already 0281 found the basic bugs because it is easier to figure out the problem in 0282 those tests then this one. 0283 */ 0284 void ModelTest::checkChildren(const QModelIndex &parent, int currentDepth) 0285 { 0286 // First just try walking back up the tree. 0287 QModelIndex p = parent; 0288 while (p.isValid()) 0289 p = p.parent(); 0290 0291 // For models that are dynamically populated 0292 if (model->canFetchMore(parent)) { 0293 fetchingMore = true; 0294 model->fetchMore(parent); 0295 fetchingMore = false; 0296 } 0297 0298 int rows = model->rowCount(parent); 0299 int columns = model->columnCount(parent); 0300 0301 if (rows > 0) 0302 Q_ASSERT(model->hasChildren(parent)); 0303 0304 // Some further testing against rows(), columns(), and hasChildren() 0305 Q_ASSERT(rows >= 0); 0306 Q_ASSERT(columns >= 0); 0307 if (rows > 0) 0308 Q_ASSERT(model->hasChildren(parent) == true); 0309 0310 //qDebug() << "parent:" << model->data(parent).toString() << "rows:" << rows 0311 // << "columns:" << columns << "parent column:" << parent.column(); 0312 0313 Q_ASSERT(model->hasIndex(rows + 1, 0, parent) == false); 0314 for (int r = 0; r < rows; ++r) { 0315 if (model->canFetchMore(parent)) { 0316 fetchingMore = true; 0317 model->fetchMore(parent); 0318 fetchingMore = false; 0319 } 0320 Q_ASSERT(model->hasIndex(r, columns + 1, parent) == false); 0321 for (int c = 0; c < columns; ++c) { 0322 Q_ASSERT(model->hasIndex(r, c, parent) == true); 0323 QModelIndex index = model->index(r, c, parent); 0324 // rowCount() and columnCount() said that it existed... 0325 Q_ASSERT(index.isValid() == true); 0326 0327 // index() should always return the same index when called twice in a row 0328 QModelIndex modifiedIndex = model->index(r, c, parent); 0329 Q_ASSERT(index == modifiedIndex); 0330 0331 // Make sure we get the same index if we request it twice in a row 0332 QModelIndex a = model->index(r, c, parent); 0333 QModelIndex b = model->index(r, c, parent); 0334 Q_ASSERT(a == b); 0335 0336 // Some basic checking on the index that is returned 0337 Q_ASSERT(index.model() == model); 0338 Q_ASSERT(index.row() == r); 0339 Q_ASSERT(index.column() == c); 0340 // While you can technically return a QVariant usually this is a sign 0341 // of an bug in data() Disable if this really is ok in your model. 0342 //Q_ASSERT(model->data(index, Qt::DisplayRole).isValid() || model->data(index, Qt::DecorationRole).isValid()); 0343 if ( ! ( model->data(index, Qt::DisplayRole).isValid() || model->data(index, Qt::DecorationRole).isValid() 0344 || model->data(index, Imap::Mailbox::RoleMessageUid).isValid() ) ) 0345 qDebug() << "ModelTest::checkChildren: got invalid data() for" << index; 0346 0347 // If the next test fails here is some somewhat useful debug you play with. 0348 /* 0349 if (model->parent(index) != parent) { 0350 qDebug() << r << c << currentDepth << model->data(index).toString() 0351 << model->data(parent).toString(); 0352 qDebug() << index << parent << model->parent(index); 0353 // And a view that you can even use to show the model. 0354 //QTreeView view; 0355 //view.setModel(model); 0356 //view.show(); 0357 }*/ 0358 0359 // Check that we can get back our real parent. 0360 Q_ASSERT(model->parent(index) == parent); 0361 0362 // recursively go down the children 0363 if (model->hasChildren(index) && currentDepth < 10 ) { 0364 //qDebug() << r << c << "has children" << model->rowCount(index); 0365 checkChildren(index, ++currentDepth); 0366 }/* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/ 0367 0368 // make sure that after testing the children that the index doesn't change. 0369 QModelIndex newerIndex = model->index(r, c, parent); 0370 Q_ASSERT(index == newerIndex); 0371 } 0372 } 0373 } 0374 0375 /*! 0376 Tests model's implementation of QAbstractItemModel::data() 0377 */ 0378 void ModelTest::data() 0379 { 0380 // Invalid index should return an invalid qvariant 0381 Q_ASSERT(!model->data(QModelIndex()).isValid()); 0382 0383 if (model->rowCount() == 0) 0384 return; 0385 0386 // A valid index should have a valid QVariant data 0387 Q_ASSERT(model->index(0, 0).isValid()); 0388 0389 // shouldn't be able to set data on an invalid index 0390 Q_ASSERT(model->setData(QModelIndex(), QLatin1String("foo"), Qt::DisplayRole) == false); 0391 0392 // General Purpose roles that should return a QString 0393 QVariant variant = model->data(model->index(0, 0), Qt::ToolTipRole); 0394 if (variant.isValid()) { 0395 Q_ASSERT(variant.canConvert(QMetaType::QString)); 0396 } 0397 variant = model->data(model->index(0, 0), Qt::StatusTipRole); 0398 if (variant.isValid()) { 0399 Q_ASSERT(variant.canConvert(QMetaType::QString)); 0400 } 0401 variant = model->data(model->index(0, 0), Qt::WhatsThisRole); 0402 if (variant.isValid()) { 0403 Q_ASSERT(variant.canConvert(QMetaType::QString)); 0404 } 0405 0406 // General Purpose roles that should return a QSize 0407 variant = model->data(model->index(0, 0), Qt::SizeHintRole); 0408 if (variant.isValid()) { 0409 Q_ASSERT(variant.canConvert(QMetaType::QSize)); 0410 } 0411 0412 // General Purpose roles that should return a QFont 0413 QVariant fontVariant = model->data(model->index(0, 0), Qt::FontRole); 0414 if (fontVariant.isValid()) { 0415 Q_ASSERT(variant.canConvert(QMetaType::QFont)); 0416 } 0417 0418 // Check that the alignment is one we know about 0419 QVariant textAlignmentVariant = model->data(model->index(0, 0), Qt::TextAlignmentRole); 0420 if (textAlignmentVariant.isValid()) { 0421 unsigned int alignment = textAlignmentVariant.toUInt(); 0422 Q_ASSERT(alignment == (alignment & (Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask))); 0423 } 0424 0425 // General Purpose roles that should return a QColor 0426 QVariant colorVariant = model->data(model->index(0, 0), Qt::BackgroundRole); 0427 if (colorVariant.isValid()) { 0428 Q_ASSERT(colorVariant.canConvert(QMetaType::QColor)); 0429 } 0430 0431 colorVariant = model->data(model->index(0, 0), Qt::ForegroundRole); 0432 if (colorVariant.isValid()) { 0433 Q_ASSERT(colorVariant.canConvert(QMetaType::QColor)); 0434 } 0435 0436 // Check that the "check state" is one we know about. 0437 QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole); 0438 if (checkStateVariant.isValid()) { 0439 int state = checkStateVariant.toInt(); 0440 Q_ASSERT(state == Qt::Unchecked || 0441 state == Qt::PartiallyChecked || 0442 state == Qt::Checked); 0443 } 0444 } 0445 0446 /*! 0447 Store what is about to be inserted to make sure it actually happens 0448 0449 \sa rowsInserted() 0450 */ 0451 void ModelTest::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end) 0452 { 0453 Q_UNUSED(end); 0454 Changing c; 0455 c.parent = parent; 0456 c.oldSize = model->rowCount(parent); 0457 c.last = model->data(model->index(start - 1, 0, parent)); 0458 c.next = model->data(model->index(start, 0, parent)); 0459 insert.push(c); 0460 } 0461 0462 /*! 0463 Confirm that what was said was going to happen actually did 0464 0465 \sa rowsAboutToBeInserted() 0466 */ 0467 void ModelTest::rowsInserted(const QModelIndex & parent, int start, int end) 0468 { 0469 Changing c = insert.pop(); 0470 Q_ASSERT(c.parent == parent); 0471 Q_ASSERT(c.oldSize + (end - start + 1) == model->rowCount(parent)); 0472 //Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent))); 0473 /* 0474 if (c.next != model->data(model->index(end + 1, 0, c.parent))) { 0475 qDebug() << start << end; 0476 for (int i=0; i < model->rowCount(); ++i) 0477 qDebug() << model->index(i, 0).data().toString(); 0478 qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent)); 0479 } 0480 */ 0481 Q_ASSERT(c.next == model->data(model->index(end + 1, 0, c.parent))); 0482 } 0483 0484 void ModelTest::layoutAboutToBeChanged() 0485 { 0486 for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i) 0487 changing.append(QPersistentModelIndex(model->index(i, 0))); 0488 } 0489 0490 void ModelTest::layoutChanged() 0491 { 0492 for (int i = 0; i < changing.count(); ++i) { 0493 QPersistentModelIndex p = changing[i]; 0494 Q_ASSERT(p == model->index(p.row(), p.column(), p.parent())); 0495 } 0496 changing.clear(); 0497 } 0498 0499 /*! 0500 Store what is about to be inserted to make sure it actually happens 0501 0502 \sa rowsRemoved() 0503 */ 0504 void ModelTest::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) 0505 { 0506 Changing c; 0507 c.parent = parent; 0508 c.oldSize = model->rowCount(parent); 0509 c.last = model->data(model->index(start - 1, 0, parent)); 0510 c.next = model->data(model->index(end + 1, 0, parent)); 0511 remove.push(c); 0512 } 0513 0514 /*! 0515 Confirm that what was said was going to happen actually did 0516 0517 \sa rowsAboutToBeRemoved() 0518 */ 0519 void ModelTest::rowsRemoved(const QModelIndex & parent, int start, int end) 0520 { 0521 Changing c = remove.pop(); 0522 Q_ASSERT(c.parent == parent); 0523 Q_ASSERT(c.oldSize - (end - start + 1) == model->rowCount(parent)); 0524 /*Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent))); 0525 Q_ASSERT(c.next == model->data(model->index(start, 0, c.parent)));*/ 0526 // FIXME jkt: these do change dynamically :( 0527 } 0528