File indexing completed on 2024-04-21 15:01:22

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     if (row >= parent->children.count()) {
0177         return false;
0178     }
0179 
0180     beginRemoveRows(index, row, row);
0181     Node *child = parent->children.takeAt(row);
0182     delete child;
0183     endRemoveRows();
0184 
0185     return true;
0186 }
0187 
0188 bool SimpleObjectModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationRow)
0189 {
0190     Node *sourceNode = static_cast<Node *>(sourceParent.internalPointer());
0191     if (!sourceNode) {
0192         sourceNode = m_root;
0193     }
0194     Node *destinationNode = static_cast<Node *>(destinationParent.internalPointer());
0195     if (!destinationNode) {
0196         destinationNode = m_root;
0197     }
0198 
0199     const int sourceLast = sourceRow + count - 1;
0200 
0201     if (sourceNode != destinationNode) {
0202         if (count <= 0 || sourceRow < 0 || sourceRow >= sourceNode->children.count() || destinationRow < 0
0203             || destinationRow > destinationNode->children.count()) {
0204             return false;
0205         }
0206 
0207         if (!beginMoveRows(sourceParent, sourceRow, sourceLast, destinationParent, destinationRow)) {
0208             return false;
0209         }
0210 
0211         Node *child = sourceNode->children.takeAt(sourceRow);
0212         child->parent = destinationNode;
0213         destinationNode->children.insert(destinationRow, child);
0214 
0215         endMoveRows();
0216         return true;
0217     }
0218 
0219     if (count <= 0 || sourceRow == destinationRow || sourceRow < 0 || sourceRow >= destinationNode->children.count() || destinationRow < 0
0220         || destinationRow > destinationNode->children.count() || count - destinationRow > destinationNode->children.count() - sourceRow) {
0221         return false;
0222     }
0223 
0224     // beginMoveRows wants indexes before the source rows are removed from the old order
0225     if (!beginMoveRows(sourceParent, sourceRow, sourceLast, destinationParent, destinationRow)) {
0226         return false;
0227     }
0228 
0229     if (sourceRow < destinationRow) {
0230         for (int i = count - 1; i >= 0; --i) {
0231             destinationNode->children.move(sourceRow + i, destinationRow - count + i);
0232         }
0233     } else {
0234         for (int i = 0; i < count; ++i) {
0235             destinationNode->children.move(sourceRow + i, destinationRow + i);
0236         }
0237     }
0238 
0239     endMoveRows();
0240     return true;
0241 }
0242 
0243 class tst_KDescendantProxyModel : public QObject
0244 {
0245     Q_OBJECT
0246     QAbstractItemModel *createTree(const QString &prefix)
0247     {
0248         /*
0249          * |- parent1
0250          * |  |- child1
0251          * |  `- child2
0252          * `- parent2
0253          *    |- child1
0254          *    `- child2
0255          */
0256         QStandardItemModel *model = new QStandardItemModel(this);
0257         for (int i = 0; i < 2; i++) {
0258             QStandardItem *item = new QStandardItem();
0259             item->setData(QString(prefix + QString::number(i)), Qt::DisplayRole);
0260             for (int j = 0; j < 2; j++) {
0261                 QStandardItem *child = new QStandardItem();
0262                 child->setData(QString(prefix + QString::number(i) + "-" + QString::number(j)), Qt::DisplayRole);
0263                 item->appendRow(child);
0264             }
0265             model->appendRow(item);
0266         }
0267         return model;
0268     }
0269 
0270 private Q_SLOTS:
0271     void testResetModelContent();
0272     void testChangeSeparator();
0273     void testChangeInvisibleSeparator();
0274     void testRemoveSeparator();
0275 
0276     void testResetCollapsedModelContent();
0277     void testInsertInCollapsedModel();
0278     void testRemoveInCollapsedModel();
0279     void testMoveInsideCollapsed();
0280     void testExpandInsideCollapsed();
0281     void testEmptyModel();
0282     void testEmptyChild();
0283 };
0284 
0285 /// Tests that replacing the source model results in data getting changed
0286 void tst_KDescendantProxyModel::testResetModelContent()
0287 {
0288     auto model1 = createTree("FirstModel");
0289     KDescendantsProxyModel proxy;
0290     proxy.setSourceModel(model1);
0291     QCOMPARE(proxy.rowCount(), 6);
0292 
0293     {
0294         QStringList results = QStringList() << "FirstModel0"
0295                                             << "FirstModel0-0"
0296                                             << "FirstModel0-1"
0297                                             << "FirstModel1"
0298                                             << "FirstModel1-0"
0299                                             << "FirstModel1-1";
0300         QCOMPARE(proxy.rowCount(), results.count());
0301         for (int i = 0; i < proxy.rowCount(); i++) {
0302             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0303         }
0304     }
0305     auto model2 = createTree("SecondModel");
0306     {
0307         proxy.setSourceModel(model2);
0308         QStringList results = QStringList() << "SecondModel0"
0309                                             << "SecondModel0-0"
0310                                             << "SecondModel0-1"
0311                                             << "SecondModel1"
0312                                             << "SecondModel1-0"
0313                                             << "SecondModel1-1";
0314         QCOMPARE(proxy.rowCount(), results.count());
0315         for (int i = 0; i < proxy.rowCount(); i++) {
0316             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0317         }
0318     }
0319 
0320     delete model2;
0321     delete model1;
0322 }
0323 
0324 /// tests that change separator works, as well as emits the relevant data changed signals
0325 void tst_KDescendantProxyModel::testChangeSeparator()
0326 {
0327     auto model1 = createTree("FirstModel");
0328     KDescendantsProxyModel proxy;
0329     proxy.setSourceModel(model1);
0330     proxy.setDisplayAncestorData(true);
0331     QSignalSpy dataChangedSpy(&proxy, &QAbstractItemModel::dataChanged);
0332     QCOMPARE(proxy.rowCount(), 6);
0333     {
0334         QStringList results = QStringList() << "FirstModel0"
0335                                             << "FirstModel0 / FirstModel0-0"
0336                                             << "FirstModel0 / FirstModel0-1"
0337                                             << "FirstModel1"
0338                                             << "FirstModel1 / FirstModel1-0"
0339                                             << "FirstModel1 / FirstModel1-1";
0340         QCOMPARE(proxy.rowCount(), results.count());
0341         for (int i = 0; i < proxy.rowCount(); i++) {
0342             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0343         }
0344     }
0345     proxy.setAncestorSeparator("LOL");
0346     QCOMPARE(dataChangedSpy.count(), 1);
0347     {
0348         QStringList results = QStringList() << "FirstModel0"
0349                                             << "FirstModel0LOLFirstModel0-0"
0350                                             << "FirstModel0LOLFirstModel0-1"
0351                                             << "FirstModel1"
0352                                             << "FirstModel1LOLFirstModel1-0"
0353                                             << "FirstModel1LOLFirstModel1-1";
0354         QCOMPARE(proxy.rowCount(), results.count());
0355         for (int i = 0; i < proxy.rowCount(); i++) {
0356             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0357         }
0358     }
0359 
0360     delete model1;
0361 }
0362 
0363 /// tests that change separator that is not shown does not change the content and does not
0364 /// emit data changed signals, since the data isn't changed
0365 void tst_KDescendantProxyModel::testChangeInvisibleSeparator()
0366 {
0367     auto model1 = createTree("FirstModel");
0368     KDescendantsProxyModel proxy;
0369     proxy.setSourceModel(model1);
0370     QSignalSpy dataChangedSpy(&proxy, &QAbstractItemModel::dataChanged);
0371     QCOMPARE(proxy.rowCount(), 6);
0372     {
0373         QStringList results = QStringList() << "FirstModel0"
0374                                             << "FirstModel0-0"
0375                                             << "FirstModel0-1"
0376                                             << "FirstModel1"
0377                                             << "FirstModel1-0"
0378                                             << "FirstModel1-1";
0379         QCOMPARE(proxy.rowCount(), results.count());
0380         for (int i = 0; i < proxy.rowCount(); i++) {
0381             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0382         }
0383     }
0384     proxy.setAncestorSeparator("LOL");
0385     QCOMPARE(dataChangedSpy.count(), 0);
0386     {
0387         QStringList results = QStringList() << "FirstModel0"
0388                                             << "FirstModel0-0"
0389                                             << "FirstModel0-1"
0390                                             << "FirstModel1"
0391                                             << "FirstModel1-0"
0392                                             << "FirstModel1-1";
0393         QCOMPARE(proxy.rowCount(), results.count());
0394         for (int i = 0; i < proxy.rowCount(); i++) {
0395             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0396         }
0397     }
0398 
0399     delete model1;
0400 }
0401 
0402 /// tests that data is properly updated when separator is removed/hidden
0403 /// and data changed signal is emitted
0404 void tst_KDescendantProxyModel::testRemoveSeparator()
0405 {
0406     auto model1 = createTree("FirstModel");
0407     KDescendantsProxyModel proxy;
0408     proxy.setSourceModel(model1);
0409     QSignalSpy dataChangedSpy(&proxy, &QAbstractItemModel::dataChanged);
0410     proxy.setDisplayAncestorData(true);
0411     QCOMPARE(dataChangedSpy.count(), 1);
0412     dataChangedSpy.clear();
0413     QCOMPARE(proxy.rowCount(), 6);
0414     {
0415         QStringList results = QStringList() << "FirstModel0"
0416                                             << "FirstModel0 / FirstModel0-0"
0417                                             << "FirstModel0 / FirstModel0-1"
0418                                             << "FirstModel1"
0419                                             << "FirstModel1 / FirstModel1-0"
0420                                             << "FirstModel1 / FirstModel1-1";
0421         QCOMPARE(proxy.rowCount(), results.count());
0422         for (int i = 0; i < proxy.rowCount(); i++) {
0423             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0424         }
0425     }
0426     proxy.setDisplayAncestorData(false);
0427     QCOMPARE(dataChangedSpy.count(), 1);
0428     {
0429         QStringList results = QStringList() << "FirstModel0"
0430                                             << "FirstModel0-0"
0431                                             << "FirstModel0-1"
0432                                             << "FirstModel1"
0433                                             << "FirstModel1-0"
0434                                             << "FirstModel1-1";
0435         QCOMPARE(proxy.rowCount(), results.count());
0436         for (int i = 0; i < proxy.rowCount(); i++) {
0437             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0438         }
0439     }
0440 }
0441 
0442 void tst_KDescendantProxyModel::testResetCollapsedModelContent()
0443 {
0444     auto model1 = createTree("FirstModel");
0445     KDescendantsProxyModel proxy;
0446     proxy.setExpandsByDefault(false);
0447     proxy.setSourceModel(model1);
0448     QCOMPARE(proxy.rowCount(), 2);
0449 
0450     {
0451         QStringList results = QStringList() << "FirstModel0"
0452                                             << "FirstModel1";
0453         QCOMPARE(proxy.rowCount(), results.count());
0454         for (int i = 0; i < proxy.rowCount(); i++) {
0455             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0456         }
0457     }
0458     {
0459         QModelIndex idx = model1->index(0, 0);
0460         proxy.expandSourceIndex(idx);
0461         QStringList results = QStringList() << "FirstModel0"
0462                                             << "FirstModel0-0"
0463                                             << "FirstModel0-1"
0464                                             << "FirstModel1";
0465         QCOMPARE(proxy.rowCount(), results.count());
0466         for (int i = 0; i < proxy.rowCount(); i++) {
0467             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0468         }
0469     }
0470     {
0471         QModelIndex idx = model1->index(1, 0);
0472         proxy.expandSourceIndex(idx);
0473         QStringList results = QStringList() << "FirstModel0"
0474                                             << "FirstModel0-0"
0475                                             << "FirstModel0-1"
0476                                             << "FirstModel1"
0477                                             << "FirstModel1-0"
0478                                             << "FirstModel1-1";
0479         QCOMPARE(proxy.rowCount(), results.count());
0480         for (int i = 0; i < proxy.rowCount(); i++) {
0481             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0482         }
0483     }
0484 
0485     auto model2 = createTree("SecondModel");
0486     {
0487         proxy.setSourceModel(model2);
0488         QModelIndex idx = model2->index(0, 0);
0489         proxy.expandSourceIndex(idx);
0490         idx = model2->index(1, 0);
0491         proxy.expandSourceIndex(idx);
0492         QStringList results = QStringList() << "SecondModel0"
0493                                             << "SecondModel0-0"
0494                                             << "SecondModel0-1"
0495                                             << "SecondModel1"
0496                                             << "SecondModel1-0"
0497                                             << "SecondModel1-1";
0498         QCOMPARE(proxy.rowCount(), results.count());
0499         for (int i = 0; i < proxy.rowCount(); i++) {
0500             QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0501         }
0502     }
0503 
0504     delete model2;
0505     delete model1;
0506 }
0507 
0508 void tst_KDescendantProxyModel::testInsertInCollapsedModel()
0509 {
0510     QStandardItemModel *model1 = static_cast<QStandardItemModel *>(createTree("Model"));
0511     KDescendantsProxyModel proxy;
0512     proxy.setExpandsByDefault(false);
0513     proxy.setSourceModel(model1);
0514     QCOMPARE(proxy.rowCount(), 2);
0515 
0516     QSignalSpy insertSpy(&proxy, &QAbstractItemModel::rowsInserted);
0517     QCOMPARE(insertSpy.count(), 0);
0518 
0519     QStandardItem *parent = model1->item(0, 0);
0520     QVERIFY(parent);
0521 
0522     QStandardItem *child = new QStandardItem();
0523     child->setData(QString(QStringLiteral("Model") + QString::number(0) + "-" + QString::number(2)), Qt::DisplayRole);
0524     parent->appendRow(child);
0525 
0526     // Adding a child to the collapsed parent doesn't have an effect to the proxy
0527     QCOMPARE(proxy.rowCount(), 2);
0528     QCOMPARE(insertSpy.count(), 0);
0529 
0530     // If we expand everything inserted should be here
0531     QModelIndex idx = model1->index(0, 0);
0532     proxy.expandSourceIndex(idx);
0533 
0534     QCOMPARE(proxy.rowCount(), 5);
0535     QCOMPARE(insertSpy.count(), 1);
0536 
0537     QCOMPARE(proxy.index(3, 0).data(Qt::DisplayRole).toString(), QStringLiteral("Model0-2"));
0538 
0539     // Add another child to the expanded node, now the proxy is affected immediately
0540     child = new QStandardItem();
0541     child->setData(QString(QStringLiteral("Model") + QString::number(0) + "-" + QString::number(3)), Qt::DisplayRole);
0542     parent->appendRow(child);
0543 
0544     QCOMPARE(proxy.rowCount(), 6);
0545     QCOMPARE(insertSpy.count(), 2);
0546 }
0547 
0548 void tst_KDescendantProxyModel::testRemoveInCollapsedModel()
0549 {
0550     QStandardItemModel *model1 = static_cast<QStandardItemModel *>(createTree("Model"));
0551     KDescendantsProxyModel proxy;
0552     proxy.setExpandsByDefault(false);
0553     proxy.setSourceModel(model1);
0554     QCOMPARE(proxy.rowCount(), 2);
0555 
0556     QSignalSpy removeSpy(&proxy, &QAbstractItemModel::rowsRemoved);
0557     QCOMPARE(removeSpy.count(), 0);
0558 
0559     QStandardItem *parent = model1->item(0, 0);
0560     QVERIFY(parent);
0561 
0562     parent->removeRow(0);
0563 
0564     // Adding a child to the collapsed parent doesn't have an effect to the proxy
0565     QCOMPARE(proxy.rowCount(), 2);
0566     QCOMPARE(removeSpy.count(), 0);
0567 
0568     // If we expand everything inserted should be here
0569     QModelIndex idx = model1->index(0, 0);
0570     proxy.expandSourceIndex(idx);
0571 
0572     QCOMPARE(proxy.rowCount(), 3);
0573 
0574     QCOMPARE(proxy.index(1, 0).data(Qt::DisplayRole).toString(), QStringLiteral("Model0-1"));
0575     parent->removeRow(0);
0576 
0577     QCOMPARE(proxy.rowCount(), 2);
0578     QCOMPARE(removeSpy.count(), 1);
0579 
0580     idx = model1->index(1, 0);
0581     proxy.expandSourceIndex(idx);
0582     QCOMPARE(proxy.rowCount(), 4);
0583 }
0584 
0585 void tst_KDescendantProxyModel::testMoveInsideCollapsed()
0586 {
0587     SimpleObjectModel *model = new SimpleObjectModel(this);
0588     model->insert(QModelIndex(), 0, QStringLiteral("Model0"));
0589     model->insert(QModelIndex(), 1, QStringLiteral("Model1"));
0590     model->insert(QModelIndex(), 2, QStringLiteral("Model2"));
0591 
0592     model->insert(model->index(0, 0, QModelIndex()), 0, QStringLiteral("Model0-0"));
0593     model->insert(model->index(1, 0, QModelIndex()), 0, QStringLiteral("Model1-0"));
0594 
0595     QCOMPARE(model->rowCount(), 3);
0596 
0597     KDescendantsProxyModel proxy;
0598     proxy.setExpandsByDefault(false);
0599     proxy.setSourceModel(model);
0600     QCOMPARE(proxy.rowCount(), 3);
0601 
0602     QSignalSpy removeSpy(&proxy, &QAbstractItemModel::rowsRemoved);
0603     QCOMPARE(removeSpy.count(), 0);
0604     QSignalSpy insertSpy(&proxy, &QAbstractItemModel::rowsInserted);
0605     QCOMPARE(insertSpy.count(), 0);
0606 
0607     model->moveRows(QModelIndex(), 2, 1, model->index(0, 0, QModelIndex()), 1);
0608 
0609     QCOMPARE(removeSpy.count(), 1);
0610 
0611     QVERIFY(!removeSpy.first()[0].value<QModelIndex>().isValid());
0612     QCOMPARE(removeSpy.first()[1].toInt(), 2);
0613     QCOMPARE(removeSpy.first()[2].toInt(), 2);
0614 
0615     QCOMPARE(model->rowCount(), 2);
0616     QCOMPARE(proxy.rowCount(), 2);
0617 
0618     model->moveRows(model->index(0, 0, QModelIndex()), 0, 1, QModelIndex(), 1);
0619     QCOMPARE(insertSpy.count(), 1);
0620     QCOMPARE(model->rowCount(), 3);
0621     QCOMPARE(proxy.rowCount(), 3);
0622 
0623     QVERIFY(!insertSpy.first()[0].value<QModelIndex>().isValid());
0624     QCOMPARE(insertSpy.first()[1].toInt(), 1);
0625     QCOMPARE(insertSpy.first()[2].toInt(), 1);
0626 
0627     QModelIndex idx = model->index(0, 0);
0628     proxy.expandSourceIndex(idx);
0629     idx = model->index(1, 0);
0630     proxy.expandSourceIndex(idx);
0631     idx = model->index(2, 0);
0632     proxy.expandSourceIndex(idx);
0633     QStringList results = QStringList() << "Model0"
0634                                         << "Model2"
0635                                         << "Model0-0"
0636                                         << "Model1"
0637                                         << "Model1-0";
0638     QCOMPARE(proxy.rowCount(), results.count());
0639     for (int i = 0; i < proxy.rowCount(); i++) {
0640         QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), results[i]);
0641     }
0642 }
0643 
0644 void tst_KDescendantProxyModel::testExpandInsideCollapsed()
0645 {
0646     SimpleObjectModel *model = new SimpleObjectModel(this);
0647     model->insert(QModelIndex(), 0, QStringLiteral("Model0"));
0648     model->insert(QModelIndex(), 1, QStringLiteral("Model1"));
0649     model->insert(QModelIndex(), 2, QStringLiteral("Model2"));
0650 
0651     auto parentIndex = model->index(0, 0, QModelIndex());
0652     model->insert(parentIndex, 0, QStringLiteral("Model0-0"));
0653     auto childIndex = model->index(0, 0, parentIndex);
0654     model->insert(childIndex, 0, QStringLiteral("Model0-0-0"));
0655 
0656     QCOMPARE(model->rowCount(), 3);
0657 
0658     KDescendantsProxyModel proxy;
0659     proxy.setExpandsByDefault(false);
0660     proxy.setSourceModel(model);
0661     QCOMPARE(proxy.rowCount(), 3);
0662 
0663     proxy.expandSourceIndex(childIndex);
0664     QVERIFY(proxy.isSourceIndexExpanded(childIndex));
0665     QVERIFY(!proxy.isSourceIndexExpanded(parentIndex));
0666     QCOMPARE(proxy.rowCount(), 3);
0667     proxy.expandSourceIndex(parentIndex);
0668     QCOMPARE(proxy.rowCount(), 5);
0669 }
0670 
0671 void tst_KDescendantProxyModel::testEmptyModel()
0672 {
0673     SimpleObjectModel *model = new SimpleObjectModel(this, true);
0674     model->insert(QModelIndex(), 0, QStringLiteral("Row0"));
0675     model->insert(QModelIndex(), 0, QStringLiteral("Row1"));
0676     KDescendantsProxyModel proxy;
0677     proxy.setSourceModel(model);
0678     QCOMPARE(proxy.rowCount(), 0);
0679 }
0680 
0681 void tst_KDescendantProxyModel::testEmptyChild()
0682 {
0683     SimpleObjectModel *model = new SimpleObjectModel(this, true);
0684     model->insert(QModelIndex(), 0, QStringLiteral("Row0"));
0685     auto parentIndex = model->index(0, 0, QModelIndex());
0686     model->insert(parentIndex, 0, QStringLiteral("Row0-0"));
0687     model->insert(parentIndex, 0, QStringLiteral("Row0-1"));
0688     model->insert(QModelIndex(), 0, QStringLiteral("Row1"));
0689 
0690     // simulate that the row count for the root node is known because it was listed
0691     model->getRootNode()->knownChildren = 2;
0692 
0693     KDescendantsProxyModel proxy;
0694     proxy.setSourceModel(model);
0695     proxy.setExpandsByDefault(false);
0696     QCOMPARE(proxy.rowCount(), 2);
0697 
0698     // remove the row that has children but a rowCount of 0
0699     model->removeRows(0, 1, QModelIndex());
0700     QCOMPARE(proxy.rowCount(), 1);
0701 }
0702 
0703 QTEST_MAIN(tst_KDescendantProxyModel)
0704 
0705 #include "kdescendantsproxymodeltest.moc"