File indexing completed on 2024-04-21 03:56:08

0001 /*
0002     SPDX-FileCopyrightText: 2015 Stephen Kelly <steveire@gmail.com>
0003     SPDX-FileCopyrightText: 2015 David Faure <faure@kde.org>
0004     SPDX-FileCopyrightText: 2016 Ableton AG <info@ableton.com>
0005     SPDX-FileContributor: Stephen Kelly <stephen.kelly@ableton.com>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "dynamictreemodel.h"
0011 #include "modeltest.h"
0012 #include "test_model_helpers.h"
0013 
0014 #include <kselectionproxymodel.h>
0015 
0016 #include <QIdentityProxyModel>
0017 #include <QSignalSpy>
0018 #include <QStringListModel>
0019 #include <QTest>
0020 
0021 using namespace TestModelHelpers;
0022 
0023 class KSelectionProxyModelTest : public QObject
0024 {
0025     Q_OBJECT
0026 public:
0027     KSelectionProxyModelTest(QObject *parent = nullptr)
0028         : QObject(parent)
0029         , days({QStringLiteral("Monday"), QStringLiteral("Tuesday"), QStringLiteral("Wednesday"), QStringLiteral("Thursday")})
0030     {
0031     }
0032 
0033 private Q_SLOTS:
0034     void columnCountShouldBeStable();
0035     void selectOnSourceReset();
0036     void selectionMapping();
0037     void removeRows_data();
0038     void removeRows();
0039 
0040     void selectionModelModelChange();
0041     void deselection_data();
0042     void deselection();
0043 
0044 private:
0045     const QStringList days;
0046 };
0047 
0048 void KSelectionProxyModelTest::columnCountShouldBeStable()
0049 {
0050     // Given a KSelectionProxy on top of a stringlist model
0051     QStringListModel strings(days);
0052     QItemSelectionModel selectionModel(&strings);
0053     KSelectionProxyModel proxy(&selectionModel);
0054     proxy.setSourceModel(&strings);
0055 
0056     QSignalSpy rowATBISpy(&proxy, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)));
0057     QSignalSpy rowInsertedSpy(&proxy, SIGNAL(rowsInserted(QModelIndex, int, int)));
0058 
0059     // No selection => the proxy should have 0 rows, 1 column
0060     // (if it had 0 columns, it would have to emit column insertions, too much trouble)
0061     QCOMPARE(proxy.rowCount(), 0);
0062     QCOMPARE(proxy.columnCount(), 1);
0063     QCOMPARE(rowATBISpy.count(), 0);
0064     QCOMPARE(rowInsertedSpy.count(), 0);
0065 
0066     // Select second entry -> the proxy should have 1 rows, 1 column
0067     selectionModel.select(QItemSelection(strings.index(1, 0), strings.index(1, 0)), QItemSelectionModel::Select);
0068     QCOMPARE(proxy.rowCount(), 1);
0069     QCOMPARE(proxy.columnCount(), 1);
0070     QCOMPARE(rowSpyToText(rowATBISpy), QStringLiteral("0,0"));
0071     QCOMPARE(rowSpyToText(rowInsertedSpy), QStringLiteral("0,0"));
0072 }
0073 
0074 void KSelectionProxyModelTest::selectOnSourceReset()
0075 {
0076     QStringListModel strings(days);
0077     QItemSelectionModel selectionModel(&strings);
0078 
0079     connect(&strings, &QAbstractItemModel::modelReset, [&] {
0080         selectionModel.select(QItemSelection(strings.index(0, 0), strings.index(2, 0)), QItemSelectionModel::Select);
0081     });
0082 
0083     KSelectionProxyModel proxy(&selectionModel);
0084     proxy.setSourceModel(&strings);
0085 
0086     selectionModel.select(QItemSelection(strings.index(0, 0), strings.index(2, 0)), QItemSelectionModel::Select);
0087 
0088     QCOMPARE(proxy.rowCount(), 3);
0089     for (int i = 0; i < 3; ++i) {
0090         QCOMPARE(proxy.index(i, 0).data().toString(), days.at(i));
0091     }
0092 
0093     QStringList numbers = {QStringLiteral("One"), QStringLiteral("Two"), QStringLiteral("Three"), QStringLiteral("Four")};
0094     strings.setStringList(numbers);
0095 
0096     QCOMPARE(proxy.rowCount(), 3);
0097     for (int i = 0; i < 3; ++i) {
0098         QCOMPARE(proxy.index(i, 0).data().toString(), numbers.at(i));
0099     }
0100 
0101     QVERIFY(selectionModel.selection().contains(strings.index(0, 0)));
0102     QVERIFY(selectionModel.selection().contains(strings.index(1, 0)));
0103     QVERIFY(selectionModel.selection().contains(strings.index(2, 0)));
0104 }
0105 
0106 void KSelectionProxyModelTest::selectionModelModelChange()
0107 {
0108     QStringListModel strings(days);
0109     QItemSelectionModel selectionModel(&strings);
0110 
0111     QIdentityProxyModel identity;
0112     identity.setSourceModel(&strings);
0113 
0114     KSelectionProxyModel proxy(&selectionModel);
0115     proxy.setSourceModel(&identity);
0116     selectionModel.select(strings.index(0, 0), QItemSelectionModel::Select);
0117 
0118     QCOMPARE(proxy.rowCount(), 1);
0119     QCOMPARE(proxy.index(0, 0).data().toString(), QStringLiteral("Monday"));
0120 
0121     QStringListModel strings2({QStringLiteral("Today"), QStringLiteral("Tomorrow")});
0122 
0123     QSignalSpy resetSpy(&proxy, &QAbstractItemModel::modelReset);
0124 
0125     selectionModel.setModel(&strings2);
0126 
0127     QCOMPARE(resetSpy.size(), 1);
0128     QCOMPARE(proxy.rowCount(), 0);
0129 
0130     proxy.setSourceModel(&strings2);
0131     selectionModel.select(strings2.index(0, 0), QItemSelectionModel::Select);
0132 
0133     QCOMPARE(proxy.rowCount(), 1);
0134     QCOMPARE(proxy.index(0, 0).data().toString(), QStringLiteral("Today"));
0135 
0136     QSignalSpy spy(&proxy, SIGNAL(modelReset()));
0137 
0138     QStringList numbers = {QStringLiteral("One"), QStringLiteral("Two"), QStringLiteral("Three"), QStringLiteral("Four")};
0139     strings.setStringList(numbers);
0140 
0141     QCOMPARE(spy.count(), 0);
0142 
0143     strings2.setStringList(numbers);
0144 
0145     QCOMPARE(spy.count(), 1);
0146 
0147     QCOMPARE(proxy.rowCount(), 0);
0148     QVERIFY(!selectionModel.hasSelection());
0149 
0150     selectionModel.select(strings2.index(0, 0), QItemSelectionModel::Select);
0151 
0152     QCOMPARE(proxy.rowCount(), 1);
0153     QCOMPARE(proxy.index(0, 0).data().toString(), numbers.at(0));
0154 }
0155 
0156 void KSelectionProxyModelTest::deselection_data()
0157 {
0158     QTest::addColumn<int>("kspm_mode");
0159     QTest::addColumn<QStringList>("selection");
0160     QTest::addColumn<int>("expectedRowCountBefore");
0161     QTest::addColumn<int>("spyCount");
0162     QTest::addColumn<QStringList>("toDeselect");
0163     QTest::addColumn<int>("expectedRowCountAfter");
0164 
0165     QTest::newRow("test-01") << static_cast<int>(KSelectionProxyModel::SubTreesWithoutRoots) << QStringList{"2"} << 2 << 1 << QStringList{"2"} << 0;
0166 
0167     QTest::newRow("test-02") << static_cast<int>(KSelectionProxyModel::SubTreesWithoutRoots) << QStringList{"3", "9"} << 4 << 1 << QStringList{"3"} << 2;
0168 
0169     QTest::newRow("test-03") << static_cast<int>(KSelectionProxyModel::SubTreesWithoutRoots) << QStringList{"3", "9"} << 4 << 1 << QStringList{"3", "9"} << 0;
0170 
0171     QTest::newRow("test-04") << static_cast<int>(KSelectionProxyModel::SubTreesWithoutRoots) << QStringList{"3", "9"} << 4 << 1 << QStringList{"9"} << 2;
0172 
0173     QTest::newRow("test-05") << static_cast<int>(KSelectionProxyModel::SubTreesWithoutRoots) << QStringList{"3", "9", "11", "15"} << 6 << 1 << QStringList{"9"}
0174                              << 7;
0175 
0176     QTest::newRow("test-06") << static_cast<int>(KSelectionProxyModel::SubTreesWithoutRoots) << QStringList{"3", "9", "11", "15"} << 6 << 1
0177                              << QStringList{"9", "15"} << 5;
0178 
0179     QTest::newRow("test-07") << static_cast<int>(KSelectionProxyModel::SubTreesWithoutRoots) << QStringList{"3", "9", "11", "15"} << 6 << 1
0180                              << QStringList{"3", "9", "15"} << 3;
0181 
0182     QTest::newRow("test-08") << static_cast<int>(KSelectionProxyModel::SubTreesWithoutRoots) << QStringList{"3", "9", "11", "15"} << 6 << 1
0183                              << QStringList{"3", "9", "11", "15"} << 0;
0184 
0185     QTest::newRow("test-09") << static_cast<int>(KSelectionProxyModel::SubTreesWithoutRoots) << QStringList{"3", "9", "11", "15"} << 6 << 0 << QStringList{"11"}
0186                              << 6;
0187 
0188     QTest::newRow("test-10") << static_cast<int>(KSelectionProxyModel::SubTreesWithoutRoots) << QStringList{"3", "9", "11", "15"} << 6 << 2
0189                              << QStringList{"3", "15"} << 2;
0190 
0191     QTest::newRow("test-11") << static_cast<int>(KSelectionProxyModel::ExactSelection) << QStringList{"3", "9", "11", "15"} << 4 << 1 << QStringList{"11"} << 3;
0192 
0193     QTest::newRow("test-12") << static_cast<int>(KSelectionProxyModel::ExactSelection) << QStringList{"3", "9", "11", "15"} << 4 << 2 << QStringList{"3", "11"}
0194                              << 2;
0195 
0196     QTest::newRow("test-13") << static_cast<int>(KSelectionProxyModel::ExactSelection) << QStringList{"3", "9", "11", "15"} << 4 << 1
0197                              << QStringList{"3", "9", "11"} << 1;
0198 
0199     QTest::newRow("test-14") << static_cast<int>(KSelectionProxyModel::ChildrenOfExactSelection) << QStringList{"3"} << 2 << 1 << QStringList{"3"} << 0;
0200 
0201     QTest::newRow("test-15") << static_cast<int>(KSelectionProxyModel::ChildrenOfExactSelection) << QStringList{"3", "9", "11", "15"} << 9 << 1
0202                              << QStringList{"3", "9", "11"} << 2;
0203 
0204     QTest::newRow("test-16") << static_cast<int>(KSelectionProxyModel::ChildrenOfExactSelection) << QStringList{"3", "9", "11", "15"} << 9 << 1
0205                              << QStringList{"9"} << 7;
0206 
0207     QTest::newRow("test-17") << static_cast<int>(KSelectionProxyModel::ChildrenOfExactSelection) << QStringList{"3", "9", "11", "15"} << 9 << 2
0208                              << QStringList{"3", "11"} << 4;
0209 
0210     QTest::newRow("test-18") << static_cast<int>(KSelectionProxyModel::ChildrenOfExactSelection) << QStringList{"4", "6", "9", "15"} << 7 << 1
0211                              << QStringList{"4"} << 5;
0212 
0213     QTest::newRow("test-19") << static_cast<int>(KSelectionProxyModel::ChildrenOfExactSelection) << QStringList{"6", "7"} << 1 << 0 << QStringList{"7"} << 1;
0214 }
0215 
0216 void KSelectionProxyModelTest::deselection()
0217 {
0218     QFETCH(int, kspm_mode);
0219     QFETCH(QStringList, selection);
0220     QFETCH(int, expectedRowCountBefore);
0221     QFETCH(int, spyCount);
0222     QFETCH(QStringList, toDeselect);
0223     QFETCH(int, expectedRowCountAfter);
0224 
0225     DynamicTreeModel tree;
0226     new ModelTest(&tree, &tree);
0227     ModelResetCommand resetCommand(&tree);
0228     resetCommand.setInitialTree(
0229         " - 1"
0230         " - - 2"
0231         " - - - 3"
0232         " - - - - 4"
0233         " - - - - - 5"
0234         " - - - - - 6"
0235         " - - - - - - 7"
0236         " - - - - 8"
0237         " - - - 9"
0238         " - - - - 10"
0239         " - - - - 11"
0240         " - - - - - 12"
0241         " - - - - - 13"
0242         " - - - - - 14"
0243         " - - 15"
0244         " - - - 16"
0245         " - - - 17");
0246     resetCommand.doCommand();
0247 
0248     QItemSelectionModel selectionModel(&tree);
0249 
0250     KSelectionProxyModel proxy(&selectionModel);
0251     new ModelTest(&proxy, &proxy);
0252     proxy.setFilterBehavior(static_cast<KSelectionProxyModel::FilterBehavior>(kspm_mode));
0253     proxy.setSourceModel(&tree);
0254 
0255     QSignalSpy beforeSpy(&proxy, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)));
0256     QSignalSpy afterSpy(&proxy, SIGNAL(rowsRemoved(QModelIndex, int, int)));
0257 
0258     QItemSelection sel;
0259     for (auto item : selection) {
0260         QModelIndexList idxs = tree.match(tree.index(0, 0), Qt::DisplayRole, item, 1, Qt::MatchRecursive);
0261         QCOMPARE(idxs.size(), 1);
0262         sel << QItemSelectionRange(idxs.at(0), idxs.at(0));
0263     }
0264     selectionModel.select(sel, QItemSelectionModel::Select);
0265 
0266     QCOMPARE(proxy.rowCount(), expectedRowCountBefore);
0267 
0268     QItemSelection desel;
0269     for (auto item : toDeselect) {
0270         QModelIndexList idxs = tree.match(tree.index(0, 0), Qt::DisplayRole, item, 1, Qt::MatchRecursive);
0271         QCOMPARE(idxs.size(), 1);
0272         desel << QItemSelectionRange(idxs.at(0), idxs.at(0));
0273     }
0274     selectionModel.select(desel, QItemSelectionModel::Deselect);
0275 
0276     QCOMPARE(beforeSpy.count(), spyCount);
0277     QCOMPARE(afterSpy.count(), spyCount);
0278 
0279     QCOMPARE(proxy.rowCount(), expectedRowCountAfter);
0280 }
0281 
0282 void KSelectionProxyModelTest::removeRows_data()
0283 {
0284     QTest::addColumn<int>("kspm_mode");
0285     QTest::addColumn<bool>("connectSelectionModelFirst");
0286     QTest::addColumn<bool>("emulateSingleSelectionMode");
0287     QTest::addColumn<QStringList>("selection");
0288     QTest::addColumn<int>("expectedRowCountBefore");
0289     QTest::addColumn<int>("spyCount");
0290     QTest::addColumn<QStringList>("toRemove");
0291     QTest::addColumn<int>("expectedRowCountAfter");
0292 
0293     // Because the KSelectionProxyModel has two dependencies - a QItemSelectionModel
0294     // and a QAbstractItemModel, whichever one signals first determines the internal
0295     // code path is used to perform removal.  That order is determined by order
0296     // of signal slot connections, and these tests use connectSelectionModelFirst
0297     // to test both.
0298 
0299     // When using a QAbstractItemView, the SelectionMode can determine how the
0300     // selection changes when a selected row is removed.  When the row is
0301     // AboutToBeRemoved, the view might change the selection to a row which is
0302     // not to be removed.  This means that depending on signal-slot connection
0303     // order, the KSelectionProxyModel::sourceRowsAboutToBeRemoved method
0304     // might be executed, but then the selection can be changed before
0305     // executing the KSelectionProxyModel::sourceRowsRemoved method.  These tests
0306     // are run with and without similar emulated behavior.
0307 
0308     for (auto emulateSingleSelectionMode : {true, false}) {
0309         for (auto kspm_mode : {KSelectionProxyModel::SubTreesWithoutRoots, KSelectionProxyModel::ChildrenOfExactSelection}) {
0310             for (auto connectSelectionModelFirst : {true, false}) {
0311                 const auto newRow = [&](const char *number) -> QTestData & {
0312                     return QTest::newRow(QByteArray(QByteArrayLiteral("A-") + (emulateSingleSelectionMode ? "1-" : "0-")
0313                                                     + (kspm_mode == KSelectionProxyModel::SubTreesWithoutRoots ? "SubTrees-" : "Children-")
0314                                                     + (connectSelectionModelFirst ? "1-" : "0-") + number));
0315                 };
0316 
0317                 newRow("01") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"2"} << 2 << 1
0318                              << QStringList{"2", "2"} << (emulateSingleSelectionMode ? 2 : 0);
0319 
0320                 newRow("02") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"2"} << 2
0321                              << (kspm_mode == KSelectionProxyModel::ChildrenOfExactSelection ? 0 : 1) << QStringList{"4", "4"} << 2;
0322 
0323                 newRow("03") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"2"} << 2
0324                              << (kspm_mode == KSelectionProxyModel::ChildrenOfExactSelection ? 0 : 1) << QStringList{"5", "5"} << 2;
0325 
0326                 newRow("04") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"2"} << 2
0327                              << (kspm_mode == KSelectionProxyModel::ChildrenOfExactSelection ? 0 : 1) << QStringList{"6", "6"} << 2;
0328 
0329                 newRow("05") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"2"} << 2
0330                              << (kspm_mode == KSelectionProxyModel::ChildrenOfExactSelection ? 0 : 1) << QStringList{"7", "7"} << 2;
0331 
0332                 newRow("06") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"2"} << 2 << 1
0333                              << QStringList{"1", "1"} << 0;
0334 
0335                 newRow("07") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"2"} << 2 << 1
0336                              << QStringList{"9", "9"} << 1;
0337 
0338                 newRow("08") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"2"} << 2 << 0
0339                              << QStringList{"15", "15"} << 2;
0340 
0341                 newRow("09") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"5"} << 0 << 0
0342                              << QStringList{"5", "5"} << (emulateSingleSelectionMode ? 1 : 0);
0343 
0344                 newRow("10") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"5"} << 0 << 0
0345                              << QStringList{"4", "4"} << 0;
0346 
0347                 newRow("11") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"5"} << 0 << 0
0348                              << QStringList{"3", "3"} << 0;
0349 
0350                 newRow("12") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"5"} << 0 << 0
0351                              << QStringList{"2", "2"} << 0;
0352 
0353                 newRow("13") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << emulateSingleSelectionMode << QStringList{"6"} << 1 << 1
0354                              << QStringList{"4", "4"} << 0;
0355             }
0356         }
0357     }
0358 
0359     for (auto connectSelectionModelFirst : {true, false}) {
0360         for (auto kspm_mode : {KSelectionProxyModel::SubTreesWithoutRoots, KSelectionProxyModel::ChildrenOfExactSelection}) {
0361             const auto newRow = [&](const char *number) -> QTestData & {
0362                 return QTest::newRow(QByteArray(QByteArrayLiteral("B-") + (connectSelectionModelFirst ? "1-" : "0-")
0363                                                 + (kspm_mode == KSelectionProxyModel::SubTreesWithoutRoots ? "SubTrees-" : "Children-") + number));
0364             };
0365 
0366             newRow("01") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"3", "15"} << 4 << 1 << QStringList{"3", "3"}
0367                          << 2;
0368 
0369             newRow("02") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "15"} << 4 << 1 << QStringList{"2", "2"}
0370                          << 2;
0371 
0372             newRow("03") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "11"} << 5 << 1 << QStringList{"2", "2"}
0373                          << 0;
0374 
0375             newRow("04") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "11"} << 5 << 1 << QStringList{"3", "3"}
0376                          << 3;
0377 
0378             newRow("05") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "11"} << 5 << 1 << QStringList{"11", "11"}
0379                          << 2;
0380 
0381             newRow("06") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "11"} << 5 << 1 << QStringList{"3", "9"}
0382                          << 0;
0383 
0384             newRow("07") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "11", "15"} << 7 << 1
0385                          << QStringList{"3", "9"} << 2;
0386 
0387             newRow("08") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "8", "10", "11", "16"} << 5 << 1
0388                          << QStringList{"3", "9"} << 0;
0389 
0390             newRow("09") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "8", "10", "11", "16"} << 5 << 1
0391                          << QStringList{"3", "3"} << 3;
0392 
0393             newRow("10") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "8", "10", "11", "16"} << 5 << 1
0394                          << QStringList{"9", "9"} << 2;
0395 
0396             newRow("11") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "11"} << 5 << 1 << QStringList{"9", "9"}
0397                          << 2;
0398 
0399             newRow("12") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "6", "11"}
0400                          << (kspm_mode == KSelectionProxyModel::ChildrenOfExactSelection ? 6 : 5) << 1 << QStringList{"9", "9"}
0401                          << (kspm_mode == KSelectionProxyModel::ChildrenOfExactSelection ? 3 : 2);
0402 
0403             newRow("13") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"4", "8", "11"} << 5 << 1 << QStringList{"9", "9"}
0404                          << 2;
0405 
0406             newRow("14") << static_cast<int>(kspm_mode) << connectSelectionModelFirst << false << QStringList{"6", "8", "11"} << 4 << 0 << QStringList{"8", "8"}
0407                          << 4;
0408         }
0409     }
0410 
0411     for (auto connectSelectionModelFirst : {true, false}) {
0412         const auto newRow = [&](const char *number) -> QTestData & {
0413             return QTest::newRow(QByteArray(QByteArrayLiteral("C-") + (connectSelectionModelFirst ? "1-" : "0-") + number));
0414         };
0415 
0416         newRow("01") << static_cast<int>(KSelectionProxyModel::ExactSelection) << connectSelectionModelFirst << false << QStringList{"2"} << 1 << 1
0417                      << QStringList{"2", "2"} << 0;
0418 
0419         newRow("02") << static_cast<int>(KSelectionProxyModel::ExactSelection) << connectSelectionModelFirst << false << QStringList{"2", "3", "4"} << 3 << 1
0420                      << QStringList{"2", "2"} << 0;
0421 
0422         newRow("03") << static_cast<int>(KSelectionProxyModel::ExactSelection) << connectSelectionModelFirst << false << QStringList{"6", "9"} << 2 << 1
0423                      << QStringList{"2", "2"} << 0;
0424 
0425         newRow("04") << static_cast<int>(KSelectionProxyModel::ExactSelection) << connectSelectionModelFirst << false << QStringList{"6", "9"} << 2 << 1
0426                      << QStringList{"4", "4"} << 1;
0427 
0428         newRow("05") << static_cast<int>(KSelectionProxyModel::ExactSelection) << connectSelectionModelFirst << false << QStringList{"4", "10", "11"} << 3 << 1
0429                      << QStringList{"3", "9"} << 0;
0430 
0431         newRow("06") << static_cast<int>(KSelectionProxyModel::ExactSelection) << connectSelectionModelFirst << false << QStringList{"4", "6", "7", "10", "11"}
0432                      << 5 << 1 << QStringList{"10", "11"} << 3;
0433 
0434         newRow("07") << static_cast<int>(KSelectionProxyModel::ExactSelection) << connectSelectionModelFirst << false << QStringList{"4", "5", "6", "7", "8"}
0435                      << 5 << 1 << QStringList{"4", "8"} << 0;
0436 
0437         newRow("08") << static_cast<int>(KSelectionProxyModel::ExactSelection) << connectSelectionModelFirst << false << QStringList{"4", "5", "6", "7", "8"}
0438                      << 5 << 1 << QStringList{"4", "4"} << 1;
0439 
0440         newRow("09") << static_cast<int>(KSelectionProxyModel::ExactSelection) << connectSelectionModelFirst << false << QStringList{"4", "5", "6", "7", "8"}
0441                      << 5 << 1 << QStringList{"6", "6"} << 3;
0442     }
0443 
0444     for (auto connectSelectionModelFirst : {true, false}) {
0445         const auto newRow = [&](const char *number) -> QTestData & {
0446             return QTest::newRow(QByteArray(QByteArrayLiteral("D-") + (connectSelectionModelFirst ? "1-" : "0-") + number));
0447         };
0448 
0449         newRow("01") << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"2"} << 1 << 1
0450                      << QStringList{"2", "2"} << 0;
0451 
0452         newRow("02") << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"2"} << 1 << 1
0453                      << QStringList{"4", "4"} << 1;
0454 
0455         newRow("03") << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"2", "4"} << 1 << 1
0456                      << QStringList{"4", "4"} << 1;
0457 
0458         newRow("04") << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"2", "4"} << 1 << 1
0459                      << QStringList{"2", "2"} << 0;
0460 
0461         newRow("05") << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"4", "9"} << 2 << 1
0462                      << QStringList{"2", "2"} << 0;
0463 
0464         newRow("06") << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"4", "9"} << 2 << 1
0465                      << QStringList{"4", "4"} << 1;
0466 
0467         newRow("07") << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"4", "9"} << 2 << 1
0468                      << QStringList{"9", "9"} << 1;
0469 
0470         newRow("08") << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"4", "9"} << 2 << 1
0471                      << QStringList{"5", "6"} << 2;
0472 
0473         newRow("09") << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"4", "9"} << 2 << 1
0474                      << QStringList{"4", "8"} << 1;
0475 
0476         newRow("10") << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"6", "11", "15"} << 3 << 1
0477                      << QStringList{"9", "9"} << 2;
0478 
0479         newRow("11") << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"6", "11", "15"} << 3 << 1
0480                      << QStringList{"11", "11"} << 2;
0481 
0482         newRow("12") << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"6", "8", "10", "11"} << 4 << 1
0483                      << QStringList{"3", "3"} << 2;
0484 
0485         newRow("13") << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"6", "8", "10", "11"} << 4 << 1
0486                      << QStringList{"2", "2"} << 0;
0487 
0488         newRow("14") << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"6", "8", "10", "11"} << 4 << 1
0489                      << QStringList{"9", "9"} << 2;
0490 
0491         newRow("15") << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"10", "11"} << 2 << 1
0492                      << QStringList{"9", "9"} << 0;
0493 
0494         newRow("16") << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"4", "8"} << 2 << 1
0495                      << QStringList{"3", "3"} << 0;
0496 
0497         newRow("17") << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"12", "13", "14"} << 3 << 1
0498                      << QStringList{"11", "11"} << 0;
0499 
0500         newRow("18") << static_cast<int>(KSelectionProxyModel::SubTrees) << connectSelectionModelFirst << false << QStringList{"12", "13", "14"} << 3 << 1
0501                      << QStringList{"10", "11"} << 0;
0502     }
0503 }
0504 
0505 void KSelectionProxyModelTest::removeRows()
0506 {
0507     QFETCH(int, kspm_mode);
0508     QFETCH(bool, connectSelectionModelFirst);
0509     QFETCH(bool, emulateSingleSelectionMode);
0510     QFETCH(QStringList, selection);
0511     QFETCH(int, expectedRowCountBefore);
0512     QFETCH(int, spyCount);
0513     QFETCH(QStringList, toRemove);
0514     QFETCH(int, expectedRowCountAfter);
0515 
0516     // TODO: Plasma 6: Try again on CI if minimum required Qt version would allow it.
0517     const QString tag = QString::fromUtf8(QTest::currentDataTag());
0518     if (tag.startsWith("A-") && tag.endsWith("-0-06")) {
0519         QSKIP("This specific test fails due to upstream bug fixed by: https://codereview.qt-project.org/c/qt/qtbase/+/487372");
0520     }
0521 
0522     DynamicTreeModel tree;
0523     new ModelTest(&tree, &tree);
0524     ModelResetCommand resetCommand(&tree);
0525     resetCommand.setInitialTree(
0526         " - 1"
0527         " - - 2"
0528         " - - - 3"
0529         " - - - - 4"
0530         " - - - - - 5"
0531         " - - - - - 6"
0532         " - - - - - - 7"
0533         " - - - - 8"
0534         " - - - 9"
0535         " - - - - 10"
0536         " - - - - 11"
0537         " - - - - - 12"
0538         " - - - - - 13"
0539         " - - - - - 14"
0540         " - - 15"
0541         " - - - 16"
0542         " - - - 17");
0543     resetCommand.doCommand();
0544 
0545     QItemSelectionModel selectionModel;
0546 
0547     if (emulateSingleSelectionMode) {
0548         QObject::connect(&tree, &QAbstractItemModel::rowsAboutToBeRemoved, &tree, [&tree, &selectionModel](QModelIndex const &p, int s, int e) {
0549             auto rmIdx = tree.index(s, 0, p);
0550             if (s == e && selectionModel.selectedIndexes().contains(rmIdx)) {
0551                 auto nextIdx = tree.index(e + 1, 0, rmIdx.parent());
0552                 selectionModel.select(nextIdx, QItemSelectionModel::ClearAndSelect);
0553             }
0554         });
0555     }
0556 
0557     KSelectionProxyModel proxy;
0558     new ModelTest(&proxy, &proxy);
0559     proxy.setFilterBehavior(static_cast<KSelectionProxyModel::FilterBehavior>(kspm_mode));
0560 
0561     if (connectSelectionModelFirst) {
0562         selectionModel.setModel(&tree);
0563         proxy.setSourceModel(&tree);
0564         proxy.setSelectionModel(&selectionModel);
0565     } else {
0566         proxy.setSourceModel(&tree);
0567         proxy.setSelectionModel(&selectionModel);
0568         selectionModel.setModel(&tree);
0569     }
0570 
0571     QSignalSpy beforeSpy(&proxy, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)));
0572     QSignalSpy afterSpy(&proxy, SIGNAL(rowsRemoved(QModelIndex, int, int)));
0573 
0574     QItemSelection sel;
0575     for (auto item : selection) {
0576         QModelIndexList idxs = tree.match(tree.index(0, 0), Qt::DisplayRole, item, 1, Qt::MatchRecursive);
0577         QCOMPARE(idxs.size(), 1);
0578         sel << QItemSelectionRange(idxs.at(0), idxs.at(0));
0579     }
0580     selectionModel.select(sel, QItemSelectionModel::Select);
0581 
0582     QCOMPARE(proxy.rowCount(), expectedRowCountBefore);
0583 
0584     for (auto removePairIndex = 0; removePairIndex < toRemove.size(); removePairIndex += 2) {
0585         QModelIndexList idxs = tree.match(tree.index(0, 0), Qt::DisplayRole, toRemove[removePairIndex], 1, Qt::MatchRecursive);
0586         QCOMPARE(idxs.size(), 1);
0587 
0588         auto startIdx = idxs.at(0);
0589 
0590         idxs = tree.match(tree.index(0, 0), Qt::DisplayRole, toRemove[removePairIndex + 1], 1, Qt::MatchRecursive);
0591         QCOMPARE(idxs.size(), 1);
0592 
0593         auto endIdx = idxs.at(0);
0594 
0595         ModelRemoveCommand remove(&tree);
0596         remove.setAncestorRowNumbers(tree.indexToPath(startIdx.parent()));
0597         remove.setStartRow(startIdx.row());
0598         remove.setEndRow(endIdx.row());
0599         remove.doCommand();
0600     }
0601 
0602     QCOMPARE(beforeSpy.count(), spyCount);
0603     QCOMPARE(afterSpy.count(), spyCount);
0604 
0605     QCOMPARE(proxy.rowCount(), expectedRowCountAfter);
0606 }
0607 
0608 void KSelectionProxyModelTest::selectionMapping()
0609 {
0610     QStringListModel strings(days);
0611     QItemSelectionModel selectionModel(&strings);
0612     KSelectionProxyModel proxy(&selectionModel);
0613     proxy.setFilterBehavior(KSelectionProxyModel::SubTrees);
0614     proxy.setSourceModel(&strings);
0615     auto idx1 = strings.index(0, 0);
0616     auto idx2 = strings.index(2, 0);
0617     QItemSelection sourceSel;
0618     sourceSel << QItemSelectionRange(idx1, idx2);
0619     selectionModel.select(sourceSel, QItemSelectionModel::Select);
0620 
0621     QItemSelection proxySel;
0622     proxySel << QItemSelectionRange(proxy.index(0, 0), proxy.index(2, 0));
0623 
0624     QCOMPARE(proxy.mapSelectionToSource(proxySel), sourceSel);
0625 }
0626 
0627 QTEST_MAIN(KSelectionProxyModelTest)
0628 
0629 #include "kselectionproxymodeltest.moc"