File indexing completed on 2024-04-14 14:25:34

0001 #include <QIdentityProxyModel>
0002 #include <QItemSelectionModel>
0003 #include <QSignalSpy>
0004 #include <QSortFilterProxyModel>
0005 #include <QStandardItemModel>
0006 #include <QTest>
0007 
0008 #include "test_model_helpers.h"
0009 #include <kconcatenaterowsproxymodel.h>
0010 using namespace TestModelHelpers;
0011 
0012 Q_DECLARE_METATYPE(QModelIndex)
0013 
0014 class tst_KConcatenateRowsProxyModel : public QObject
0015 {
0016     Q_OBJECT
0017 
0018 private Q_SLOTS:
0019 
0020     void initTestCase()
0021     {
0022     }
0023 
0024     void init()
0025     {
0026         // Prepare some source models to use later on
0027         mod.clear();
0028         mod.insertRow(0, makeStandardItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")));
0029         mod.setHorizontalHeaderLabels(QStringList() << QStringLiteral("H1") << QStringLiteral("H2") << QStringLiteral("H3"));
0030         mod.setVerticalHeaderLabels(QStringList() << QStringLiteral("One"));
0031 
0032         mod2.clear();
0033         mod2.insertRow(0, makeStandardItems(QStringList() << QStringLiteral("D") << QStringLiteral("E") << QStringLiteral("F")));
0034         mod2.setHorizontalHeaderLabels(QStringList() << QStringLiteral("H1") << QStringLiteral("H2") << QStringLiteral("H3"));
0035         mod2.setVerticalHeaderLabels(QStringList() << QStringLiteral("Two"));
0036     }
0037 
0038     void shouldAggregateTwoModelsCorrectly()
0039     {
0040         // Given a combining proxy
0041         KConcatenateRowsProxyModel pm;
0042 
0043         // When adding two source models
0044         pm.addSourceModel(&mod);
0045         pm.addSourceModel(&mod2);
0046 
0047         // Then the proxy should show 2 rows
0048         QCOMPARE(pm.rowCount(), 2);
0049         QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC"));
0050         QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("DEF"));
0051 
0052         // ... and correct headers
0053         QCOMPARE(pm.headerData(0, Qt::Horizontal).toString(), QStringLiteral("H1"));
0054         QCOMPARE(pm.headerData(1, Qt::Horizontal).toString(), QStringLiteral("H2"));
0055         QCOMPARE(pm.headerData(2, Qt::Horizontal).toString(), QStringLiteral("H3"));
0056         QCOMPARE(pm.headerData(0, Qt::Vertical).toString(), QStringLiteral("One"));
0057         QCOMPARE(pm.headerData(1, Qt::Vertical).toString(), QStringLiteral("Two"));
0058 
0059         QVERIFY(!pm.canFetchMore(QModelIndex()));
0060     }
0061 
0062     void shouldAggregateThenRemoveTwoEmptyModelsCorrectly()
0063     {
0064         // Given a combining proxy
0065         KConcatenateRowsProxyModel pm;
0066 
0067         // When adding two empty models
0068         QSignalSpy rowATBISpy(&pm, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)));
0069         QSignalSpy rowInsertedSpy(&pm, SIGNAL(rowsInserted(QModelIndex, int, int)));
0070         QSignalSpy rowATBRSpy(&pm, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)));
0071         QSignalSpy rowRemovedSpy(&pm, SIGNAL(rowsRemoved(QModelIndex, int, int)));
0072         QIdentityProxyModel i1;
0073         QIdentityProxyModel i2;
0074         pm.addSourceModel(&i1);
0075         pm.addSourceModel(&i2);
0076 
0077         // Then the proxy should still be empty (and no signals emitted)
0078         QCOMPARE(pm.rowCount(), 0);
0079         QCOMPARE(pm.columnCount(), 0);
0080         QCOMPARE(rowATBISpy.count(), 0);
0081         QCOMPARE(rowInsertedSpy.count(), 0);
0082 
0083         // When removing the empty models
0084         pm.removeSourceModel(&i1);
0085         pm.removeSourceModel(&i2);
0086 
0087         // Then the proxy should still be empty (and no signals emitted)
0088         QCOMPARE(pm.rowCount(), 0);
0089         QCOMPARE(pm.columnCount(), 0);
0090         QCOMPARE(rowATBRSpy.count(), 0);
0091         QCOMPARE(rowRemovedSpy.count(), 0);
0092     }
0093 
0094     void shouldAggregateTwoEmptyModelsWhichThenGetFilled()
0095     {
0096         // Given a combining proxy
0097         KConcatenateRowsProxyModel pm;
0098 
0099         // When adding two empty models
0100         QIdentityProxyModel i1;
0101         QIdentityProxyModel i2;
0102         pm.addSourceModel(&i1);
0103         pm.addSourceModel(&i2);
0104 
0105         QCOMPARE(pm.rowCount(), 0);
0106         QCOMPARE(pm.columnCount(), 0);
0107 
0108         i1.setSourceModel(&mod);
0109         i2.setSourceModel(&mod2);
0110 
0111         // Then the proxy should show 2 rows
0112         QCOMPARE(pm.rowCount(), 2);
0113         QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC"));
0114         QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("DEF"));
0115 
0116         // ... and correct headers
0117         QCOMPARE(pm.headerData(0, Qt::Horizontal).toString(), QStringLiteral("H1"));
0118         QCOMPARE(pm.headerData(1, Qt::Horizontal).toString(), QStringLiteral("H2"));
0119         QCOMPARE(pm.headerData(2, Qt::Horizontal).toString(), QStringLiteral("H3"));
0120         QCOMPARE(pm.headerData(0, Qt::Vertical).toString(), QStringLiteral("One"));
0121         QCOMPARE(pm.headerData(1, Qt::Vertical).toString(), QStringLiteral("Two"));
0122 
0123         QVERIFY(!pm.canFetchMore(QModelIndex()));
0124     }
0125 
0126     void shouldHandleDataChanged()
0127     {
0128         // Given two models combined
0129         KConcatenateRowsProxyModel pm;
0130         pm.addSourceModel(&mod);
0131         pm.addSourceModel(&mod2);
0132         QSignalSpy dataChangedSpy(&pm, SIGNAL(dataChanged(QModelIndex, QModelIndex)));
0133 
0134         // When a cell in a source model changes
0135         mod.item(0, 0)->setData("a", Qt::EditRole);
0136 
0137         // Then the change should be notified to the proxy
0138         QCOMPARE(dataChangedSpy.count(), 1);
0139         QCOMPARE(dataChangedSpy.at(0).at(0).toModelIndex(), pm.index(0, 0));
0140         QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("aBC"));
0141 
0142         // Same test with the other model
0143         mod2.item(0, 2)->setData("f", Qt::EditRole);
0144 
0145         QCOMPARE(dataChangedSpy.count(), 2);
0146         QCOMPARE(dataChangedSpy.at(1).at(0).toModelIndex(), pm.index(1, 2));
0147         QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("DEf"));
0148     }
0149 
0150     void shouldHandleSetData()
0151     {
0152         // Given two models combined
0153         KConcatenateRowsProxyModel pm;
0154         pm.addSourceModel(&mod);
0155         pm.addSourceModel(&mod2);
0156         QSignalSpy dataChangedSpy(&pm, SIGNAL(dataChanged(QModelIndex, QModelIndex)));
0157 
0158         // When changing a cell using setData
0159         pm.setData(pm.index(0, 0), "a", Qt::EditRole);
0160 
0161         // Then the change should be notified to the proxy
0162         QCOMPARE(dataChangedSpy.count(), 1);
0163         QCOMPARE(dataChangedSpy.at(0).at(0).toModelIndex(), pm.index(0, 0));
0164         QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("aBC"));
0165 
0166         // Same test with the other model
0167         pm.setData(pm.index(1, 2), "f", Qt::EditRole);
0168 
0169         QCOMPARE(dataChangedSpy.count(), 2);
0170         QCOMPARE(dataChangedSpy.at(1).at(0).toModelIndex(), pm.index(1, 2));
0171         QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("DEf"));
0172     }
0173 
0174     void shouldHandleRowInsertionAndRemoval()
0175     {
0176         // Given two models combined
0177         KConcatenateRowsProxyModel pm;
0178         pm.addSourceModel(&mod);
0179         pm.addSourceModel(&mod2);
0180         QSignalSpy rowATBISpy(&pm, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)));
0181         QSignalSpy rowInsertedSpy(&pm, SIGNAL(rowsInserted(QModelIndex, int, int)));
0182 
0183         // When a source model inserts a new row
0184         QList<QStandardItem *> row;
0185         row.append(new QStandardItem(QStringLiteral("1")));
0186         row.append(new QStandardItem(QStringLiteral("2")));
0187         row.append(new QStandardItem(QStringLiteral("3")));
0188         mod2.insertRow(0, row);
0189 
0190         // Then the proxy should notify its users and show changes
0191         QCOMPARE(rowSpyToText(rowATBISpy), QStringLiteral("1,1"));
0192         QCOMPARE(rowSpyToText(rowInsertedSpy), QStringLiteral("1,1"));
0193         QCOMPARE(pm.rowCount(), 3);
0194         QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC"));
0195         QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("123"));
0196         QCOMPARE(extractRowTexts(&pm, 2), QStringLiteral("DEF"));
0197 
0198         // When removing that row
0199         QSignalSpy rowATBRSpy(&pm, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)));
0200         QSignalSpy rowRemovedSpy(&pm, SIGNAL(rowsRemoved(QModelIndex, int, int)));
0201         mod2.removeRow(0);
0202 
0203         // Then the proxy should notify its users and show changes
0204         QCOMPARE(rowATBRSpy.count(), 1);
0205         QCOMPARE(rowATBRSpy.at(0).at(1).toInt(), 1);
0206         QCOMPARE(rowATBRSpy.at(0).at(2).toInt(), 1);
0207         QCOMPARE(rowRemovedSpy.count(), 1);
0208         QCOMPARE(rowRemovedSpy.at(0).at(1).toInt(), 1);
0209         QCOMPARE(rowRemovedSpy.at(0).at(2).toInt(), 1);
0210         QCOMPARE(pm.rowCount(), 2);
0211         QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC"));
0212         QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("DEF"));
0213 
0214         // When removing the last row from mod2
0215         rowATBRSpy.clear();
0216         rowRemovedSpy.clear();
0217         mod2.removeRow(0);
0218 
0219         // Then the proxy should notify its users and show changes
0220         QCOMPARE(rowATBRSpy.count(), 1);
0221         QCOMPARE(rowATBRSpy.at(0).at(1).toInt(), 1);
0222         QCOMPARE(rowATBRSpy.at(0).at(2).toInt(), 1);
0223         QCOMPARE(rowRemovedSpy.count(), 1);
0224         QCOMPARE(rowRemovedSpy.at(0).at(1).toInt(), 1);
0225         QCOMPARE(rowRemovedSpy.at(0).at(2).toInt(), 1);
0226         QCOMPARE(pm.rowCount(), 1);
0227         QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC"));
0228     }
0229 
0230     void shouldAggregateAnotherModelThenRemoveModels()
0231     {
0232         // Given two models combined, and a third model
0233         KConcatenateRowsProxyModel pm;
0234         pm.addSourceModel(&mod);
0235         pm.addSourceModel(&mod2);
0236 
0237         QStandardItemModel mod3;
0238         mod3.appendRow(makeStandardItems(QStringList() << QStringLiteral("1") << QStringLiteral("2") << QStringLiteral("3")));
0239         mod3.appendRow(makeStandardItems(QStringList() << QStringLiteral("4") << QStringLiteral("5") << QStringLiteral("6")));
0240 
0241         QSignalSpy rowATBISpy(&pm, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)));
0242         QSignalSpy rowInsertedSpy(&pm, SIGNAL(rowsInserted(QModelIndex, int, int)));
0243 
0244         // When adding the new source model
0245         pm.addSourceModel(&mod3);
0246 
0247         // Then the proxy should notify its users about the two rows inserted
0248         QCOMPARE(rowSpyToText(rowATBISpy), QStringLiteral("2,3"));
0249         QCOMPARE(rowSpyToText(rowInsertedSpy), QStringLiteral("2,3"));
0250         QCOMPARE(pm.rowCount(), 4);
0251         QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC"));
0252         QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("DEF"));
0253         QCOMPARE(extractRowTexts(&pm, 2), QStringLiteral("123"));
0254         QCOMPARE(extractRowTexts(&pm, 3), QStringLiteral("456"));
0255 
0256         // When removing that source model again
0257         QSignalSpy rowATBRSpy(&pm, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)));
0258         QSignalSpy rowRemovedSpy(&pm, SIGNAL(rowsRemoved(QModelIndex, int, int)));
0259         pm.removeSourceModel(&mod3);
0260 
0261         // Then the proxy should notify its users about the row removed
0262         QCOMPARE(rowATBRSpy.count(), 1);
0263         QCOMPARE(rowATBRSpy.at(0).at(1).toInt(), 2);
0264         QCOMPARE(rowATBRSpy.at(0).at(2).toInt(), 3);
0265         QCOMPARE(rowRemovedSpy.count(), 1);
0266         QCOMPARE(rowRemovedSpy.at(0).at(1).toInt(), 2);
0267         QCOMPARE(rowRemovedSpy.at(0).at(2).toInt(), 3);
0268         QCOMPARE(pm.rowCount(), 2);
0269         QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC"));
0270         QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("DEF"));
0271 
0272         // When removing model 2
0273         rowATBRSpy.clear();
0274         rowRemovedSpy.clear();
0275         pm.removeSourceModel(&mod2);
0276         QCOMPARE(rowATBRSpy.count(), 1);
0277         QCOMPARE(rowATBRSpy.at(0).at(1).toInt(), 1);
0278         QCOMPARE(rowATBRSpy.at(0).at(2).toInt(), 1);
0279         QCOMPARE(rowRemovedSpy.count(), 1);
0280         QCOMPARE(rowRemovedSpy.at(0).at(1).toInt(), 1);
0281         QCOMPARE(rowRemovedSpy.at(0).at(2).toInt(), 1);
0282         QCOMPARE(pm.rowCount(), 1);
0283         QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC"));
0284 
0285         // When removing model 1
0286         rowATBRSpy.clear();
0287         rowRemovedSpy.clear();
0288         pm.removeSourceModel(&mod);
0289         QCOMPARE(rowATBRSpy.count(), 1);
0290         QCOMPARE(rowATBRSpy.at(0).at(1).toInt(), 0);
0291         QCOMPARE(rowATBRSpy.at(0).at(2).toInt(), 0);
0292         QCOMPARE(rowRemovedSpy.count(), 1);
0293         QCOMPARE(rowRemovedSpy.at(0).at(1).toInt(), 0);
0294         QCOMPARE(rowRemovedSpy.at(0).at(2).toInt(), 0);
0295         QCOMPARE(pm.rowCount(), 0);
0296     }
0297 
0298     void shouldHandleColumnInsertionAndRemoval()
0299     {
0300         // Given two models combined
0301         KConcatenateRowsProxyModel pm;
0302         pm.addSourceModel(&mod);
0303         pm.addSourceModel(&mod2);
0304         QSignalSpy colATBISpy(&pm, SIGNAL(columnsAboutToBeInserted(QModelIndex, int, int)));
0305         QSignalSpy colInsertedSpy(&pm, SIGNAL(columnsInserted(QModelIndex, int, int)));
0306 
0307         // When the first source model inserts a new column
0308         QCOMPARE(mod.columnCount(), 3);
0309         mod.setColumnCount(4);
0310 
0311         // Then the proxy should notify its users and show changes
0312         QCOMPARE(rowSpyToText(colATBISpy), QStringLiteral("3,3"));
0313         QCOMPARE(rowSpyToText(colInsertedSpy), QStringLiteral("3,3"));
0314         QCOMPARE(pm.rowCount(), 2);
0315         QCOMPARE(pm.columnCount(), 4);
0316         QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC "));
0317         QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("DEF "));
0318     }
0319 
0320     void shouldPropagateLayoutChanged()
0321     {
0322         // Given two source models, the second one being a QSFPM
0323         KConcatenateRowsProxyModel pm;
0324         pm.addSourceModel(&mod);
0325 
0326         QStandardItemModel mod3;
0327         QList<QStandardItem *> row;
0328         row.append(new QStandardItem(QStringLiteral("1")));
0329         row.append(new QStandardItem(QStringLiteral("2")));
0330         row.append(new QStandardItem(QStringLiteral("3")));
0331         mod3.insertRow(0, row);
0332         row.clear();
0333         row.append(new QStandardItem(QStringLiteral("4")));
0334         row.append(new QStandardItem(QStringLiteral("5")));
0335         row.append(new QStandardItem(QStringLiteral("6")));
0336         mod3.appendRow(row);
0337 
0338         QSortFilterProxyModel qsfpm;
0339         qsfpm.setSourceModel(&mod3);
0340         pm.addSourceModel(&qsfpm);
0341 
0342         QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC"));
0343         QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("123"));
0344         QCOMPARE(extractRowTexts(&pm, 2), QStringLiteral("456"));
0345 
0346         // And a selection (row 1)
0347         QItemSelectionModel selection(&pm);
0348         selection.select(pm.index(1, 0), QItemSelectionModel::Select | QItemSelectionModel::Rows);
0349         const QModelIndexList lst = selection.selectedIndexes();
0350         QCOMPARE(lst.count(), 3);
0351         for (int col = 0; col < lst.count(); ++col) {
0352             QCOMPARE(lst.at(col).row(), 1);
0353             QCOMPARE(lst.at(col).column(), col);
0354         }
0355 
0356         QSignalSpy layoutATBCSpy(&pm, SIGNAL(layoutAboutToBeChanged()));
0357         QSignalSpy layoutChangedSpy(&pm, SIGNAL(layoutChanged()));
0358 
0359         // When changing the sorting in the QSFPM
0360         qsfpm.sort(0, Qt::DescendingOrder);
0361 
0362         // Then the proxy should emit the layoutChanged signals, and show re-sorted data
0363         QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC"));
0364         QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("456"));
0365         QCOMPARE(extractRowTexts(&pm, 2), QStringLiteral("123"));
0366         QCOMPARE(layoutATBCSpy.count(), 1);
0367         QCOMPARE(layoutChangedSpy.count(), 1);
0368 
0369         // And the selection should be updated accordingly (it became row 2)
0370         const QModelIndexList lstAfter = selection.selectedIndexes();
0371         QCOMPARE(lstAfter.count(), 3);
0372         for (int col = 0; col < lstAfter.count(); ++col) {
0373             QCOMPARE(lstAfter.at(col).row(), 2);
0374             QCOMPARE(lstAfter.at(col).column(), col);
0375         }
0376     }
0377 
0378     void shouldReactToModelReset()
0379     {
0380         // Given two source models, the second one being a QSFPM
0381         KConcatenateRowsProxyModel pm;
0382         pm.addSourceModel(&mod);
0383 
0384         QStandardItemModel mod3;
0385         QList<QStandardItem *> row;
0386         row.append(new QStandardItem(QStringLiteral("1")));
0387         row.append(new QStandardItem(QStringLiteral("2")));
0388         row.append(new QStandardItem(QStringLiteral("3")));
0389         mod3.insertRow(0, row);
0390         row.clear();
0391         row.append(new QStandardItem(QStringLiteral("4")));
0392         row.append(new QStandardItem(QStringLiteral("5")));
0393         row.append(new QStandardItem(QStringLiteral("6")));
0394         mod3.appendRow(row);
0395 
0396         QSortFilterProxyModel qsfpm;
0397         qsfpm.setSourceModel(&mod3);
0398         pm.addSourceModel(&qsfpm);
0399 
0400         QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC"));
0401         QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("123"));
0402         QCOMPARE(extractRowTexts(&pm, 2), QStringLiteral("456"));
0403         QSignalSpy rowATBRSpy(&pm, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)));
0404         QSignalSpy rowRemovedSpy(&pm, SIGNAL(rowsRemoved(QModelIndex, int, int)));
0405         QSignalSpy rowATBISpy(&pm, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)));
0406         QSignalSpy rowInsertedSpy(&pm, SIGNAL(rowsInserted(QModelIndex, int, int)));
0407 
0408         // When changing the source model of the QSFPM
0409         qsfpm.setSourceModel(&mod2);
0410 
0411         // Then the proxy should emit the row removed/inserted signals, and show the new data
0412         QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC"));
0413         QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("DEF"));
0414         QCOMPARE(rowSpyToText(rowATBRSpy), QStringLiteral("1,2"));
0415         QCOMPARE(rowSpyToText(rowRemovedSpy), QStringLiteral("1,2"));
0416         QCOMPARE(rowSpyToText(rowATBISpy), QStringLiteral("1,1"));
0417         QCOMPARE(rowSpyToText(rowInsertedSpy), QStringLiteral("1,1"));
0418     }
0419 
0420 private:
0421     QStandardItemModel mod;
0422     QStandardItemModel mod2;
0423 };
0424 
0425 QTEST_MAIN(tst_KConcatenateRowsProxyModel)
0426 
0427 #include "kconcatenaterowsproxymodeltest.moc"