File indexing completed on 2025-04-13 03:42:36
0001 /* 0002 Copyright (C) 2019 David Edmundson <davidedmundson@kde.org> 0003 0004 This library is free software; you can redistribute it and/or modify it 0005 under the terms of the GNU Lesser General Public License as published by 0006 the Free Software Foundation; either version 2.1 of the License, or (at your 0007 option) any later version. 0008 0009 This library is distributed in the hope that it will be useful, but WITHOUT 0010 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 0011 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 0012 License for more details. 0013 0014 You should have received a copy of the GNU Library General Public License 0015 along with this library; see the file COPYING.LIB. If not, write to the 0016 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 0017 02110-1301, USA. 0018 */ 0019 0020 #include <QObject> 0021 0022 #include <QSignalSpy> 0023 #include <QTest> 0024 0025 #include <QQmlApplicationEngine> 0026 #include <QQmlContext> 0027 #include <QQmlEngine> 0028 0029 #include <QAbstractItemModel> 0030 #include <QSortFilterProxyModel> 0031 #include <QStandardItemModel> 0032 0033 #ifdef IMPORT_ITEMMODELSPLUGIN 0034 #include <QPluginLoader> 0035 Q_IMPORT_PLUGIN(Plugin) 0036 #endif 0037 0038 class tst_KSortFilterProxyModelQml : public QObject 0039 { 0040 Q_OBJECT 0041 0042 public: 0043 enum Roles { 0044 // Some non-default role ID with a QString data 0045 MonthNameRole = Qt::UserRole, 0046 IntUserRole, 0047 }; 0048 Q_ENUM(Roles) 0049 0050 private Q_SLOTS: 0051 void init(); 0052 void testFilterCallback(); 0053 void testSortRole_data(); 0054 void testSortRole(); 0055 void testFilterRegExp(); 0056 void testFilterRegExpRole(); 0057 void testRoleVsModelRace(); 0058 0059 private: 0060 QAbstractItemModel *createMonthTestModel(QObject *parent); 0061 }; 0062 0063 QAbstractItemModel *tst_KSortFilterProxyModelQml::createMonthTestModel(QObject *parent) 0064 { 0065 auto testModel = new QStandardItemModel(parent); 0066 for (int i = 1; i <= 12; i++) { 0067 auto entry = new QStandardItem(); 0068 const auto month = QLocale::c().monthName(i); 0069 entry->setData(month, Qt::DisplayRole); 0070 entry->setData(month, MonthNameRole); 0071 entry->setData(i, IntUserRole); 0072 testModel->appendRow(entry); 0073 } 0074 testModel->setItemRoleNames({ 0075 {Qt::DisplayRole, "display"}, 0076 {MonthNameRole, "month"}, 0077 {IntUserRole, "user"}, 0078 }); 0079 return testModel; 0080 } 0081 0082 void tst_KSortFilterProxyModelQml::init() 0083 { 0084 qmlRegisterType<tst_KSortFilterProxyModelQml>("org.kde.kitemmodels.test", 1, 0, "Self"); 0085 } 0086 0087 void tst_KSortFilterProxyModelQml::testFilterCallback() 0088 { 0089 QQmlApplicationEngine app; 0090 app.loadData( 0091 "import QtQml 2.0\n" 0092 "import org.kde.kitemmodels 1.0\n" 0093 "KSortFilterProxyModel\n" 0094 "{\n" 0095 " property int modulo: 2\n" 0096 " sourceModel: KNumberModel {\n" 0097 " minimumValue: 1\n" 0098 " maximumValue: 10\n" 0099 " }\n" 0100 " filterRowCallback: function(source_row, source_parent) {\n" 0101 " return sourceModel.data(sourceModel.index(source_row, 0, source_parent), Qt.DisplayRole) % modulo == 1;\n" 0102 " };\n" 0103 "}\n"); 0104 QCOMPARE(app.rootObjects().count(), 1); 0105 0106 auto filterModel = qobject_cast<QAbstractItemModel *>(app.rootObjects().first()); 0107 QVERIFY(filterModel); 0108 0109 QCOMPARE(filterModel->rowCount(), 5); 0110 QCOMPARE(filterModel->data(filterModel->index(0, 0)).toString(), "1"); 0111 QCOMPARE(filterModel->data(filterModel->index(1, 0)).toString(), "3"); 0112 QCOMPARE(filterModel->data(filterModel->index(2, 0)).toString(), "5"); 0113 QCOMPARE(filterModel->data(filterModel->index(3, 0)).toString(), "7"); 0114 QCOMPARE(filterModel->data(filterModel->index(4, 0)).toString(), "9"); 0115 0116 filterModel->setProperty("modulo", 3); 0117 0118 // Nothing should change until we call invalidateFilter 0119 QCOMPARE(filterModel->rowCount(), 5); 0120 QCOMPARE(filterModel->data(filterModel->index(0, 0)).toString(), "1"); 0121 QCOMPARE(filterModel->data(filterModel->index(1, 0)).toString(), "3"); 0122 QCOMPARE(filterModel->data(filterModel->index(2, 0)).toString(), "5"); 0123 QCOMPARE(filterModel->data(filterModel->index(3, 0)).toString(), "7"); 0124 QCOMPARE(filterModel->data(filterModel->index(4, 0)).toString(), "9"); 0125 0126 // Simulate call from QML by going through metaobject rather than calling it directly 0127 const bool invalidateFilterCallOk = QMetaObject::invokeMethod(filterModel, "invalidateFilter"); 0128 QVERIFY(invalidateFilterCallOk); 0129 0130 QCOMPARE(filterModel->rowCount(), 4); 0131 QCOMPARE(filterModel->data(filterModel->index(0, 0)).toString(), "1"); 0132 QCOMPARE(filterModel->data(filterModel->index(1, 0)).toString(), "4"); 0133 QCOMPARE(filterModel->data(filterModel->index(2, 0)).toString(), "7"); 0134 QCOMPARE(filterModel->data(filterModel->index(3, 0)).toString(), "10"); 0135 } 0136 0137 void tst_KSortFilterProxyModelQml::testSortRole_data() 0138 { 0139 // test model consists of all month names + month number as Display and UserRoler respectively 0140 0141 QTest::addColumn<QString>("qmlContents"); 0142 QTest::addColumn<QString>("result"); 0143 0144 QTest::newRow("sort by role name - display") << R"( 0145 KSortFilterProxyModel { 0146 sourceModel: testModel 0147 sortRoleName: "display" 0148 } 0149 )" 0150 << "April"; 0151 0152 QTest::newRow("sort by role name - value") << R"( 0153 KSortFilterProxyModel { 0154 sourceModel: testModel 0155 sortRoleName: "user" 0156 } 0157 )" 0158 << "January"; 0159 0160 QTest::newRow("sort by role name - reset") << R"( 0161 KSortFilterProxyModel { 0162 sourceModel: testModel 0163 sortRoleName: "" 0164 Component.onCompleted: sortRoleName = "" 0165 } 0166 )" 0167 << "January"; 0168 } 0169 0170 void tst_KSortFilterProxyModelQml::testSortRole() 0171 { 0172 QQmlApplicationEngine app; 0173 QFETCH(QString, qmlContents); 0174 QFETCH(QString, result); 0175 0176 qmlContents = R"( 0177 import org.kde.kitemmodels 1.0 0178 import QtQuick 2.0 0179 0180 )" + qmlContents; 0181 0182 app.rootContext()->setContextProperty("testModel", createMonthTestModel(&app)); 0183 0184 app.loadData(qmlContents.toLatin1()); 0185 0186 QCOMPARE(app.rootObjects().count(), 1); 0187 auto filterModel = qobject_cast<QAbstractItemModel *>(app.rootObjects().first()); 0188 QVERIFY(filterModel); 0189 QCOMPARE(filterModel->rowCount(), 12); 0190 QCOMPARE(filterModel->data(filterModel->index(0, 0), Qt::DisplayRole).toString(), result); 0191 } 0192 0193 void tst_KSortFilterProxyModelQml::testFilterRegExp() 0194 { 0195 // filterRegExp comes from the QSortFilterProxyModel directly, confirm it still works 0196 QQmlApplicationEngine app; 0197 0198 app.rootContext()->setContextProperty("testModel", createMonthTestModel(&app)); 0199 0200 auto qmlSrc = QByteArray( 0201 "import QtQml 2.0\n" 0202 "import org.kde.kitemmodels 1.0\n" 0203 "KSortFilterProxyModel {\n" 0204 " sourceModel: testModel\n" 0205 " filterRegularExpression: /Ma.*/\n" 0206 "}\n"); 0207 app.loadData(qmlSrc); 0208 0209 QCOMPARE(app.rootObjects().count(), 1); 0210 auto filterModel = qobject_cast<QAbstractItemModel *>(app.rootObjects().first()); 0211 QVERIFY(filterModel); 0212 QCOMPARE(filterModel->rowCount(), 2); 0213 QCOMPARE(filterModel->data(filterModel->index(0, 0), Qt::DisplayRole).toString(), "March"); 0214 QCOMPARE(filterModel->data(filterModel->index(1, 0), Qt::DisplayRole).toString(), "May"); 0215 } 0216 0217 void tst_KSortFilterProxyModelQml::testFilterRegExpRole() 0218 { 0219 // filterRegExp comes from the QSortFilterProxyModel directly, confirm it still works 0220 QQmlApplicationEngine app; 0221 0222 app.rootContext()->setContextProperty("testModel", createMonthTestModel(&app)); 0223 0224 auto qmlSrc = QByteArray( 0225 "import QtQml 2.0\n" 0226 "import org.kde.kitemmodels 1.0\n" 0227 "KSortFilterProxyModel {\n" 0228 " sourceModel: testModel\n" 0229 " filterRoleName: \"user\"\n" 0230 " filterRegularExpression: /1[0-9]/\n" // month value is 10 or more 0231 "}\n"); 0232 app.loadData(qmlSrc); 0233 0234 QCOMPARE(app.rootObjects().count(), 1); 0235 auto filterModel = qobject_cast<QAbstractItemModel *>(app.rootObjects().first()); 0236 QVERIFY(filterModel); 0237 QCOMPARE(filterModel->rowCount(), 3); 0238 QCOMPARE(filterModel->data(filterModel->index(0, 0), Qt::DisplayRole).toString(), "October"); 0239 QCOMPARE(filterModel->data(filterModel->index(1, 0), Qt::DisplayRole).toString(), "November"); 0240 QCOMPARE(filterModel->data(filterModel->index(2, 0), Qt::DisplayRole).toString(), "December"); 0241 } 0242 0243 void tst_KSortFilterProxyModelQml::testRoleVsModelRace() 0244 { 0245 // Our QML type extends base type's role property with a roleName 0246 // property, thus we have kinda two sources of truth, and they both reset 0247 // if a model is assigned after component's completion. 0248 // See BUG 476950 0249 QQmlApplicationEngine app; 0250 0251 auto qmlSrcModel = QByteArray(R"( 0252 import QtQml 0253 import org.kde.kitemmodels as KItemModels 0254 import org.kde.kitemmodels.test as KItemModelsTest 0255 0256 KItemModels.KSortFilterProxyModel { 0257 id: root 0258 0259 sortCaseSensitivity: Qt.CaseInsensitive 0260 sortRole: KItemModelsTest.Self.MonthNameRole 0261 0262 filterCaseSensitivity: Qt.CaseInsensitive 0263 filterRole: KItemModelsTest.Self.MonthNameRole 0264 } 0265 )"); 0266 app.loadData(qmlSrcModel); 0267 auto filterModel = qobject_cast<QSortFilterProxyModel *>(app.rootObjects().first()); 0268 QVERIFY(filterModel); 0269 0270 // There is no model yet, so the role names are default so far 0271 QCOMPARE(filterModel->property("sortRole").toInt(), MonthNameRole); 0272 QCOMPARE(filterModel->property("sortRoleName").toString(), QStringLiteral("display")); 0273 QCOMPARE(filterModel->property("filterRole").toInt(), MonthNameRole); 0274 QCOMPARE(filterModel->property("filterRoleName").toString(), QStringLiteral("display")); 0275 0276 const auto model = createMonthTestModel(&app); 0277 0278 QVERIFY(filterModel->setProperty("filterString", QStringLiteral("a"))); 0279 QCOMPARE(filterModel->rowCount(), 0); 0280 0281 filterModel->setSourceModel(model); 0282 QCOMPARE(filterModel->rowCount(), 6); 0283 0284 QCOMPARE(filterModel->property("sortRole").toInt(), MonthNameRole); 0285 QCOMPARE(filterModel->property("sortRoleName").toString(), QStringLiteral("month")); 0286 QCOMPARE(filterModel->property("filterRole").toInt(), MonthNameRole); 0287 QCOMPARE(filterModel->property("filterRoleName").toString(), QStringLiteral("month")); 0288 0289 // and now swap the source of truth to roleName 0290 QVERIFY(filterModel->setProperty("sortRoleName", QStringLiteral("user"))); 0291 QCOMPARE(filterModel->property("sortRole").toInt(), IntUserRole); 0292 QVERIFY(filterModel->setProperty("filterRoleName", QStringLiteral("user"))); 0293 QCOMPARE(filterModel->property("filterRole").toInt(), IntUserRole); 0294 0295 QVERIFY(filterModel->setProperty("filterString", QStringLiteral("9"))); 0296 0297 // Reset the model. Roles should persist. 0298 QCOMPARE(filterModel->rowCount(), 1); 0299 filterModel->setSourceModel(nullptr); 0300 filterModel->setSourceModel(model); 0301 QCOMPARE(filterModel->rowCount(), 1); 0302 0303 QCOMPARE(filterModel->property("sortRole").toInt(), IntUserRole); 0304 QCOMPARE(filterModel->property("sortRoleName").toString(), QStringLiteral("user")); 0305 QCOMPARE(filterModel->property("filterRole").toInt(), IntUserRole); 0306 QCOMPARE(filterModel->property("filterRoleName").toString(), QStringLiteral("user")); 0307 } 0308 0309 QTEST_GUILESS_MAIN(tst_KSortFilterProxyModelQml) 0310 0311 #include "ksortfilterproxymodel_qml.moc"