File indexing completed on 2025-04-20 09:44:42
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"