You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
735 lines
27 KiB
735 lines
27 KiB
/* |
|
Copyright (c) 2009 Stephen Kelly <steveire@gmail.com> |
|
|
|
This library is free software; you can redistribute it and/or modify it |
|
under the terms of the GNU Library General Public License as published by |
|
the Free Software Foundation; either version 2 of the License, or (at your |
|
option) any later version. |
|
|
|
This library is distributed in the hope that it will be useful, but WITHOUT |
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public |
|
License for more details. |
|
|
|
You should have received a copy of the GNU Library General Public License |
|
along with this library; see the file COPYING.LIB. If not, write to the |
|
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
02110-1301, USA. |
|
*/ |
|
|
|
#include "proxymodeltest.h" |
|
|
|
#include "dynamictreemodel.h" |
|
|
|
#include <QItemSelectionModel> |
|
#include <QSortFilterProxyModel> |
|
|
|
#include "modelspy.h" |
|
|
|
ProxyModelTest::ProxyModelTest(QObject *parent) |
|
: QObject(parent), |
|
m_rootModel(new DynamicTreeModel(this)), |
|
m_sourceModel(m_rootModel), |
|
m_proxyModel(nullptr), |
|
m_intermediateProxyModel(nullptr), |
|
m_modelSpy(new ModelSpy(this)), |
|
m_modelCommander(new ModelCommander(m_rootModel, this)) |
|
{ |
|
} |
|
|
|
void ProxyModelTest::setLazyPersistence(Persistence persistence) |
|
{ |
|
m_modelSpy->setLazyPersistence(persistence == LazyPersistence); |
|
} |
|
|
|
void ProxyModelTest::setUseIntermediateProxy(SourceModel sourceModel) |
|
{ |
|
if (sourceModel == DynamicTree) { |
|
return; |
|
} |
|
|
|
m_intermediateProxyModel = new QSortFilterProxyModel(this); |
|
m_intermediateProxyModel->setSourceModel(m_rootModel); |
|
m_sourceModel = m_intermediateProxyModel; |
|
} |
|
|
|
static bool hasMetaMethodStartingWith(QObject *object, const QString &checkedSignature) |
|
{ |
|
const QMetaObject *mo = object->metaObject(); |
|
bool found = false; |
|
for (int methodIndex = 0; methodIndex < mo->methodCount(); ++methodIndex) { |
|
QMetaMethod mm = mo->method(methodIndex); |
|
QString signature = QString::fromLatin1(mm.methodSignature()); |
|
|
|
if (signature.startsWith(checkedSignature)) { |
|
found = true; |
|
break; |
|
} |
|
} |
|
return found; |
|
} |
|
|
|
void ProxyModelTest::initRootModel(DynamicTreeModel *rootModel, const QString ¤tTest, const QString ¤tTag) |
|
{ |
|
Q_UNUSED(rootModel) |
|
// Get the model into the state it is expected to be in. |
|
|
|
if (!hasMetaMethodStartingWith(m_modelCommander, "init_" + currentTest)) { |
|
return; |
|
} |
|
|
|
QMetaObject::invokeMethod(m_modelCommander, QString("init_" + currentTest).toLatin1(), Q_ARG(QString, currentTag)); |
|
} |
|
|
|
void ProxyModelTest::verifyExecutedTests() |
|
{ |
|
if (m_dataTags.contains(ProxyModelTestData::failTag())) { |
|
return; |
|
} |
|
const QSet<QString> unimplemented = m_modelCommanderTags.toSet().subtract(m_dataTags.toSet()); |
|
QString unimplementedTestsString(QStringLiteral("(")); |
|
for (const QString &test : unimplemented) { |
|
unimplementedTestsString.append(test + ","); |
|
} |
|
unimplementedTestsString.append(")"); |
|
|
|
if (!unimplemented.isEmpty()) { |
|
QString failString = QStringLiteral("Some tests in %1 were not implemented: %2").arg(m_currentTest, unimplementedTestsString); |
|
m_dataTags.clear(); |
|
m_currentTest = QTest::currentTestFunction(); |
|
QFAIL(failString.toLatin1()); |
|
} |
|
} |
|
|
|
void ProxyModelTest::init() |
|
{ |
|
QVERIFY(m_modelSpy->isEmpty()); |
|
m_rootModel->clear(); |
|
|
|
const char *currentTest = QTest::currentTestFunction(); |
|
const char *currentTag = QTest::currentDataTag(); |
|
QVERIFY(currentTest != nullptr); |
|
initRootModel(m_rootModel, currentTest, currentTag); |
|
|
|
Q_ASSERT(sourceModel()); |
|
QAbstractProxyModel *proxyModel = getProxy(); |
|
|
|
Q_ASSERT(proxyModel); |
|
// Don't set the sourceModel in getProxy. |
|
Q_ASSERT(!proxyModel->sourceModel()); |
|
connectProxy(proxyModel); |
|
|
|
// Get the model into the state it is expected to be in. |
|
m_modelSpy->startSpying(); |
|
QVERIFY(m_modelSpy->isEmpty()); |
|
|
|
if (m_currentTest != currentTest) { |
|
verifyExecutedTests(); |
|
m_dataTags.clear(); |
|
|
|
QString metaMethod = QString("execute_" + QLatin1String(currentTest)); |
|
|
|
if (!hasMetaMethodStartingWith(m_modelCommander, metaMethod)) { |
|
return; |
|
} |
|
|
|
QMetaObject::invokeMethod(m_modelCommander, metaMethod.toLatin1(), Q_RETURN_ARG(QStringList, m_modelCommanderTags), Q_ARG(QString, QString())); |
|
m_currentTest = currentTest; |
|
} |
|
m_dataTags.append(currentTag); |
|
} |
|
|
|
void ProxyModelTest::cleanup() |
|
{ |
|
QVERIFY(m_modelSpy->isEmpty()); |
|
m_modelSpy->stopSpying(); |
|
m_modelSpy->setModel(nullptr); |
|
m_proxyModel->setSourceModel(nullptr); |
|
delete m_proxyModel; |
|
m_proxyModel = nullptr; |
|
QVERIFY(m_modelSpy->isEmpty()); |
|
} |
|
|
|
void ProxyModelTest::cleanupTestCase() |
|
{ |
|
verifyExecutedTests(); |
|
m_modelCommanderTags.clear(); |
|
if (!m_intermediateProxyModel) { |
|
return; |
|
} |
|
|
|
m_sourceModel = m_rootModel; |
|
delete m_intermediateProxyModel; |
|
m_intermediateProxyModel = nullptr; |
|
|
|
m_modelSpy->clear(); |
|
} |
|
|
|
PersistentIndexChange ProxyModelTest::getChange(IndexFinder parentFinder, int start, int end, int difference, bool toInvalid) |
|
{ |
|
Q_ASSERT(start <= end); |
|
PersistentIndexChange change; |
|
change.parentFinder = parentFinder; |
|
change.startRow = start; |
|
change.endRow = end; |
|
change.difference = difference; |
|
change.toInvalid = toInvalid; |
|
return change; |
|
} |
|
|
|
void ProxyModelTest::handleSignal(QVariantList expected) |
|
{ |
|
QVERIFY(!expected.isEmpty()); |
|
int signalType = expected.takeAt(0).toInt(); |
|
if (NoSignal == signalType) { |
|
return; |
|
} |
|
|
|
Q_ASSERT(!m_modelSpy->isEmpty()); |
|
QVariantList result = getResultSignal(); |
|
|
|
QCOMPARE(result.takeAt(0).toInt(), signalType); |
|
// Check that the signal we expected to receive was emitted exactly. |
|
switch (signalType) { |
|
case RowsAboutToBeInserted: |
|
case RowsInserted: |
|
case RowsAboutToBeRemoved: |
|
case RowsRemoved: { |
|
QVERIFY(expected.size() == 3); |
|
IndexFinder parentFinder = qvariant_cast<IndexFinder>(expected.at(0)); |
|
parentFinder.setModel(m_proxyModel); |
|
QModelIndex parent = parentFinder.getIndex(); |
|
|
|
// This is where is usually goes wrong... |
|
#if 0 |
|
qDebug() << qvariant_cast<QModelIndex>(result.at(0)) << parent; |
|
qDebug() << result.at(1) << expected.at(1); |
|
qDebug() << result.at(2) << expected.at(2); |
|
#endif |
|
|
|
QCOMPARE(qvariant_cast<QModelIndex>(result.at(0)), parent); |
|
QCOMPARE(result.at(1), expected.at(1)); |
|
QCOMPARE(result.at(2), expected.at(2)); |
|
break; |
|
} |
|
case LayoutAboutToBeChanged: |
|
case LayoutChanged: { |
|
QVERIFY(expected.size() == 0); |
|
QVERIFY(result.size() == 0); |
|
break; |
|
} |
|
case RowsAboutToBeMoved: |
|
case RowsMoved: { |
|
QVERIFY(expected.size() == 5); |
|
IndexFinder scrParentFinder = qvariant_cast<IndexFinder>(expected.at(0)); |
|
scrParentFinder.setModel(m_proxyModel); |
|
QModelIndex srcParent = scrParentFinder.getIndex(); |
|
QCOMPARE(qvariant_cast<QModelIndex>(result.at(0)), srcParent); |
|
QCOMPARE(result.at(1), expected.at(1)); |
|
QCOMPARE(result.at(2), expected.at(2)); |
|
IndexFinder destParentFinder = qvariant_cast<IndexFinder>(expected.at(3)); |
|
destParentFinder.setModel(m_proxyModel); |
|
QModelIndex destParent = destParentFinder.getIndex(); |
|
QCOMPARE(qvariant_cast<QModelIndex>(result.at(3)), destParent); |
|
QCOMPARE(result.at(4), expected.at(4)); |
|
break; |
|
} |
|
case DataChanged: { |
|
QVERIFY(expected.size() == 2); |
|
IndexFinder topLeftFinder = qvariant_cast<IndexFinder>(expected.at(0)); |
|
topLeftFinder.setModel(m_proxyModel); |
|
QModelIndex topLeft = topLeftFinder.getIndex(); |
|
IndexFinder bottomRightFinder = qvariant_cast<IndexFinder>(expected.at(1)); |
|
bottomRightFinder.setModel(m_proxyModel); |
|
|
|
QModelIndex bottomRight = bottomRightFinder.getIndex(); |
|
|
|
QVERIFY(topLeft.isValid() && bottomRight.isValid()); |
|
|
|
#if 0 |
|
qDebug() << qvariant_cast<QModelIndex>(result.at(0)) << topLeft; |
|
qDebug() << qvariant_cast<QModelIndex>(result.at(1)) << bottomRight; |
|
#endif |
|
|
|
QCOMPARE(qvariant_cast<QModelIndex>(result.at(0)), topLeft); |
|
QCOMPARE(qvariant_cast<QModelIndex>(result.at(1)), bottomRight); |
|
} |
|
|
|
} |
|
} |
|
|
|
QVariantList ProxyModelTest::getResultSignal() |
|
{ |
|
return m_modelSpy->takeFirst(); |
|
} |
|
|
|
void ProxyModelTest::testEmptyModel() |
|
{ |
|
Q_ASSERT(sourceModel()); |
|
QAbstractProxyModel *proxyModel = getProxy(); |
|
// Many of these just check that the proxy does not crash when it does not have a source model. |
|
QCOMPARE(proxyModel->rowCount(), 0); |
|
QCOMPARE(proxyModel->columnCount(), 0); |
|
QVERIFY(!proxyModel->index(0, 0).isValid()); |
|
QVERIFY(!proxyModel->data(QModelIndex()).isValid()); |
|
QVERIFY(!proxyModel->parent(QModelIndex()).isValid()); |
|
QVERIFY(!proxyModel->mapToSource(QModelIndex()).isValid()); |
|
QVERIFY(!proxyModel->mapFromSource(QModelIndex()).isValid()); |
|
QVERIFY(!proxyModel->headerData(0, Qt::Horizontal, Qt::DisplayRole).isValid()); |
|
QVERIFY(!proxyModel->headerData(0, Qt::Vertical, Qt::DisplayRole).isValid()); |
|
Qt::ItemFlags flags = proxyModel->flags(QModelIndex()); |
|
QVERIFY(flags == Qt::ItemIsDropEnabled || flags == 0); |
|
QVERIFY(proxyModel->itemData(QModelIndex()).isEmpty()); |
|
QVERIFY(proxyModel->mapSelectionToSource(QItemSelection()).isEmpty()); |
|
QVERIFY(proxyModel->mapSelectionFromSource(QItemSelection()).isEmpty()); |
|
proxyModel->revert(); |
|
QVERIFY(proxyModel->submit()); |
|
QVERIFY(!proxyModel->sourceModel()); |
|
QVERIFY(!proxyModel->canFetchMore(QModelIndex())); |
|
proxyModel->fetchMore(QModelIndex()); |
|
QMimeData *data = new QMimeData(); |
|
QVERIFY(!proxyModel->dropMimeData(data, Qt::CopyAction, 0, 0, QModelIndex())); |
|
delete data; |
|
QVERIFY(!proxyModel->hasChildren()); |
|
QVERIFY(!proxyModel->hasIndex(0, 0, QModelIndex())); |
|
proxyModel->supportedDragActions(); |
|
proxyModel->supportedDropActions(); |
|
delete proxyModel; |
|
} |
|
|
|
void ProxyModelTest::testSourceReset() |
|
{ |
|
m_modelSpy->stopSpying(); |
|
ModelInsertCommand *ins = new ModelInsertCommand(m_rootModel, this); |
|
ins->setStartRow(0); |
|
ins->interpret( |
|
QLatin1String("- 1" |
|
"- 2" |
|
"- - 3" |
|
"- - 4" |
|
"- 5" |
|
"- 6" |
|
"- 7" |
|
"- 8" |
|
"- 9" |
|
"- - 10") |
|
); |
|
ins->doCommand(); |
|
// The proxymodel should reset any internal state it holds when the source model is reset. |
|
QPersistentModelIndex pmi = m_proxyModel->index(0, 0); |
|
testMappings(); |
|
m_rootModel->clear(); // Resets the model. |
|
testMappings(); // Calls some rowCount() etc which should test internal structures in the proxy. |
|
m_proxyModel->setSourceModel(nullptr); |
|
|
|
m_modelSpy->startSpying(); |
|
} |
|
|
|
void ProxyModelTest::testDestroyModel() |
|
{ |
|
QAbstractItemModel *currentSourceModel = m_sourceModel; |
|
DynamicTreeModel *rootModel = new DynamicTreeModel(this); |
|
m_sourceModel = rootModel; |
|
|
|
ModelInsertCommand *ins = new ModelInsertCommand(rootModel, this); |
|
ins->setStartRow(0); |
|
ins->interpret( |
|
QLatin1String(" - 1" |
|
" - 1" |
|
" - - 1" |
|
" - 1" |
|
" - 1" |
|
" - 1" |
|
" - 1" |
|
" - 1" |
|
" - - 1") |
|
); |
|
ins->doCommand(); |
|
|
|
QAbstractProxyModel *proxyModel = getProxy(); |
|
connectProxy(proxyModel); |
|
|
|
if (proxyModel->hasChildren()) { |
|
m_modelSpy->startSpying(); |
|
delete m_sourceModel; |
|
m_sourceModel = nullptr; |
|
m_modelSpy->stopSpying(); |
|
testMappings(); |
|
// QCOMPARE(m_modelSpy->size(), 1); |
|
// QVERIFY(m_modelSpy->takeFirst().first() == ModelReset); |
|
} |
|
m_sourceModel = currentSourceModel; |
|
} |
|
|
|
void ProxyModelTest::doTestMappings(const QModelIndex &parent) |
|
{ |
|
if (!m_proxyModel) { |
|
return; |
|
} |
|
QModelIndex idx; |
|
QModelIndex srcIdx; |
|
for (int column = 0; column < m_proxyModel->columnCount(parent); ++column) { |
|
for (int row = 0; row < m_proxyModel->rowCount(parent); ++row) { |
|
idx = m_proxyModel->index(row, column, parent); |
|
QVERIFY(idx.isValid()); |
|
QVERIFY(idx.row() == row); |
|
QVERIFY(idx.column() == column); |
|
QVERIFY(idx.parent() == parent); |
|
QVERIFY(idx.model() == m_proxyModel); |
|
srcIdx = m_proxyModel->mapToSource(idx); |
|
QVERIFY(srcIdx.isValid()); |
|
QVERIFY(srcIdx.model() == m_proxyModel->sourceModel()); |
|
QVERIFY(m_sourceModel == m_proxyModel->sourceModel()); |
|
QVERIFY(idx.data() == srcIdx.data()); |
|
QVERIFY(m_proxyModel->mapFromSource(srcIdx) == idx); |
|
if (m_proxyModel->hasChildren(idx)) { |
|
doTestMappings(idx); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void ProxyModelTest::testMappings() |
|
{ |
|
doTestMappings(QModelIndex()); |
|
} |
|
|
|
void ProxyModelTest::verifyModel(const QModelIndex &parent, int start, int end) |
|
{ |
|
Q_UNUSED(start); |
|
Q_UNUSED(end); |
|
|
|
QVERIFY(parent.model() == m_proxyModel || !parent.isValid()); |
|
} |
|
|
|
void ProxyModelTest::verifyModel(const QModelIndex &parent, int start, int end, const QModelIndex &destParent, int dest) |
|
{ |
|
Q_UNUSED(start); |
|
Q_UNUSED(end); |
|
Q_UNUSED(dest); |
|
|
|
QVERIFY(parent.model() == m_proxyModel || !parent.isValid()); |
|
QVERIFY(destParent.model() == m_proxyModel || !destParent.isValid()); |
|
} |
|
|
|
void ProxyModelTest::verifyModel(const QModelIndex &topLeft, const QModelIndex &bottomRight) |
|
{ |
|
QVERIFY(topLeft.model() == m_proxyModel || !topLeft.isValid()); |
|
QVERIFY(bottomRight.model() == m_proxyModel || !bottomRight.isValid()); |
|
} |
|
|
|
void ProxyModelTest::connectProxy(QAbstractProxyModel *proxyModel) |
|
{ |
|
if (m_proxyModel) { |
|
disconnect(m_proxyModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), |
|
this, SLOT(testMappings())); |
|
disconnect(m_proxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), |
|
this, SLOT(testMappings())); |
|
disconnect(m_proxyModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), |
|
this, SLOT(testMappings())); |
|
disconnect(m_proxyModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), |
|
this, SLOT(testMappings())); |
|
disconnect(m_proxyModel, SIGNAL(layoutAboutToBeChanged()), |
|
this, SLOT(testMappings())); |
|
disconnect(m_proxyModel, SIGNAL(layoutChanged()), |
|
this, SLOT(testMappings())); |
|
disconnect(m_proxyModel, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), |
|
this, SLOT(testMappings())); |
|
disconnect(m_proxyModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), |
|
this, SLOT(testMappings())); |
|
disconnect(m_proxyModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), |
|
this, SLOT(testMappings())); |
|
|
|
disconnect(m_proxyModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), |
|
this, SLOT(verifyModel(QModelIndex,int,int))); |
|
disconnect(m_proxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), |
|
this, SLOT(verifyModel(QModelIndex,int,int))); |
|
disconnect(m_proxyModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), |
|
this, SLOT(verifyModel(QModelIndex,int,int))); |
|
disconnect(m_proxyModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), |
|
this, SLOT(verifyModel(QModelIndex,int,int))); |
|
disconnect(m_proxyModel, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), |
|
this, SLOT(verifyModel(QModelIndex,int,int,QModelIndex,int))); |
|
disconnect(m_proxyModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), |
|
this, SLOT(verifyModel(QModelIndex,int,int,QModelIndex,int))); |
|
disconnect(m_proxyModel, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), |
|
this, SLOT(verifyModel(QModelIndex,int,int))); |
|
disconnect(m_proxyModel, SIGNAL(columnsInserted(QModelIndex,int,int)), |
|
this, SLOT(verifyModel(QModelIndex,int,int))); |
|
disconnect(m_proxyModel, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), |
|
this, SLOT(verifyModel(QModelIndex,int,int))); |
|
disconnect(m_proxyModel, SIGNAL(columnsRemoved(QModelIndex,int,int)), |
|
this, SLOT(verifyModel(QModelIndex,int,int))); |
|
disconnect(m_proxyModel, SIGNAL(columnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), |
|
this, SLOT(verifyModel(QModelIndex,int,int,QModelIndex,int))); |
|
disconnect(m_proxyModel, SIGNAL(columnsMoved(QModelIndex,int,int,QModelIndex,int)), |
|
this, SLOT(verifyModel(QModelIndex,int,int,QModelIndex,int))); |
|
disconnect(m_proxyModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), |
|
this, SLOT(verifyModel(QModelIndex,QModelIndex))); |
|
} |
|
|
|
m_proxyModel = proxyModel; |
|
QVERIFY(m_modelSpy->isEmpty()); |
|
m_modelSpy->setModel(m_proxyModel); |
|
|
|
QVERIFY(m_modelSpy->isEmpty()); |
|
|
|
m_modelSpy->startSpying(); |
|
m_proxyModel->setSourceModel(m_sourceModel); |
|
m_modelSpy->stopSpying(); |
|
|
|
QVERIFY(m_modelSpy->size() == 2); |
|
QVERIFY(m_modelSpy->takeFirst().at(0) == ModelAboutToBeReset); |
|
QVERIFY(m_modelSpy->takeFirst().at(0) == ModelReset); |
|
QVERIFY(m_modelSpy->isEmpty()); |
|
testMappings(); |
|
|
|
connect(m_proxyModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), |
|
SLOT(testMappings())); |
|
connect(m_proxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), |
|
SLOT(testMappings())); |
|
connect(m_proxyModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), |
|
SLOT(testMappings())); |
|
connect(m_proxyModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), |
|
SLOT(testMappings())); |
|
connect(m_proxyModel, SIGNAL(layoutAboutToBeChanged()), |
|
SLOT(testMappings())); |
|
connect(m_proxyModel, SIGNAL(layoutChanged()), |
|
SLOT(testMappings())); |
|
connect(m_proxyModel, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), |
|
SLOT(testMappings())); |
|
connect(m_proxyModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), |
|
SLOT(testMappings())); |
|
connect(m_proxyModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), |
|
SLOT(testMappings())); |
|
|
|
connect(m_proxyModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), |
|
SLOT(verifyModel(QModelIndex,int,int))); |
|
connect(m_proxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), |
|
SLOT(verifyModel(QModelIndex,int,int))); |
|
connect(m_proxyModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), |
|
SLOT(verifyModel(QModelIndex,int,int))); |
|
connect(m_proxyModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), |
|
SLOT(verifyModel(QModelIndex,int,int))); |
|
connect(m_proxyModel, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), |
|
SLOT(verifyModel(QModelIndex,int,int,QModelIndex,int))); |
|
connect(m_proxyModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), |
|
SLOT(verifyModel(QModelIndex,int,int,QModelIndex,int))); |
|
connect(m_proxyModel, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), |
|
SLOT(verifyModel(QModelIndex,int,int))); |
|
connect(m_proxyModel, SIGNAL(columnsInserted(QModelIndex,int,int)), |
|
SLOT(verifyModel(QModelIndex,int,int))); |
|
connect(m_proxyModel, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), |
|
SLOT(verifyModel(QModelIndex,int,int))); |
|
connect(m_proxyModel, SIGNAL(columnsRemoved(QModelIndex,int,int)), |
|
SLOT(verifyModel(QModelIndex,int,int))); |
|
connect(m_proxyModel, SIGNAL(columnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), |
|
SLOT(verifyModel(QModelIndex,int,int,QModelIndex,int))); |
|
connect(m_proxyModel, SIGNAL(columnsMoved(QModelIndex,int,int,QModelIndex,int)), |
|
SLOT(verifyModel(QModelIndex,int,int,QModelIndex,int))); |
|
connect(m_proxyModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), |
|
SLOT(verifyModel(QModelIndex,QModelIndex))); |
|
|
|
} |
|
|
|
void ProxyModelTest::doTest() |
|
{ |
|
QFETCH(SignalList, signalList); |
|
QFETCH(PersistentChangeList, changeList); |
|
|
|
QVERIFY(m_modelSpy->isEmpty()); |
|
|
|
QString testName = QTest::currentTestFunction(); |
|
QString testDataTag = QTest::currentDataTag(); |
|
|
|
if (testDataTag == ProxyModelTestData::failTag()) { |
|
return; |
|
} |
|
|
|
if ((signalList.size() == 1 && signalList.first().size() == 1) |
|
&& signalList.first().first().toString() == QLatin1String("skip")) { |
|
return; |
|
} |
|
|
|
static int numTests = 0; |
|
if (qApp->arguments().contains(QStringLiteral("-count"))) { |
|
qDebug() << "numTests" << ++numTests; |
|
} |
|
|
|
m_modelSpy->preTestPersistIndexes(changeList); |
|
|
|
// Run the test. |
|
|
|
Q_ASSERT(m_modelSpy->isEmpty()); |
|
m_modelSpy->startSpying(); |
|
QMetaObject::invokeMethod(m_modelCommander, QString("execute_" + testName).toLatin1(), Q_ARG(QString, testDataTag)); |
|
m_modelSpy->stopSpying(); |
|
|
|
if (modelSpy()->isEmpty()) { |
|
QVERIFY(signalList.isEmpty()); |
|
} |
|
|
|
// Make sure we didn't get any signals we didn't expect. |
|
if (signalList.isEmpty()) { |
|
QVERIFY(modelSpy()->isEmpty()); |
|
} |
|
|
|
const bool isLayoutChange = signalList.contains(QVariantList() << LayoutAboutToBeChanged); |
|
|
|
while (!signalList.isEmpty()) { |
|
// Process each signal we received as a result of running the test. |
|
QVariantList expected = signalList.takeAt(0); |
|
handleSignal(expected); |
|
} |
|
|
|
// Make sure we didn't get any signals we didn't expect. |
|
QVERIFY(m_modelSpy->isEmpty()); |
|
|
|
// Persistent indexes should change by the amount described in change objects. |
|
const auto lst = m_modelSpy->getChangeList(); |
|
for (PersistentIndexChange change : lst) { |
|
for (int i = 0; i < change.indexes.size(); i++) { |
|
QModelIndex idx = change.indexes.at(i); |
|
QPersistentModelIndex persistentIndex = change.persistentIndexes.at(i); |
|
|
|
// Persistent indexes go to an invalid state if they are removed from the model. |
|
if (change.toInvalid) { |
|
QVERIFY(!persistentIndex.isValid()); |
|
continue; |
|
} |
|
#if 0 |
|
qDebug() << idx << idx.data() << change.difference << change.toInvalid << persistentIndex.row(); |
|
#endif |
|
|
|
QCOMPARE(idx.row() + change.difference, persistentIndex.row()); |
|
QCOMPARE(idx.column(), persistentIndex.column()); |
|
if (!isLayoutChange) { |
|
QCOMPARE(idx.parent(), persistentIndex.parent()); |
|
} |
|
} |
|
|
|
for (int i = 0; i < change.descendantIndexes.size(); i++) { |
|
QModelIndex idx = change.descendantIndexes.at(i); |
|
QPersistentModelIndex persistentIndex = change.persistentDescendantIndexes.at(i); |
|
|
|
// The descendant indexes of indexes which were removed should now also be invalid. |
|
if (change.toInvalid) { |
|
QVERIFY(!persistentIndex.isValid()); |
|
continue; |
|
} |
|
// Otherwise they should be unchanged. |
|
QCOMPARE(idx.row(), persistentIndex.row()); |
|
QCOMPARE(idx.column(), persistentIndex.column()); |
|
if (!isLayoutChange) { |
|
QCOMPARE(idx.parent(), persistentIndex.parent()); |
|
} |
|
} |
|
} |
|
|
|
QModelIndexList unchangedIndexes = m_modelSpy->getUnchangedIndexes(); |
|
QList<QPersistentModelIndex> unchangedPersistentIndexes = m_modelSpy->getUnchangedPersistentIndexes(); |
|
|
|
// Indexes unaffected by the signals should be unchanged. |
|
for (int i = 0; i < unchangedIndexes.size(); ++i) { |
|
QModelIndex unchangedIdx = unchangedIndexes.at(i); |
|
QPersistentModelIndex persistentIndex = unchangedPersistentIndexes.at(i); |
|
QCOMPARE(unchangedIdx.row(), persistentIndex.row()); |
|
QCOMPARE(unchangedIdx.column(), persistentIndex.column()); |
|
if (!isLayoutChange) { |
|
QCOMPARE(unchangedIdx.parent(), persistentIndex.parent()); |
|
} |
|
} |
|
m_modelSpy->clearTestData(); |
|
} |
|
|
|
void ProxyModelTest::connectTestSignals(QObject *receiver) |
|
{ |
|
if (!receiver) { |
|
return; |
|
} |
|
for (int methodIndex = 0; methodIndex < metaObject()->methodCount(); ++methodIndex) { |
|
QMetaMethod mm = metaObject()->method(methodIndex); |
|
if (mm.methodType() == QMetaMethod::Signal |
|
&& QString(mm.methodSignature()).startsWith(QLatin1String("test")) |
|
&& QString(mm.methodSignature()).endsWith(QLatin1String("Data()"))) { |
|
int slotIndex = receiver->metaObject()->indexOfSlot(mm.methodSignature()); |
|
Q_ASSERT(slotIndex >= 0); |
|
metaObject()->connect(this, methodIndex, receiver, slotIndex); |
|
} |
|
} |
|
} |
|
|
|
void ProxyModelTest::disconnectTestSignals(QObject *receiver) |
|
{ |
|
if (!receiver) { |
|
return; |
|
} |
|
for (int methodIndex = 0; methodIndex < metaObject()->methodCount(); ++methodIndex) { |
|
QMetaMethod mm = metaObject()->method(methodIndex); |
|
if (mm.methodType() == QMetaMethod::Signal |
|
&& QString(mm.methodSignature()).startsWith(QLatin1String("test")) |
|
&& QString(mm.methodSignature()).endsWith(QLatin1String("Data()"))) { |
|
int slotIndex = receiver->metaObject()->indexOfSlot(mm.methodSignature()); |
|
Q_ASSERT(slotIndex >= 0); |
|
metaObject()->disconnect(this, methodIndex, receiver, slotIndex); |
|
} |
|
} |
|
} |
|
|
|
uint qHash(const QVariant &var) |
|
{ |
|
if (!var.isValid() || var.isNull()) { |
|
return -1; |
|
} |
|
|
|
switch (var.type()) { |
|
case QVariant::Int: |
|
return qHash(var.toInt()); |
|
case QVariant::UInt: |
|
return qHash(var.toUInt()); |
|
case QVariant::Bool: |
|
return qHash(var.toUInt()); |
|
case QVariant::Double: |
|
return qHash(var.toUInt()); |
|
case QVariant::LongLong: |
|
return qHash(var.toLongLong()); |
|
case QVariant::ULongLong: |
|
return qHash(var.toULongLong()); |
|
case QVariant::String: |
|
return qHash(var.toString()); |
|
case QVariant::Char: |
|
return qHash(var.toChar()); |
|
case QVariant::StringList: |
|
return qHash(var.toString()); |
|
case QVariant::ByteArray: |
|
return qHash(var.toByteArray()); |
|
case QVariant::Date: |
|
case QVariant::Time: |
|
case QVariant::DateTime: |
|
case QVariant::Url: |
|
case QVariant::Locale: |
|
case QVariant::RegExp: |
|
return qHash(var.toString()); |
|
case QVariant::Map: |
|
case QVariant::List: |
|
case QVariant::BitArray: |
|
case QVariant::Size: |
|
case QVariant::SizeF: |
|
case QVariant::Rect: |
|
case QVariant::LineF: |
|
case QVariant::Line: |
|
case QVariant::RectF: |
|
case QVariant::Point: |
|
case QVariant::PointF: |
|
// not supported yet |
|
break; |
|
case QVariant::UserType: |
|
case QVariant::Invalid: |
|
default: |
|
return -1; |
|
} |
|
|
|
// could not generate a hash for the given variant |
|
Q_ASSERT(0); |
|
return -1; |
|
}
|
|
|