Sfoglia il codice sorgente

ENH: Add a QAbstractItemModel tester. Plugged into a model, it checks the validity of the methods every time a signal is emitted.

Julien Finet 15 anni fa
parent
commit
43cec1cd4f
3 ha cambiato i file con 598 aggiunte e 0 eliminazioni
  1. 3 0
      Libs/Core/CMakeLists.txt
  2. 516 0
      Libs/Core/ctkModelTester.cxx
  3. 79 0
      Libs/Core/ctkModelTester.h

+ 3 - 0
Libs/Core/CMakeLists.txt

@@ -35,12 +35,15 @@ SET(KIT_include_directories
   
 # Source files
 SET(KIT_SRCS
+  ctkModelTester.cxx
+  ctkModelTester.h
   ctkUtils.cxx
   ctkUtils.h
   )
 
 # Headers that should run through moc
 SET(KIT_MOC_SRCS
+  ctkModelTester.h
   )
 
 # UI files

+ 516 - 0
Libs/Core/ctkModelTester.cxx

@@ -0,0 +1,516 @@
+/*=========================================================================
+
+  Library:   ctk
+
+  Copyright (c) Kitware Inc. 
+  All rights reserved.
+  Distributed under a BSD License. See LICENSE.txt file.
+
+  This software is distributed "AS IS" WITHOUT ANY WARRANTY; without even
+  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the above copyright notice for more information.
+
+=========================================================================*/
+
+#include "ctkModelTester.h"
+
+#include <QDebug>
+#include <QStack>
+
+//-----------------------------------------------------------------------------
+class ctkModelTesterPrivate: public qCTKPrivate<ctkModelTester>
+{
+public:
+  ctkModelTesterPrivate();
+  QAbstractItemModel *Model;
+  bool ThrowOnError;
+  bool NestedInserts;
+
+  struct Change
+  {
+    QModelIndex Parent;
+    Qt::Orientation Orientation;
+    int Start;
+    int End;
+    
+    int Count;
+    QList<QPersistentModelIndex> Items;
+  };
+
+  QStack<Change> AboutToBeInserted;
+  QStack<Change> AboutToBeRemoved;
+  QList<QPersistentModelIndex> LayoutAboutToBeChanged;
+};
+
+//-----------------------------------------------------------------------------
+ctkModelTesterPrivate::ctkModelTesterPrivate()
+{
+  this->Model = 0;
+  this->ThrowOnError = true;
+  this->NestedInserts = false;
+}
+
+//-----------------------------------------------------------------------------
+ctkModelTester::ctkModelTester(QAbstractItemModel *_model, QObject *_parent)
+  :QObject(_parent)
+{
+  QCTK_INIT_PRIVATE(ctkModelTester);
+  this->setModel(_model);
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::setModel(QAbstractItemModel *_model)
+{
+  QCTK_D(ctkModelTester);
+  if (d->Model)
+    {
+    // disconnect
+    d->Model->disconnect(this);
+    d->AboutToBeInserted.clear();
+    d->AboutToBeRemoved.clear();
+    d->LayoutAboutToBeChanged.clear();
+    }
+  if (_model)
+    {
+    connect(_model, SIGNAL(columnsAboutToBeInserted(const QModelIndex &, int, int)),
+            this, SLOT(onColumnsAboutToBeInserted(const QModelIndex& , int, int)));
+    connect(_model, SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)),
+            this, SLOT(onColumnsAboutToBeRemoved(const QModelIndex& , int, int)));
+    connect(_model, SIGNAL(columnsInserted(const QModelIndex &, int, int)),
+            this, SLOT(onColumnsInserted(const QModelIndex& , int, int)));
+    connect(_model, SIGNAL(columnsRemoved(const QModelIndex &, int, int)),
+            this, SLOT(onColumnsRemoved(const QModelIndex& , int, int)));
+    connect(_model, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),
+            this, SLOT(onDataChanged(const QModelIndex& , const QModelIndex &)));
+    connect(_model, SIGNAL(layoutAboutToBeChanged()), this, SLOT(onLayoutAboutToBeChanged()));
+    connect(_model, SIGNAL(layoutChanged()), this, SLOT(onLayoutChanged()));
+    connect(_model, SIGNAL(modelAboutToBeReset()), this, SLOT(onModelAboutToBeReset()));
+    connect(_model, SIGNAL(modelReset()), this, SLOT(onModelReset()));
+    connect(_model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)),
+            this, SLOT(onRowsAboutToBeInserted(const QModelIndex& , int, int)));
+    connect(_model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
+            this, SLOT(onRowsAboutToBeRemoved(const QModelIndex& , int, int)));
+    connect(_model, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
+            this, SLOT(onRowsInserted(const QModelIndex& , int, int)));
+    connect(_model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
+            this, SLOT(onRowsRemoved(const QModelIndex& , int, int)));
+    }
+  d->Model = _model;
+  this->testModel();
+}
+
+//-----------------------------------------------------------------------------
+QAbstractItemModel* ctkModelTester::model()const
+{
+  return qctk_d()->Model;
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::setThrowOnError(bool throwException)
+{
+  qctk_d()->ThrowOnError = throwException;
+}
+
+//-----------------------------------------------------------------------------
+bool ctkModelTester::throwOnError()const
+{
+  return qctk_d()->ThrowOnError;
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::setNestedInserts( bool nestedInserts )
+{
+  qctk_d()->NestedInserts = nestedInserts;
+}
+
+//-----------------------------------------------------------------------------
+bool ctkModelTester::nestedInserts()const
+{
+  return qctk_d()->NestedInserts;
+}
+
+//-----------------------------------------------------------------------------
+void  ctkModelTester::test(bool result, const QString& errorString)const
+{
+  if (result)
+    {
+    return;
+    }
+  qDebug() << errorString;
+  if (this->throwOnError())
+    {
+    throw errorString;
+    }
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::testModelIndex(const QModelIndex& index)const
+{
+  QCTK_D(const ctkModelTester);
+  if (!index.isValid())
+    {// invalid index
+    this->test(index.model() == 0, "An invalid index can't have a valid model.");
+    this->test(index.model() != d->Model, "An invalid index can't have a valid model.");
+    this->test(index.column() == -1, "An invalid index can't have a valid column.");
+    this->test(index.row() == -1, "An invalid index can't have a valid row.");
+    this->test(index.parent().isValid() == false, "An invalid index can't have a valid row.");
+    this->test(index.row() == -1, "An invalid index can't have a valid row.");
+    for(int i = 0; i < 100; ++i)
+      {
+      this->test(index.sibling(i % 10, i / 10).isValid() == false, "An invalid index can't have valid sibling.");
+      }
+    }
+  else
+    {// valid index
+    this->test(index.model() == d->Model, "A valid index must have a valid model.");
+    this->test(index.column() >= 0, "An valid index can't have an invalid column.");
+    this->test(index.row() >= 0, "An valid index can't have an invalid row.");
+    this->test(index == index.sibling(index.row(), index.column()), "Index's row and/or column is wrong.");
+    }
+  this->testData(index);
+  this->testParent(index);
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::testData(const QModelIndex& index)const
+{
+  if (!index.isValid())
+    {
+    this->test(!index.data(Qt::DisplayRole).isValid(), 
+               QString("An invalid index can't have valid data: %1")
+               .arg(index.data(Qt::DisplayRole).toString()));
+    }
+  else
+    {
+    this->test(index.data(Qt::DisplayRole).isValid(), 
+               QString("A valid index can't have invalid data: %1, %2, %3")
+               .arg(index.row()).arg(index.column()).arg(long(index.internalPointer())));
+    }
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::testParent(const QModelIndex& vparent)const
+{
+  QCTK_D(const ctkModelTester);
+  if (!d->Model->hasChildren(vparent))
+    {
+    // it's asking a lot :-)
+    //this->test(d->Model->columnCount(vparent) <= 0, "A parent with no children can't have a columnCount > 0.");
+    this->test(d->Model->rowCount(vparent) <= 0, "A parent with no children can't have a rowCount > 0.");
+    }
+  else
+    {
+    this->test(d->Model->columnCount(vparent) > 0, "A parent with children can't have a columnCount <= 0.");
+    this->test(d->Model->rowCount(vparent) > 0 || d->Model->canFetchMore(vparent), "A parent with children can't have a rowCount <= 0. or if it does, canFetchMore should return true");
+    }
+
+  if (!vparent.isValid())
+    {// otherwise there will be an infinite loop
+    return;
+    }
+  
+  for (int i = 0 ; i < d->Model->rowCount(vparent); ++i)
+    {
+    for (int j = 0; j < d->Model->columnCount(vparent); ++j)
+      {
+      this->test(d->Model->hasIndex(i, j, vparent), "hasIndex should return true for int range {0->rowCount(), 0->columnCount()}");
+      QModelIndex child = vparent.child(i, j);
+      this->test(child.parent() == vparent, "A child's parent can't be different from its parent");
+      this->testModelIndex(child);
+      }
+    }
+}
+//-----------------------------------------------------------------------------
+void ctkModelTester::testPersistentModelIndex(const QPersistentModelIndex& index)const
+{
+  QCTK_D(const ctkModelTester);
+  //qDebug() << "Test persistent Index: " << index ;
+  this->test(index.isValid(), "Persistent model index can't be invalid");
+  // did you forget to call QAbstractItemModel::changePersistentIndex() between 
+  // beginLayoutChanged() and changePersistentIndex() ?
+  QModelIndex modelIndex = d->Model->index(index.row(), index.column(), index.parent());
+  this->test(modelIndex == index, 
+             QString("Persistent index (%1, %2) can't be invalid").arg(index.row()).arg(index.column()));
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::testModel()const
+{
+  QCTK_D(const ctkModelTester);
+  if (d->Model == 0)
+    {
+    return;
+    }
+  for (int i = 0 ; i < d->Model->rowCount(); ++i)
+    {
+    for (int j = 0; j < d->Model->columnCount(); ++j)
+      {
+      this->test(d->Model->hasIndex(i, j), "hasIndex should return true for int range {0->rowCount(), 0->columnCount()}");
+      QModelIndex child = d->Model->index(i, j);
+      this->test(!child.parent().isValid(), "A child's parent can't be different from its parent");
+      this->testModelIndex(child);
+      }
+    }
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::onColumnsAboutToBeInserted(const QModelIndex & vparent, int start, int end)
+{
+  //qDebug() << "columnsAboutToBeInserted: " << vparent << start << end;
+  this->onItemsAboutToBeInserted(vparent, Qt::Horizontal, start, end);
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::onColumnsAboutToBeRemoved(const QModelIndex & vparent, int start, int end)
+{
+  //qDebug() << "columnsAboutToBeRemoved: " << vparent << start << end;
+  this->onItemsAboutToBeRemoved(vparent, Qt::Horizontal, start, end);
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::onColumnsInserted(const QModelIndex & vparent, int start, int end)
+{
+  //qDebug() << "columnsInserted: " << vparent << start << end;
+  this->onItemsInserted(vparent, Qt::Horizontal, start, end);
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::onColumnsRemoved(const QModelIndex & vparent, int start, int end)
+{
+  //qDebug() << "columnsRemoved: " << vparent << start << end;
+  this->onItemsRemoved(vparent, Qt::Horizontal, start, end);
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::onDataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight)
+{
+  this->test(topLeft.parent() == bottomRight.parent(), "DataChanged support items with the same parent only");
+  this->test(topLeft.row() >= bottomRight.row(), "topLeft can't have a row lower than bottomRight");
+  this->test(bottomRight.column() >= topLeft.column(), "topLeft can't have a column lower than bottomRight");
+  for (int i = topLeft.row(); i <= bottomRight.row(); ++i)
+    {
+    for (int j = topLeft.column(); j < bottomRight.column(); ++j)
+      {
+      this->test(topLeft.sibling(i,j).isValid(), "Changed data must be valid");
+      // do the test on the indexes here, it's easier to debug than in testModel();
+      this->testModelIndex(topLeft.sibling(i,j));
+      }
+    }
+  this->testModel();
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::onHeaderDataChanged(Qt::Orientation orientation, int first, int last)
+{
+  QCTK_D(ctkModelTester);
+  this->test(first <= last, "Changed headers have wrong indexes");
+  switch (orientation)
+    {
+    case Qt::Horizontal:
+      this->test(d->Model->columnCount() > last, "There is no more horizontal headers than columns.");
+      break;
+    case Qt::Vertical:
+      this->test(d->Model->rowCount() > last, "There is no more vertical headers than rows.");
+      break;
+    default:
+      this->test(orientation == Qt::Horizontal || orientation == Qt::Vertical, "Wrong orientation.");
+      break;
+    }
+  this->testModel();
+}
+
+//-----------------------------------------------------------------------------
+QList<QPersistentModelIndex> ctkModelTester::persistentModelIndexes(const QModelIndex& index)const
+{
+  QCTK_D(const ctkModelTester);
+  QList<QPersistentModelIndex> list;
+  for (int i = 0; i < d->Model->rowCount(index); ++i)
+    {
+    for (int j = 0; j < d->Model->columnCount(index); ++j)
+      {
+      QPersistentModelIndex child = d->Model->index(i, j, index);
+      list.append(child);
+      list += this->ctkModelTester::persistentModelIndexes(child);
+      }
+    }
+  return list;
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::onLayoutAboutToBeChanged()
+{
+  QCTK_D(ctkModelTester);
+
+  d->LayoutAboutToBeChanged = this->persistentModelIndexes(QModelIndex());
+  this->testModel();
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::onLayoutChanged()
+{
+  QCTK_D(ctkModelTester);
+  foreach (const QPersistentModelIndex& index, d->LayoutAboutToBeChanged)
+    {
+    this->testPersistentModelIndex(index);
+    }
+  d->LayoutAboutToBeChanged.clear();
+  this->testModel();
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::onModelAboutToBeReset()
+{
+  this->testModel();
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::onModelReset()
+{
+  this->testModel();
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::onRowsAboutToBeInserted(const QModelIndex &vparent, int start, int end)
+{
+  //qDebug() << "rowsAboutToBeInserted: " << vparent << start << end;
+  this->onItemsAboutToBeInserted(vparent, Qt::Vertical, start, end);
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::onRowsAboutToBeRemoved(const QModelIndex &vparent, int start, int end)
+{
+  //qDebug() << "rowsAboutToBeRemoved: " << vparent << start << end;
+  this->onItemsAboutToBeRemoved(vparent, Qt::Vertical, start, end);
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::onRowsInserted(const QModelIndex & vparent, int start, int end)
+{
+  //qDebug() << "rowsInserted: " << vparent << start << end;
+  this->onItemsInserted(vparent, Qt::Vertical, start, end);
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::onRowsRemoved(const QModelIndex & vparent, int start, int end)
+{
+  //qDebug() << "rowsRemoved: " << vparent << start << end;
+  this->onItemsRemoved(vparent, Qt::Vertical, start, end);
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::onItemsAboutToBeInserted(const QModelIndex &vparent, Qt::Orientation orientation, int start, int end)
+{
+  QCTK_D(ctkModelTester);
+  this->test(start <= end, "Start can't be higher than end");
+  //Not sure about that
+  if (!d->NestedInserts)
+    {
+    this->test(d->AboutToBeInserted.size() == 0, "While inserting items, you can't insert other items.");
+    }
+  //Not sure about that
+  this->test(d->AboutToBeRemoved.size() == 0, "While removing items, you can't insert other items.");
+
+  ctkModelTesterPrivate::Change change;
+  change.Parent = vparent;
+  change.Orientation = orientation;
+  change.Start = start;
+  change.End = end;
+  change.Count = (orientation == Qt::Vertical ? d->Model->rowCount(vparent) :d->Model->columnCount(vparent) );
+  change.Items = this->persistentModelIndexes(vparent);
+  d->AboutToBeInserted.push(change);
+  
+  this->testModel();
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::onItemsAboutToBeRemoved(const QModelIndex &vparent, Qt::Orientation orientation, int start, int end)
+{
+  QCTK_D(ctkModelTester);
+  this->test(start <= end, "Start can't be higher than end");
+  //Not sure about that
+  this->test(d->AboutToBeInserted.size() == 0, "While inserting items, you can't remove other items.");
+  //Not sure about that
+  this->test(d->AboutToBeRemoved.size() == 0, "While removing items, you can't remove other items.");
+  
+  int count = (orientation == Qt::Vertical ? d->Model->rowCount(vparent) :d->Model->columnCount(vparent) );
+  this->test(start < count, "Item to remove can't be invalid");
+  this->test(end < count, "Item to remove can't be invalid");
+  
+  ctkModelTesterPrivate::Change change;
+  change.Parent = vparent;
+  change.Orientation = orientation;
+  change.Start = start;
+  change.End = end;
+  change.Count = count;
+  for (int i = 0 ; i < count; ++i)
+    {
+    QPersistentModelIndex index;
+    index = (orientation == Qt::Vertical ? d->Model->index(i, 0, vparent) : d->Model->index(0, i, vparent));
+    this->test(index.isValid(), "Index invalid");
+    if (orientation == Qt::Vertical && (index.row() < start || index.row() > end))
+      {
+      change.Items.append(index);
+      }
+    if (orientation == Qt::Horizontal && (index.column() < start || index.column() > end))
+      {
+      change.Items.append(index);
+      }
+    }
+  d->AboutToBeRemoved.push(change);
+
+  this->testModel();
+  //qDebug() << "About to be removed: " << start << " " << end <<vparent << count << change.Items.count();
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::onItemsInserted(const QModelIndex & vparent, Qt::Orientation orientation, int start, int end)
+{
+  QCTK_D(ctkModelTester);
+  this->test(start <= end, "Start can't be higher end");
+  this->test(d->AboutToBeInserted.size() != 0, "rowsInserted() has been emitted, but not rowsAboutToBeInserted.");
+  //Not sure about that
+  this->test(d->AboutToBeRemoved.size() == 0, "While removing items, you can't insert other items.");
+
+  ctkModelTesterPrivate::Change change = d->AboutToBeInserted.pop();
+  this->test(change.Parent == vparent, "Parent can't be different");
+  this->test(change.Orientation == Qt::Vertical, "Orientation can't be different");
+  this->test(change.Start == start, "Start can't be different");
+  this->test(change.End == end, "End can't be different");
+  int count =  (orientation == Qt::Vertical ? d->Model->rowCount(vparent) :d->Model->columnCount(vparent) );
+  this->test(change.Count < count, "The new count number can't be lower");
+  this->test(count - change.Count == (end - start + 1) , "The new count number can't be lower");
+  foreach(const QPersistentModelIndex& index, change.Items)
+    {
+    this->testPersistentModelIndex(index);
+    }
+  change.Items.clear();
+  
+  this->testModel();
+}
+
+//-----------------------------------------------------------------------------
+void ctkModelTester::onItemsRemoved(const QModelIndex & vparent, Qt::Orientation orientation, int start, int end)
+{ 
+  QCTK_D(ctkModelTester);
+  this->test(start <= end, "Start can't be higher end");
+  this->test(d->AboutToBeRemoved.size() != 0, "rowsRemoved() has been emitted, but not rowsAboutToBeRemoved.");
+  //Not sure about that
+  this->test(d->AboutToBeInserted.size() == 0, "While inserted items, you can't remove other items.");
+
+  ctkModelTesterPrivate::Change change = d->AboutToBeRemoved.pop();
+  this->test(change.Parent == vparent, "Parent can't be different");
+  this->test(change.Orientation == Qt::Vertical, "Orientation can't be different");
+  this->test(change.Start == start, "Start can't be different");
+  this->test(change.End == end, "End can't be different");
+  int count = (orientation == Qt::Vertical ? d->Model->rowCount(vparent) :d->Model->columnCount(vparent) );
+  this->test(change.Count > count, "The new count number can't be higher");
+  this->test(change.Count - count == (end - start + 1) , "The new count number can't be higher");
+  foreach(const QPersistentModelIndex& index, change.Items)
+    {
+    this->testPersistentModelIndex(index);
+    }
+  change.Items.clear();
+  
+  this->testModel();
+}
+

+ 79 - 0
Libs/Core/ctkModelTester.h

@@ -0,0 +1,79 @@
+/*=========================================================================
+
+  Library:   ctk
+
+  Copyright (c) Kitware Inc. 
+  All rights reserved.
+  Distributed under a BSD License. See LICENSE.txt file.
+
+  This software is distributed "AS IS" WITHOUT ANY WARRANTY; without even
+  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the above copyright notice for more information.
+
+=========================================================================*/
+
+#ifndef __ctkModelTester_h
+#define __ctkModelTester_h
+
+/// QT includes
+#include <QObject> 
+#include <QModelIndex>
+#include <QPersistentModelIndex>
+#include <QList>
+
+/// CTK includes
+#include "ctkPimpl.h"
+#include "CTKCoreExport.h"
+
+class QAbstractItemModel;
+class ctkModelTesterPrivate;
+
+class CTK_CORE_EXPORT ctkModelTester: public QObject
+{
+  Q_OBJECT
+  Q_PROPERTY(bool nestedInserts READ nestedInserts WRITE setNestedInserts);
+public:
+  ctkModelTester(QAbstractItemModel *model, QObject *parent = 0);
+
+  void setModel(QAbstractItemModel* model);
+  QAbstractItemModel* model()const;
+
+  void setThrowOnError(bool throwException);
+  bool throwOnError()const;
+ 
+  void setNestedInserts(bool enable);
+  bool nestedInserts()const;
+
+  virtual void testData(const QModelIndex& index)const;
+  virtual void testModel()const;
+  virtual void testModelIndex(const QModelIndex& index)const;
+  virtual void testParent(const QModelIndex& parent)const;
+  virtual void testPersistentModelIndex(const QPersistentModelIndex& index)const;
+
+protected slots:
+  void onColumnsAboutToBeInserted(const QModelIndex & parent, int start, int end);
+  void onColumnsAboutToBeRemoved(const QModelIndex & parent, int start, int end);
+  void onColumnsInserted(const QModelIndex & parent, int start, int end);
+  void onColumnsRemoved(const QModelIndex & parent, int start, int end);
+  void onDataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight);
+  void onHeaderDataChanged(Qt::Orientation orientation, int first, int last);
+  void onLayoutAboutToBeChanged();
+  void onLayoutChanged();
+  void onModelAboutToBeReset();
+  void onModelReset();
+  void onRowsAboutToBeInserted(const QModelIndex &parent, int start, int end);
+  void onRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end);
+  void onRowsInserted(const QModelIndex & parent, int start, int end);
+  void onRowsRemoved(const QModelIndex & parent, int start, int end);
+protected:
+  virtual void onItemsAboutToBeInserted(const QModelIndex& parent, Qt::Orientation, int start, int end);
+  virtual void onItemsAboutToBeRemoved(const QModelIndex& parent, Qt::Orientation, int start, int end);
+  virtual void onItemsInserted(const QModelIndex& parent, Qt::Orientation, int start, int end);
+  virtual void onItemsRemoved(const QModelIndex& parent, Qt::Orientation, int start, int end);
+  QList<QPersistentModelIndex> persistentModelIndexes(const QModelIndex& index)const;
+  virtual void test(bool result, const QString& errorString)const;
+private:
+  QCTK_DECLARE_PRIVATE(ctkModelTester);
+};
+
+#endif