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