File indexing completed on 2024-04-28 15:27:50

0001 /*
0002     SPDX-FileCopyrightText: 2009 Stephen Kelly <steveire@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "proxymodeltest.h"
0008 
0009 #include <QMimeData>
0010 #include <QSortFilterProxyModel>
0011 
0012 ProxyModelTest::ProxyModelTest(QObject *parent)
0013     : QObject(parent)
0014     , m_rootModel(new DynamicTreeModel(this))
0015     , m_sourceModel(m_rootModel)
0016     , m_proxyModel(nullptr)
0017     , m_intermediateProxyModel(nullptr)
0018     , m_modelSpy(new ModelSpy(this))
0019     , m_modelCommander(new ModelCommander(m_rootModel, this))
0020 {
0021 }
0022 
0023 void ProxyModelTest::setLazyPersistence(Persistence persistence)
0024 {
0025     m_modelSpy->setLazyPersistence(persistence == LazyPersistence);
0026 }
0027 
0028 void ProxyModelTest::setUseIntermediateProxy(SourceModel sourceModel)
0029 {
0030     if (sourceModel == DynamicTree) {
0031         return;
0032     }
0033 
0034     m_intermediateProxyModel = new QSortFilterProxyModel(this);
0035     m_intermediateProxyModel->setSourceModel(m_rootModel);
0036     m_sourceModel = m_intermediateProxyModel;
0037 }
0038 
0039 static bool hasMetaMethodStartingWith(QObject *object, const QString &checkedSignature)
0040 {
0041     const QMetaObject *mo = object->metaObject();
0042     bool found = false;
0043     for (int methodIndex = 0; methodIndex < mo->methodCount(); ++methodIndex) {
0044         QMetaMethod mm = mo->method(methodIndex);
0045         QString signature = QString::fromLatin1(mm.methodSignature());
0046 
0047         if (signature.startsWith(checkedSignature)) {
0048             found = true;
0049             break;
0050         }
0051     }
0052     return found;
0053 }
0054 
0055 void ProxyModelTest::initRootModel(DynamicTreeModel *rootModel, const QString &currentTest, const QString &currentTag)
0056 {
0057     Q_UNUSED(rootModel)
0058     // Get the model into the state it is expected to be in.
0059 
0060     if (!hasMetaMethodStartingWith(m_modelCommander, "init_" + currentTest)) {
0061         return;
0062     }
0063 
0064     QMetaObject::invokeMethod(m_modelCommander, QString("init_" + currentTest).toLatin1(), Q_ARG(QString, currentTag));
0065 }
0066 
0067 static QSet<QString> unmatchedItems(const QStringList &list, const QStringList &items)
0068 {
0069     const QSet<QString> itemsAset(items.constBegin(), items.constEnd());
0070     const QSet<QString> listAsSet(list.constBegin(), list.constEnd());
0071     return listAsSet - itemsAset;
0072 }
0073 
0074 void ProxyModelTest::verifyExecutedTests()
0075 {
0076     if (m_dataTags.contains(ProxyModelTestData::failTag())) {
0077         return;
0078     }
0079     const QSet<QString> unimplemented = unmatchedItems(m_modelCommanderTags, m_dataTags);
0080     QString unimplementedTestsString(QStringLiteral("("));
0081     for (const QString &test : unimplemented) {
0082         unimplementedTestsString.append(test + ",");
0083     }
0084     unimplementedTestsString.append(")");
0085 
0086     if (!unimplemented.isEmpty()) {
0087         QString failString = QStringLiteral("Some tests in %1 were not implemented: %2").arg(m_currentTest, unimplementedTestsString);
0088         m_dataTags.clear();
0089         m_currentTest = QTest::currentTestFunction();
0090         QFAIL(failString.toLatin1());
0091     }
0092 }
0093 
0094 void ProxyModelTest::init()
0095 {
0096     QVERIFY(m_modelSpy->isEmpty());
0097     m_rootModel->clear();
0098 
0099     const char *currentTest = QTest::currentTestFunction();
0100     const char *currentTag = QTest::currentDataTag();
0101     QVERIFY(currentTest != nullptr);
0102     initRootModel(m_rootModel, currentTest, currentTag);
0103 
0104     Q_ASSERT(sourceModel());
0105     QAbstractProxyModel *proxyModel = getProxy();
0106 
0107     Q_ASSERT(proxyModel);
0108     // Don't set the sourceModel in getProxy.
0109     Q_ASSERT(!proxyModel->sourceModel());
0110     connectProxy(proxyModel);
0111 
0112     // Get the model into the state it is expected to be in.
0113     m_modelSpy->startSpying();
0114     QVERIFY(m_modelSpy->isEmpty());
0115 
0116     if (m_currentTest != currentTest) {
0117         verifyExecutedTests();
0118         m_dataTags.clear();
0119 
0120         QString metaMethod = QString("execute_" + QLatin1String(currentTest));
0121 
0122         if (!hasMetaMethodStartingWith(m_modelCommander, metaMethod)) {
0123             return;
0124         }
0125 
0126         QMetaObject::invokeMethod(m_modelCommander, metaMethod.toLatin1(), Q_RETURN_ARG(QStringList, m_modelCommanderTags), Q_ARG(QString, QString()));
0127         m_currentTest = currentTest;
0128     }
0129     m_dataTags.append(currentTag);
0130 }
0131 
0132 void ProxyModelTest::cleanup()
0133 {
0134     QVERIFY(m_modelSpy->isEmpty());
0135     m_modelSpy->stopSpying();
0136     m_modelSpy->setModel(nullptr);
0137     m_proxyModel->setSourceModel(nullptr);
0138     delete m_proxyModel;
0139     m_proxyModel = nullptr;
0140     QVERIFY(m_modelSpy->isEmpty());
0141 }
0142 
0143 void ProxyModelTest::cleanupTestCase()
0144 {
0145     verifyExecutedTests();
0146     m_modelCommanderTags.clear();
0147     if (!m_intermediateProxyModel) {
0148         return;
0149     }
0150 
0151     m_sourceModel = m_rootModel;
0152     delete m_intermediateProxyModel;
0153     m_intermediateProxyModel = nullptr;
0154 
0155     m_modelSpy->clear();
0156 }
0157 
0158 PersistentIndexChange ProxyModelTest::getChange(IndexFinder parentFinder, int start, int end, int difference, bool toInvalid)
0159 {
0160     Q_ASSERT(start <= end);
0161     PersistentIndexChange change;
0162     change.parentFinder = parentFinder;
0163     change.startRow = start;
0164     change.endRow = end;
0165     change.difference = difference;
0166     change.toInvalid = toInvalid;
0167     return change;
0168 }
0169 
0170 void ProxyModelTest::handleSignal(QVariantList expected)
0171 {
0172     QVERIFY(!expected.isEmpty());
0173     int signalType = expected.takeAt(0).toInt();
0174     if (NoSignal == signalType) {
0175         return;
0176     }
0177 
0178     Q_ASSERT(!m_modelSpy->isEmpty());
0179     QVariantList result = getResultSignal();
0180 
0181     QCOMPARE(result.takeAt(0).toInt(), signalType);
0182     // Check that the signal we expected to receive was emitted exactly.
0183     switch (signalType) {
0184     case RowsAboutToBeInserted:
0185     case RowsInserted:
0186     case RowsAboutToBeRemoved:
0187     case RowsRemoved: {
0188         QVERIFY(expected.size() == 3);
0189         IndexFinder parentFinder = qvariant_cast<IndexFinder>(expected.at(0));
0190         parentFinder.setModel(m_proxyModel);
0191         QModelIndex parent = parentFinder.getIndex();
0192 
0193 // This is where is usually goes wrong...
0194 #if 0
0195         qDebug() << qvariant_cast<QModelIndex>(result.at(0)) << parent;
0196         qDebug() << result.at(1) << expected.at(1);
0197         qDebug() << result.at(2) << expected.at(2);
0198 #endif
0199 
0200         QCOMPARE(qvariant_cast<QModelIndex>(result.at(0)), parent);
0201         QCOMPARE(result.at(1), expected.at(1));
0202         QCOMPARE(result.at(2), expected.at(2));
0203         break;
0204     }
0205     case LayoutAboutToBeChanged:
0206     case LayoutChanged: {
0207         QVERIFY(expected.size() == 0);
0208         QVERIFY(result.size() == 0);
0209         break;
0210     }
0211     case RowsAboutToBeMoved:
0212     case RowsMoved: {
0213         QVERIFY(expected.size() == 5);
0214         IndexFinder scrParentFinder = qvariant_cast<IndexFinder>(expected.at(0));
0215         scrParentFinder.setModel(m_proxyModel);
0216         QModelIndex srcParent = scrParentFinder.getIndex();
0217         QCOMPARE(qvariant_cast<QModelIndex>(result.at(0)), srcParent);
0218         QCOMPARE(result.at(1), expected.at(1));
0219         QCOMPARE(result.at(2), expected.at(2));
0220         IndexFinder destParentFinder = qvariant_cast<IndexFinder>(expected.at(3));
0221         destParentFinder.setModel(m_proxyModel);
0222         QModelIndex destParent = destParentFinder.getIndex();
0223         QCOMPARE(qvariant_cast<QModelIndex>(result.at(3)), destParent);
0224         QCOMPARE(result.at(4), expected.at(4));
0225         break;
0226     }
0227     case DataChanged: {
0228         QCOMPARE(expected.size(), 2);
0229         IndexFinder topLeftFinder = qvariant_cast<IndexFinder>(expected.at(0));
0230         topLeftFinder.setModel(m_proxyModel);
0231         QModelIndex topLeft = topLeftFinder.getIndex();
0232         IndexFinder bottomRightFinder = qvariant_cast<IndexFinder>(expected.at(1));
0233         bottomRightFinder.setModel(m_proxyModel);
0234 
0235         QModelIndex bottomRight = bottomRightFinder.getIndex();
0236 
0237         QVERIFY(topLeft.isValid() && bottomRight.isValid());
0238 
0239 #if 0
0240         qDebug() << qvariant_cast<QModelIndex>(result.at(0)) << topLeft;
0241         qDebug() << qvariant_cast<QModelIndex>(result.at(1)) << bottomRight;
0242 #endif
0243 
0244         QCOMPARE(qvariant_cast<QModelIndex>(result.at(0)), topLeft);
0245         QCOMPARE(qvariant_cast<QModelIndex>(result.at(1)), bottomRight);
0246     }
0247     }
0248 }
0249 
0250 QVariantList ProxyModelTest::getResultSignal()
0251 {
0252     return m_modelSpy->takeFirst();
0253 }
0254 
0255 void ProxyModelTest::testEmptyModel()
0256 {
0257     Q_ASSERT(sourceModel());
0258     QAbstractProxyModel *proxyModel = getProxy();
0259     // Many of these just check that the proxy does not crash when it does not have a source model.
0260     QCOMPARE(proxyModel->rowCount(), 0);
0261     QCOMPARE(proxyModel->columnCount(), 0);
0262     QVERIFY(!proxyModel->index(0, 0).isValid());
0263     QVERIFY(!proxyModel->data(QModelIndex()).isValid());
0264     QVERIFY(!proxyModel->parent(QModelIndex()).isValid());
0265     QVERIFY(!proxyModel->mapToSource(QModelIndex()).isValid());
0266     QVERIFY(!proxyModel->mapFromSource(QModelIndex()).isValid());
0267     QVERIFY(!proxyModel->headerData(0, Qt::Horizontal, Qt::DisplayRole).isValid());
0268     QVERIFY(!proxyModel->headerData(0, Qt::Vertical, Qt::DisplayRole).isValid());
0269     Qt::ItemFlags flags = proxyModel->flags(QModelIndex());
0270     QVERIFY(flags == Qt::ItemIsDropEnabled || flags == 0);
0271     QVERIFY(proxyModel->itemData(QModelIndex()).isEmpty());
0272     QVERIFY(proxyModel->mapSelectionToSource(QItemSelection()).isEmpty());
0273     QVERIFY(proxyModel->mapSelectionFromSource(QItemSelection()).isEmpty());
0274     proxyModel->revert();
0275     QVERIFY(proxyModel->submit());
0276     QVERIFY(!proxyModel->sourceModel());
0277     QVERIFY(!proxyModel->canFetchMore(QModelIndex()));
0278     proxyModel->fetchMore(QModelIndex());
0279     QMimeData *data = new QMimeData();
0280     QVERIFY(!proxyModel->dropMimeData(data, Qt::CopyAction, 0, 0, QModelIndex()));
0281     delete data;
0282     QVERIFY(!proxyModel->hasChildren());
0283     QVERIFY(!proxyModel->hasIndex(0, 0, QModelIndex()));
0284     proxyModel->supportedDragActions();
0285     proxyModel->supportedDropActions();
0286     delete proxyModel;
0287 }
0288 
0289 void ProxyModelTest::testSourceReset()
0290 {
0291     m_modelSpy->stopSpying();
0292     ModelInsertCommand *ins = new ModelInsertCommand(m_rootModel, this);
0293     ins->setStartRow(0);
0294     ins->interpret(
0295         QLatin1String("- 1"
0296                       "- 2"
0297                       "- - 3"
0298                       "- - 4"
0299                       "- 5"
0300                       "- 6"
0301                       "- 7"
0302                       "- 8"
0303                       "- 9"
0304                       "- - 10"));
0305     ins->doCommand();
0306     // The proxymodel should reset any internal state it holds when the source model is reset.
0307     QPersistentModelIndex pmi = m_proxyModel->index(0, 0);
0308     testMappings();
0309     m_rootModel->clear(); // Resets the model.
0310     testMappings(); // Calls some rowCount() etc which should test internal structures in the proxy.
0311     m_proxyModel->setSourceModel(nullptr);
0312 
0313     m_modelSpy->startSpying();
0314 }
0315 
0316 void ProxyModelTest::testDestroyModel()
0317 {
0318     QAbstractItemModel *currentSourceModel = m_sourceModel;
0319     DynamicTreeModel *rootModel = new DynamicTreeModel(this);
0320     m_sourceModel = rootModel;
0321 
0322     ModelInsertCommand *ins = new ModelInsertCommand(rootModel, this);
0323     ins->setStartRow(0);
0324     ins->interpret(
0325         QLatin1String(" - 1"
0326                       " - 1"
0327                       " - - 1"
0328                       " - 1"
0329                       " - 1"
0330                       " - 1"
0331                       " - 1"
0332                       " - 1"
0333                       " - - 1"));
0334     ins->doCommand();
0335 
0336     QAbstractProxyModel *proxyModel = getProxy();
0337     connectProxy(proxyModel);
0338 
0339     if (proxyModel->hasChildren()) {
0340         m_modelSpy->startSpying();
0341         const bool prevPersistMode = m_modelSpy->useLazyPersistence();
0342         m_modelSpy->setLazyPersistence(false); // don't persist on model reset during destruction
0343         QCOMPARE(m_modelSpy->size(), 0);
0344         delete m_sourceModel;
0345         m_sourceModel = nullptr;
0346         m_modelSpy->stopSpying();
0347         m_modelSpy->setLazyPersistence(prevPersistMode);
0348         testMappings();
0349         // QItemSelectionModel in Qt6 signals a source model change if the source is destroyed
0350         QVERIFY(m_modelSpy->size() == 0 || m_modelSpy->size() == 2);
0351         if (m_modelSpy->size() == 2) {
0352             QVERIFY(m_modelSpy->takeFirst().first() == ModelAboutToBeReset);
0353             QVERIFY(m_modelSpy->takeFirst().first() == ModelReset);
0354         }
0355     }
0356     m_sourceModel = currentSourceModel;
0357 }
0358 
0359 void ProxyModelTest::doTestMappings(const QModelIndex &parent)
0360 {
0361     if (!m_proxyModel) {
0362         return;
0363     }
0364     QModelIndex idx;
0365     QModelIndex srcIdx;
0366     for (int column = 0; column < m_proxyModel->columnCount(parent); ++column) {
0367         for (int row = 0; row < m_proxyModel->rowCount(parent); ++row) {
0368             idx = m_proxyModel->index(row, column, parent);
0369             QVERIFY(idx.isValid());
0370             QVERIFY(idx.row() == row);
0371             QVERIFY(idx.column() == column);
0372             QVERIFY(idx.parent() == parent);
0373             QVERIFY(idx.model() == m_proxyModel);
0374             srcIdx = m_proxyModel->mapToSource(idx);
0375             QVERIFY(srcIdx.isValid());
0376             QVERIFY(srcIdx.model() == m_proxyModel->sourceModel());
0377             QVERIFY(m_sourceModel == m_proxyModel->sourceModel());
0378             QVERIFY(idx.data() == srcIdx.data());
0379             QVERIFY(m_proxyModel->mapFromSource(srcIdx) == idx);
0380             if (m_proxyModel->hasChildren(idx)) {
0381                 doTestMappings(idx);
0382             }
0383         }
0384     }
0385 }
0386 
0387 void ProxyModelTest::testMappings()
0388 {
0389     doTestMappings(QModelIndex());
0390 }
0391 
0392 void ProxyModelTest::verifyModel(const QModelIndex &parent, int start, int end)
0393 {
0394     Q_UNUSED(start);
0395     Q_UNUSED(end);
0396 
0397     QVERIFY(parent.model() == m_proxyModel || !parent.isValid());
0398 }
0399 
0400 void ProxyModelTest::verifyModel(const QModelIndex &parent, int start, int end, const QModelIndex &destParent, int dest)
0401 {
0402     Q_UNUSED(start);
0403     Q_UNUSED(end);
0404     Q_UNUSED(dest);
0405 
0406     QVERIFY(parent.model() == m_proxyModel || !parent.isValid());
0407     QVERIFY(destParent.model() == m_proxyModel || !destParent.isValid());
0408 }
0409 
0410 void ProxyModelTest::verifyModel(const QModelIndex &topLeft, const QModelIndex &bottomRight)
0411 {
0412     QVERIFY(topLeft.model() == m_proxyModel || !topLeft.isValid());
0413     QVERIFY(bottomRight.model() == m_proxyModel || !bottomRight.isValid());
0414 }
0415 
0416 void ProxyModelTest::connectProxy(QAbstractProxyModel *proxyModel)
0417 {
0418     if (m_proxyModel) {
0419         disconnect(m_proxyModel, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this, SLOT(testMappings()));
0420         disconnect(m_proxyModel, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(testMappings()));
0421         disconnect(m_proxyModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(testMappings()));
0422         disconnect(m_proxyModel, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(testMappings()));
0423         disconnect(m_proxyModel, SIGNAL(layoutAboutToBeChanged()), this, SLOT(testMappings()));
0424         disconnect(m_proxyModel, SIGNAL(layoutChanged()), this, SLOT(testMappings()));
0425         disconnect(m_proxyModel, SIGNAL(rowsAboutToBeMoved(QModelIndex, int, int, QModelIndex, int)), this, SLOT(testMappings()));
0426         disconnect(m_proxyModel, SIGNAL(rowsMoved(QModelIndex, int, int, QModelIndex, int)), this, SLOT(testMappings()));
0427         disconnect(m_proxyModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(testMappings()));
0428 
0429         disconnect(m_proxyModel, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this, SLOT(verifyModel(QModelIndex, int, int)));
0430         disconnect(m_proxyModel, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(verifyModel(QModelIndex, int, int)));
0431         disconnect(m_proxyModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(verifyModel(QModelIndex, int, int)));
0432         disconnect(m_proxyModel, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(verifyModel(QModelIndex, int, int)));
0433         disconnect(m_proxyModel,
0434                    SIGNAL(rowsAboutToBeMoved(QModelIndex, int, int, QModelIndex, int)),
0435                    this,
0436                    SLOT(verifyModel(QModelIndex, int, int, QModelIndex, int)));
0437         disconnect(m_proxyModel, SIGNAL(rowsMoved(QModelIndex, int, int, QModelIndex, int)), this, SLOT(verifyModel(QModelIndex, int, int, QModelIndex, int)));
0438         disconnect(m_proxyModel, SIGNAL(columnsAboutToBeInserted(QModelIndex, int, int)), this, SLOT(verifyModel(QModelIndex, int, int)));
0439         disconnect(m_proxyModel, SIGNAL(columnsInserted(QModelIndex, int, int)), this, SLOT(verifyModel(QModelIndex, int, int)));
0440         disconnect(m_proxyModel, SIGNAL(columnsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(verifyModel(QModelIndex, int, int)));
0441         disconnect(m_proxyModel, SIGNAL(columnsRemoved(QModelIndex, int, int)), this, SLOT(verifyModel(QModelIndex, int, int)));
0442         disconnect(m_proxyModel,
0443                    SIGNAL(columnsAboutToBeMoved(QModelIndex, int, int, QModelIndex, int)),
0444                    this,
0445                    SLOT(verifyModel(QModelIndex, int, int, QModelIndex, int)));
0446         disconnect(m_proxyModel,
0447                    SIGNAL(columnsMoved(QModelIndex, int, int, QModelIndex, int)),
0448                    this,
0449                    SLOT(verifyModel(QModelIndex, int, int, QModelIndex, int)));
0450         disconnect(m_proxyModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(verifyModel(QModelIndex, QModelIndex)));
0451     }
0452 
0453     m_proxyModel = proxyModel;
0454     QVERIFY(m_modelSpy->isEmpty());
0455     m_modelSpy->setModel(m_proxyModel);
0456 
0457     QVERIFY(m_modelSpy->isEmpty());
0458 
0459     m_modelSpy->startSpying();
0460     m_proxyModel->setSourceModel(m_sourceModel);
0461     m_modelSpy->stopSpying();
0462 
0463     QVERIFY(m_modelSpy->size() == 2);
0464     QVERIFY(m_modelSpy->takeFirst().at(0) == ModelAboutToBeReset);
0465     QVERIFY(m_modelSpy->takeFirst().at(0) == ModelReset);
0466     QVERIFY(m_modelSpy->isEmpty());
0467     testMappings();
0468 
0469     connect(m_proxyModel, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), SLOT(testMappings()));
0470     connect(m_proxyModel, SIGNAL(rowsInserted(QModelIndex, int, int)), SLOT(testMappings()));
0471     connect(m_proxyModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), SLOT(testMappings()));
0472     connect(m_proxyModel, SIGNAL(rowsRemoved(QModelIndex, int, int)), SLOT(testMappings()));
0473     connect(m_proxyModel, SIGNAL(layoutAboutToBeChanged()), SLOT(testMappings()));
0474     connect(m_proxyModel, SIGNAL(layoutChanged()), SLOT(testMappings()));
0475     connect(m_proxyModel, SIGNAL(rowsAboutToBeMoved(QModelIndex, int, int, QModelIndex, int)), SLOT(testMappings()));
0476     connect(m_proxyModel, SIGNAL(rowsMoved(QModelIndex, int, int, QModelIndex, int)), SLOT(testMappings()));
0477     connect(m_proxyModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), SLOT(testMappings()));
0478 
0479     connect(m_proxyModel, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), SLOT(verifyModel(QModelIndex, int, int)));
0480     connect(m_proxyModel, SIGNAL(rowsInserted(QModelIndex, int, int)), SLOT(verifyModel(QModelIndex, int, int)));
0481     connect(m_proxyModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), SLOT(verifyModel(QModelIndex, int, int)));
0482     connect(m_proxyModel, SIGNAL(rowsRemoved(QModelIndex, int, int)), SLOT(verifyModel(QModelIndex, int, int)));
0483     connect(m_proxyModel, SIGNAL(rowsAboutToBeMoved(QModelIndex, int, int, QModelIndex, int)), SLOT(verifyModel(QModelIndex, int, int, QModelIndex, int)));
0484     connect(m_proxyModel, SIGNAL(rowsMoved(QModelIndex, int, int, QModelIndex, int)), SLOT(verifyModel(QModelIndex, int, int, QModelIndex, int)));
0485     connect(m_proxyModel, SIGNAL(columnsAboutToBeInserted(QModelIndex, int, int)), SLOT(verifyModel(QModelIndex, int, int)));
0486     connect(m_proxyModel, SIGNAL(columnsInserted(QModelIndex, int, int)), SLOT(verifyModel(QModelIndex, int, int)));
0487     connect(m_proxyModel, SIGNAL(columnsAboutToBeRemoved(QModelIndex, int, int)), SLOT(verifyModel(QModelIndex, int, int)));
0488     connect(m_proxyModel, SIGNAL(columnsRemoved(QModelIndex, int, int)), SLOT(verifyModel(QModelIndex, int, int)));
0489     connect(m_proxyModel, SIGNAL(columnsAboutToBeMoved(QModelIndex, int, int, QModelIndex, int)), SLOT(verifyModel(QModelIndex, int, int, QModelIndex, int)));
0490     connect(m_proxyModel, SIGNAL(columnsMoved(QModelIndex, int, int, QModelIndex, int)), SLOT(verifyModel(QModelIndex, int, int, QModelIndex, int)));
0491     connect(m_proxyModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), SLOT(verifyModel(QModelIndex, QModelIndex)));
0492 }
0493 
0494 void ProxyModelTest::doTest()
0495 {
0496     QFETCH(SignalList, signalList);
0497     QFETCH(PersistentChangeList, changeList);
0498 
0499     QVERIFY(m_modelSpy->isEmpty());
0500 
0501     QString testName = QTest::currentTestFunction();
0502     QString testDataTag = QTest::currentDataTag();
0503 
0504     if (testDataTag == ProxyModelTestData::failTag()) {
0505         return;
0506     }
0507 
0508     if ((signalList.size() == 1 && signalList.first().size() == 1) && signalList.first().first().toString() == QLatin1String("skip")) {
0509         return;
0510     }
0511 
0512     static int numTests = 0;
0513     if (qApp->arguments().contains(QLatin1String("-count"))) {
0514         qDebug() << "numTests" << ++numTests;
0515     }
0516 
0517     m_modelSpy->preTestPersistIndexes(changeList);
0518 
0519     // Run the test.
0520 
0521     Q_ASSERT(m_modelSpy->isEmpty());
0522     m_modelSpy->startSpying();
0523     QMetaObject::invokeMethod(m_modelCommander, QString("execute_" + testName).toLatin1(), Q_ARG(QString, testDataTag));
0524     m_modelSpy->stopSpying();
0525 
0526     if (modelSpy()->isEmpty()) {
0527         QVERIFY(signalList.isEmpty());
0528     }
0529 
0530     // Make sure we didn't get any signals we didn't expect.
0531     if (signalList.isEmpty()) {
0532         QVERIFY(modelSpy()->isEmpty());
0533     }
0534 
0535     const bool isLayoutChange = signalList.contains(QVariantList() << LayoutAboutToBeChanged);
0536 
0537     while (!signalList.isEmpty()) {
0538         // Process each signal we received as a result of running the test.
0539         QVariantList expected = signalList.takeAt(0);
0540         handleSignal(expected);
0541     }
0542 
0543     // Make sure we didn't get any signals we didn't expect.
0544     QVERIFY(m_modelSpy->isEmpty());
0545 
0546     // Persistent indexes should change by the amount described in change objects.
0547     const auto lst = m_modelSpy->getChangeList();
0548     for (PersistentIndexChange change : lst) {
0549         for (int i = 0; i < change.indexes.size(); i++) {
0550             QModelIndex idx = change.indexes.at(i);
0551             QPersistentModelIndex persistentIndex = change.persistentIndexes.at(i);
0552 
0553             // Persistent indexes go to an invalid state if they are removed from the model.
0554             if (change.toInvalid) {
0555                 QVERIFY(!persistentIndex.isValid());
0556                 continue;
0557             }
0558 #if 0
0559             qDebug() << idx << idx.data() << change.difference << change.toInvalid << persistentIndex.row();
0560 #endif
0561 
0562             QCOMPARE(idx.row() + change.difference, persistentIndex.row());
0563             QCOMPARE(idx.column(), persistentIndex.column());
0564             if (!isLayoutChange) {
0565                 QCOMPARE(idx.parent(), persistentIndex.parent());
0566             }
0567         }
0568 
0569         for (int i = 0; i < change.descendantIndexes.size(); i++) {
0570             QModelIndex idx = change.descendantIndexes.at(i);
0571             QPersistentModelIndex persistentIndex = change.persistentDescendantIndexes.at(i);
0572 
0573             // The descendant indexes of indexes which were removed should now also be invalid.
0574             if (change.toInvalid) {
0575                 QVERIFY(!persistentIndex.isValid());
0576                 continue;
0577             }
0578             // Otherwise they should be unchanged.
0579             QCOMPARE(idx.row(), persistentIndex.row());
0580             QCOMPARE(idx.column(), persistentIndex.column());
0581             if (!isLayoutChange) {
0582                 QCOMPARE(idx.parent(), persistentIndex.parent());
0583             }
0584         }
0585     }
0586 
0587     QModelIndexList unchangedIndexes = m_modelSpy->getUnchangedIndexes();
0588     QList<QPersistentModelIndex> unchangedPersistentIndexes = m_modelSpy->getUnchangedPersistentIndexes();
0589 
0590     // Indexes unaffected by the signals should be unchanged.
0591     for (int i = 0; i < unchangedIndexes.size(); ++i) {
0592         QModelIndex unchangedIdx = unchangedIndexes.at(i);
0593         QPersistentModelIndex persistentIndex = unchangedPersistentIndexes.at(i);
0594         QCOMPARE(unchangedIdx.row(), persistentIndex.row());
0595         QCOMPARE(unchangedIdx.column(), persistentIndex.column());
0596         if (!isLayoutChange) {
0597             QCOMPARE(unchangedIdx.parent(), persistentIndex.parent());
0598         }
0599     }
0600     m_modelSpy->clearTestData();
0601 }
0602 
0603 void ProxyModelTest::connectTestSignals(QObject *receiver)
0604 {
0605     if (!receiver) {
0606         return;
0607     }
0608     for (int methodIndex = 0; methodIndex < metaObject()->methodCount(); ++methodIndex) {
0609         QMetaMethod mm = metaObject()->method(methodIndex);
0610         if (mm.methodType() == QMetaMethod::Signal && QString(mm.methodSignature()).startsWith(QLatin1String("test"))
0611             && QString(mm.methodSignature()).endsWith(QLatin1String("Data()"))) {
0612             int slotIndex = receiver->metaObject()->indexOfSlot(mm.methodSignature());
0613             Q_ASSERT(slotIndex >= 0);
0614             metaObject()->connect(this, methodIndex, receiver, slotIndex);
0615         }
0616     }
0617 }
0618 
0619 void ProxyModelTest::disconnectTestSignals(QObject *receiver)
0620 {
0621     if (!receiver) {
0622         return;
0623     }
0624     for (int methodIndex = 0; methodIndex < metaObject()->methodCount(); ++methodIndex) {
0625         QMetaMethod mm = metaObject()->method(methodIndex);
0626         if (mm.methodType() == QMetaMethod::Signal && QString(mm.methodSignature()).startsWith(QLatin1String("test"))
0627             && QString(mm.methodSignature()).endsWith(QLatin1String("Data()"))) {
0628             int slotIndex = receiver->metaObject()->indexOfSlot(mm.methodSignature());
0629             Q_ASSERT(slotIndex >= 0);
0630             metaObject()->disconnect(this, methodIndex, receiver, slotIndex);
0631         }
0632     }
0633 }
0634 
0635 uint qHash(const QVariant &var)
0636 {
0637     if (!var.isValid() || var.isNull()) {
0638         return -1;
0639     }
0640 
0641     switch (var.type()) {
0642     case QVariant::Int:
0643         return qHash(var.toInt());
0644     case QVariant::UInt:
0645         return qHash(var.toUInt());
0646     case QVariant::Bool:
0647         return qHash(var.toUInt());
0648     case QVariant::Double:
0649         return qHash(var.toUInt());
0650     case QVariant::LongLong:
0651         return qHash(var.toLongLong());
0652     case QVariant::ULongLong:
0653         return qHash(var.toULongLong());
0654     case QVariant::String:
0655         return qHash(var.toString());
0656     case QVariant::Char:
0657         return qHash(var.toChar());
0658     case QVariant::StringList:
0659         return qHash(var.toString());
0660     case QVariant::ByteArray:
0661         return qHash(var.toByteArray());
0662     case QVariant::Date:
0663     case QVariant::Time:
0664     case QVariant::DateTime:
0665     case QVariant::Url:
0666     case QVariant::Locale:
0667 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0668     case QVariant::RegExp:
0669 #endif
0670         return qHash(var.toString());
0671     case QVariant::Map:
0672     case QVariant::List:
0673     case QVariant::BitArray:
0674     case QVariant::Size:
0675     case QVariant::SizeF:
0676     case QVariant::Rect:
0677     case QVariant::LineF:
0678     case QVariant::Line:
0679     case QVariant::RectF:
0680     case QVariant::Point:
0681     case QVariant::PointF:
0682         // not supported yet
0683         break;
0684     case QVariant::UserType:
0685     case QVariant::Invalid:
0686     default:
0687         return -1;
0688     }
0689 
0690     // could not generate a hash for the given variant
0691     Q_ASSERT(0);
0692     return -1;
0693 }
0694 
0695 #include "moc_proxymodeltest.cpp"