File indexing completed on 2024-04-28 07:44:56

0001 /*
0002     SPDX-FileCopyrightText: 2016 Sune Vuorela <sune@debian.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #include "kdescendantsproxymodel.h"
0008 
0009 #include <QAbstractListModel>
0010 #include <QSignalSpy>
0011 #include <QStandardItemModel>
0012 #include <QTest>
0013 
0014 struct Node {
0015     ~Node()
0016     {
0017         qDeleteAll(children);
0018     }
0019 
0020     QString label;
0021     Node *parent = nullptr;
0022     QList<Node *> children;
0023     int knownChildren = 0;
0024 };
0025 
0026 class SimpleObjectModel : public QAbstractListModel
0027 {
0028     Q_OBJECT
0029 public:
0030     explicit SimpleObjectModel(QObject *parent = nullptr, bool incremental = false);
0031     ~SimpleObjectModel() override;
0032 
0033     QModelIndex index(int, int, const QModelIndex &parent = QModelIndex()) const override;
0034     QModelIndex parent(const QModelIndex &) const override;
0035     int rowCount(const QModelIndex &parent = QModelIndex()) const override;
0036     bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
0037     QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
0038 
0039     bool insert(const QModelIndex &parent, int row, const QString &text);
0040     bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
0041     bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationRow) override;
0042 
0043     Node *getRootNode() const
0044     {
0045         return m_root;
0046     }
0047 
0048 private:
0049     Node *m_root;
0050     // simulate a model that loads in new rows with fetchMore()
0051     bool m_incremental;
0052 };
0053 
0054 SimpleObjectModel::SimpleObjectModel(QObject *parent, bool incremental)
0055     : QAbstractListModel(parent)
0056     , m_incremental(incremental)
0057 {
0058     m_root = new Node;
0059 }
0060 
0061 SimpleObjectModel::~SimpleObjectModel()
0062 {
0063     delete m_root;
0064 }
0065 
0066 QModelIndex SimpleObjectModel::index(int row, int col, const QModelIndex &parent) const
0067 {
0068     Node *parentItem;
0069     if (!parent.isValid()) {
0070         parentItem = static_cast<Node *>(m_root);
0071     } else {
0072         parentItem = static_cast<Node *>(parent.internalPointer());
0073     }
0074 
0075     if (row < 0 || parentItem->children.size() <= row) {
0076         return QModelIndex();
0077     }
0078     Node *childItem = parentItem->children[row];
0079 
0080     return createIndex(row, col, childItem);
0081 }
0082 
0083 QModelIndex SimpleObjectModel::parent(const QModelIndex &index) const
0084 {
0085     Node *childItem = static_cast<Node *>(index.internalPointer());
0086     if (!childItem) {
0087         return QModelIndex();
0088     }
0089 
0090     Node *parent = childItem->parent;
0091     Node *grandParent = parent->parent;
0092 
0093     int childRow = 0;
0094     if (grandParent) {
0095         childRow = grandParent->children.indexOf(parent);
0096     }
0097 
0098     if (parent == m_root) {
0099         return QModelIndex();
0100     }
0101     return createIndex(childRow, 0, parent);
0102 }
0103 
0104 int SimpleObjectModel::rowCount(const QModelIndex &index) const
0105 {
0106     Node *item = static_cast<Node *>(index.internalPointer());
0107     if (!item) {
0108         item = m_root;
0109     }
0110 
0111     if (m_incremental) {
0112         return item->knownChildren;
0113     }
0114     return item->children.count();
0115 }
0116 
0117 bool SimpleObjectModel::hasChildren(const QModelIndex &index) const
0118 {
0119     Node *item = static_cast<Node *>(index.internalPointer());
0120     if (!item) {
0121         item = m_root;
0122     }
0123 
0124     return !item->children.isEmpty();
0125 }
0126 
0127 QVariant SimpleObjectModel::data(const QModelIndex &index, int role) const
0128 {
0129     if (role != Qt::DisplayRole) {
0130         return QVariant();
0131     }
0132 
0133     Node *node = static_cast<Node *>(index.internalPointer());
0134     if (!node) {
0135         return QVariant();
0136     }
0137     return node->label;
0138 }
0139 
0140 bool SimpleObjectModel::insert(const QModelIndex &index, int row, const QString &text)
0141 {
0142     if (row < 0) {
0143         return false;
0144     }
0145 
0146     Node *parent = static_cast<Node *>(index.internalPointer());
0147     if (!parent) {
0148         parent = m_root;
0149     }
0150 
0151     if (row > parent->children.count()) {
0152         return false;
0153     }
0154 
0155     beginInsertRows(index, row, row);
0156     Node *child = new Node;
0157     child->parent = parent;
0158     child->label = text;
0159     parent->children.insert(row, child);
0160     endInsertRows();
0161 
0162     return true;
0163 }
0164 
0165 bool SimpleObjectModel::removeRows(int row, int count, const QModelIndex &index)
0166 {
0167     if (row < 0) {
0168         return false;
0169     }
0170 
0171     Node *parent = static_cast<Node *>(index.internalPointer());
0172     if (!parent) {
0173         parent = m_root;
0174     }
0175 
0176     const int last = row + count - 1;
0177 
0178     if (last >= parent->children.count()) {
0179         return false;
0180     }
0181 
0182     beginRemoveRows(index, row, last);
0183     for (int i = last; i >= row; i--) {
0184         Node *child = parent->children.takeAt(i);
0185         delete child;
0186     }
0187     endRemoveRows();
0188 
0189     return true;
0190 }
0191 
0192 bool SimpleObjectModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationRow)
0193 {
0194     Node *sourceNode = static_cast<Node *>(sourceParent.internalPointer());
0195     if (!sourceNode) {
0196         sourceNode = m_root;
0197     }
0198     Node *destinationNode = static_cast<Node *>(destinationParent.internalPointer());
0199     if (!destinationNode) {
0200         destinationNode = m_root;
0201     }
0202 
0203     const int sourceLast = sourceRow + count - 1;
0204 
0205     if (sourceNode != destinationNode) {
0206         if (count <= 0 || sourceRow < 0 || sourceRow >= sourceNode->children.count() || destinationRow < 0
0207             || destinationRow > destinationNode->children.count()) {
0208             return false;
0209         }
0210 
0211         if (!beginMoveRows(sourceParent, sourceRow, sourceLast, destinationParent, destinationRow)) {
0212             return false;
0213         }
0214 
0215         Node *child = sourceNode->children.takeAt(sourceRow);
0216         child->parent = destinationNode;
0217         destinationNode->children.insert(destinationRow, child);
0218 
0219         endMoveRows();
0220         return true;
0221     }
0222 
0223     if (count <= 0 || sourceRow == destinationRow || sourceRow < 0 || sourceRow >= destinationNode->children.count() || destinationRow < 0
0224         || destinationRow > destinationNode->children.count() || count - destinationRow > destinationNode->children.count() - sourceRow) {
0225         return false;
0226     }
0227 
0228     // beginMoveRows wants indexes before the source rows are removed from the old order
0229     if (!beginMoveRows(sourceParent, sourceRow, sourceLast, destinationParent, destinationRow)) {
0230         return false;
0231     }
0232 
0233     if (sourceRow < destinationRow) {
0234         for (int i = count - 1; i >= 0; --i) {
0235             destinationNode->children.move(sourceRow + i, destinationRow - count + i);
0236         }
0237     } else {
0238         for (int i = 0; i < count; ++i) {
0239             destinationNode->children.move(sourceRow + i, destinationRow + i);
0240         }
0241     }
0242 
0243     endMoveRows();
0244     return true;
0245 }
0246 
0247 class tst_KDescendantProxyModel : public QObject
0248 {
0249     Q_OBJECT
0250     QAbstractItemModel *createTree(const QString &prefix)
0251     {
0252         /*
0253          * |- parent1
0254          * |  |- child1
0255          * |  `- child2
0256          * `- parent2
0257          *    |- child1
0258          *    `- child2
0259          */
0260         QStandardItemModel *model = new QStandardItemModel(this);
0261         for (int i = 0; i < 2; i++) {
0262             QStandardItem *item = new QStandardItem();
0263             item->setData(QString(prefix + QString::number(i)), Qt::DisplayRole);
0264             for (int j = 0; j < 2; j++) {
0265                 QStandardItem *child = new QStandardItem();
0266                 child->setData(QString(prefix + QString::number(i) + "-" + QString::number(j)), Qt::DisplayRole);
0267                 item->appendRow(child);
0268             }
0269             model->appendRow(item);
0270         }
0271         return model;
0272     }
0273 
0274 private Q_SLOTS:
0275     void testResetModelContent();
0276     void testChangeSeparator();
0277     void testChangeInvisibleSeparator();
0278     void testRemoveSeparator();
0279 
0280     void testResetCollapsedModelContent();
0281     void testInsertInCollapsedModel();
0282     void testRemoveInCollapsedModel();
0283     void testMoveInsideCollapsed();
0284     void testExpandInsideCollapsed();
0285     void testEmptyModel();
0286     void testEmptyChild();
0287 };
0288 
0289 /// Tests that replacing the source model results in data getting changed
0290 void tst_KDescendantProxyModel::testResetModelContent()
0291 {
0292     auto model1 = createTree("FirstModel");
0293     KDescendantsProxyModel proxy;
0294     proxy.setSourceModel(model1);
0295     QCOMPARE(proxy.rowCount(), 6);
0296 
0297     {
0298         QStringList results = QStringList() << "FirstModel0"
0299                                             << "FirstModel0-0"
0300                                             << "FirstModel0-1"
0301                                             << "FirstModel1"
0302                                             << "FirstModel1-0"
0303                                             << "FirstModel1-1";
0304         QCOMPARE(proxy.rowCount(), results.count());
0305         for (int i = 0; i < proxy.rowCount(); i++) {
0306             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0307         }
0308     }
0309     auto model2 = createTree("SecondModel");
0310     {
0311         proxy.setSourceModel(model2);
0312         QStringList results = QStringList() << "SecondModel0"
0313                                             << "SecondModel0-0"
0314                                             << "SecondModel0-1"
0315                                             << "SecondModel1"
0316                                             << "SecondModel1-0"
0317                                             << "SecondModel1-1";
0318         QCOMPARE(proxy.rowCount(), results.count());
0319         for (int i = 0; i < proxy.rowCount(); i++) {
0320             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0321         }
0322     }
0323 
0324     delete model2;
0325     delete model1;
0326 }
0327 
0328 /// tests that change separator works, as well as emits the relevant data changed signals
0329 void tst_KDescendantProxyModel::testChangeSeparator()
0330 {
0331     auto model1 = createTree("FirstModel");
0332     KDescendantsProxyModel proxy;
0333     proxy.setSourceModel(model1);
0334     proxy.setDisplayAncestorData(true);
0335     QSignalSpy dataChangedSpy(&proxy, &QAbstractItemModel::dataChanged);
0336     QCOMPARE(proxy.rowCount(), 6);
0337     {
0338         QStringList results = QStringList() << "FirstModel0"
0339                                             << "FirstModel0 / FirstModel0-0"
0340                                             << "FirstModel0 / FirstModel0-1"
0341                                             << "FirstModel1"
0342                                             << "FirstModel1 / FirstModel1-0"
0343                                             << "FirstModel1 / FirstModel1-1";
0344         QCOMPARE(proxy.rowCount(), results.count());
0345         for (int i = 0; i < proxy.rowCount(); i++) {
0346             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0347         }
0348     }
0349     proxy.setAncestorSeparator("LOL");
0350     QCOMPARE(dataChangedSpy.count(), 1);
0351     {
0352         QStringList results = QStringList() << "FirstModel0"
0353                                             << "FirstModel0LOLFirstModel0-0"
0354                                             << "FirstModel0LOLFirstModel0-1"
0355                                             << "FirstModel1"
0356                                             << "FirstModel1LOLFirstModel1-0"
0357                                             << "FirstModel1LOLFirstModel1-1";
0358         QCOMPARE(proxy.rowCount(), results.count());
0359         for (int i = 0; i < proxy.rowCount(); i++) {
0360             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0361         }
0362     }
0363 
0364     delete model1;
0365 }
0366 
0367 /// tests that change separator that is not shown does not change the content and does not
0368 /// emit data changed signals, since the data isn't changed
0369 void tst_KDescendantProxyModel::testChangeInvisibleSeparator()
0370 {
0371     auto model1 = createTree("FirstModel");
0372     KDescendantsProxyModel proxy;
0373     proxy.setSourceModel(model1);
0374     QSignalSpy dataChangedSpy(&proxy, &QAbstractItemModel::dataChanged);
0375     QCOMPARE(proxy.rowCount(), 6);
0376     {
0377         QStringList results = QStringList() << "FirstModel0"
0378                                             << "FirstModel0-0"
0379                                             << "FirstModel0-1"
0380                                             << "FirstModel1"
0381                                             << "FirstModel1-0"
0382                                             << "FirstModel1-1";
0383         QCOMPARE(proxy.rowCount(), results.count());
0384         for (int i = 0; i < proxy.rowCount(); i++) {
0385             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0386         }
0387     }
0388     proxy.setAncestorSeparator("LOL");
0389     QCOMPARE(dataChangedSpy.count(), 0);
0390     {
0391         QStringList results = QStringList() << "FirstModel0"
0392                                             << "FirstModel0-0"
0393                                             << "FirstModel0-1"
0394                                             << "FirstModel1"
0395                                             << "FirstModel1-0"
0396                                             << "FirstModel1-1";
0397         QCOMPARE(proxy.rowCount(), results.count());
0398         for (int i = 0; i < proxy.rowCount(); i++) {
0399             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0400         }
0401     }
0402 
0403     delete model1;
0404 }
0405 
0406 /// tests that data is properly updated when separator is removed/hidden
0407 /// and data changed signal is emitted
0408 void tst_KDescendantProxyModel::testRemoveSeparator()
0409 {
0410     auto model1 = createTree("FirstModel");
0411     KDescendantsProxyModel proxy;
0412     proxy.setSourceModel(model1);
0413     QSignalSpy dataChangedSpy(&proxy, &QAbstractItemModel::dataChanged);
0414     proxy.setDisplayAncestorData(true);
0415     QCOMPARE(dataChangedSpy.count(), 1);
0416     dataChangedSpy.clear();
0417     QCOMPARE(proxy.rowCount(), 6);
0418     {
0419         QStringList results = QStringList() << "FirstModel0"
0420                                             << "FirstModel0 / FirstModel0-0"
0421                                             << "FirstModel0 / FirstModel0-1"
0422                                             << "FirstModel1"
0423                                             << "FirstModel1 / FirstModel1-0"
0424                                             << "FirstModel1 / FirstModel1-1";
0425         QCOMPARE(proxy.rowCount(), results.count());
0426         for (int i = 0; i < proxy.rowCount(); i++) {
0427             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0428         }
0429     }
0430     proxy.setDisplayAncestorData(false);
0431     QCOMPARE(dataChangedSpy.count(), 1);
0432     {
0433         QStringList results = QStringList() << "FirstModel0"
0434                                             << "FirstModel0-0"
0435                                             << "FirstModel0-1"
0436                                             << "FirstModel1"
0437                                             << "FirstModel1-0"
0438                                             << "FirstModel1-1";
0439         QCOMPARE(proxy.rowCount(), results.count());
0440         for (int i = 0; i < proxy.rowCount(); i++) {
0441             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0442         }
0443     }
0444 }
0445 
0446 void tst_KDescendantProxyModel::testResetCollapsedModelContent()
0447 {
0448     auto model1 = createTree("FirstModel");
0449     KDescendantsProxyModel proxy;
0450     proxy.setExpandsByDefault(false);
0451     proxy.setSourceModel(model1);
0452     QCOMPARE(proxy.rowCount(), 2);
0453 
0454     {
0455         QStringList results = QStringList() << "FirstModel0"
0456                                             << "FirstModel1";
0457         QCOMPARE(proxy.rowCount(), results.count());
0458         for (int i = 0; i < proxy.rowCount(); i++) {
0459             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0460         }
0461     }
0462     {
0463         QModelIndex idx = model1->index(0, 0);
0464         proxy.expandSourceIndex(idx);
0465         QStringList results = QStringList() << "FirstModel0"
0466                                             << "FirstModel0-0"
0467                                             << "FirstModel0-1"
0468                                             << "FirstModel1";
0469         QCOMPARE(proxy.rowCount(), results.count());
0470         for (int i = 0; i < proxy.rowCount(); i++) {
0471             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0472         }
0473     }
0474     {
0475         QModelIndex idx = model1->index(1, 0);
0476         proxy.expandSourceIndex(idx);
0477         QStringList results = QStringList() << "FirstModel0"
0478                                             << "FirstModel0-0"
0479                                             << "FirstModel0-1"
0480                                             << "FirstModel1"
0481                                             << "FirstModel1-0"
0482                                             << "FirstModel1-1";
0483         QCOMPARE(proxy.rowCount(), results.count());
0484         for (int i = 0; i < proxy.rowCount(); i++) {
0485             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0486         }
0487     }
0488 
0489     auto model2 = createTree("SecondModel");
0490     {
0491         proxy.setSourceModel(model2);
0492         QModelIndex idx = model2->index(0, 0);
0493         proxy.expandSourceIndex(idx);
0494         idx = model2->index(1, 0);
0495         proxy.expandSourceIndex(idx);
0496         QStringList results = QStringList() << "SecondModel0"
0497                                             << "SecondModel0-0"
0498                                             << "SecondModel0-1"
0499                                             << "SecondModel1"
0500                                             << "SecondModel1-0"
0501                                             << "SecondModel1-1";
0502         QCOMPARE(proxy.rowCount(), results.count());
0503         for (int i = 0; i < proxy.rowCount(); i++) {
0504             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0505         }
0506     }
0507 
0508     delete model2;
0509     delete model1;
0510 }
0511 
0512 void tst_KDescendantProxyModel::testInsertInCollapsedModel()
0513 {
0514     QStandardItemModel *model1 = static_cast<QStandardItemModel *>(createTree("Model"));
0515     KDescendantsProxyModel proxy;
0516     proxy.setExpandsByDefault(false);
0517     proxy.setSourceModel(model1);
0518     QCOMPARE(proxy.rowCount(), 2);
0519 
0520     QSignalSpy insertSpy(&proxy, &QAbstractItemModel::rowsInserted);
0521     QCOMPARE(insertSpy.count(), 0);
0522 
0523     QStandardItem *parent = model1->item(0, 0);
0524     QVERIFY(parent);
0525 
0526     QStandardItem *child = new QStandardItem();
0527     child->setData(QString(QStringLiteral("Model") + QString::number(0) + "-" + QString::number(2)), Qt::DisplayRole);
0528     parent->appendRow(child);
0529 
0530     // Adding a child to the collapsed parent doesn't have an effect to the proxy
0531     QCOMPARE(proxy.rowCount(), 2);
0532     QCOMPARE(insertSpy.count(), 0);
0533 
0534     // If we expand everything inserted should be here
0535     QModelIndex idx = model1->index(0, 0);
0536     proxy.expandSourceIndex(idx);
0537 
0538     QCOMPARE(proxy.rowCount(), 5);
0539     QCOMPARE(insertSpy.count(), 1);
0540 
0541     QCOMPARE(proxy.index(3, 0).data(Qt::DisplayRole).toString(), QStringLiteral("Model0-2"));
0542 
0543     // Add another child to the expanded node, now the proxy is affected immediately
0544     child = new QStandardItem();
0545     child->setData(QString(QStringLiteral("Model") + QString::number(0) + "-" + QString::number(3)), Qt::DisplayRole);
0546     parent->appendRow(child);
0547 
0548     QCOMPARE(proxy.rowCount(), 6);
0549     QCOMPARE(insertSpy.count(), 2);
0550 }
0551 
0552 void tst_KDescendantProxyModel::testRemoveInCollapsedModel()
0553 {
0554     QStandardItemModel *model1 = static_cast<QStandardItemModel *>(createTree("Model"));
0555     KDescendantsProxyModel proxy;
0556     proxy.setExpandsByDefault(false);
0557     proxy.setSourceModel(model1);
0558     QCOMPARE(proxy.rowCount(), 2);
0559 
0560     QSignalSpy removeSpy(&proxy, &QAbstractItemModel::rowsRemoved);
0561     QCOMPARE(removeSpy.count(), 0);
0562 
0563     QStandardItem *parent = model1->item(0, 0);
0564     QVERIFY(parent);
0565 
0566     parent->removeRow(0);
0567 
0568     // Adding a child to the collapsed parent doesn't have an effect to the proxy
0569     QCOMPARE(proxy.rowCount(), 2);
0570     QCOMPARE(removeSpy.count(), 0);
0571 
0572     // If we expand everything inserted should be here
0573     QModelIndex idx = model1->index(0, 0);
0574     proxy.expandSourceIndex(idx);
0575 
0576     QCOMPARE(proxy.rowCount(), 3);
0577 
0578     QCOMPARE(proxy.index(1, 0).data(Qt::DisplayRole).toString(), QStringLiteral("Model0-1"));
0579     parent->removeRow(0);
0580 
0581     QCOMPARE(proxy.rowCount(), 2);
0582     QCOMPARE(removeSpy.count(), 1);
0583 
0584     idx = model1->index(1, 0);
0585     proxy.expandSourceIndex(idx);
0586     QCOMPARE(proxy.rowCount(), 4);
0587 }
0588 
0589 void tst_KDescendantProxyModel::testMoveInsideCollapsed()
0590 {
0591     SimpleObjectModel *model = new SimpleObjectModel(this);
0592     model->insert(QModelIndex(), 0, QStringLiteral("Model0"));
0593     model->insert(QModelIndex(), 1, QStringLiteral("Model1"));
0594     model->insert(QModelIndex(), 2, QStringLiteral("Model2"));
0595 
0596     model->insert(model->index(0, 0, QModelIndex()), 0, QStringLiteral("Model0-0"));
0597     model->insert(model->index(1, 0, QModelIndex()), 0, QStringLiteral("Model1-0"));
0598 
0599     QCOMPARE(model->rowCount(), 3);
0600 
0601     KDescendantsProxyModel proxy;
0602     proxy.setExpandsByDefault(false);
0603     proxy.setSourceModel(model);
0604     QCOMPARE(proxy.rowCount(), 3);
0605 
0606     QSignalSpy removeSpy(&proxy, &QAbstractItemModel::rowsRemoved);
0607     QCOMPARE(removeSpy.count(), 0);
0608     QSignalSpy insertSpy(&proxy, &QAbstractItemModel::rowsInserted);
0609     QCOMPARE(insertSpy.count(), 0);
0610 
0611     model->moveRows(QModelIndex(), 2, 1, model->index(0, 0, QModelIndex()), 1);
0612 
0613     QCOMPARE(removeSpy.count(), 1);
0614 
0615     QVERIFY(!removeSpy.first()[0].value<QModelIndex>().isValid());
0616     QCOMPARE(removeSpy.first()[1].toInt(), 2);
0617     QCOMPARE(removeSpy.first()[2].toInt(), 2);
0618 
0619     QCOMPARE(model->rowCount(), 2);
0620     QCOMPARE(proxy.rowCount(), 2);
0621 
0622     model->moveRows(model->index(0, 0, QModelIndex()), 0, 1, QModelIndex(), 1);
0623     QCOMPARE(insertSpy.count(), 1);
0624     QCOMPARE(model->rowCount(), 3);
0625     QCOMPARE(proxy.rowCount(), 3);
0626 
0627     QVERIFY(!insertSpy.first()[0].value<QModelIndex>().isValid());
0628     QCOMPARE(insertSpy.first()[1].toInt(), 1);
0629     QCOMPARE(insertSpy.first()[2].toInt(), 1);
0630 
0631     QModelIndex idx = model->index(0, 0);
0632     proxy.expandSourceIndex(idx);
0633     idx = model->index(1, 0);
0634     proxy.expandSourceIndex(idx);
0635     idx = model->index(2, 0);
0636     proxy.expandSourceIndex(idx);
0637     QStringList results = QStringList() << "Model0"
0638                                         << "Model2"
0639                                         << "Model0-0"
0640                                         << "Model1"
0641                                         << "Model1-0";
0642     QCOMPARE(proxy.rowCount(), results.count());
0643     for (int i = 0; i < proxy.rowCount(); i++) {
0644         QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0645     }
0646 }
0647 
0648 void tst_KDescendantProxyModel::testExpandInsideCollapsed()
0649 {
0650     SimpleObjectModel *model = new SimpleObjectModel(this);
0651     model->insert(QModelIndex(), 0, QStringLiteral("Model0"));
0652     model->insert(QModelIndex(), 1, QStringLiteral("Model1"));
0653     model->insert(QModelIndex(), 2, QStringLiteral("Model2"));
0654 
0655     auto parentIndex = model->index(0, 0, QModelIndex());
0656     model->insert(parentIndex, 0, QStringLiteral("Model0-0"));
0657     auto childIndex = model->index(0, 0, parentIndex);
0658     model->insert(childIndex, 0, QStringLiteral("Model0-0-0"));
0659 
0660     QCOMPARE(model->rowCount(), 3);
0661 
0662     KDescendantsProxyModel proxy;
0663     proxy.setExpandsByDefault(false);
0664     proxy.setSourceModel(model);
0665     QCOMPARE(proxy.rowCount(), 3);
0666 
0667     proxy.expandSourceIndex(childIndex);
0668     QVERIFY(proxy.isSourceIndexExpanded(childIndex));
0669     QVERIFY(!proxy.isSourceIndexExpanded(parentIndex));
0670     QCOMPARE(proxy.rowCount(), 3);
0671     proxy.expandSourceIndex(parentIndex);
0672     QCOMPARE(proxy.rowCount(), 5);
0673 }
0674 
0675 void tst_KDescendantProxyModel::testEmptyModel()
0676 {
0677     SimpleObjectModel *model = new SimpleObjectModel(this, true);
0678     model->insert(QModelIndex(), 0, QStringLiteral("Row0"));
0679     model->insert(QModelIndex(), 0, QStringLiteral("Row1"));
0680     KDescendantsProxyModel proxy;
0681     proxy.setSourceModel(model);
0682     QCOMPARE(proxy.rowCount(), 0);
0683 }
0684 
0685 void tst_KDescendantProxyModel::testEmptyChild()
0686 {
0687     SimpleObjectModel *model = new SimpleObjectModel(this, true);
0688     model->insert(QModelIndex(), 0, QStringLiteral("Row0"));
0689     auto parentIndex = model->index(0, 0, QModelIndex());
0690     model->insert(parentIndex, 0, QStringLiteral("Row0-0"));
0691     model->insert(parentIndex, 0, QStringLiteral("Row0-1"));
0692     model->insert(QModelIndex(), 0, QStringLiteral("Row1"));
0693 
0694     // simulate that the row count for the root node is known because it was listed
0695     model->getRootNode()->knownChildren = 2;
0696 
0697     KDescendantsProxyModel proxy;
0698     proxy.setSourceModel(model);
0699     proxy.setExpandsByDefault(false);
0700     QCOMPARE(proxy.rowCount(), 2);
0701 
0702     // remove the row that has children but a rowCount of 0
0703     model->removeRows(0, 1, QModelIndex());
0704     QCOMPARE(proxy.rowCount(), 1);
0705 }
0706 
0707 QTEST_MAIN(tst_KDescendantProxyModel)
0708 
0709 #include "kdescendantsproxymodeltest.moc"