File indexing completed on 2024-04-28 08:45:24
0001 /* 0002 SPDX-FileCopyrightText: 2022 Eric Jiang 0003 SPDX-FileCopyrightText: 2017-2019 Nicolas Carion <french.ebook.lover@gmail.com> 0004 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 #include "catch.hpp" 0007 #include "test_utils.hpp" 0008 // test specific headers 0009 #include <QString> 0010 #include <cmath> 0011 #include <iostream> 0012 #include <tuple> 0013 #include <unordered_set> 0014 0015 #include "abstractmodel/abstracttreemodel.hpp" 0016 #include "abstractmodel/treeitem.hpp" 0017 #include "effects/effectlist/model/effecttreemodel.hpp" 0018 #include "effects/effectlist/model/effectfilter.hpp" 0019 0020 TEST_CASE("Basic tree testing", "[TreeModel]") 0021 { 0022 auto model = AbstractTreeModel::construct(); 0023 0024 REQUIRE(model->checkConsistency()); 0025 REQUIRE(model->rowCount() == 0); 0026 0027 SECTION("Item creation Test") 0028 { 0029 auto item = TreeItem::construct(QList<QVariant>{QString("test")}, model, false); 0030 int id = item->getId(); 0031 REQUIRE(item->depth() == 0); 0032 REQUIRE(model->checkConsistency()); 0033 0034 // check that a valid Id has been assigned 0035 REQUIRE(id != -1); 0036 0037 // check that the item is not yet registered (not valid parent) 0038 REQUIRE(model->m_allItems.size() == 1); 0039 0040 // Assign this to a parent 0041 model->getRoot()->appendChild(item); 0042 REQUIRE(model->checkConsistency()); 0043 // Now the item should be registered, we query it 0044 REQUIRE(model->m_allItems.size() == 2); 0045 REQUIRE(model->getItemById(id) == item); 0046 REQUIRE(item->depth() == 1); 0047 REQUIRE(model->rowCount() == 1); 0048 REQUIRE(model->rowCount(model->getIndexFromItem(item)) == 0); 0049 0050 // Retrieve data member 0051 REQUIRE(model->data(model->getIndexFromItem(item), 0) == QStringLiteral("test")); 0052 0053 // Try joint creation / assignation 0054 auto item2 = item->appendChild(QList<QVariant>{QString("test2")}); 0055 auto state = [&]() { 0056 REQUIRE(model->checkConsistency()); 0057 REQUIRE(item->depth() == 1); 0058 REQUIRE(item2->depth() == 2); 0059 REQUIRE(model->rowCount() == 1); 0060 REQUIRE(item->row() == 0); 0061 REQUIRE(item2->row() == 0); 0062 REQUIRE(model->data(model->getIndexFromItem(item2), 0) == QStringLiteral("test2")); 0063 REQUIRE(model->rowCount(model->getIndexFromItem(item2)) == 0); 0064 }; 0065 state(); 0066 REQUIRE(model->rowCount(model->getIndexFromItem(item)) == 1); 0067 REQUIRE(model->m_allItems.size() == 3); 0068 0069 // Add a second child to item to check if everything collapses 0070 auto item3 = item->appendChild(QList<QVariant>{QString("test3")}); 0071 state(); 0072 REQUIRE(model->rowCount(model->getIndexFromItem(item3)) == 0); 0073 REQUIRE(model->rowCount(model->getIndexFromItem(item)) == 2); 0074 REQUIRE(model->m_allItems.size() == 4); 0075 REQUIRE(item3->depth() == 2); 0076 REQUIRE(item3->row() == 1); 0077 REQUIRE(model->data(model->getIndexFromItem(item3), 0) == QStringLiteral("test3")); 0078 } 0079 0080 SECTION("Invalid moves") 0081 { 0082 auto item = model->getRoot()->appendChild(QList<QVariant>{QString("test")}); 0083 auto state = [&]() { 0084 REQUIRE(model->checkConsistency()); 0085 REQUIRE(model->rowCount() == 1); 0086 REQUIRE(item->depth() == 1); 0087 REQUIRE(item->row() == 0); 0088 REQUIRE(model->data(model->getIndexFromItem(item), 0) == QStringLiteral("test")); 0089 }; 0090 state(); 0091 REQUIRE(model->rowCount(model->getIndexFromItem(item)) == 0); 0092 0093 // Try to move the root 0094 REQUIRE_FALSE(item->appendChild(model->getRoot())); 0095 state(); 0096 REQUIRE(model->rowCount(model->getIndexFromItem(item)) == 0); 0097 0098 auto item2 = item->appendChild(QList<QVariant>{QString("test2")}); 0099 auto item3 = item2->appendChild(QList<QVariant>{QString("test3")}); 0100 auto item4 = item3->appendChild(QList<QVariant>{QString("test4")}); 0101 auto state2 = [&]() { 0102 state(); 0103 REQUIRE(item2->depth() == 2); 0104 REQUIRE(item2->row() == 0); 0105 REQUIRE(model->data(model->getIndexFromItem(item2), 0) == QStringLiteral("test2")); 0106 REQUIRE(item3->depth() == 3); 0107 REQUIRE(item3->row() == 0); 0108 REQUIRE(model->data(model->getIndexFromItem(item3), 0) == QStringLiteral("test3")); 0109 REQUIRE(item4->depth() == 4); 0110 REQUIRE(item4->row() == 0); 0111 REQUIRE(model->data(model->getIndexFromItem(item4), 0) == QStringLiteral("test4")); 0112 }; 0113 state2(); 0114 0115 // Try to make a loop 0116 REQUIRE_FALSE(item->changeParent(item3)); 0117 state2(); 0118 REQUIRE_FALSE(item->changeParent(item4)); 0119 state2(); 0120 0121 // Try to append a child that already have a parent 0122 REQUIRE_FALSE(item->appendChild(item4)); 0123 state2(); 0124 0125 // valid move 0126 REQUIRE(item4->changeParent(item2)); 0127 REQUIRE(model->checkConsistency()); 0128 } 0129 0130 SECTION("Deregistration tests") 0131 { 0132 // we construct a non trivial structure 0133 auto item = model->getRoot()->appendChild(QList<QVariant>{QString("test")}); 0134 auto item2 = item->appendChild(QList<QVariant>{QString("test2")}); 0135 auto item3 = item2->appendChild(QList<QVariant>{QString("test3")}); 0136 auto item4 = item3->appendChild(QList<QVariant>{QString("test4")}); 0137 auto item5 = item2->appendChild(QList<QVariant>{QString("test5")}); 0138 auto state = [&]() { 0139 REQUIRE(model->checkConsistency()); 0140 REQUIRE(model->rowCount() == 1); 0141 REQUIRE(item->depth() == 1); 0142 REQUIRE(item->row() == 0); 0143 REQUIRE(model->data(model->getIndexFromItem(item), 0) == QStringLiteral("test")); 0144 REQUIRE(model->rowCount(model->getIndexFromItem(item)) == 1); 0145 REQUIRE(item2->depth() == 2); 0146 REQUIRE(item2->row() == 0); 0147 REQUIRE(model->data(model->getIndexFromItem(item2), 0) == QStringLiteral("test2")); 0148 REQUIRE(model->rowCount(model->getIndexFromItem(item2)) == 2); 0149 REQUIRE(item3->depth() == 3); 0150 REQUIRE(item3->row() == 0); 0151 REQUIRE(model->data(model->getIndexFromItem(item3), 0) == QStringLiteral("test3")); 0152 REQUIRE(model->rowCount(model->getIndexFromItem(item3)) == 1); 0153 REQUIRE(item4->depth() == 4); 0154 REQUIRE(item4->row() == 0); 0155 REQUIRE(model->data(model->getIndexFromItem(item4), 0) == QStringLiteral("test4")); 0156 REQUIRE(model->rowCount(model->getIndexFromItem(item4)) == 0); 0157 REQUIRE(item5->depth() == 3); 0158 REQUIRE(item5->row() == 1); 0159 REQUIRE(model->data(model->getIndexFromItem(item5), 0) == QStringLiteral("test5")); 0160 REQUIRE(model->rowCount(model->getIndexFromItem(item5)) == 0); 0161 REQUIRE(model->m_allItems.size() == 6); 0162 REQUIRE(item->isInModel()); 0163 REQUIRE(item2->isInModel()); 0164 REQUIRE(item3->isInModel()); 0165 REQUIRE(item4->isInModel()); 0166 REQUIRE(item5->isInModel()); 0167 }; 0168 state(); 0169 0170 // deregister the topmost item, should also deregister its children 0171 item->changeParent(std::shared_ptr<TreeItem>()); 0172 REQUIRE(model->m_allItems.size() == 1); 0173 REQUIRE(model->rowCount() == 0); 0174 REQUIRE(!item->isInModel()); 0175 REQUIRE(!item2->isInModel()); 0176 REQUIRE(!item3->isInModel()); 0177 REQUIRE(!item4->isInModel()); 0178 REQUIRE(!item5->isInModel()); 0179 0180 // reinsert 0181 REQUIRE(model->getRoot()->appendChild(item)); 0182 state(); 0183 0184 item2->removeChild(item5); 0185 REQUIRE(!item5->isInModel()); 0186 REQUIRE(model->rowCount(model->getIndexFromItem(item2)) == 1); 0187 REQUIRE(model->m_allItems.size() == 5); 0188 0189 // reinsert 0190 REQUIRE(item5->changeParent(item2)); 0191 state(); 0192 } 0193 } 0194 0195 // Tests the logic for matching the user-supplied search string against the list 0196 // of items. The actual logic is in AssetFilter but since it's an abstract 0197 // class, we test EffectFilter instead. 0198 TEST_CASE("Effect filter text-matching logic") 0199 { 0200 auto model = EffectTreeModel::construct("", nullptr); 0201 EffectFilter filter{nullptr}; 0202 // rootData copied from effecttreemodel.cpp 0203 QList<QVariant> rootData{"Name", "ID", "Type", "isFav"}; 0204 0205 SECTION("Handles basic alphanum search") 0206 { 0207 auto item = TreeItem::construct(rootData, model, true); 0208 item->setData(AssetTreeModel::IdCol, "Hello World"); 0209 item->setData(AssetTreeModel::NameCol, "This is K-denlive"); 0210 0211 filter.setFilterName(true, "wORL"); 0212 CHECK(filter.filterName(item) == true); 0213 0214 // should not search across spaces 0215 filter.setFilterName(true, "Thisis"); 0216 CHECK(filter.filterName(item) == false); 0217 0218 // should ignore punctuation 0219 filter.setFilterName(true, "Kden"); 0220 CHECK(filter.filterName(item) == true); 0221 } 0222 0223 // Tests for bug 432699 where filtering the effects list in Chinese fails 0224 // because the filter only works for ascii alphanum characters. 0225 SECTION("Handles Chinese/Japanese search") 0226 { 0227 auto item = TreeItem::construct(rootData, model, true); 0228 item->setData(AssetTreeModel::IdCol, "静音"); 0229 item->setData(AssetTreeModel::NameCol, "ミュート"); 0230 0231 filter.setFilterName(true, "音"); 0232 CHECK(filter.filterName(item) == true); 0233 filter.setFilterName(true, "默"); 0234 CHECK(filter.filterName(item) == false); 0235 0236 // should ignore punctuation 0237 filter.setFilterName(true, "ミュ"); 0238 CHECK(filter.filterName(item) == true); 0239 } 0240 0241 SECTION("Ignores diacritics") 0242 { 0243 auto item = TreeItem::construct(rootData, model, true); 0244 item->setData(AssetTreeModel::IdCol, "rgb parade"); 0245 item->setData(AssetTreeModel::NameCol, "Défilé RVB"); 0246 0247 // should be able to search with and without diacritics 0248 filter.setFilterName(true, "defile"); 0249 CHECK(filter.filterName(item) == true); 0250 filter.setFilterName(true, "ilé"); 0251 CHECK(filter.filterName(item) == true); 0252 } 0253 }