/*
    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(0),
    m_intermediateProxyModel(0),
    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.signature() );

    if (signature.startsWith(checkedSignature)) {
      found = true;
      break;
    }
  }
  return found;
}

void ProxyModelTest::initRootModel(DynamicTreeModel *rootModel, const QString &currentTest, const QString &currentTag)
{
  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;
  QSet<QString> unimplemented = m_modelCommanderTags.toSet().subtract(m_dataTags.toSet());
  QString unimplementedTestsString("(");
  foreach(const QString &test, unimplemented)
    unimplementedTestsString.append(test + ",");
  unimplementedTestsString.append(")");

  if (!unimplemented.isEmpty())
  {
    QString failString = QString("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 != 0);
  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(0);
  m_proxyModel->setSourceModel(0);
  delete m_proxyModel;
  m_proxyModel = 0;
  QVERIFY(m_modelSpy->isEmpty());
}

void ProxyModelTest::cleanupTestCase()
{
  verifyExecutedTests();
  m_modelCommanderTags.clear();
  if (!m_intermediateProxyModel)
    return;

  m_sourceModel = m_rootModel;
  delete m_intermediateProxyModel;
  m_intermediateProxyModel = 0;

  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 recieve 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(
    "- 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(0);

  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(
    " - 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 = 0;
    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().first() == ModelAboutToBeReset);
  QVERIFY(m_modelSpy->takeFirst().first() == 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() == "skip")
  {
    return;
  }

  static int numTests = 0;
  if (qApp->arguments().contains("-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 recieved 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.
  foreach (PersistentIndexChange change, m_modelSpy->getChangeList())
  {
    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 *reciever)
{
  if (!reciever)
    return;
  for (int methodIndex = 0; methodIndex < metaObject()->methodCount(); ++methodIndex) {
    QMetaMethod mm = metaObject()->method(methodIndex);
    if (mm.methodType() == QMetaMethod::Signal
      && QString(mm.signature()).startsWith("test")
      && QString(mm.signature()).endsWith("Data()"))
      {
        int slotIndex = reciever->metaObject()->indexOfSlot(mm.signature());
        Q_ASSERT(slotIndex >= 0);
        metaObject()->connect(this, methodIndex, reciever, slotIndex);
      }
  }
}

void ProxyModelTest::disconnectTestSignals(QObject *reciever)
{
  if (!reciever)
    return;
  for (int methodIndex = 0; methodIndex < metaObject()->methodCount(); ++methodIndex) {
    QMetaMethod mm = metaObject()->method(methodIndex);
    if (mm.methodType() == QMetaMethod::Signal
      && QString(mm.signature()).startsWith("test")
      && QString(mm.signature()).endsWith("Data()"))
    {
      int slotIndex = reciever->metaObject()->indexOfSlot(mm.signature());
      Q_ASSERT(slotIndex >= 0);
      metaObject()->disconnect(this, methodIndex, reciever, slotIndex);
    }
  }
}

uint qHash( const QVariant & var )
{
    if ( !var.isValid() || var.isNull() )
        return -1;

    switch ( var.type() )
    {
        case QVariant::Int:
                return qHash( var.toInt() );
            break;
        case QVariant::UInt:
                return qHash( var.toUInt() );
            break;
        case QVariant::Bool:
                return qHash( var.toUInt() );
            break;
        case QVariant::Double:
                return qHash( var.toUInt() );
            break;
        case QVariant::LongLong:
                return qHash( var.toLongLong() );
            break;
        case QVariant::ULongLong:
                return qHash( var.toULongLong() );
            break;
        case QVariant::String:
                return qHash( var.toString() );
            break;
        case QVariant::Char:
                return qHash( var.toChar() );
            break;
        case QVariant::StringList:
                return qHash( var.toString() );
            break;
        case QVariant::ByteArray:
                return qHash( var.toByteArray() );
            break;
        case QVariant::Date:
        case QVariant::Time:
        case QVariant::DateTime:
        case QVariant::Url:
        case QVariant::Locale:
        case QVariant::RegExp:
                return qHash( var.toString() );
            break;
        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;
}
