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