diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 13cf4f2f1..a27554bb8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,7 +13,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) configure_file( config-keepassx.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-keepassx.h ) @@ -26,6 +26,7 @@ set(keepassx_SOURCES core/Parser.cpp core/TimeInfo.cpp core/Uuid.cpp + gui/GroupModel.cpp ) automoc4_add_library( keepassx_core STATIC ${keepassx_SOURCES} ) diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 368c96683..bc36693fa 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -35,7 +35,7 @@ Group* Database::rootGroup() void Database::setRootGroup(Group* group) { - Q_ASSERT(group == 0 || group->parent() == this); + Q_ASSERT(group == 0 || group->database() == this); m_rootGroup = group; } diff --git a/src/core/Group.cpp b/src/core/Group.cpp index dc4359484..3a9561c82 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -122,6 +122,11 @@ void Group::setLastTopVisibleEntry(Entry* entry) m_lastTopVisibleEntry = entry; } +const Group* Group::parentGroup() const +{ + return m_parent; +} + void Group::setParent(Group* parent) { Q_ASSERT(parent != 0); @@ -168,12 +173,32 @@ const Database* Group::database() const return m_db; } -QList Group::children() const +QList Group::children() const +{ + QList constChildren; + Q_FOREACH (Group* group, m_children) { + constChildren << group; + } + + return constChildren; +} + +QList Group::children() { return m_children; } -QList Group::entries() const +QList Group::entries() const +{ + QList constEntries; + Q_FOREACH (Entry* entry, m_entries) { + constEntries << entry; + } + + return constEntries; +} + +QList Group::entries() { return m_entries; } diff --git a/src/core/Group.h b/src/core/Group.h index 7f851443d..7140df4c5 100644 --- a/src/core/Group.h +++ b/src/core/Group.h @@ -51,12 +51,15 @@ public: void setDefaultAutoTypeSequence(const QString& sequence); void setLastTopVisibleEntry(Entry* entry); + const Group* parentGroup() const; void setParent(Group* parent); void setParent(Database* db); const Database* database() const; - QList children() const; - QList entries() const; + QList children() const; + QList children(); + QList entries() const; + QList entries(); void addEntry(Entry* entry); void removeEntry(Entry* entry); diff --git a/src/gui/GroupModel.cpp b/src/gui/GroupModel.cpp new file mode 100644 index 000000000..2092438bf --- /dev/null +++ b/src/gui/GroupModel.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2010 Felix Geyer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "GroupModel.h" + +#include "core/Group.h" + +GroupModel::GroupModel(const Group* rootGroup, QObject* parent) : QAbstractItemModel(parent) +{ + m_root = rootGroup; +} + +void GroupModel::setRootGroup(const Group* group) +{ + m_root = group; +} + +int GroupModel::rowCount(const QModelIndex& parent) const +{ + if (!parent.isValid()) { + return 1; + } + else { + const Group* group = groupFromIndex(parent); + return group->children().size(); + } +} + +int GroupModel::columnCount(const QModelIndex& parent) const +{ + Q_UNUSED(parent); + + return 1; +} + +QModelIndex GroupModel::index(int row, int column, const QModelIndex& parent) const +{ + if (!hasIndex(row, column, parent)) { + return QModelIndex(); + } + + const Group* group; + + if (!parent.isValid()) { + group = m_root; + } + else { + group = groupFromIndex(parent)->children().at(row); + } + + return createIndex(row, column, group); +} + +QModelIndex GroupModel::parent(const QModelIndex& index) const +{ + if (!index.isValid()) { + return QModelIndex(); + } + + const Group* childGroup = groupFromIndex(index); + const Group* parentGroup = childGroup->parentGroup(); + + if (!parentGroup) { + return QModelIndex(); + } + else { + const Group* grandParentGroup = parentGroup->parentGroup(); + if (!grandParentGroup) { + return createIndex(0, 0, parentGroup); + } + else { + return createIndex(grandParentGroup->children().indexOf(parentGroup), 0, parentGroup); + } + } +} + +QVariant GroupModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid() || role != Qt::DisplayRole) { + return QVariant(); + } + + const Group* group = groupFromIndex(index); + return group->name(); +} + +QVariant GroupModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_UNUSED(section); + Q_UNUSED(orientation); + Q_UNUSED(role); + + return QVariant(); +} + +QModelIndex GroupModel::createIndex(int row, int column, const Group* group) const +{ + return QAbstractItemModel::createIndex(row, column, const_cast(group)); +} + +const Group* GroupModel::groupFromIndex(const QModelIndex& index) const +{ + Q_ASSERT(index.internalPointer()); + + return static_cast(index.internalPointer()); +} diff --git a/src/gui/GroupModel.h b/src/gui/GroupModel.h new file mode 100644 index 000000000..36be8aa0a --- /dev/null +++ b/src/gui/GroupModel.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2010 Felix Geyer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_GROUPMODEL_H +#define KEEPASSX_GROUPMODEL_H + +#include + +class Group; + +class GroupModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit GroupModel(const Group* rootGroup, QObject* parent = 0); + void setRootGroup(const Group* group); + + int rowCount(const QModelIndex& parent = QModelIndex()) const; + int columnCount(const QModelIndex& parent = QModelIndex()) const; + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex& index) const; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + +private: + QModelIndex createIndex(int row, int column, const Group* group) const; + const Group* groupFromIndex(const QModelIndex& index) const; + + const Group* m_root; +}; + +#endif // KEEPASSX_GROUPMODEL_H diff --git a/src/main.cpp b/src/main.cpp index 851f68f18..8fe9ce69c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,9 +15,29 @@ * along with this program. If not, see . */ +#include +#include + #include "core/Database.h" +#include "core/Parser.h" +#include "gui/GroupModel.h" + +#include "../tests/config-keepassx-tests.h" int main(int argc, char **argv) { + QApplication app(argc, argv); + Database* db= new Database(); + Parser* parser = new Parser(db); + parser->parse(QString(KEEPASSX_TEST_DIR).append("/NewDatabase.xml")); + + GroupModel groupModel(db->rootGroup()); + + QTreeView view; + view.setModel(&groupModel); + view.setHeaderHidden(true); + view.expandAll(); + view.show(); + return app.exec(); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bbb9a0566..63fabf3c7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -68,3 +68,6 @@ target_link_libraries( testgroup keepassx_core ${QT_QTCORE_LIBRARY} ${QT_QTGUI_L add_unit_test( testparser TestParser.cpp ) target_link_libraries( testparser keepassx_core ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTTEST_LIBRARY} ) + +add_unit_test( testgroupmodel TestGroupModel.cpp modeltest.cpp ) +target_link_libraries( testgroupmodel keepassx_core ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTTEST_LIBRARY} ) diff --git a/tests/TestGroup.cpp b/tests/TestGroup.cpp index f1ee2d71c..5a8800653 100644 --- a/tests/TestGroup.cpp +++ b/tests/TestGroup.cpp @@ -62,8 +62,8 @@ void TestGroup::testParenting() QVERIFY(g3->children().size() == 1); QVERIFY(g4->children().size() == 0); - QVERIFY(g1->children().contains(g2)); - QVERIFY(g1->children().contains(g3)); + QVERIFY(g1->children().at(0) == g2); + QVERIFY(g1->children().at(1) == g3); QVERIFY(g3->children().contains(g4)); } diff --git a/tests/TestGroupModel.cpp b/tests/TestGroupModel.cpp new file mode 100644 index 000000000..41342d152 --- /dev/null +++ b/tests/TestGroupModel.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2010 Felix Geyer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "modeltest.h" +#include "core/Group.h" +#include "gui/GroupModel.h" + +class TestGroupModel : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void test(); +}; + +void TestGroupModel::test() +{ + Group* groupRoot = new Group(); + groupRoot->setName("groupRoot"); + + Group* group1 = new Group(); + group1->setName("group1"); + group1->setParent(groupRoot); + + Group* group11 = new Group(); + group11->setName("group11"); + group11->setParent(group1); + + Group* group12 = new Group(); + group12->setName("group12"); + group12->setParent(group1); + + Group* group121 = new Group(); + group121->setName("group121"); + group121->setParent(group12); + + Group* group2 = new Group(); + group2->setName("group2"); + group2->setParent(groupRoot); + + GroupModel* model = new GroupModel(groupRoot, this); + + new ModelTest(model, this); + + QModelIndex indexRoot = model->index(0, 0); + QModelIndex index1 = model->index(0, 0, indexRoot); + QModelIndex index11 = model->index(0, 0, index1); + QModelIndex index12 = model->index(1, 0, index1); + QModelIndex index121 = model->index(0, 0, index12); + QModelIndex index2 = model->index(1, 0, indexRoot); + + QVERIFY(model->data(indexRoot) == "groupRoot"); + QVERIFY(model->data(index1) == "group1"); + QVERIFY(model->data(index11) == "group11"); + QVERIFY(model->data(index12) == "group12"); + QVERIFY(model->data(index121) == "group121"); + QVERIFY(model->data(index2) == "group2"); + + delete groupRoot; +} + +QTEST_MAIN(TestGroupModel); + +#include "TestGroupModel.moc" diff --git a/tests/modeltest.cpp b/tests/modeltest.cpp new file mode 100644 index 000000000..2a927985f --- /dev/null +++ b/tests/modeltest.cpp @@ -0,0 +1,539 @@ +/**************************************************************************** +** +** Copyright (C) 2007 Trolltech ASA. All rights reserved. +** +** This file is part of the Qt Concurrent project on Trolltech Labs. +** +** This file may be used under the terms of the GNU General Public +** License version 2.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL-2 included in the packaging of +** this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** http://www.trolltech.com/products/qt/opensource.html +** +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://www.trolltech.com/products/qt/licensing.html or contact the +** sales department at sales@trolltech.com. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +****************************************************************************/ + +#include + +#include "modeltest.h" + +Q_DECLARE_METATYPE(QModelIndex) + +/*! + Connect to all of the models signals. Whenever anything happens recheck everything. +*/ +ModelTest::ModelTest(QAbstractItemModel *_model, QObject *parent) : QObject(parent), model(_model), fetchingMore(false) +{ + Q_ASSERT(model); + + connect(model, SIGNAL(columnsAboutToBeInserted(const QModelIndex &, int, int)), + this, SLOT(runAllTests())); + connect(model, SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)), + this, SLOT(runAllTests())); + connect(model, SIGNAL(columnsInserted(const QModelIndex &, int, int)), + this, SLOT(runAllTests())); + connect(model, SIGNAL(columnsRemoved(const QModelIndex &, int, int)), + this, SLOT(runAllTests())); + connect(model, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), + this, SLOT(runAllTests())); + connect(model, SIGNAL(headerDataChanged(Qt::Orientation, int, int)), + this, SLOT(runAllTests())); + connect(model, SIGNAL(layoutAboutToBeChanged ()), this, SLOT(runAllTests())); + connect(model, SIGNAL(layoutChanged ()), this, SLOT(runAllTests())); + connect(model, SIGNAL(modelReset ()), this, SLOT(runAllTests())); + connect(model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)), + this, SLOT(runAllTests())); + connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)), + this, SLOT(runAllTests())); + connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)), + this, SLOT(runAllTests())); + connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + this, SLOT(runAllTests())); + + // Special checks for inserting/removing + connect(model, SIGNAL(layoutAboutToBeChanged()), + this, SLOT(layoutAboutToBeChanged())); + connect(model, SIGNAL(layoutChanged()), + this, SLOT(layoutChanged())); + + connect(model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)), + this, SLOT(rowsAboutToBeInserted(const QModelIndex &, int, int))); + connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)), + this, SLOT(rowsAboutToBeRemoved(const QModelIndex &, int, int))); + connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)), + this, SLOT(rowsInserted(const QModelIndex &, int, int))); + connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + this, SLOT(rowsRemoved(const QModelIndex &, int, int))); + + runAllTests(); +} + +void ModelTest::runAllTests() +{ + if (fetchingMore) + return; + nonDestructiveBasicTest(); + rowCount(); + columnCount(); + hasIndex(); + index(); + parent(); + data(); +} + +/*! + nonDestructiveBasicTest tries to call a number of the basic functions (not all) + to make sure the model doesn't outright segfault, testing the functions that makes sense. +*/ +void ModelTest::nonDestructiveBasicTest() +{ + Q_ASSERT(model->buddy(QModelIndex()) == QModelIndex()); + model->canFetchMore(QModelIndex()); + Q_ASSERT(model->columnCount(QModelIndex()) >= 0); + Q_ASSERT(model->data(QModelIndex()) == QVariant()); + fetchingMore = true; + model->fetchMore(QModelIndex()); + fetchingMore = false; + Qt::ItemFlags flags = model->flags(QModelIndex()); + Q_ASSERT(flags == Qt::ItemIsDropEnabled || flags == 0); + model->hasChildren(QModelIndex()); + model->hasIndex(0, 0); + model->headerData(0, Qt::Horizontal); + model->index(0, 0); + Q_ASSERT(model->index(-1, -1) == QModelIndex()); + model->itemData(QModelIndex()); + QVariant cache; + model->match(QModelIndex(), -1, cache); + model->mimeTypes(); + Q_ASSERT(model->parent(QModelIndex()) == QModelIndex()); + Q_ASSERT(model->rowCount() >= 0); + QVariant variant; + model->setData(QModelIndex(), variant, -1); + model->setHeaderData(-1, Qt::Horizontal, QVariant()); + model->setHeaderData(0, Qt::Horizontal, QVariant()); + model->setHeaderData(999999, Qt::Horizontal, QVariant()); + QMap roles; + model->sibling(0, 0, QModelIndex()); + model->span(QModelIndex()); + model->supportedDropActions(); +} + +/*! + Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren() + + Models that are dynamically populated are not as fully tested here. + */ +void ModelTest::rowCount() +{ + // check top row + QModelIndex topIndex = model->index(0, 0, QModelIndex()); + int rows = model->rowCount(topIndex); + Q_ASSERT(rows >= 0); + if (rows > 0) + Q_ASSERT(model->hasChildren(topIndex) == true); + + QModelIndex secondLevelIndex = model->index(0, 0, topIndex); + if (secondLevelIndex.isValid()) { // not the top level + // check a row count where parent is valid + rows = model->rowCount(secondLevelIndex); + Q_ASSERT(rows >= 0); + if (rows > 0) + Q_ASSERT(model->hasChildren(secondLevelIndex) == true); + } + + // The models rowCount() is tested more extensively in checkChildren(), + // but this catches the big mistakes +} + +/*! + Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren() + */ +void ModelTest::columnCount() +{ + // check top row + QModelIndex topIndex = model->index(0, 0, QModelIndex()); + Q_ASSERT(model->columnCount(topIndex) >= 0); + + // check a column count where parent is valid + QModelIndex childIndex = model->index(0, 0, topIndex); + if (childIndex.isValid()) + Q_ASSERT(model->columnCount(childIndex) >= 0); + + // columnCount() is tested more extensively in checkChildren(), + // but this catches the big mistakes +} + +/*! + Tests model's implementation of QAbstractItemModel::hasIndex() + */ +void ModelTest::hasIndex() +{ + // Make sure that invalid values returns an invalid index + Q_ASSERT(model->hasIndex(-2, -2) == false); + Q_ASSERT(model->hasIndex(-2, 0) == false); + Q_ASSERT(model->hasIndex(0, -2) == false); + + int rows = model->rowCount(); + int columns = model->columnCount(); + + // check out of bounds + Q_ASSERT(model->hasIndex(rows, columns) == false); + Q_ASSERT(model->hasIndex(rows + 1, columns + 1) == false); + + if (rows > 0) + Q_ASSERT(model->hasIndex(0, 0) == true); + + // hasIndex() is tested more extensively in checkChildren(), + // but this catches the big mistakes +} + +/*! + Tests model's implementation of QAbstractItemModel::index() + */ +void ModelTest::index() +{ + // Make sure that invalid values returns an invalid index + Q_ASSERT(model->index(-2, -2) == QModelIndex()); + Q_ASSERT(model->index(-2, 0) == QModelIndex()); + Q_ASSERT(model->index(0, -2) == QModelIndex()); + + int rows = model->rowCount(); + int columns = model->columnCount(); + + if (rows == 0) + return; + + // Catch off by one errors + Q_ASSERT(model->index(rows, columns) == QModelIndex()); + Q_ASSERT(model->index(0, 0).isValid() == true); + + // Make sure that the same index is *always* returned + QModelIndex a = model->index(0, 0); + QModelIndex b = model->index(0, 0); + Q_ASSERT(a == b); + + // index() is tested more extensively in checkChildren(), + // but this catches the big mistakes +} + +/*! + Tests model's implementation of QAbstractItemModel::parent() + */ +void ModelTest::parent() +{ + // Make sure the model wont crash and will return an invalid QModelIndex + // when asked for the parent of an invalid index. + Q_ASSERT(model->parent(QModelIndex()) == QModelIndex()); + + if (model->rowCount() == 0) + return; + + // Column 0 | Column 1 | + // QModelIndex() | | + // \- topIndex | topIndex1 | + // \- childIndex | childIndex1 | + + // Common error test #1, make sure that a top level index has a parent + // that is a invalid QModelIndex. + QModelIndex topIndex = model->index(0, 0, QModelIndex()); + Q_ASSERT(model->parent(topIndex) == QModelIndex()); + + // Common error test #2, make sure that a second level index has a parent + // that is the first level index. + if (model->rowCount(topIndex) > 0) { + QModelIndex childIndex = model->index(0, 0, topIndex); + Q_ASSERT(model->parent(childIndex) == topIndex); + } + + // Common error test #3, the second column should NOT have the same children + // as the first column in a row. + // Usually the second column shouldn't have children. + QModelIndex topIndex1 = model->index(0, 1, QModelIndex()); + if (model->rowCount(topIndex1) > 0) { + QModelIndex childIndex = model->index(0, 0, topIndex); + QModelIndex childIndex1 = model->index(0, 0, topIndex1); + Q_ASSERT(childIndex != childIndex1); + } + + // Full test, walk n levels deep through the model making sure that all + // parent's children correctly specify their parent. + checkChildren(QModelIndex()); +} + +/*! + Called from the parent() test. + + A model that returns an index of parent X should also return X when asking + for the parent of the index. + + This recursive function does pretty extensive testing on the whole model in an + effort to catch edge cases. + + This function assumes that rowCount(), columnCount() and index() already work. + If they have a bug it will point it out, but the above tests should have already + found the basic bugs because it is easier to figure out the problem in + those tests then this one. + */ +void ModelTest::checkChildren(const QModelIndex &parent, int currentDepth) +{ + // First just try walking back up the tree. + QModelIndex p = parent; + while (p.isValid()) + p = p.parent(); + + // For models that are dynamically populated + if (model->canFetchMore(parent)) { + fetchingMore = true; + model->fetchMore(parent); + fetchingMore = false; + } + + int rows = model->rowCount(parent); + int columns = model->columnCount(parent); + + if (rows > 0) + Q_ASSERT(model->hasChildren(parent)); + + // Some further testing against rows(), columns(), and hasChildren() + Q_ASSERT(rows >= 0); + Q_ASSERT(columns >= 0); + if (rows > 0) + Q_ASSERT(model->hasChildren(parent) == true); + + //qDebug() << "parent:" << model->data(parent).toString() << "rows:" << rows + // << "columns:" << columns << "parent column:" << parent.column(); + + Q_ASSERT(model->hasIndex(rows + 1, 0, parent) == false); + for (int r = 0; r < rows; ++r) { + if (model->canFetchMore(parent)) { + fetchingMore = true; + model->fetchMore(parent); + fetchingMore = false; + } + Q_ASSERT(model->hasIndex(r, columns + 1, parent) == false); + for (int c = 0; c < columns; ++c) { + Q_ASSERT(model->hasIndex(r, c, parent) == true); + QModelIndex index = model->index(r, c, parent); + // rowCount() and columnCount() said that it existed... + Q_ASSERT(index.isValid() == true); + + // index() should always return the same index when called twice in a row + QModelIndex modifiedIndex = model->index(r, c, parent); + Q_ASSERT(index == modifiedIndex); + + // Make sure we get the same index if we request it twice in a row + QModelIndex a = model->index(r, c, parent); + QModelIndex b = model->index(r, c, parent); + Q_ASSERT(a == b); + + // Some basic checking on the index that is returned + Q_ASSERT(index.model() == model); + Q_ASSERT(index.row() == r); + Q_ASSERT(index.column() == c); + // While you can technically return a QVariant usually this is a sign + // of an bug in data() Disable if this really is ok in your model. + //Q_ASSERT(model->data(index, Qt::DisplayRole).isValid() == true); + + // If the next test fails here is some somewhat useful debug you play with. + /* + if (model->parent(index) != parent) { + qDebug() << r << c << currentDepth << model->data(index).toString() + << model->data(parent).toString(); + qDebug() << index << parent << model->parent(index); + // And a view that you can even use to show the model. + //QTreeView view; + //view.setModel(model); + //view.show(); + }*/ + + // Check that we can get back our real parent. + QModelIndex p = model->parent(index); + //qDebug() << "child:" << index; + //qDebug() << p; + //qDebug() << parent; + Q_ASSERT(model->parent(index) == parent); + + // recursively go down the children + if (model->hasChildren(index) && currentDepth < 10 ) { + //qDebug() << r << c << "has children" << model->rowCount(index); + checkChildren(index, ++currentDepth); + }/* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/ + + // make sure that after testing the children that the index doesn't change. + QModelIndex newerIndex = model->index(r, c, parent); + Q_ASSERT(index == newerIndex); + } + } +} + +/*! + Tests model's implementation of QAbstractItemModel::data() + */ +void ModelTest::data() +{ + // Invalid index should return an invalid qvariant + Q_ASSERT(!model->data(QModelIndex()).isValid()); + + if (model->rowCount() == 0) + return; + + // A valid index should have a valid QVariant data + Q_ASSERT(model->index(0, 0).isValid()); + + // shouldn't be able to set data on an invalid index + Q_ASSERT(model->setData(QModelIndex(), QLatin1String("foo"), Qt::DisplayRole) == false); + + // General Purpose roles that should return a QString + QVariant variant = model->data(model->index(0, 0), Qt::ToolTipRole); + if (variant.isValid()) { + Q_ASSERT(qVariantCanConvert(variant)); + } + variant = model->data(model->index(0, 0), Qt::StatusTipRole); + if (variant.isValid()) { + Q_ASSERT(qVariantCanConvert(variant)); + } + variant = model->data(model->index(0, 0), Qt::WhatsThisRole); + if (variant.isValid()) { + Q_ASSERT(qVariantCanConvert(variant)); + } + + // General Purpose roles that should return a QSize + variant = model->data(model->index(0, 0), Qt::SizeHintRole); + if (variant.isValid()) { + Q_ASSERT(qVariantCanConvert(variant)); + } + + // General Purpose roles that should return a QFont + QVariant fontVariant = model->data(model->index(0, 0), Qt::FontRole); + if (fontVariant.isValid()) { + Q_ASSERT(qVariantCanConvert(fontVariant)); + } + + // Check that the alignment is one we know about + QVariant textAlignmentVariant = model->data(model->index(0, 0), Qt::TextAlignmentRole); + if (textAlignmentVariant.isValid()) { + int alignment = textAlignmentVariant.toInt(); + Q_ASSERT(alignment == Qt::AlignLeft || + alignment == Qt::AlignRight || + alignment == Qt::AlignHCenter || + alignment == Qt::AlignJustify || + alignment == Qt::AlignTop || + alignment == Qt::AlignBottom || + alignment == Qt::AlignVCenter || + alignment == Qt::AlignCenter || + alignment == Qt::AlignAbsolute || + alignment == Qt::AlignLeading || + alignment == Qt::AlignTrailing); + } + + // General Purpose roles that should return a QColor + QVariant colorVariant = model->data(model->index(0, 0), Qt::BackgroundColorRole); + if (colorVariant.isValid()) { + Q_ASSERT(qVariantCanConvert(colorVariant)); + } + + colorVariant = model->data(model->index(0, 0), Qt::TextColorRole); + if (colorVariant.isValid()) { + Q_ASSERT(qVariantCanConvert(colorVariant)); + } + + // Check that the "check state" is one we know about. + QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole); + if (checkStateVariant.isValid()) { + int state = checkStateVariant.toInt(); + Q_ASSERT(state == Qt::Unchecked || + state == Qt::PartiallyChecked || + state == Qt::Checked); + } +} + +/*! + Store what is about to be inserted to make sure it actually happens + + \sa rowsInserted() + */ +void ModelTest::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end) +{ + Q_UNUSED(end); + Changing c; + c.parent = parent; + c.oldSize = model->rowCount(parent); + c.last = model->data(model->index(start - 1, 0, parent)); + c.next = model->data(model->index(start, 0, parent)); + insert.push(c); +} + +/*! + Confirm that what was said was going to happen actually did + + \sa rowsAboutToBeInserted() + */ +void ModelTest::rowsInserted(const QModelIndex & parent, int start, int end) +{ + Changing c = insert.pop(); + Q_ASSERT(c.parent == parent); + Q_ASSERT(c.oldSize + (end - start + 1) == model->rowCount(parent)); + Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent))); + /* + if (c.next != model->data(model->index(end + 1, 0, c.parent))) { + qDebug() << start << end; + for (int i=0; i < model->rowCount(); ++i) + qDebug() << model->index(i, 0).data().toString(); + qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent)); + } + */ + Q_ASSERT(c.next == model->data(model->index(end + 1, 0, c.parent))); +} + +void ModelTest::layoutAboutToBeChanged() +{ + for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i) + changing.append(QPersistentModelIndex(model->index(i, 0))); +} + +void ModelTest::layoutChanged() +{ + for (int i = 0; i < changing.count(); ++i) { + QPersistentModelIndex p = changing[i]; + Q_ASSERT(p == model->index(p.row(), p.column(), p.parent())); + } + changing.clear(); +} + +/*! + Store what is about to be inserted to make sure it actually happens + + \sa rowsRemoved() + */ +void ModelTest::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + Changing c; + c.parent = parent; + c.oldSize = model->rowCount(parent); + c.last = model->data(model->index(start - 1, 0, parent)); + c.next = model->data(model->index(end + 1, 0, parent)); + remove.push(c); +} + +/*! + Confirm that what was said was going to happen actually did + + \sa rowsAboutToBeRemoved() + */ +void ModelTest::rowsRemoved(const QModelIndex & parent, int start, int end) +{ + Changing c = remove.pop(); + Q_ASSERT(c.parent == parent); + Q_ASSERT(c.oldSize - (end - start + 1) == model->rowCount(parent)); + Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent))); + Q_ASSERT(c.next == model->data(model->index(start, 0, c.parent))); +} + diff --git a/tests/modeltest.h b/tests/modeltest.h new file mode 100644 index 000000000..783a5214d --- /dev/null +++ b/tests/modeltest.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2007 Trolltech ASA. All rights reserved. +** +** This file is part of the Qt Concurrent project on Trolltech Labs. +** +** This file may be used under the terms of the GNU General Public +** License version 2.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL-2 included in the packaging of +** this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** http://www.trolltech.com/products/qt/opensource.html +** +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://www.trolltech.com/products/qt/licensing.html or contact the +** sales department at sales@trolltech.com. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +****************************************************************************/ + +#ifndef MODELTEST_H +#define MODELTEST_H + +#include +#include +#include + +class ModelTest : public QObject +{ + Q_OBJECT + +public: + ModelTest(QAbstractItemModel *model, QObject *parent = 0); + +private Q_SLOTS: + void nonDestructiveBasicTest(); + void rowCount(); + void columnCount(); + void hasIndex(); + void index(); + void parent(); + void data(); + +protected Q_SLOTS: + void runAllTests(); + void layoutAboutToBeChanged(); + void layoutChanged(); + void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end); + void rowsInserted(const QModelIndex & parent, int start, int end); + void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + void rowsRemoved(const QModelIndex & parent, int start, int end); + +private: + void checkChildren(const QModelIndex &parent, int currentDepth = 0); + + QAbstractItemModel *model; + + struct Changing + { + QModelIndex parent; + int oldSize; + QVariant last; + QVariant next; + }; + QStack insert; + QStack remove; + + bool fetchingMore; + + QList changing; +}; + +#endif