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