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"