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 }