File indexing completed on 2024-12-08 07:28:46
0001 /* 0002 SPDX-FileCopyrightText: 2018-2022 Jean-Baptiste Mardelle <jb@kdenlive.org> 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 #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" 0010 #pragma GCC diagnostic push 0011 #include "bin/model/markerlistmodel.hpp" 0012 #include "bin/projectclip.h" 0013 #include "bin/projectfolder.h" 0014 #include "bin/projectitemmodel.h" 0015 #include "core.h" 0016 #include "doc/docundostack.hpp" 0017 #include "doc/kdenlivedoc.h" 0018 #include "fakeit.hpp" 0019 #include "project/projectmanager.h" 0020 #include "timeline2/model/clipmodel.hpp" 0021 #include "timeline2/model/groupsmodel.hpp" 0022 #include "timeline2/model/timelineitemmodel.hpp" 0023 #include "timeline2/model/timelinemodel.hpp" 0024 #include "timeline2/model/trackmodel.hpp" 0025 #include <iostream> 0026 #include <mlt++/MltProducer.h> 0027 #include <mlt++/MltProfile.h> 0028 #include <unordered_set> 0029 0030 TEST_CASE("Functional test of the group hierarchy", "[GroupsModel]") 0031 { 0032 auto binModel = pCore->projectItemModel(); 0033 binModel->clean(); 0034 std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr); 0035 // Here we do some trickery to enable testing. 0036 // We mock the project class so that the undoStack function returns our undoStack 0037 0038 // Create document 0039 KdenliveDoc document(undoStack); 0040 pCore->projectManager()->m_project = &document; 0041 std::function<bool(void)> undo = []() { return true; }; 0042 std::function<bool(void)> redo = []() { return true; }; 0043 QDateTime documentDate = QDateTime::currentDateTime(); 0044 pCore->projectManager()->updateTimeline(false, QString(), QString(), documentDate, 0); 0045 auto timeline = document.getTimeline(document.uuid()); 0046 pCore->projectManager()->m_activeTimelineModel = timeline; 0047 pCore->projectManager()->testSetActiveDocument(&document, timeline); 0048 KdenliveDoc::next_id = 0; 0049 0050 GroupsModel groups(timeline); 0051 for (int i = 0; i < 10; i++) { 0052 groups.createGroupItem(i); 0053 } 0054 0055 SECTION("Test Basic Creation") 0056 { 0057 for (int i = 0; i < 10; i++) { 0058 REQUIRE(groups.getRootId(i) == i); 0059 REQUIRE(groups.isLeaf(i)); 0060 REQUIRE(groups.getLeaves(i).size() == 1); 0061 REQUIRE(groups.getSubtree(i).size() == 1); 0062 } 0063 } 0064 0065 groups.setGroup(0, 1); 0066 groups.setGroup(1, 2); 0067 groups.setGroup(3, 2); 0068 groups.setGroup(9, 3); 0069 groups.setGroup(6, 3); 0070 groups.setGroup(4, 3); 0071 groups.setGroup(7, 3); 0072 groups.setGroup(8, 5); 0073 0074 SECTION("Test leaf nodes") 0075 { 0076 std::unordered_set<int> nodes = {1, 2, 3, 5}; 0077 for (int i = 0; i < 10; i++) { 0078 REQUIRE(groups.isLeaf(i) != (nodes.count(i) > 0)); 0079 if (nodes.count(i) == 0) { 0080 REQUIRE(groups.getSubtree(i) == std::unordered_set<int>({i})); 0081 REQUIRE(groups.getLeaves(i) == std::unordered_set<int>({i})); 0082 } 0083 } 0084 } 0085 0086 SECTION("Test leaves retrieving") 0087 { 0088 REQUIRE(groups.getLeaves(2) == std::unordered_set<int>({0, 4, 6, 7, 9})); 0089 REQUIRE(groups.getLeaves(3) == std::unordered_set<int>({4, 6, 7, 9})); 0090 REQUIRE(groups.getLeaves(1) == std::unordered_set<int>({0})); 0091 REQUIRE(groups.getLeaves(5) == std::unordered_set<int>({8})); 0092 } 0093 0094 SECTION("Test subtree retrieving") 0095 { 0096 REQUIRE(groups.getSubtree(2) == std::unordered_set<int>({0, 1, 2, 3, 4, 6, 7, 9})); 0097 REQUIRE(groups.getSubtree(3) == std::unordered_set<int>({3, 4, 6, 7, 9})); 0098 REQUIRE(groups.getSubtree(5) == std::unordered_set<int>({5, 8})); 0099 } 0100 0101 SECTION("Test root retrieving") 0102 { 0103 std::set<int> first_tree = {0, 1, 2, 3, 4, 6, 7, 9}; 0104 for (int n : first_tree) { 0105 CAPTURE(n); 0106 REQUIRE(groups.getRootId(n) == 2); 0107 } 0108 std::unordered_set<int> second_tree = {5, 8}; 0109 for (int n : second_tree) { 0110 REQUIRE(groups.getRootId(n) == 5); 0111 } 0112 } 0113 0114 groups.setGroup(3, 8); 0115 SECTION("Test leaf nodes 2") 0116 { 0117 std::unordered_set<int> nodes = {1, 2, 3, 5, 8}; 0118 for (int i = 0; i < 10; i++) { 0119 REQUIRE(groups.isLeaf(i) != (nodes.count(i) > 0)); 0120 if (nodes.count(i) == 0) { 0121 REQUIRE(groups.getSubtree(i) == std::unordered_set<int>({i})); 0122 REQUIRE(groups.getLeaves(i) == std::unordered_set<int>({i})); 0123 } 0124 } 0125 } 0126 0127 SECTION("Test leaves retrieving 2") 0128 { 0129 REQUIRE(groups.getLeaves(1) == std::unordered_set<int>({0})); 0130 REQUIRE(groups.getLeaves(2) == std::unordered_set<int>({0})); 0131 REQUIRE(groups.getLeaves(3) == std::unordered_set<int>({4, 6, 7, 9})); 0132 REQUIRE(groups.getLeaves(5) == std::unordered_set<int>({4, 6, 7, 9})); 0133 REQUIRE(groups.getLeaves(8) == std::unordered_set<int>({4, 6, 7, 9})); 0134 } 0135 0136 SECTION("Test subtree retrieving 2") 0137 { 0138 REQUIRE(groups.getSubtree(2) == std::unordered_set<int>({0, 1, 2})); 0139 REQUIRE(groups.getSubtree(3) == std::unordered_set<int>({3, 4, 6, 7, 9})); 0140 REQUIRE(groups.getSubtree(5) == std::unordered_set<int>({5, 8, 3, 4, 6, 7, 9})); 0141 } 0142 0143 SECTION("Test root retrieving 2") 0144 { 0145 std::set<int> first_tree = {0, 1, 2}; 0146 for (int n : first_tree) { 0147 CAPTURE(n); 0148 REQUIRE(groups.getRootId(n) == 2); 0149 } 0150 std::unordered_set<int> second_tree = {5, 8, 3, 4, 6, 7, 9}; 0151 for (int n : second_tree) { 0152 REQUIRE(groups.getRootId(n) == 5); 0153 } 0154 } 0155 0156 groups.setGroup(5, 2); 0157 SECTION("Test leaf nodes 3") 0158 { 0159 std::unordered_set<int> nodes = {1, 2, 3, 5, 8}; 0160 for (int i = 0; i < 10; i++) { 0161 REQUIRE(groups.isLeaf(i) != (nodes.count(i) > 0)); 0162 if (nodes.count(i) == 0) { 0163 REQUIRE(groups.getSubtree(i) == std::unordered_set<int>({i})); 0164 REQUIRE(groups.getLeaves(i) == std::unordered_set<int>({i})); 0165 } 0166 } 0167 } 0168 0169 SECTION("Test leaves retrieving 3") 0170 { 0171 REQUIRE(groups.getLeaves(1) == std::unordered_set<int>({0})); 0172 REQUIRE(groups.getLeaves(2) == std::unordered_set<int>({0, 4, 6, 7, 9})); 0173 REQUIRE(groups.getLeaves(3) == std::unordered_set<int>({4, 6, 7, 9})); 0174 REQUIRE(groups.getLeaves(5) == std::unordered_set<int>({4, 6, 7, 9})); 0175 REQUIRE(groups.getLeaves(8) == std::unordered_set<int>({4, 6, 7, 9})); 0176 } 0177 0178 SECTION("Test subtree retrieving 3") 0179 { 0180 REQUIRE(groups.getSubtree(2) == std::unordered_set<int>({0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); 0181 REQUIRE(groups.getSubtree(3) == std::unordered_set<int>({3, 4, 6, 7, 9})); 0182 REQUIRE(groups.getSubtree(5) == std::unordered_set<int>({5, 8, 3, 4, 6, 7, 9})); 0183 } 0184 0185 SECTION("Test root retrieving 3") 0186 { 0187 for (int i = 0; i < 10; i++) { 0188 CAPTURE(i); 0189 REQUIRE(groups.getRootId(i) == 2); 0190 } 0191 } 0192 0193 groups.destructGroupItem(8, false, undo, redo); 0194 SECTION("Test leaf nodes 4") 0195 { 0196 std::unordered_set<int> nodes = {1, 2, 3}; 0197 for (int i = 0; i < 10; i++) { 0198 if (i == 8) continue; 0199 REQUIRE(groups.isLeaf(i) != (nodes.count(i) > 0)); 0200 if (nodes.count(i) == 0) { 0201 REQUIRE(groups.getSubtree(i) == std::unordered_set<int>({i})); 0202 REQUIRE(groups.getLeaves(i) == std::unordered_set<int>({i})); 0203 } 0204 } 0205 } 0206 0207 SECTION("Test leaves retrieving 4") 0208 { 0209 REQUIRE(groups.getLeaves(1) == std::unordered_set<int>({0})); 0210 REQUIRE(groups.getLeaves(2) == std::unordered_set<int>({0, 5})); 0211 REQUIRE(groups.getLeaves(3) == std::unordered_set<int>({4, 6, 7, 9})); 0212 } 0213 0214 SECTION("Test subtree retrieving 4") 0215 { 0216 REQUIRE(groups.getSubtree(2) == std::unordered_set<int>({0, 1, 2, 5})); 0217 REQUIRE(groups.getSubtree(3) == std::unordered_set<int>({3, 4, 6, 7, 9})); 0218 REQUIRE(groups.getSubtree(5) == std::unordered_set<int>({5})); 0219 } 0220 0221 SECTION("Test root retrieving 4") 0222 { 0223 std::set<int> first_tree = {0, 1, 2, 5}; 0224 for (int n : first_tree) { 0225 CAPTURE(n); 0226 REQUIRE(groups.getRootId(n) == 2); 0227 } 0228 std::unordered_set<int> second_tree = {3, 4, 6, 7, 9}; 0229 for (int n : second_tree) { 0230 CAPTURE(n); 0231 REQUIRE(groups.getRootId(n) == 3); 0232 } 0233 } 0234 pCore->projectManager()->closeCurrentDocument(false, false); 0235 } 0236 0237 TEST_CASE("Interface test of the group hierarchy", "[GroupsModel]") 0238 { 0239 auto binModel = pCore->projectItemModel(); 0240 binModel->clean(); 0241 std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr); 0242 0243 // Create document 0244 KdenliveDoc document(undoStack); 0245 pCore->projectManager()->m_project = &document; 0246 std::function<bool(void)> undo = []() { return true; }; 0247 std::function<bool(void)> redo = []() { return true; }; 0248 QDateTime documentDate = QDateTime::currentDateTime(); 0249 pCore->projectManager()->updateTimeline(false, QString(), QString(), documentDate, 0); 0250 auto timeline = document.getTimeline(document.uuid()); 0251 pCore->projectManager()->m_activeTimelineModel = timeline; 0252 pCore->projectManager()->testSetActiveDocument(&document, timeline); 0253 0254 GroupsModel groups(timeline); 0255 0256 for (int i = 0; i < 10; i++) { 0257 groups.createGroupItem(i); 0258 // the following call shouldn't do anything, but we test that behaviour too. 0259 groups.ungroupItem(i, undo, redo); 0260 REQUIRE(groups.getRootId(i) == i); 0261 REQUIRE(groups.isLeaf(i)); 0262 REQUIRE(groups.getLeaves(i).size() == 1); 0263 REQUIRE(groups.getSubtree(i).size() == 1); 0264 REQUIRE(groups.checkConsistency(false)); 0265 } 0266 KdenliveDoc::next_id = 10; 0267 0268 auto g1 = std::unordered_set<int>({4, 6, 7, 9}); 0269 int gid1 = groups.groupItems(g1, undo, redo); 0270 0271 SECTION("One single group") 0272 { 0273 for (int i = 0; i < 10; i++) { 0274 if (g1.count(i) > 0) { 0275 REQUIRE(groups.getRootId(i) == gid1); 0276 } else { 0277 REQUIRE(groups.getRootId(i) == i); 0278 } 0279 REQUIRE(groups.getSubtree(i) == std::unordered_set<int>({i})); 0280 REQUIRE(groups.getLeaves(i) == std::unordered_set<int>({i})); 0281 } 0282 REQUIRE(groups.getLeaves(gid1) == g1); 0283 auto g1b = g1; 0284 g1b.insert(gid1); 0285 REQUIRE(groups.getSubtree(gid1) == g1b); 0286 REQUIRE(groups.checkConsistency(false)); 0287 } 0288 SECTION("Twice the same group") 0289 { 0290 int old_gid1 = gid1; 0291 gid1 = groups.groupItems(g1, undo, redo); // recreate the same group (will create a parent with the old group as only element) 0292 for (int i = 0; i < 10; i++) { 0293 if (g1.count(i) > 0) { 0294 REQUIRE(groups.getRootId(i) == gid1); 0295 } else { 0296 REQUIRE(groups.getRootId(i) == i); 0297 } 0298 REQUIRE(groups.getSubtree(i) == std::unordered_set<int>({i})); 0299 REQUIRE(groups.getLeaves(i) == std::unordered_set<int>({i})); 0300 } 0301 REQUIRE(groups.getLeaves(gid1) == g1); 0302 REQUIRE(groups.getLeaves(old_gid1) == g1); 0303 auto g1b = g1; 0304 g1b.insert(old_gid1); 0305 REQUIRE(groups.getSubtree(old_gid1) == g1b); 0306 g1b.insert(gid1); 0307 REQUIRE(groups.getSubtree(gid1) == g1b); 0308 REQUIRE(groups.checkConsistency(false)); 0309 } 0310 0311 auto g2 = std::unordered_set<int>({3, 5, 7}); 0312 int gid2 = groups.groupItems(g2, undo, redo); 0313 auto all_g2 = g2; 0314 all_g2.insert(4); 0315 all_g2.insert(6); 0316 all_g2.insert(9); 0317 0318 SECTION("Heterogeneous group") 0319 { 0320 for (int i = 0; i < 10; i++) { 0321 CAPTURE(i); 0322 if (all_g2.count(i) > 0) { 0323 REQUIRE(groups.getRootId(i) == gid2); 0324 } else { 0325 REQUIRE(groups.getRootId(i) == i); 0326 } 0327 REQUIRE(groups.getSubtree(i) == std::unordered_set<int>({i})); 0328 REQUIRE(groups.getLeaves(i) == std::unordered_set<int>({i})); 0329 } 0330 REQUIRE(groups.getLeaves(gid1) == g1); 0331 REQUIRE(groups.getLeaves(gid2) == all_g2); 0332 REQUIRE(groups.checkConsistency(false)); 0333 } 0334 0335 auto g3 = std::unordered_set<int>({0, 1}); 0336 int gid3 = groups.groupItems(g3, undo, redo); 0337 0338 auto g4 = std::unordered_set<int>({0, 4}); 0339 int gid4 = groups.groupItems(g4, undo, redo); 0340 auto all_g4 = all_g2; 0341 for (int i : g3) 0342 all_g4.insert(i); 0343 0344 SECTION("Group of group") 0345 { 0346 for (int i = 0; i < 10; i++) { 0347 CAPTURE(i); 0348 if (all_g4.count(i) > 0) { 0349 REQUIRE(groups.getRootId(i) == gid4); 0350 } else { 0351 REQUIRE(groups.getRootId(i) == i); 0352 } 0353 REQUIRE(groups.getSubtree(i) == std::unordered_set<int>({i})); 0354 REQUIRE(groups.getLeaves(i) == std::unordered_set<int>({i})); 0355 } 0356 REQUIRE(groups.getLeaves(gid1) == g1); 0357 REQUIRE(groups.getLeaves(gid2) == all_g2); 0358 REQUIRE(groups.getLeaves(gid3) == g3); 0359 REQUIRE(groups.getLeaves(gid4) == all_g4); 0360 REQUIRE(groups.checkConsistency(false)); 0361 } 0362 0363 // the following should delete g4 0364 groups.ungroupItem(3, undo, redo); 0365 0366 SECTION("Ungroup") 0367 { 0368 for (int i = 0; i < 10; i++) { 0369 CAPTURE(i); 0370 if (all_g2.count(i) > 0) { 0371 REQUIRE(groups.getRootId(i) == gid2); 0372 } else if (g3.count(i) > 0) { 0373 REQUIRE(groups.getRootId(i) == gid3); 0374 } else { 0375 REQUIRE(groups.getRootId(i) == i); 0376 } 0377 REQUIRE(groups.getSubtree(i) == std::unordered_set<int>({i})); 0378 REQUIRE(groups.getLeaves(i) == std::unordered_set<int>({i})); 0379 } 0380 REQUIRE(groups.getLeaves(gid1) == g1); 0381 REQUIRE(groups.checkConsistency(false)); 0382 REQUIRE(groups.getLeaves(gid2) == all_g2); 0383 REQUIRE(groups.getLeaves(gid3) == g3); 0384 } 0385 pCore->projectManager()->closeCurrentDocument(false, false); 0386 } 0387 0388 TEST_CASE("Orphan groups deletion", "[GroupsModel]") 0389 { 0390 auto binModel = pCore->projectItemModel(); 0391 binModel->clean(); 0392 std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr); 0393 0394 // Create document 0395 KdenliveDoc document(undoStack); 0396 pCore->projectManager()->m_project = &document; 0397 std::function<bool(void)> undo = []() { return true; }; 0398 std::function<bool(void)> redo = []() { return true; }; 0399 QDateTime documentDate = QDateTime::currentDateTime(); 0400 pCore->projectManager()->updateTimeline(false, QString(), QString(), documentDate, 0); 0401 auto timeline = document.getTimeline(document.uuid()); 0402 pCore->projectManager()->m_activeTimelineModel = timeline; 0403 pCore->projectManager()->testSetActiveDocument(&document, timeline); 0404 0405 KdenliveDoc::next_id = 0; 0406 GroupsModel groups(timeline); 0407 0408 for (int i = 0; i < 4; i++) { 0409 groups.createGroupItem(i); 0410 } 0411 KdenliveDoc::next_id = 5; 0412 auto g1 = std::unordered_set<int>({0, 1}); 0413 int gid1 = groups.groupItems(g1, undo, redo); 0414 0415 auto g2 = std::unordered_set<int>({2, 3}); 0416 int gid2 = groups.groupItems(g2, undo, redo); 0417 Q_UNUSED(gid2); 0418 0419 auto g3 = std::unordered_set<int>({0, 3}); 0420 int gid3 = groups.groupItems(g3, undo, redo); 0421 0422 REQUIRE(groups.getLeaves(gid3) == std::unordered_set<int>({0, 1, 2, 3})); 0423 REQUIRE(groups.checkConsistency(false)); 0424 0425 groups.destructGroupItem(0, true, undo, redo); 0426 0427 REQUIRE(groups.getLeaves(gid3) == std::unordered_set<int>({1, 2, 3})); 0428 REQUIRE(groups.checkConsistency(false)); 0429 0430 SECTION("Normal deletion") 0431 { 0432 groups.destructGroupItem(1, false, undo, redo); 0433 0434 REQUIRE(groups.getLeaves(gid3) == std::unordered_set<int>({gid1, 2, 3})); 0435 REQUIRE(groups.checkConsistency(false)); 0436 0437 groups.destructGroupItem(gid1, true, undo, redo); 0438 0439 REQUIRE(groups.getLeaves(gid3) == std::unordered_set<int>({2, 3})); 0440 REQUIRE(groups.checkConsistency(false)); 0441 } 0442 0443 SECTION("Cascade deletion") 0444 { 0445 groups.destructGroupItem(1, true, undo, redo); 0446 0447 REQUIRE(groups.getLeaves(gid3) == std::unordered_set<int>({2, 3})); 0448 REQUIRE(groups.checkConsistency(false)); 0449 0450 groups.destructGroupItem(2, true, undo, redo); 0451 0452 REQUIRE(groups.getLeaves(gid3) == std::unordered_set<int>({3})); 0453 REQUIRE(groups.checkConsistency(false)); 0454 0455 REQUIRE(groups.m_downLink.count(gid3) > 0); 0456 groups.destructGroupItem(3, true, undo, redo); 0457 REQUIRE(groups.m_downLink.count(gid3) == 0); 0458 REQUIRE(groups.m_downLink.size() == 0); 0459 REQUIRE(groups.checkConsistency(false)); 0460 } 0461 pCore->projectManager()->closeCurrentDocument(false, false); 0462 } 0463 0464 TEST_CASE("Integration with timeline", "[GroupsModel]") 0465 { 0466 qDebug() << "STARTING PASS"; 0467 auto binModel = pCore->projectItemModel(); 0468 binModel->clean(); 0469 std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr); 0470 0471 // Create document 0472 KdenliveDoc document(undoStack); 0473 pCore->projectManager()->m_project = &document; 0474 std::function<bool(void)> undo = []() { return true; }; 0475 std::function<bool(void)> redo = []() { return true; }; 0476 QDateTime documentDate = QDateTime::currentDateTime(); 0477 const QUuid uuid1 = document.uuid(); 0478 pCore->projectManager()->updateTimeline(false, QString(), QString(), documentDate, 0); 0479 auto timeline = document.getTimeline(uuid1); 0480 pCore->projectManager()->m_activeTimelineModel = timeline; 0481 pCore->projectManager()->testSetActiveDocument(&document, timeline); 0482 0483 // Create a new sequence clip 0484 std::pair<int, int> tracks = {2, 2}; 0485 const QString seqId = ClipCreator::createPlaylistClip(QStringLiteral("Seq 2"), tracks, QStringLiteral("-1"), binModel); 0486 REQUIRE(seqId != QLatin1String("-1")); 0487 0488 // Now use the new timeline sequence 0489 QUuid uuid2; 0490 QMap<QUuid, QString> allSequences = binModel->getAllSequenceClips(); 0491 QMapIterator<QUuid, QString> i(allSequences); 0492 while (i.hasNext()) { 0493 // Find clips with the tag 0494 i.next(); 0495 if (i.value() == seqId) { 0496 uuid2 = i.key(); 0497 } 0498 } 0499 auto timeline2 = document.getTimeline(uuid2); 0500 0501 /*TimelineItemModel tim(mockedDoc.uuid(), pCore->getProjectProfile(), undoStack); 0502 Mock<TimelineItemModel> timMock(tim); 0503 auto timeline = std::shared_ptr<TimelineItemModel>(&timMock.get(), [](...) {}); 0504 TimelineItemModel::finishConstruct(timeline); 0505 0506 QUuid uuid2 = QUuid::createUuid(); 0507 TimelineItemModel tim2(uuid2, pCore->getProjectProfile(), undoStack); 0508 Mock<TimelineItemModel> timMock2(tim2); 0509 auto timeline2 = std::shared_ptr<TimelineItemModel>(&timMock2.get(), [](...) {}); 0510 TimelineItemModel::finishConstruct(timeline2); 0511 mockedDoc.addTimeline(uuid2, timeline2); 0512 0513 RESET(timMock2);*/ 0514 0515 QString binId = createProducer(pCore->getProjectProfile(), "red", binModel); 0516 QString binId2 = createProducerWithSound(pCore->getProjectProfile(), binModel); 0517 0518 int length = binModel->getClipByBinID(binId)->frameDuration(); 0519 GroupsModel groups(timeline); 0520 0521 std::vector<int> clips; 0522 for (int i = 0; i < 4; i++) { 0523 if (i % 2 == 0) { 0524 clips.push_back(ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly)); 0525 } else { 0526 clips.push_back(ClipModel::construct(timeline, binId, -1, PlaylistState::AudioOnly)); 0527 timeline->m_allClips[clips.back()]->m_canBeAudio = true; 0528 } 0529 } 0530 std::vector<int> clips2; 0531 for (int i = 0; i < 4; i++) { 0532 if (i % 2 == 0) { 0533 clips2.push_back(ClipModel::construct(timeline2, binId, -1, PlaylistState::VideoOnly)); 0534 } else { 0535 clips2.push_back(ClipModel::construct(timeline2, binId, -1, PlaylistState::AudioOnly)); 0536 timeline2->m_allClips[clips2.back()]->m_canBeAudio = true; 0537 } 0538 } 0539 int tid1 = TrackModel::construct(timeline); 0540 int tid2 = TrackModel::construct(timeline); 0541 Q_UNUSED(tid2); 0542 int tid3 = TrackModel::construct(timeline, -1, -1, QStringLiteral("audio"), true); 0543 0544 int tid1_2 = TrackModel::construct(timeline2); 0545 int tid2_2 = TrackModel::construct(timeline2); 0546 Q_UNUSED(tid2_2); 0547 int tid3_2 = TrackModel::construct(timeline2, -1, -1, QStringLiteral("audio2"), true); 0548 0549 int init_index = undoStack->index(); 0550 pCore->projectManager()->setActiveTimeline(uuid2); 0551 REQUIRE(timeline2->checkConsistency()); 0552 pCore->projectManager()->setActiveTimeline(uuid1); 0553 SECTION("Basic Creation and export/import from json") 0554 { 0555 auto check_roots = [&](int r1, int r2, int r3, int r4) { 0556 REQUIRE(timeline->m_groups->getRootId(clips[0]) == r1); 0557 REQUIRE(timeline->m_groups->getRootId(clips[1]) == r2); 0558 REQUIRE(timeline->m_groups->getRootId(clips[2]) == r3); 0559 REQUIRE(timeline->m_groups->getRootId(clips[3]) == r4); 0560 }; 0561 0562 // the following function is a recursive function to check the correctness of a json import 0563 // Basically, it takes as input a groupId in the imported (target) group hierarchy, and outputs the corresponding groupId from the original one. If no 0564 // match is found, it returns -1 0565 std::function<int(int)> rec_check; 0566 rec_check = [&](int gid) { 0567 // we first check if the gid is a leaf 0568 if (timeline2->m_groups->isLeaf(gid)) { 0569 // then it must be a clip/composition 0570 int found = -1; 0571 for (int i = 0; i < 4; i++) { 0572 if (clips2[i] == gid) { 0573 found = i; 0574 break; 0575 } 0576 } 0577 if (found != -1) { 0578 return clips[found]; 0579 } else { 0580 qDebug() << "ERROR: did not find correspondence for group" << gid; 0581 } 0582 } else { 0583 // we find correspondences of all the children 0584 auto children = timeline2->m_groups->getDirectChildren(gid); 0585 std::unordered_set<int> corresp; 0586 for (int c : children) { 0587 corresp.insert(rec_check(c)); 0588 } 0589 if (corresp.count(-1) > 0) { 0590 return -1; // something went wrong 0591 } 0592 std::unordered_set<int> parents; 0593 for (int c : corresp) { 0594 // we find the parents of the corresponding groups in the original hierarchy 0595 parents.insert(timeline->m_groups->m_upLink[c]); 0596 } 0597 // if the matching is correct, we should have found only one parent 0598 if (parents.size() != 1) { 0599 return -1; // something went wrong 0600 } 0601 return *parents.begin(); 0602 } 0603 return -1; 0604 }; 0605 auto checkJsonParsing = [&]() { 0606 // we first destroy all groups in target timeline 0607 Fun undo = []() { return true; }; 0608 Fun redo = []() { return true; }; 0609 for (int i = 0; i < 4; i++) { 0610 while (timeline2->m_groups->getRootId(clips2[i]) != clips2[i]) { 0611 timeline2->m_groups->ungroupItem(clips2[i], undo, redo); 0612 } 0613 } 0614 // we do the export then import 0615 REQUIRE(timeline2->m_groups->fromJson(timeline->m_groups->toJson())); 0616 std::unordered_map<int, int> roots; 0617 for (int i = 0; i < 4; i++) { 0618 int r = timeline2->m_groups->getRootId(clips2[i]); 0619 if (roots.count(r) == 0) { 0620 roots[r] = rec_check(r); 0621 REQUIRE(roots[r] != -1); 0622 } 0623 } 0624 for (int i = 0; i < 4; i++) { 0625 int r = timeline->m_groups->getRootId(clips[i]); 0626 int r2 = timeline2->m_groups->getRootId(clips2[i]); 0627 REQUIRE(roots[r2] == r); 0628 } 0629 pCore->projectManager()->setActiveTimeline(uuid1); 0630 REQUIRE(timeline->checkConsistency()); 0631 pCore->projectManager()->setActiveTimeline(uuid2); 0632 REQUIRE(timeline2->checkConsistency()); 0633 }; 0634 pCore->projectManager()->setActiveTimeline(uuid1); 0635 REQUIRE(timeline->checkConsistency()); 0636 pCore->projectManager()->setActiveTimeline(uuid2); 0637 REQUIRE(timeline2->checkConsistency()); 0638 auto g1 = std::unordered_set<int>({clips[0], clips[1]}); 0639 int gid1, gid2, gid3; 0640 // this fails because clips are not inserted 0641 pCore->projectManager()->setActiveTimeline(uuid1); 0642 REQUIRE(timeline->requestClipsGroup(g1) == -1); 0643 REQUIRE(timeline->checkConsistency()); 0644 pCore->projectManager()->setActiveTimeline(uuid2); 0645 REQUIRE(timeline2->checkConsistency()); 0646 0647 for (int i = 0; i < 4; i++) { 0648 REQUIRE(timeline->requestClipMove(clips[i], (i % 2 == 0) ? tid1 : tid3, i * length)); 0649 } 0650 for (int i = 0; i < 4; i++) { 0651 REQUIRE(timeline2->requestClipMove(clips2[i], (i % 2 == 0) ? tid1_2 : tid3_2, i * length)); 0652 } 0653 pCore->projectManager()->setActiveTimeline(uuid1); 0654 REQUIRE(timeline->checkConsistency()); 0655 pCore->projectManager()->setActiveTimeline(uuid2); 0656 REQUIRE(timeline2->checkConsistency()); 0657 init_index = undoStack->index(); 0658 REQUIRE(timeline->requestClipsGroup(g1, true, GroupType::Normal) > 0); 0659 auto state1 = [&]() { 0660 gid1 = timeline->m_groups->getRootId(clips[0]); 0661 check_roots(gid1, gid1, clips[2], clips[3]); 0662 REQUIRE(timeline->m_groups->getType(gid1) == GroupType::Normal); 0663 REQUIRE(timeline->m_groups->getSubtree(gid1) == std::unordered_set<int>({gid1, clips[0], clips[1]})); 0664 REQUIRE(timeline->m_groups->getLeaves(gid1) == std::unordered_set<int>({clips[0], clips[1]})); 0665 REQUIRE(undoStack->index() == init_index + 1); 0666 pCore->projectManager()->setActiveTimeline(uuid1); 0667 REQUIRE(timeline->checkConsistency()); 0668 }; 0669 INFO("Test 1"); 0670 state1(); 0671 checkJsonParsing(); 0672 state1(); 0673 0674 auto g2 = std::unordered_set<int>({clips[2], clips[3]}); 0675 REQUIRE(timeline->requestClipsGroup(g2, true, GroupType::AVSplit) > 0); 0676 auto state2 = [&]() { 0677 gid2 = timeline->m_groups->getRootId(clips[2]); 0678 check_roots(gid1, gid1, gid2, gid2); 0679 REQUIRE(timeline->m_groups->getType(gid1) == GroupType::Normal); 0680 REQUIRE(timeline->m_groups->getType(gid2) == GroupType::AVSplit); 0681 REQUIRE(timeline->m_groups->getSubtree(gid2) == std::unordered_set<int>({gid2, clips[2], clips[3]})); 0682 REQUIRE(timeline->m_groups->getLeaves(gid2) == std::unordered_set<int>({clips[2], clips[3]})); 0683 REQUIRE(timeline->m_groups->getSubtree(gid1) == std::unordered_set<int>({gid1, clips[0], clips[1]})); 0684 REQUIRE(timeline->m_groups->getLeaves(gid1) == std::unordered_set<int>({clips[0], clips[1]})); 0685 REQUIRE(undoStack->index() == init_index + 2); 0686 pCore->projectManager()->setActiveTimeline(uuid1); 0687 REQUIRE(timeline->checkConsistency()); 0688 }; 0689 INFO("Test 2"); 0690 checkJsonParsing(); 0691 state2(); 0692 0693 auto g3 = std::unordered_set<int>({clips[0], clips[3]}); 0694 REQUIRE(timeline->requestClipsGroup(g3, true, GroupType::Normal) > 0); 0695 auto state3 = [&]() { 0696 REQUIRE(undoStack->index() == init_index + 3); 0697 gid3 = timeline->m_groups->getRootId(clips[0]); 0698 check_roots(gid3, gid3, gid3, gid3); 0699 REQUIRE(timeline->m_groups->getType(gid1) == GroupType::Normal); 0700 REQUIRE(timeline->m_groups->getType(gid2) == GroupType::AVSplit); 0701 REQUIRE(timeline->m_groups->getType(gid3) == GroupType::Normal); 0702 REQUIRE(timeline->m_groups->getSubtree(gid3) == std::unordered_set<int>({gid1, clips[0], clips[1], gid3, gid2, clips[2], clips[3]})); 0703 REQUIRE(timeline->m_groups->getLeaves(gid3) == std::unordered_set<int>({clips[2], clips[3], clips[0], clips[1]})); 0704 REQUIRE(timeline->m_groups->getSubtree(gid2) == std::unordered_set<int>({gid2, clips[2], clips[3]})); 0705 REQUIRE(timeline->m_groups->getLeaves(gid2) == std::unordered_set<int>({clips[2], clips[3]})); 0706 REQUIRE(timeline->m_groups->getSubtree(gid1) == std::unordered_set<int>({gid1, clips[0], clips[1]})); 0707 REQUIRE(timeline->m_groups->getLeaves(gid1) == std::unordered_set<int>({clips[0], clips[1]})); 0708 pCore->projectManager()->setActiveTimeline(uuid1); 0709 REQUIRE(timeline->checkConsistency()); 0710 }; 0711 0712 INFO("Test 3"); 0713 checkJsonParsing(); 0714 state3(); 0715 0716 undoStack->undo(); 0717 INFO("Test 4"); 0718 checkJsonParsing(); 0719 state2(); 0720 undoStack->redo(); 0721 INFO("Test 5"); 0722 checkJsonParsing(); 0723 state3(); 0724 undoStack->undo(); 0725 INFO("Test 6"); 0726 checkJsonParsing(); 0727 state2(); 0728 undoStack->undo(); 0729 INFO("Test 8"); 0730 checkJsonParsing(); 0731 state1(); 0732 undoStack->undo(); 0733 INFO("Test 9"); 0734 checkJsonParsing(); 0735 check_roots(clips[0], clips[1], clips[2], clips[3]); 0736 undoStack->redo(); 0737 INFO("Test 10"); 0738 checkJsonParsing(); 0739 state1(); 0740 undoStack->redo(); 0741 INFO("Test 11"); 0742 checkJsonParsing(); 0743 state2(); 0744 0745 REQUIRE(timeline->requestClipsGroup(g3) > 0); 0746 checkJsonParsing(); 0747 state3(); 0748 0749 undoStack->undo(); 0750 checkJsonParsing(); 0751 state2(); 0752 0753 undoStack->undo(); 0754 checkJsonParsing(); 0755 state1(); 0756 undoStack->undo(); 0757 checkJsonParsing(); 0758 check_roots(clips[0], clips[1], clips[2], clips[3]); 0759 } 0760 0761 SECTION("Group deletion undo") 0762 { 0763 CAPTURE(clips[0]); 0764 CAPTURE(clips[1]); 0765 CAPTURE(clips[2]); 0766 CAPTURE(clips[3]); 0767 REQUIRE(timeline->requestClipMove(clips[0], tid1, 10)); 0768 REQUIRE(timeline->requestClipMove(clips[1], tid3, 10 + length)); 0769 REQUIRE(timeline->requestClipMove(clips[2], tid1, 15 + 2 * length)); 0770 REQUIRE(timeline->requestClipMove(clips[3], tid3, 50 + 3 * length)); 0771 0772 auto state0 = [&]() { 0773 REQUIRE(timeline->getTrackById(tid1)->checkConsistency()); 0774 REQUIRE(timeline->getTrackClipsCount(tid1) == 2); 0775 REQUIRE(timeline->getTrackClipsCount(tid3) == 2); 0776 REQUIRE(timeline->getClipsCount() == 4); 0777 REQUIRE(timeline->getClipTrackId(clips[0]) == tid1); 0778 REQUIRE(timeline->getClipTrackId(clips[2]) == tid1); 0779 REQUIRE(timeline->getClipTrackId(clips[1]) == tid3); 0780 REQUIRE(timeline->getClipTrackId(clips[3]) == tid3); 0781 REQUIRE(timeline->getClipPosition(clips[0]) == 10); 0782 REQUIRE(timeline->getClipPosition(clips[1]) == 10 + length); 0783 REQUIRE(timeline->getClipPosition(clips[2]) == 15 + 2 * length); 0784 REQUIRE(timeline->getClipPosition(clips[3]) == 50 + 3 * length); 0785 REQUIRE(timeline->checkConsistency()); 0786 }; 0787 0788 auto state = [&](int gid1, int gid2, int gid3) { 0789 state0(); 0790 REQUIRE(timeline->m_groups->getType(gid1) == GroupType::AVSplit); 0791 REQUIRE(timeline->m_groups->getType(gid2) == GroupType::AVSplit); 0792 REQUIRE(timeline->m_groups->getType(gid3) == GroupType::Normal); 0793 REQUIRE(timeline->checkConsistency()); 0794 }; 0795 state0(); 0796 auto g1 = std::unordered_set<int>({clips[0], clips[1]}); 0797 int gid1, gid2, gid3; 0798 gid1 = timeline->requestClipsGroup(g1, true, GroupType::AVSplit); 0799 REQUIRE(gid1 > 0); 0800 auto g2 = std::unordered_set<int>({clips[2], clips[3]}); 0801 gid2 = timeline->requestClipsGroup(g2, true, GroupType::AVSplit); 0802 REQUIRE(gid2 > 0); 0803 auto g3 = std::unordered_set<int>({clips[0], clips[3]}); 0804 gid3 = timeline->requestClipsGroup(g3, true, GroupType::Normal); 0805 REQUIRE(gid3 > 0); 0806 state(gid1, gid2, gid3); 0807 0808 std::vector<int> all_clips{clips[0], clips[2], clips[1], clips[3]}; 0809 for (int i = 0; i < 4; i++) { 0810 REQUIRE(timeline->requestItemDeletion(all_clips[i])); 0811 REQUIRE(timeline->getTrackClipsCount(tid1) == 0); 0812 REQUIRE(timeline->getTrackClipsCount(tid3) == 0); 0813 REQUIRE(timeline->getClipsCount() == 0); 0814 REQUIRE(timeline->getTrackById(tid1)->checkConsistency()); 0815 REQUIRE(timeline->getTrackById(tid3)->checkConsistency()); 0816 REQUIRE(timeline->checkConsistency()); 0817 0818 undoStack->undo(); 0819 state(gid1, gid2, gid3); 0820 undoStack->redo(); 0821 REQUIRE(timeline->getTrackClipsCount(tid1) == 0); 0822 REQUIRE(timeline->getTrackClipsCount(tid3) == 0); 0823 REQUIRE(timeline->getClipsCount() == 0); 0824 REQUIRE(timeline->checkConsistency()); 0825 undoStack->undo(); 0826 state(gid1, gid2, gid3); 0827 } 0828 // we undo the three grouping operations 0829 undoStack->undo(); 0830 state0(); 0831 undoStack->undo(); 0832 state0(); 0833 undoStack->undo(); 0834 state0(); 0835 } 0836 0837 SECTION("Group creation and query from timeline") 0838 { 0839 REQUIRE(timeline->requestClipMove(clips[0], tid1, 10)); 0840 REQUIRE(timeline->requestClipMove(clips[1], tid1, 10 + length)); 0841 REQUIRE(timeline->requestClipMove(clips[2], tid1, 15 + 2 * length)); 0842 REQUIRE(timeline->requestClipMove(clips[3], tid1, 50 + 3 * length)); 0843 auto state1 = [&]() { 0844 REQUIRE(timeline->getGroupElements(clips[2]) == std::unordered_set<int>({clips[2]})); 0845 REQUIRE(timeline->getGroupElements(clips[1]) == std::unordered_set<int>({clips[1]})); 0846 REQUIRE(timeline->getGroupElements(clips[3]) == std::unordered_set<int>({clips[3]})); 0847 REQUIRE(timeline->getGroupElements(clips[0]) == std::unordered_set<int>({clips[0]})); 0848 REQUIRE(timeline->checkConsistency()); 0849 }; 0850 state1(); 0851 0852 auto g1 = std::unordered_set<int>({clips[0], clips[3]}); 0853 REQUIRE(timeline->requestClipsGroup(g1) > 0); 0854 auto state2 = [&]() { 0855 REQUIRE(timeline->getGroupElements(clips[0]) == g1); 0856 REQUIRE(timeline->getGroupElements(clips[3]) == g1); 0857 REQUIRE(timeline->getGroupElements(clips[2]) == std::unordered_set<int>({clips[2]})); 0858 REQUIRE(timeline->getGroupElements(clips[1]) == std::unordered_set<int>({clips[1]})); 0859 REQUIRE(timeline->checkConsistency()); 0860 }; 0861 state2(); 0862 0863 undoStack->undo(); 0864 state1(); 0865 0866 undoStack->redo(); 0867 state2(); 0868 0869 undoStack->undo(); 0870 state1(); 0871 0872 undoStack->redo(); 0873 state2(); 0874 0875 auto g2 = std::unordered_set<int>({clips[2], clips[1]}); 0876 REQUIRE(timeline->requestClipsGroup(g2) > 0); 0877 auto state3 = [&]() { 0878 REQUIRE(timeline->getGroupElements(clips[0]) == g1); 0879 REQUIRE(timeline->getGroupElements(clips[3]) == g1); 0880 REQUIRE(timeline->getGroupElements(clips[2]) == g2); 0881 REQUIRE(timeline->getGroupElements(clips[1]) == g2); 0882 REQUIRE(timeline->checkConsistency()); 0883 }; 0884 state3(); 0885 0886 undoStack->undo(); 0887 state2(); 0888 0889 undoStack->redo(); 0890 state3(); 0891 0892 auto g3 = std::unordered_set<int>({clips[0], clips[1]}); 0893 REQUIRE(timeline->requestClipsGroup(g3) > 0); 0894 auto all_g = std::unordered_set<int>({clips[0], clips[1], clips[2], clips[3]}); 0895 auto state4 = [&]() { 0896 REQUIRE(timeline->getGroupElements(clips[0]) == all_g); 0897 REQUIRE(timeline->getGroupElements(clips[3]) == all_g); 0898 REQUIRE(timeline->getGroupElements(clips[2]) == all_g); 0899 REQUIRE(timeline->getGroupElements(clips[1]) == all_g); 0900 REQUIRE(timeline->checkConsistency()); 0901 }; 0902 state4(); 0903 0904 undoStack->undo(); 0905 state3(); 0906 0907 undoStack->redo(); 0908 state4(); 0909 0910 REQUIRE(timeline->requestClipUngroup(clips[0])); 0911 state3(); 0912 0913 undoStack->undo(); 0914 state4(); 0915 0916 REQUIRE(timeline->requestClipUngroup(clips[1])); 0917 state3(); 0918 0919 undoStack->undo(); 0920 state4(); 0921 0922 undoStack->redo(); 0923 state3(); 0924 0925 REQUIRE(timeline->requestClipUngroup(clips[0])); 0926 REQUIRE(timeline->getGroupElements(clips[2]) == g2); 0927 REQUIRE(timeline->getGroupElements(clips[1]) == g2); 0928 REQUIRE(timeline->getGroupElements(clips[3]) == std::unordered_set<int>({clips[3]})); 0929 REQUIRE(timeline->getGroupElements(clips[0]) == std::unordered_set<int>({clips[0]})); 0930 0931 REQUIRE(timeline->requestClipUngroup(clips[1])); 0932 state1(); 0933 } 0934 SECTION("Ungroup multiple groups") 0935 { 0936 REQUIRE(timeline->requestClipMove(clips[0], tid1, 10)); 0937 REQUIRE(timeline->requestClipMove(clips[1], tid1, 10 + length)); 0938 REQUIRE(timeline->requestClipMove(clips[2], tid1, 15 + 2 * length)); 0939 REQUIRE(timeline->requestClipMove(clips[3], tid1, 50 + 3 * length)); 0940 auto state1 = [&]() { 0941 REQUIRE(timeline->getGroupElements(clips[2]) == std::unordered_set<int>({clips[2]})); 0942 REQUIRE(timeline->getGroupElements(clips[1]) == std::unordered_set<int>({clips[1]})); 0943 REQUIRE(timeline->getGroupElements(clips[3]) == std::unordered_set<int>({clips[3]})); 0944 REQUIRE(timeline->getGroupElements(clips[0]) == std::unordered_set<int>({clips[0]})); 0945 REQUIRE(timeline->checkConsistency()); 0946 }; 0947 state1(); 0948 0949 auto g1 = std::unordered_set<int>({clips[0], clips[3]}); 0950 REQUIRE(timeline->requestClipsGroup(g1) > 0); 0951 auto g2 = std::unordered_set<int>({clips[1], clips[2]}); 0952 REQUIRE(timeline->requestClipsGroup(g2) > 0); 0953 auto state2 = [&]() { 0954 REQUIRE(timeline->getGroupElements(clips[0]) == g1); 0955 REQUIRE(timeline->getGroupElements(clips[3]) == g1); 0956 REQUIRE(timeline->getGroupElements(clips[1]) == g2); 0957 REQUIRE(timeline->getGroupElements(clips[2]) == g2); 0958 REQUIRE(timeline->checkConsistency()); 0959 }; 0960 state2(); 0961 0962 // ungroup clips from same group 0963 REQUIRE(timeline->requestClipsUngroup({clips[0], clips[3]})); 0964 auto state3 = [&]() { 0965 REQUIRE(timeline->getGroupElements(clips[3]) == std::unordered_set<int>({clips[3]})); 0966 REQUIRE(timeline->getGroupElements(clips[0]) == std::unordered_set<int>({clips[0]})); 0967 REQUIRE(timeline->getGroupElements(clips[1]) == g2); 0968 REQUIRE(timeline->getGroupElements(clips[2]) == g2); 0969 REQUIRE(timeline->checkConsistency()); 0970 }; 0971 state3(); 0972 0973 undoStack->undo(); 0974 state2(); 0975 0976 // ungroup clips from different groups 0977 REQUIRE(timeline->requestClipsUngroup({clips[0], clips[1]})); 0978 state1(); 0979 0980 undoStack->undo(); 0981 state2(); 0982 0983 // ungroup all clips 0984 REQUIRE(timeline->requestClipsUngroup({clips[0], clips[1], clips[2], clips[3]})); 0985 state1(); 0986 0987 undoStack->undo(); 0988 state2(); 0989 } 0990 pCore->projectManager()->closeCurrentDocument(false, false); 0991 } 0992 0993 TEST_CASE("Complex Functions", "[GroupsModel]") 0994 { 0995 auto binModel = pCore->projectItemModel(); 0996 binModel->clean(); 0997 std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr); 0998 0999 // Create document 1000 KdenliveDoc document(undoStack); 1001 pCore->projectManager()->m_project = &document; 1002 std::function<bool(void)> undo = []() { return true; }; 1003 std::function<bool(void)> redo = []() { return true; }; 1004 QDateTime documentDate = QDateTime::currentDateTime(); 1005 pCore->projectManager()->updateTimeline(false, QString(), QString(), documentDate, 0); 1006 auto timeline = document.getTimeline(document.uuid()); 1007 pCore->projectManager()->m_activeTimelineModel = timeline; 1008 pCore->projectManager()->testSetActiveDocument(&document, timeline); 1009 KdenliveDoc::next_id = 0; 1010 1011 GroupsModel groups(timeline); 1012 1013 SECTION("MergeSingleGroups") 1014 { 1015 Fun undo = []() { return true; }; 1016 Fun redo = []() { return true; }; 1017 REQUIRE(groups.m_upLink.size() == 0); 1018 1019 for (int i = 0; i < 6; i++) { 1020 groups.createGroupItem(i); 1021 } 1022 groups.setGroup(0, 3); 1023 groups.setGroup(2, 4); 1024 groups.setGroup(3, 1); 1025 groups.setGroup(4, 1); 1026 groups.setGroup(5, 0); 1027 1028 auto test_tree = [&]() { 1029 REQUIRE(groups.getSubtree(1) == std::unordered_set<int>({0, 1, 2, 3, 4, 5})); 1030 REQUIRE(groups.getDirectChildren(0) == std::unordered_set<int>({5})); 1031 REQUIRE(groups.getDirectChildren(1) == std::unordered_set<int>({3, 4})); 1032 REQUIRE(groups.getDirectChildren(2) == std::unordered_set<int>({})); 1033 REQUIRE(groups.getDirectChildren(3) == std::unordered_set<int>({0})); 1034 REQUIRE(groups.getDirectChildren(4) == std::unordered_set<int>({2})); 1035 REQUIRE(groups.getDirectChildren(5) == std::unordered_set<int>({})); 1036 REQUIRE(groups.checkConsistency(false)); 1037 }; 1038 test_tree(); 1039 1040 REQUIRE(groups.mergeSingleGroups(1, undo, redo)); 1041 auto test_tree2 = [&]() { 1042 REQUIRE(groups.getSubtree(1) == std::unordered_set<int>({1, 2, 5})); 1043 REQUIRE(groups.getDirectChildren(1) == std::unordered_set<int>({2, 5})); 1044 REQUIRE(groups.getDirectChildren(2) == std::unordered_set<int>({})); 1045 REQUIRE(groups.getDirectChildren(5) == std::unordered_set<int>({})); 1046 REQUIRE(groups.checkConsistency()); 1047 }; 1048 test_tree2(); 1049 1050 undo(); 1051 test_tree(); 1052 1053 redo(); 1054 test_tree2(); 1055 } 1056 1057 SECTION("MergeSingleGroups2") 1058 { 1059 Fun undo = []() { return true; }; 1060 Fun redo = []() { return true; }; 1061 REQUIRE(groups.m_upLink.size() == 0); 1062 1063 for (int i = 0; i < 3; i++) { 1064 groups.createGroupItem(i); 1065 } 1066 groups.setGroup(1, 0); 1067 groups.setGroup(2, 1); 1068 1069 auto test_tree = [&]() { 1070 REQUIRE(groups.getSubtree(0) == std::unordered_set<int>({0, 1, 2})); 1071 REQUIRE(groups.getDirectChildren(0) == std::unordered_set<int>({1})); 1072 REQUIRE(groups.getDirectChildren(1) == std::unordered_set<int>({2})); 1073 REQUIRE(groups.getDirectChildren(2) == std::unordered_set<int>({})); 1074 REQUIRE(groups.checkConsistency(false)); 1075 }; 1076 test_tree(); 1077 1078 REQUIRE(groups.mergeSingleGroups(0, undo, redo)); 1079 auto test_tree2 = [&]() { 1080 REQUIRE(groups.getSubtree(2) == std::unordered_set<int>({2})); 1081 REQUIRE(groups.getDirectChildren(2) == std::unordered_set<int>({})); 1082 REQUIRE(groups.getRootId(2) == 2); 1083 REQUIRE(groups.checkConsistency()); 1084 }; 1085 test_tree2(); 1086 1087 undo(); 1088 test_tree(); 1089 1090 redo(); 1091 test_tree2(); 1092 } 1093 1094 SECTION("MergeSingleGroups3") 1095 { 1096 Fun undo = []() { return true; }; 1097 Fun redo = []() { return true; }; 1098 REQUIRE(groups.m_upLink.size() == 0); 1099 1100 for (int i = 0; i < 6; i++) { 1101 groups.createGroupItem(i); 1102 } 1103 groups.setGroup(0, 2); 1104 groups.setGroup(1, 0); 1105 groups.setGroup(3, 1); 1106 groups.setGroup(4, 1); 1107 groups.setGroup(5, 4); 1108 1109 auto test_tree = [&]() { 1110 for (int i = 0; i < 6; i++) { 1111 REQUIRE(groups.getRootId(i) == 2); 1112 } 1113 REQUIRE(groups.getSubtree(2) == std::unordered_set<int>({0, 1, 2, 3, 4, 5})); 1114 REQUIRE(groups.getDirectChildren(0) == std::unordered_set<int>({1})); 1115 REQUIRE(groups.getDirectChildren(1) == std::unordered_set<int>({4, 3})); 1116 REQUIRE(groups.getDirectChildren(2) == std::unordered_set<int>({0})); 1117 REQUIRE(groups.getDirectChildren(3) == std::unordered_set<int>({})); 1118 REQUIRE(groups.getDirectChildren(4) == std::unordered_set<int>({5})); 1119 REQUIRE(groups.getDirectChildren(5) == std::unordered_set<int>({})); 1120 REQUIRE(groups.checkConsistency(false)); 1121 }; 1122 test_tree(); 1123 1124 REQUIRE(groups.mergeSingleGroups(2, undo, redo)); 1125 auto test_tree2 = [&]() { 1126 REQUIRE(groups.getRootId(1) == 1); 1127 REQUIRE(groups.getRootId(3) == 1); 1128 REQUIRE(groups.getRootId(5) == 1); 1129 REQUIRE(groups.getSubtree(1) == std::unordered_set<int>({1, 3, 5})); 1130 REQUIRE(groups.getDirectChildren(1) == std::unordered_set<int>({3, 5})); 1131 REQUIRE(groups.getDirectChildren(3) == std::unordered_set<int>({})); 1132 REQUIRE(groups.getDirectChildren(5) == std::unordered_set<int>({})); 1133 REQUIRE(groups.checkConsistency()); 1134 }; 1135 test_tree2(); 1136 1137 undo(); 1138 test_tree(); 1139 1140 redo(); 1141 test_tree2(); 1142 } 1143 SECTION("Split leaf") 1144 { 1145 Fun undo = []() { return true; }; 1146 Fun redo = []() { return true; }; 1147 REQUIRE(groups.m_upLink.size() == 0); 1148 1149 // This is a dummy split criterion 1150 auto criterion = [](int a) { return a % 2 == 0; }; 1151 auto criterion2 = [](int a) { return a % 2 != 0; }; 1152 1153 // We create a leaf 1154 groups.createGroupItem(1); 1155 auto test_leaf = [&]() { 1156 REQUIRE(groups.getRootId(1) == 1); 1157 REQUIRE(groups.isLeaf(1)); 1158 REQUIRE(groups.m_upLink.size() == 1); 1159 REQUIRE(groups.checkConsistency()); 1160 }; 1161 test_leaf(); 1162 1163 REQUIRE(groups.split(1, criterion, undo, redo)); 1164 test_leaf(); 1165 undo(); 1166 test_leaf(); 1167 redo(); 1168 REQUIRE(groups.split(1, criterion2, undo, redo)); 1169 test_leaf(); 1170 undo(); 1171 test_leaf(); 1172 redo(); 1173 } 1174 SECTION("Simple split Tree") 1175 { 1176 Fun undo = []() { return true; }; 1177 Fun redo = []() { return true; }; 1178 REQUIRE(groups.m_upLink.size() == 0); 1179 1180 // This is a dummy split criterion 1181 auto criterion = [](int a) { return a % 2 == 0; }; 1182 KdenliveDoc::next_id = 0; 1183 1184 // We create a very simple tree 1185 for (int i = 0; i < 3; i++) { 1186 groups.createGroupItem(i); 1187 } 1188 groups.setGroup(1, 0); 1189 groups.setGroup(2, 0); 1190 KdenliveDoc::next_id = 3; 1191 auto test_tree = [&]() { 1192 REQUIRE(groups.getRootId(0) == 0); 1193 REQUIRE(groups.getRootId(1) == 0); 1194 REQUIRE(groups.getRootId(2) == 0); 1195 REQUIRE(groups.getSubtree(0) == std::unordered_set<int>({0, 1, 2})); 1196 REQUIRE(groups.getDirectChildren(0) == std::unordered_set<int>({1, 2})); 1197 REQUIRE(groups.getDirectChildren(1) == std::unordered_set<int>({})); 1198 REQUIRE(groups.getDirectChildren(2) == std::unordered_set<int>({})); 1199 REQUIRE(groups.checkConsistency()); 1200 }; 1201 test_tree(); 1202 1203 REQUIRE(groups.split(0, criterion, undo, redo)); 1204 auto test_tree2 = [&]() { 1205 REQUIRE(groups.getRootId(1) == 1); 1206 REQUIRE(groups.getRootId(2) == 2); 1207 REQUIRE(groups.getSubtree(2) == std::unordered_set<int>({2})); 1208 REQUIRE(groups.getSubtree(1) == std::unordered_set<int>({1})); 1209 REQUIRE(groups.getDirectChildren(2) == std::unordered_set<int>({})); 1210 REQUIRE(groups.getDirectChildren(1) == std::unordered_set<int>({})); 1211 REQUIRE(groups.checkConsistency()); 1212 }; 1213 test_tree2(); 1214 1215 undo(); 1216 test_tree(); 1217 1218 redo(); 1219 test_tree2(); 1220 } 1221 1222 SECTION("complex split Tree") 1223 { 1224 Fun undo = []() { return true; }; 1225 Fun redo = []() { return true; }; 1226 REQUIRE(groups.m_upLink.size() == 0); 1227 1228 // This is a dummy split criterion 1229 auto criterion = [](int a) { return a % 2 != 0; }; 1230 1231 for (int i = 0; i < 9; i++) { 1232 groups.createGroupItem(i); 1233 } 1234 KdenliveDoc::next_id = 9; 1235 groups.setGroup(0, 3); 1236 groups.setGroup(1, 0); 1237 groups.setGroup(3, 2); 1238 groups.setGroup(4, 3); 1239 groups.setGroup(5, 8); 1240 groups.setGroup(6, 0); 1241 groups.setGroup(7, 8); 1242 groups.setGroup(8, 2); 1243 1244 auto test_tree = [&]() { 1245 for (int i = 0; i < 9; i++) { 1246 REQUIRE(groups.getRootId(i) == 2); 1247 } 1248 REQUIRE(groups.getSubtree(2) == std::unordered_set<int>({0, 1, 2, 3, 4, 5, 6, 7, 8})); 1249 REQUIRE(groups.getDirectChildren(0) == std::unordered_set<int>({1, 6})); 1250 REQUIRE(groups.getDirectChildren(1) == std::unordered_set<int>({})); 1251 REQUIRE(groups.getDirectChildren(2) == std::unordered_set<int>({3, 8})); 1252 REQUIRE(groups.getDirectChildren(3) == std::unordered_set<int>({0, 4})); 1253 REQUIRE(groups.getDirectChildren(4) == std::unordered_set<int>({})); 1254 REQUIRE(groups.getDirectChildren(5) == std::unordered_set<int>({})); 1255 REQUIRE(groups.getDirectChildren(6) == std::unordered_set<int>({})); 1256 REQUIRE(groups.getDirectChildren(7) == std::unordered_set<int>({})); 1257 REQUIRE(groups.getDirectChildren(8) == std::unordered_set<int>({5, 7})); 1258 REQUIRE(groups.checkConsistency()); 1259 }; 1260 test_tree(); 1261 1262 REQUIRE(groups.split(2, criterion, undo, redo)); 1263 auto test_tree2 = [&]() { 1264 REQUIRE(groups.getRootId(6) == 3); 1265 REQUIRE(groups.getRootId(3) == 3); 1266 REQUIRE(groups.getRootId(4) == 3); 1267 REQUIRE(groups.getSubtree(3) == std::unordered_set<int>({3, 4, 6})); 1268 REQUIRE(groups.getDirectChildren(6) == std::unordered_set<int>({})); 1269 REQUIRE(groups.getDirectChildren(4) == std::unordered_set<int>({})); 1270 REQUIRE(groups.getDirectChildren(3) == std::unordered_set<int>({6, 4})); 1271 // new tree 1272 int newRoot = groups.getRootId(1); 1273 REQUIRE(groups.getRootId(1) == newRoot); 1274 REQUIRE(groups.getRootId(5) == newRoot); 1275 REQUIRE(groups.getRootId(7) == newRoot); 1276 int other = -1; 1277 REQUIRE(groups.getDirectChildren(newRoot).size() == 2); 1278 for (int c : groups.getDirectChildren(newRoot)) 1279 if (c != 1) other = c; 1280 REQUIRE(other != -1); 1281 REQUIRE(groups.getSubtree(newRoot) == std::unordered_set<int>({1, 5, 7, newRoot, other})); 1282 REQUIRE(groups.getDirectChildren(1) == std::unordered_set<int>({})); 1283 REQUIRE(groups.getDirectChildren(5) == std::unordered_set<int>({})); 1284 REQUIRE(groups.getDirectChildren(7) == std::unordered_set<int>({})); 1285 REQUIRE(groups.getDirectChildren(newRoot) == std::unordered_set<int>({1, other})); 1286 REQUIRE(groups.getDirectChildren(other) == std::unordered_set<int>({5, 7})); 1287 REQUIRE(groups.checkConsistency()); 1288 }; 1289 test_tree2(); 1290 1291 undo(); 1292 test_tree(); 1293 1294 redo(); 1295 test_tree2(); 1296 } 1297 SECTION("Splitting preserves group type") 1298 { 1299 Fun undo = []() { return true; }; 1300 Fun redo = []() { return true; }; 1301 REQUIRE(groups.m_upLink.size() == 0); 1302 1303 // This is a dummy split criterion 1304 auto criterion = [](int a) { return a % 2 == 0; }; 1305 1306 // We create a very simple tree 1307 for (int i = 0; i <= 6; i++) { 1308 groups.createGroupItem(i); 1309 } 1310 KdenliveDoc::next_id = 7; 1311 groups.setGroup(0, 4); 1312 groups.setGroup(2, 4); 1313 groups.setGroup(1, 5); 1314 groups.setGroup(3, 5); 1315 1316 groups.setGroup(4, 6); 1317 groups.setGroup(5, 6); 1318 1319 groups.setType(4, GroupType::AVSplit); 1320 groups.setType(5, GroupType::AVSplit); 1321 groups.setType(6, GroupType::Normal); 1322 1323 auto test_tree = [&]() { 1324 REQUIRE(groups.m_upLink.size() == 7); 1325 for (int i = 0; i <= 6; i++) { 1326 REQUIRE(groups.getRootId(i) == 6); 1327 } 1328 REQUIRE(groups.getSubtree(6) == std::unordered_set<int>({0, 1, 2, 3, 4, 5, 6})); 1329 REQUIRE(groups.getDirectChildren(0) == std::unordered_set<int>({})); 1330 REQUIRE(groups.getDirectChildren(1) == std::unordered_set<int>({})); 1331 REQUIRE(groups.getDirectChildren(2) == std::unordered_set<int>({})); 1332 REQUIRE(groups.getDirectChildren(3) == std::unordered_set<int>({})); 1333 REQUIRE(groups.getDirectChildren(4) == std::unordered_set<int>({0, 2})); 1334 REQUIRE(groups.getDirectChildren(5) == std::unordered_set<int>({1, 3})); 1335 REQUIRE(groups.getDirectChildren(6) == std::unordered_set<int>({4, 5})); 1336 REQUIRE(groups.getType(4) == GroupType::AVSplit); 1337 REQUIRE(groups.getType(5) == GroupType::AVSplit); 1338 REQUIRE(groups.getType(6) == GroupType::Normal); 1339 REQUIRE(groups.checkConsistency()); 1340 }; 1341 test_tree(); 1342 qDebug() << " done testing"; 1343 1344 REQUIRE(groups.split(6, criterion, undo, redo)); 1345 qDebug() << " done splitting"; 1346 auto test_tree2 = [&]() { 1347 // REQUIRE(groups.m_upLink.size() == 6); 1348 int r1 = groups.getRootId(0); 1349 int r2 = groups.getRootId(1); 1350 bool ok = r1 == 4 || r2 == 5; 1351 REQUIRE(ok); 1352 REQUIRE(groups.getRootId(2) == r1); 1353 REQUIRE(groups.getRootId(3) == r2); 1354 REQUIRE(groups.getSubtree(r1) == std::unordered_set<int>({r1, 0, 2})); 1355 REQUIRE(groups.getSubtree(r2) == std::unordered_set<int>({r2, 1, 3})); 1356 REQUIRE(groups.getDirectChildren(0) == std::unordered_set<int>({})); 1357 REQUIRE(groups.getDirectChildren(1) == std::unordered_set<int>({})); 1358 REQUIRE(groups.getDirectChildren(2) == std::unordered_set<int>({})); 1359 REQUIRE(groups.getDirectChildren(3) == std::unordered_set<int>({})); 1360 REQUIRE(groups.getDirectChildren(r1) == std::unordered_set<int>({0, 2})); 1361 REQUIRE(groups.getDirectChildren(r2) == std::unordered_set<int>({1, 3})); 1362 REQUIRE(groups.getType(r1) == GroupType::AVSplit); 1363 REQUIRE(groups.getType(r2) == GroupType::AVSplit); 1364 REQUIRE(groups.checkConsistency()); 1365 }; 1366 test_tree2(); 1367 1368 undo(); 1369 test_tree(); 1370 1371 redo(); 1372 test_tree2(); 1373 undo(); 1374 test_tree(); 1375 redo(); 1376 test_tree2(); 1377 } 1378 pCore->projectManager()->closeCurrentDocument(false, false); 1379 }