File indexing completed on 2024-12-01 07:35:31

0001 /*
0002     SPDX-FileCopyrightText: 2019-2022 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003     SPDX-FileCopyrightText: 2017-2019 Nicolas Carion <french.ebook.lover@gmail.com>
0004     SPDX-FileCopyrightText: 2022 Eric Jiang
0005     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 #include "test_utils.hpp"
0008 // test specific headers
0009 #include "doc/kdenlivedoc.h"
0010 #include <QUndoGroup>
0011 
0012 using namespace fakeit;
0013 std::default_random_engine g(42);
0014 
0015 TEST_CASE("Basic creation/deletion of a track", "[TrackModel]")
0016 {
0017     auto binModel = pCore->projectItemModel();
0018     std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
0019 
0020     // Here we do some trickery to enable testing.
0021     KdenliveDoc document(undoStack);
0022     pCore->projectManager()->m_project = &document;
0023     TimelineItemModel tim(document.uuid(), undoStack);
0024     Mock<TimelineItemModel> timMock(tim);
0025     auto timeline = std::shared_ptr<TimelineItemModel>(&timMock.get(), [](...) {});
0026     TimelineItemModel::finishConstruct(timeline);
0027     pCore->projectManager()->testSetActiveDocument(&document, timeline);
0028 
0029     Fake(Method(timMock, adjustAssetRange));
0030 
0031     // This is faked to allow to count calls
0032 
0033     int id1, id2, id3;
0034     REQUIRE(timeline->requestTrackInsertion(-1, id1));
0035     REQUIRE(timeline->checkConsistency());
0036     REQUIRE(timeline->getTracksCount() == 1);
0037     REQUIRE(timeline->getTrackPosition(id1) == 0);
0038     RESET(timMock);
0039 
0040     REQUIRE(timeline->requestTrackInsertion(-1, id2));
0041     REQUIRE(timeline->checkConsistency());
0042     REQUIRE(timeline->getTracksCount() == 2);
0043     REQUIRE(timeline->getTrackPosition(id2) == 1);
0044     RESET(timMock);
0045 
0046     REQUIRE(timeline->requestTrackInsertion(-1, id3));
0047     REQUIRE(timeline->checkConsistency());
0048     REQUIRE(timeline->getTracksCount() == 3);
0049     REQUIRE(timeline->getTrackPosition(id3) == 2);
0050     RESET(timMock);
0051 
0052     int id4;
0053     REQUIRE(timeline->requestTrackInsertion(1, id4));
0054     REQUIRE(timeline->checkConsistency());
0055     REQUIRE(timeline->getTracksCount() == 4);
0056     REQUIRE(timeline->getTrackPosition(id1) == 0);
0057     REQUIRE(timeline->getTrackPosition(id4) == 1);
0058     REQUIRE(timeline->getTrackPosition(id2) == 2);
0059     REQUIRE(timeline->getTrackPosition(id3) == 3);
0060     RESET(timMock);
0061 
0062     // Test deletion
0063     REQUIRE(timeline->requestTrackDeletion(id3));
0064     REQUIRE(timeline->checkConsistency());
0065     REQUIRE(timeline->getTracksCount() == 3);
0066     RESET(timMock);
0067 
0068     REQUIRE(timeline->requestTrackDeletion(id1));
0069     REQUIRE(timeline->checkConsistency());
0070     REQUIRE(timeline->getTracksCount() == 2);
0071     RESET(timMock);
0072 
0073     REQUIRE(timeline->requestTrackDeletion(id4));
0074     REQUIRE(timeline->checkConsistency());
0075     REQUIRE(timeline->getTracksCount() == 1);
0076     RESET(timMock);
0077 
0078     // We are not allowed to delete the last track
0079     REQUIRE_FALSE(timeline->requestTrackDeletion(id2));
0080     REQUIRE(timeline->checkConsistency());
0081     REQUIRE(timeline->getTracksCount() == 1);
0082     RESET(timMock);
0083 
0084     SECTION("Delete a track with groups")
0085     {
0086         int tid1, tid2;
0087         REQUIRE(timeline->requestTrackInsertion(-1, tid1));
0088         REQUIRE(timeline->requestTrackInsertion(-1, tid2));
0089         REQUIRE(timeline->checkConsistency());
0090 
0091         QString binId = createProducer(pCore->getProjectProfile(), "red", binModel);
0092         int length = 20;
0093         int cid1, cid2, cid3, cid4;
0094         REQUIRE(timeline->requestClipInsertion(binId, tid1, 2, cid1));
0095         REQUIRE(timeline->requestClipInsertion(binId, tid2, 0, cid2));
0096         REQUIRE(timeline->requestClipInsertion(binId, tid2, length, cid3));
0097         REQUIRE(timeline->requestClipInsertion(binId, tid2, 2 * length, cid4));
0098         REQUIRE(timeline->checkConsistency());
0099         REQUIRE(timeline->getClipsCount() == 4);
0100         REQUIRE(timeline->getTracksCount() == 3);
0101 
0102         auto g1 = std::unordered_set<int>({cid1, cid3});
0103         auto g2 = std::unordered_set<int>({cid2, cid4});
0104         auto g3 = std::unordered_set<int>({cid1, cid4});
0105         REQUIRE(timeline->requestClipsGroup(g1));
0106         REQUIRE(timeline->requestClipsGroup(g2));
0107         REQUIRE(timeline->requestClipsGroup(g3));
0108         REQUIRE(timeline->checkConsistency());
0109 
0110         REQUIRE(timeline->requestTrackDeletion(tid1));
0111         REQUIRE(timeline->getClipsCount() == 3);
0112         REQUIRE(timeline->getTracksCount() == 2);
0113         REQUIRE(timeline->checkConsistency());
0114     }
0115     pCore->projectManager()->closeCurrentDocument(false, false);
0116 }
0117 
0118 TEST_CASE("Adding multiple A/V tracks", "[TrackModel]")
0119 {
0120 
0121     auto binModel = pCore->projectItemModel();
0122     std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
0123 
0124     KdenliveDoc document(undoStack);
0125 
0126     // We also mock timeline object to spy few functions and mock others
0127     pCore->projectManager()->m_project = &document;
0128     TimelineItemModel tim(document.uuid(), undoStack);
0129     Mock<TimelineItemModel> timMock(tim);
0130     auto timeline = std::shared_ptr<TimelineItemModel>(&timMock.get(), [](...) {});
0131     TimelineItemModel::finishConstruct(timeline);
0132     pCore->projectManager()->testSetActiveDocument(&document, timeline);
0133 
0134     SECTION("Check AV track ordering")
0135     {
0136         // start state:
0137         // * V2 (position 3)
0138         // * V1
0139         // * A1
0140         // * A2 (position 0)
0141         int a1, a2, v1, v2;
0142         REQUIRE(timeline->requestTrackInsertion(0, a2, QString(), true));
0143         REQUIRE(timeline->requestTrackInsertion(1, a1, QString(), true));
0144         REQUIRE(timeline->requestTrackInsertion(2, v1, QString(), false));
0145         REQUIRE(timeline->requestTrackInsertion(3, v2, QString(), false));
0146 
0147         // when we add 3 AV tracks above V1, we should have:
0148         // * V5 (position 9)
0149         // * V4
0150         // * V3
0151         // * V2
0152         // * V1
0153         // * A1
0154         // * A2
0155         // * A3
0156         // * A4
0157         // * A5 (position 0)
0158         QString trackName("New track");
0159         REQUIRE(timeline->addTracksAtPosition(3, 3, trackName, false, true, false));
0160         // but if the new tracks keep getting added at the same position 3, then we'll get
0161         // * V2
0162         // * V3
0163         // * V1
0164         // * V4
0165         // * A4
0166         // * V5
0167         // * A5
0168         // * A1
0169         // * A3
0170         // * A2
0171         // (numbering doesn't look like this in the GUI)
0172 
0173         REQUIRE(timeline->getTracksCount() == 10);
0174 
0175         // first 5 tracks should be audio, and last 5 tracks should be video
0176         auto it = timeline->m_allTracks.cbegin();
0177         int position = 0;
0178         while (it != timeline->m_allTracks.cend()) {
0179             if (position < 5) {
0180                 CHECK((*it)->isAudioTrack());
0181             } else {
0182                 CHECK(!(*it)->isAudioTrack());
0183             }
0184             it++;
0185             position++;
0186         }
0187         // V1 track should be at index 5 (i.e. we shouldn't have inserted any video
0188         // tracks before it)
0189         REQUIRE(timeline->getTrackIndexFromPosition(5) == v1);
0190     }
0191     pCore->projectManager()->closeCurrentDocument(false, false);
0192 }
0193 
0194 TEST_CASE("Basic creation/deletion of a clip", "[ClipModel]")
0195 {
0196 
0197     auto binModel = pCore->projectItemModel();
0198     std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
0199 
0200     // Here we do some trickery to enable testing.
0201     // We mock the project class so that the undoStack function returns our undoStack
0202     KdenliveDoc document(undoStack);
0203 
0204     // We also mock timeline object to spy few functions and mock others
0205     pCore->projectManager()->m_project = &document;
0206     TimelineItemModel tim(document.uuid(), undoStack);
0207     Mock<TimelineItemModel> timMock(tim);
0208     auto timeline = std::shared_ptr<TimelineItemModel>(&timMock.get(), [](...) {});
0209     TimelineItemModel::finishConstruct(timeline);
0210     pCore->projectManager()->testSetActiveDocument(&document, timeline);
0211 
0212     QString binId = createProducer(pCore->getProjectProfile(), "red", binModel);
0213     QString binId2 = createProducer(pCore->getProjectProfile(), "green", binModel);
0214 
0215     REQUIRE(timeline->getClipsCount() == 0);
0216     int id1 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly);
0217     REQUIRE(timeline->getClipsCount() == 1);
0218     REQUIRE(timeline->checkConsistency());
0219 
0220     int id2 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly);
0221     REQUIRE(timeline->getClipsCount() == 2);
0222     REQUIRE(timeline->checkConsistency());
0223 
0224     int id3 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly);
0225     REQUIRE(timeline->getClipsCount() == 3);
0226     REQUIRE(timeline->checkConsistency());
0227 
0228     // Test deletion
0229     REQUIRE(timeline->requestItemDeletion(id2));
0230     REQUIRE(timeline->checkConsistency());
0231     REQUIRE(timeline->getClipsCount() == 2);
0232     REQUIRE(timeline->requestItemDeletion(id3));
0233     REQUIRE(timeline->checkConsistency());
0234     REQUIRE(timeline->getClipsCount() == 1);
0235     REQUIRE(timeline->requestItemDeletion(id1));
0236     REQUIRE(timeline->checkConsistency());
0237     REQUIRE(timeline->getClipsCount() == 0);
0238     pCore->projectManager()->closeCurrentDocument(false, false);
0239 }
0240 
0241 TEST_CASE("Clip manipulation", "[ClipModel]")
0242 {
0243     auto binModel = pCore->projectItemModel();
0244     binModel->clean();
0245     std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
0246 
0247     // Here we do some trickery to enable testing.
0248     // We mock the project class so that the undoStack function returns our undoStack
0249     KdenliveDoc document(undoStack);
0250 
0251     // We also mock timeline object to spy few functions and mock others
0252     pCore->projectManager()->m_project = &document;
0253     TimelineItemModel tim(document.uuid(), undoStack);
0254     Mock<TimelineItemModel> timMock(tim);
0255     auto timeline = std::shared_ptr<TimelineItemModel>(&timMock.get(), [](...) {});
0256     TimelineItemModel::finishConstruct(timeline);
0257 
0258     pCore->projectManager()->testSetActiveDocument(&document, timeline);
0259 
0260     Fake(Method(timMock, adjustAssetRange));
0261 
0262     // This is faked to allow to count calls
0263     Fake(Method(timMock, _beginInsertRows));
0264     Fake(Method(timMock, _beginRemoveRows));
0265     Fake(Method(timMock, _endInsertRows));
0266     Fake(Method(timMock, _endRemoveRows));
0267 
0268     QString binId = createProducer(pCore->getProjectProfile(), "red", binModel);
0269     QString binId2 = createProducer(pCore->getProjectProfile(), "blue", binModel);
0270     QString binId3 = createProducer(pCore->getProjectProfile(), "green", binModel);
0271     QString binId_unlimited = createProducer(pCore->getProjectProfile(), "green", binModel, 20, false);
0272 
0273     int cid1 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly);
0274     int tid1, tid2, tid3;
0275     REQUIRE(timeline->requestTrackInsertion(-1, tid1));
0276     REQUIRE(timeline->requestTrackInsertion(-1, tid2));
0277     REQUIRE(timeline->requestTrackInsertion(-1, tid3));
0278     int cid2 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly);
0279     int cid3 = ClipModel::construct(timeline, binId3, -1, PlaylistState::VideoOnly);
0280     int cid4 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly);
0281     int cid5 = ClipModel::construct(timeline, binId_unlimited, -1, PlaylistState::VideoOnly);
0282 
0283     RESET(timMock);
0284 
0285     SECTION("Endless clips can be resized both sides")
0286     {
0287 
0288         REQUIRE(timeline->checkConsistency());
0289         REQUIRE(timeline->getTrackClipsCount(tid1) == 0);
0290         REQUIRE(timeline->getTrackClipsCount(tid2) == 0);
0291         int l = timeline->getClipPlaytime(cid5);
0292 
0293         // try resizing uninserted clip
0294         REQUIRE(timeline->requestItemResize(cid5, l + 2, false) == l + 2);
0295         REQUIRE(timeline->getClipPlaytime(cid5) == l + 2);
0296         undoStack->undo();
0297         REQUIRE(timeline->getClipPlaytime(cid5) == l);
0298         undoStack->redo();
0299         REQUIRE(timeline->getClipPlaytime(cid5) == l + 2);
0300         undoStack->undo();
0301         REQUIRE(timeline->getClipPlaytime(cid5) == l);
0302 
0303         REQUIRE(timeline->requestItemResize(cid5, 3 * l, true) == 3 * l);
0304         REQUIRE(timeline->getClipPlaytime(cid5) == 3 * l);
0305         undoStack->undo();
0306         REQUIRE(timeline->getClipPlaytime(cid5) == l);
0307         undoStack->redo();
0308         REQUIRE(timeline->getClipPlaytime(cid5) == 3 * l);
0309         undoStack->undo();
0310         REQUIRE(timeline->getClipPlaytime(cid5) == l);
0311 
0312         // try resizing inserted clip
0313         int pos = 10;
0314         REQUIRE(timeline->requestClipMove(cid5, tid1, pos));
0315 
0316         auto state = [&](int s, int p) {
0317             REQUIRE(timeline->checkConsistency());
0318             REQUIRE(timeline->getClipTrackId(cid5) == tid1);
0319             REQUIRE(timeline->getClipPosition(cid5) == p);
0320             REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
0321             REQUIRE(timeline->getTrackClipsCount(tid2) == 0);
0322             REQUIRE(timeline->getClipPlaytime(cid5) == s);
0323         };
0324         state(l, pos);
0325 
0326         // too big
0327         REQUIRE(timeline->requestItemResize(cid5, l + pos + 2, false) == l + pos);
0328         undoStack->undo();
0329 
0330         REQUIRE(timeline->requestItemResize(cid5, l + 2, false) == l + 2);
0331         state(l + 2, pos - 2);
0332         undoStack->undo();
0333         state(l, pos);
0334         undoStack->redo();
0335         state(l + 2, pos - 2);
0336         undoStack->undo();
0337         state(l, pos);
0338 
0339         REQUIRE(timeline->requestItemResize(cid5, 3 * l, true) == 3 * l);
0340         state(3 * l, pos);
0341         undoStack->undo();
0342         state(l, pos);
0343         undoStack->redo();
0344         state(3 * l, pos);
0345         undoStack->undo();
0346         state(l, pos);
0347     }
0348 
0349     SECTION("Insert a clip in a track and change track")
0350     {
0351         REQUIRE(timeline->checkConsistency());
0352         REQUIRE(timeline->getTrackClipsCount(tid1) == 0);
0353         REQUIRE(timeline->getTrackClipsCount(tid2) == 0);
0354 
0355         REQUIRE(timeline->getClipTrackId(cid1) == -1);
0356         REQUIRE(timeline->getClipPosition(cid1) == -1);
0357 
0358         int pos = 10;
0359         REQUIRE(timeline->requestClipMove(cid1, tid1, pos));
0360         REQUIRE(timeline->checkConsistency());
0361         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
0362         REQUIRE(timeline->getClipPosition(cid1) == pos);
0363         REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
0364         REQUIRE(timeline->getTrackClipsCount(tid2) == 0);
0365         // Check that the model was correctly notified
0366         CHECK_INSERT(Once);
0367 
0368         pos = 1;
0369         REQUIRE(timeline->requestClipMove(cid1, tid2, pos));
0370         REQUIRE(timeline->checkConsistency());
0371         REQUIRE(timeline->getClipTrackId(cid1) == tid2);
0372         REQUIRE(timeline->getClipPosition(cid1) == pos);
0373         REQUIRE(timeline->getTrackClipsCount(tid2) == 1);
0374         REQUIRE(timeline->getTrackClipsCount(tid1) == 0);
0375         CHECK_MOVE(Once);
0376 
0377         // Check conflicts
0378         int pos2 = binModel->getClipByBinID(binId)->frameDuration();
0379         REQUIRE(timeline->requestClipMove(cid2, tid1, pos2));
0380         REQUIRE(timeline->checkConsistency());
0381         REQUIRE(timeline->getClipTrackId(cid2) == tid1);
0382         REQUIRE(timeline->getClipPosition(cid2) == pos2);
0383         REQUIRE(timeline->getTrackClipsCount(tid2) == 1);
0384         REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
0385         CHECK_INSERT(Once);
0386 
0387         REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, pos2 + 2));
0388         REQUIRE(timeline->checkConsistency());
0389         REQUIRE(timeline->getTrackClipsCount(tid2) == 1);
0390         REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
0391         REQUIRE(timeline->getClipTrackId(cid1) == tid2);
0392         REQUIRE(timeline->getClipPosition(cid1) == pos);
0393         REQUIRE(timeline->getClipTrackId(cid2) == tid1);
0394         REQUIRE(timeline->getClipPosition(cid2) == pos2);
0395         CHECK_MOVE(Once);
0396 
0397         REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, pos2 - 2));
0398         REQUIRE(timeline->checkConsistency());
0399         REQUIRE(timeline->getTrackClipsCount(tid2) == 1);
0400         REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
0401         REQUIRE(timeline->getClipTrackId(cid1) == tid2);
0402         REQUIRE(timeline->getClipPosition(cid1) == pos);
0403         REQUIRE(timeline->getClipTrackId(cid2) == tid1);
0404         REQUIRE(timeline->getClipPosition(cid2) == pos2);
0405         CHECK_MOVE(Once);
0406 
0407         REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
0408         REQUIRE(timeline->checkConsistency());
0409         REQUIRE(timeline->getTrackClipsCount(tid2) == 0);
0410         REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
0411         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
0412         REQUIRE(timeline->getClipPosition(cid1) == 0);
0413         REQUIRE(timeline->getClipTrackId(cid2) == tid1);
0414         REQUIRE(timeline->getClipPosition(cid2) == pos2);
0415         CHECK_MOVE(Once);
0416     }
0417 
0418     int length = binModel->getClipByBinID(binId)->frameDuration();
0419     SECTION("Insert consecutive clips")
0420     {
0421         REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
0422         REQUIRE(timeline->checkConsistency());
0423         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
0424         REQUIRE(timeline->getClipPosition(cid1) == 0);
0425         REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
0426         CHECK_INSERT(Once);
0427 
0428         REQUIRE(timeline->requestClipMove(cid2, tid1, length));
0429         REQUIRE(timeline->checkConsistency());
0430         REQUIRE(timeline->getClipTrackId(cid2) == tid1);
0431         REQUIRE(timeline->getClipPosition(cid2) == length);
0432         REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
0433         CHECK_INSERT(Once);
0434     }
0435 
0436     SECTION("Resize orphan clip")
0437     {
0438         REQUIRE(timeline->getClipPlaytime(cid2) == length);
0439         REQUIRE(timeline->requestItemResize(cid2, 5, true) == 5);
0440         REQUIRE(timeline->checkConsistency());
0441         REQUIRE(binModel->getClipByBinID(binId)->getFramePlaytime() == length);
0442         auto inOut = std::pair<int, int>{0, 4};
0443         REQUIRE(timeline->m_allClips[cid2]->getInOut() == inOut);
0444         REQUIRE(timeline->getClipPlaytime(cid2) == 5);
0445         REQUIRE(timeline->requestItemResize(cid2, 10, false) == -1);
0446         REQUIRE(timeline->requestItemResize(cid2, length + 1, true) == -1);
0447         REQUIRE(timeline->checkConsistency());
0448         REQUIRE(timeline->getClipPlaytime(cid2) == 5);
0449         REQUIRE(timeline->getClipPlaytime(cid2) == 5);
0450         REQUIRE(timeline->requestItemResize(cid2, 2, false) == 2);
0451         REQUIRE(timeline->checkConsistency());
0452         inOut = std::pair<int, int>{3, 4};
0453         REQUIRE(timeline->m_allClips[cid2]->getInOut() == inOut);
0454         REQUIRE(timeline->getClipPlaytime(cid2) == 2);
0455         REQUIRE(timeline->requestItemResize(cid2, length, true) == -1);
0456         REQUIRE(timeline->checkConsistency());
0457         REQUIRE(timeline->getClipPlaytime(cid2) == 2);
0458         CAPTURE(timeline->m_allClips[cid2]->m_producer->get_in());
0459         REQUIRE(timeline->requestItemResize(cid2, length - 2, true) == -1);
0460         REQUIRE(timeline->checkConsistency());
0461         REQUIRE(timeline->requestItemResize(cid2, length - 3, true) == length - 3);
0462         REQUIRE(timeline->checkConsistency());
0463         REQUIRE(timeline->getClipPlaytime(cid2) == length - 3);
0464     }
0465 
0466     SECTION("Resize inserted clips")
0467     {
0468         REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
0469         REQUIRE(timeline->checkConsistency());
0470         CHECK_INSERT(Once);
0471 
0472         REQUIRE(timeline->requestItemResize(cid1, 5, true) == 5);
0473         REQUIRE(timeline->checkConsistency());
0474         REQUIRE(timeline->getClipPlaytime(cid1) == 5);
0475         REQUIRE(timeline->getClipPosition(cid1) == 0);
0476         CHECK_RESIZE(Once);
0477 
0478         REQUIRE(timeline->requestClipMove(cid2, tid1, 5));
0479         REQUIRE(timeline->checkConsistency());
0480         REQUIRE(binModel->getClipByBinID(binId)->getFramePlaytime() == length);
0481         CHECK_INSERT(Once);
0482 
0483         REQUIRE(timeline->requestItemResize(cid1, 6, true) == -1);
0484         REQUIRE(timeline->requestItemResize(cid1, 6, false) == -1);
0485         REQUIRE(timeline->checkConsistency());
0486         NO_OTHERS();
0487 
0488         REQUIRE(timeline->requestItemResize(cid2, length - 5, false) == length - 5);
0489         REQUIRE(timeline->checkConsistency());
0490         REQUIRE(timeline->getClipPosition(cid2) == 10);
0491         CHECK_RESIZE(Once);
0492 
0493         REQUIRE(timeline->requestItemResize(cid1, 10, true) == 10);
0494         REQUIRE(timeline->checkConsistency());
0495         REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
0496         CHECK_RESIZE(Once);
0497     }
0498 
0499     SECTION("Change track of resized clips")
0500     {
0501         // // REQUIRE(timeline->allowClipMove(cid2, tid1, 5));
0502         REQUIRE(timeline->requestClipMove(cid2, tid1, 5));
0503         REQUIRE(timeline->checkConsistency());
0504         REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
0505 
0506         // // REQUIRE(timeline->allowClipMove(cid1, tid2, 10));
0507         REQUIRE(timeline->requestClipMove(cid1, tid2, 10));
0508         REQUIRE(timeline->checkConsistency());
0509         REQUIRE(timeline->getTrackClipsCount(tid2) == 1);
0510 
0511         REQUIRE(timeline->requestItemResize(cid1, 5, false) == 5);
0512         REQUIRE(timeline->checkConsistency());
0513 
0514         // // REQUIRE(timeline->allowClipMove(cid1, tid1, 0));
0515         REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
0516         REQUIRE(timeline->checkConsistency());
0517         REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
0518         REQUIRE(timeline->getTrackClipsCount(tid2) == 0);
0519     }
0520 
0521     SECTION("Clip Move")
0522     {
0523         REQUIRE(timeline->requestClipMove(cid2, tid1, 5));
0524         REQUIRE(timeline->checkConsistency());
0525         REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
0526         REQUIRE(timeline->getClipTrackId(cid2) == tid1);
0527         REQUIRE(timeline->getClipPosition(cid2) == 5);
0528 
0529         REQUIRE(timeline->requestClipMove(cid1, tid1, 5 + length));
0530         auto state = [&]() {
0531             REQUIRE(timeline->checkConsistency());
0532             REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
0533             REQUIRE(timeline->getClipTrackId(cid1) == tid1);
0534             REQUIRE(timeline->getClipTrackId(cid2) == tid1);
0535             REQUIRE(timeline->getClipPosition(cid1) == 5 + length);
0536             REQUIRE(timeline->getClipPosition(cid2) == 5);
0537         };
0538         state();
0539 
0540         REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, 3 + length));
0541         state();
0542 
0543         REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, 0));
0544         state();
0545 
0546         REQUIRE(timeline->requestClipMove(cid2, tid1, 0));
0547         auto state2 = [&]() {
0548             REQUIRE(timeline->checkConsistency());
0549             REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
0550             REQUIRE(timeline->getClipTrackId(cid1) == tid1);
0551             REQUIRE(timeline->getClipTrackId(cid2) == tid1);
0552             REQUIRE(timeline->getClipPosition(cid1) == 5 + length);
0553             REQUIRE(timeline->getClipPosition(cid2) == 0);
0554         };
0555         state2();
0556 
0557         REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, 0));
0558         state2();
0559 
0560         REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, length - 5));
0561         state2();
0562 
0563         REQUIRE(timeline->requestClipMove(cid1, tid1, length));
0564         REQUIRE(timeline->checkConsistency());
0565         REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
0566         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
0567         REQUIRE(timeline->getClipTrackId(cid2) == tid1);
0568         REQUIRE(timeline->getClipPosition(cid1) == length);
0569         REQUIRE(timeline->getClipPosition(cid2) == 0);
0570 
0571         REQUIRE(timeline->requestItemResize(cid2, length - 5, true) == length - 5);
0572         REQUIRE(timeline->checkConsistency());
0573         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
0574         REQUIRE(timeline->getClipTrackId(cid2) == tid1);
0575         REQUIRE(timeline->getClipPosition(cid1) == length);
0576         REQUIRE(timeline->getClipPosition(cid2) == 0);
0577 
0578         REQUIRE(timeline->requestClipMove(cid1, tid1, length - 5));
0579         REQUIRE(timeline->checkConsistency());
0580         REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
0581         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
0582         REQUIRE(timeline->getClipTrackId(cid2) == tid1);
0583         REQUIRE(timeline->getClipPosition(cid1) == length - 5);
0584         REQUIRE(timeline->getClipPosition(cid2) == 0);
0585 
0586         REQUIRE(timeline->requestItemResize(cid2, length - 10, false) == length - 10);
0587         REQUIRE(timeline->checkConsistency());
0588         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
0589         REQUIRE(timeline->getClipTrackId(cid2) == tid1);
0590         REQUIRE(timeline->getClipPosition(cid1) == length - 5);
0591         REQUIRE(timeline->getClipPosition(cid2) == 5);
0592 
0593         REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, 0));
0594         REQUIRE(timeline->checkConsistency());
0595         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
0596         REQUIRE(timeline->getClipTrackId(cid2) == tid1);
0597         REQUIRE(timeline->getClipPosition(cid1) == length - 5);
0598         REQUIRE(timeline->getClipPosition(cid2) == 5);
0599 
0600         REQUIRE(timeline->requestClipMove(cid2, tid1, 0));
0601         REQUIRE(timeline->checkConsistency());
0602         REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
0603         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
0604         REQUIRE(timeline->getClipTrackId(cid2) == tid1);
0605         REQUIRE(timeline->getClipPosition(cid1) == length - 5);
0606         REQUIRE(timeline->getClipPosition(cid2) == 0);
0607     }
0608 
0609     SECTION("Move and resize")
0610     {
0611         REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
0612         REQUIRE(timeline->requestItemResize(cid1, length - 2, false) == length - 2);
0613         REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
0614         auto state = [&]() {
0615             REQUIRE(timeline->checkConsistency());
0616             REQUIRE(timeline->getClipTrackId(cid1) == tid1);
0617             REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
0618             REQUIRE(timeline->getClipPosition(cid1) == 0);
0619             REQUIRE(timeline->getClipPlaytime(cid1) == length - 2);
0620         };
0621         state();
0622 
0623         // try to resize past the left end
0624         REQUIRE(timeline->requestItemResize(cid1, length, false) == -1);
0625         state();
0626 
0627         REQUIRE(timeline->requestItemResize(cid1, length - 4, true) == length - 4);
0628         REQUIRE(timeline->requestClipMove(cid2, tid1, length - 4 + 1));
0629         REQUIRE(timeline->requestItemResize(cid2, length - 2, false) == length - 2);
0630         REQUIRE(timeline->requestClipMove(cid2, tid1, length - 4 + 1));
0631         auto state2 = [&]() {
0632             REQUIRE(timeline->checkConsistency());
0633             REQUIRE(timeline->getClipTrackId(cid1) == tid1);
0634             REQUIRE(timeline->getClipTrackId(cid2) == tid1);
0635             REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
0636             REQUIRE(timeline->getClipPosition(cid1) == 0);
0637             REQUIRE(timeline->getClipPlaytime(cid1) == length - 4);
0638             REQUIRE(timeline->getClipPosition(cid2) == length - 4 + 1);
0639             REQUIRE(timeline->getClipPlaytime(cid2) == length - 2);
0640         };
0641         state2();
0642 
0643         // the gap between the two clips is 1 frame, we try to resize them by 2 frames
0644         // It will only be resized by one frame
0645         REQUIRE(timeline->requestItemResize(cid1, length - 2, true) == length - 3);
0646         undoStack->undo();
0647         state2();
0648         // Resize a clip over another clip will resize it to fill the gap
0649         REQUIRE(timeline->requestItemResize(cid2, length, false) == length - 1);
0650         undoStack->undo();
0651         state2();
0652 
0653         REQUIRE(timeline->requestClipMove(cid2, tid1, length - 4));
0654         auto state3 = [&]() {
0655             REQUIRE(timeline->checkConsistency());
0656             REQUIRE(timeline->getClipTrackId(cid1) == tid1);
0657             REQUIRE(timeline->getClipTrackId(cid2) == tid1);
0658             REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
0659             REQUIRE(timeline->getClipPosition(cid1) == 0);
0660             REQUIRE(timeline->getClipPlaytime(cid1) == length - 4);
0661             REQUIRE(timeline->getClipPosition(cid2) == length - 4);
0662             REQUIRE(timeline->getClipPlaytime(cid2) == length - 2);
0663         };
0664         state3();
0665 
0666         // Now the gap is 0 frames, the resize should still fail
0667         REQUIRE(timeline->requestItemResize(cid1, length - 2, true) == -1);
0668         state3();
0669         REQUIRE(timeline->requestItemResize(cid2, length, false) == -1);
0670         state3();
0671 
0672         // We move cid1 out of the way
0673         REQUIRE(timeline->requestClipMove(cid1, tid2, 0));
0674         // now resize should work
0675         REQUIRE(timeline->requestItemResize(cid1, length - 2, true) == length - 2);
0676         REQUIRE(timeline->requestItemResize(cid2, length, false) == length);
0677         REQUIRE(timeline->checkConsistency());
0678     }
0679 
0680     SECTION("Group and selection")
0681     {
0682         REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
0683         REQUIRE(timeline->requestClipMove(cid2, tid1, length + 3));
0684         REQUIRE(timeline->requestClipMove(cid3, tid1, 2 * length + 5));
0685         auto pos_state = [&]() {
0686             REQUIRE(timeline->checkConsistency());
0687             REQUIRE(timeline->getTrackClipsCount(tid1) == 3);
0688             REQUIRE(timeline->getClipTrackId(cid1) == tid1);
0689             REQUIRE(timeline->getClipTrackId(cid2) == tid1);
0690             REQUIRE(timeline->getClipTrackId(cid3) == tid1);
0691             REQUIRE(timeline->getClipPosition(cid1) == 0);
0692             REQUIRE(timeline->getClipPosition(cid2) == length + 3);
0693             REQUIRE(timeline->getClipPosition(cid3) == 2 * length + 5);
0694         };
0695         auto state0 = [&]() {
0696             pos_state();
0697             REQUIRE_FALSE(timeline->m_groups->isInGroup(cid1));
0698             REQUIRE_FALSE(timeline->m_groups->isInGroup(cid2));
0699             REQUIRE_FALSE(timeline->m_groups->isInGroup(cid3));
0700         };
0701         state0();
0702 
0703         REQUIRE(timeline->requestClipsGroup({cid1, cid2}));
0704         auto state = [&]() {
0705             pos_state();
0706             REQUIRE_FALSE(timeline->m_groups->isInGroup(cid3));
0707             REQUIRE(timeline->m_groups->isInGroup(cid1));
0708             int gid = timeline->m_groups->getRootId(cid1);
0709             REQUIRE(timeline->m_groups->getLeaves(gid) == std::unordered_set<int>{cid1, cid2});
0710         };
0711         state();
0712 
0713         // undo/redo should work fine
0714         undoStack->undo();
0715         state0();
0716         undoStack->redo();
0717         state();
0718 
0719         // Tricky case, we do a non-trivial selection before undoing
0720         REQUIRE(timeline->requestSetSelection({cid1, cid3}));
0721         REQUIRE(timeline->getCurrentSelection() == std::unordered_set<int>{cid1, cid2, cid3});
0722         undoStack->undo();
0723         state0();
0724         REQUIRE(timeline->requestSetSelection({cid1, cid3}));
0725         REQUIRE(timeline->getCurrentSelection() == std::unordered_set<int>{cid1, cid3});
0726         undoStack->redo();
0727         state();
0728 
0729         // same thing, but when ungrouping manually
0730         REQUIRE(timeline->requestSetSelection({cid1, cid3}));
0731         REQUIRE(timeline->getCurrentSelection() == std::unordered_set<int>{cid1, cid2, cid3});
0732         REQUIRE(timeline->requestClipUngroup(cid1));
0733         state0();
0734 
0735         // normal undo/redo
0736         undoStack->undo();
0737         state();
0738         undoStack->redo();
0739         state0();
0740 
0741         // undo/redo mixed with selections
0742         REQUIRE(timeline->requestSetSelection({cid1, cid3}));
0743         REQUIRE(timeline->getCurrentSelection() == std::unordered_set<int>{cid1, cid3});
0744         undoStack->undo();
0745         state();
0746         REQUIRE(timeline->requestSetSelection({cid1, cid3}));
0747         REQUIRE(timeline->getCurrentSelection() == std::unordered_set<int>{cid1, cid2, cid3});
0748         undoStack->redo();
0749         state0();
0750     }
0751 
0752     SECTION("Group move")
0753     {
0754         REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
0755         REQUIRE(timeline->requestClipMove(cid2, tid1, length + 3));
0756         REQUIRE(timeline->requestClipMove(cid3, tid1, 2 * length + 5));
0757         REQUIRE(timeline->requestClipMove(cid4, tid2, 4));
0758 
0759         REQUIRE(timeline->checkConsistency());
0760         REQUIRE(timeline->getTrackClipsCount(tid1) == 3);
0761         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
0762         REQUIRE(timeline->getClipTrackId(cid2) == tid1);
0763         REQUIRE(timeline->getClipTrackId(cid3) == tid1);
0764         REQUIRE(timeline->getClipTrackId(cid4) == tid2);
0765         REQUIRE(timeline->getClipPosition(cid1) == 0);
0766         REQUIRE(timeline->getClipPosition(cid2) == length + 3);
0767         REQUIRE(timeline->getClipPosition(cid3) == 2 * length + 5);
0768         REQUIRE(timeline->getClipPosition(cid4) == 4);
0769 
0770         // check that move is possible without groups
0771         REQUIRE(timeline->requestClipMove(cid3, tid1, 2 * length + 3));
0772         REQUIRE(timeline->checkConsistency());
0773         undoStack->undo();
0774         REQUIRE(timeline->checkConsistency());
0775         // check that move is possible without groups
0776         REQUIRE(timeline->requestClipMove(cid4, tid2, 9));
0777         REQUIRE(timeline->checkConsistency());
0778         undoStack->undo();
0779         REQUIRE(timeline->checkConsistency());
0780 
0781         auto state = [&]() {
0782             REQUIRE(timeline->checkConsistency());
0783             REQUIRE(timeline->getTrackClipsCount(tid1) == 3);
0784             REQUIRE(timeline->getClipTrackId(cid1) == tid1);
0785             REQUIRE(timeline->getClipTrackId(cid2) == tid1);
0786             REQUIRE(timeline->getClipTrackId(cid3) == tid1);
0787             REQUIRE(timeline->getClipTrackId(cid4) == tid2);
0788             REQUIRE(timeline->getClipPosition(cid1) == 0);
0789             REQUIRE(timeline->getClipPosition(cid2) == length + 3);
0790             REQUIRE(timeline->getClipPosition(cid3) == 2 * length + 5);
0791             REQUIRE(timeline->getClipPosition(cid4) == 4);
0792         };
0793         state();
0794 
0795         // grouping
0796         REQUIRE(timeline->requestClipsGroup({cid1, cid3}));
0797         REQUIRE(timeline->requestClipsGroup({cid1, cid4}));
0798 
0799         // move left is now forbidden, because clip1 is at position 0
0800         REQUIRE_FALSE(timeline->requestClipMove(cid3, tid1, 2 * length + 3));
0801         state();
0802 
0803         // this move is impossible, because clip1 runs into clip2
0804         REQUIRE_FALSE(timeline->requestClipMove(cid4, tid2, 9));
0805         state();
0806 
0807         // this move is possible
0808         REQUIRE(timeline->requestClipMove(cid3, tid1, 2 * length + 8));
0809         auto state1 = [&]() {
0810             REQUIRE(timeline->checkConsistency());
0811             REQUIRE(timeline->getTrackClipsCount(tid1) == 3);
0812             REQUIRE(timeline->getTrackClipsCount(tid2) == 1);
0813             REQUIRE(timeline->getTrackClipsCount(tid3) == 0);
0814             REQUIRE(timeline->getClipTrackId(cid1) == tid1);
0815             REQUIRE(timeline->getClipTrackId(cid2) == tid1);
0816             REQUIRE(timeline->getClipTrackId(cid3) == tid1);
0817             REQUIRE(timeline->getClipTrackId(cid4) == tid2);
0818             REQUIRE(timeline->getClipPosition(cid1) == 3);
0819             REQUIRE(timeline->getClipPosition(cid2) == length + 3);
0820             REQUIRE(timeline->getClipPosition(cid3) == 2 * length + 8);
0821             REQUIRE(timeline->getClipPosition(cid4) == 7);
0822         };
0823         state1();
0824 
0825         // this move is possible
0826         REQUIRE(timeline->requestClipMove(cid1, tid2, 8));
0827         auto state2 = [&]() {
0828             REQUIRE(timeline->checkConsistency());
0829             REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
0830             REQUIRE(timeline->getTrackClipsCount(tid2) == 2);
0831             REQUIRE(timeline->getTrackClipsCount(tid3) == 1);
0832             REQUIRE(timeline->getClipTrackId(cid1) == tid2);
0833             REQUIRE(timeline->getClipTrackId(cid2) == tid1);
0834             REQUIRE(timeline->getClipTrackId(cid3) == tid2);
0835             REQUIRE(timeline->getClipTrackId(cid4) == tid3);
0836             REQUIRE(timeline->getClipPosition(cid1) == 8);
0837             REQUIRE(timeline->getClipPosition(cid2) == length + 3);
0838             REQUIRE(timeline->getClipPosition(cid3) == 2 * length + 5 + 8);
0839             REQUIRE(timeline->getClipPosition(cid4) == 4 + 8);
0840         };
0841         state2();
0842 
0843         undoStack->undo();
0844         state1();
0845 
0846         undoStack->redo();
0847         state2();
0848 
0849         REQUIRE(timeline->requestClipMove(cid1, tid1, 3));
0850         state1();
0851     }
0852 
0853     SECTION("Group move consecutive clips")
0854     {
0855         REQUIRE(timeline->requestClipMove(cid1, tid1, 7));
0856         REQUIRE(timeline->requestClipMove(cid2, tid1, 7 + length));
0857         REQUIRE(timeline->requestClipMove(cid3, tid1, 7 + 2 * length));
0858         REQUIRE(timeline->requestClipMove(cid4, tid1, 7 + 3 * length));
0859         REQUIRE(timeline->requestClipsGroup({cid1, cid2, cid3, cid4}));
0860 
0861         auto state = [&](int tid, int start) {
0862             REQUIRE(timeline->checkConsistency());
0863             REQUIRE(timeline->getTrackClipsCount(tid) == 4);
0864             int i = 0;
0865             for (int cid : std::vector<int>({cid1, cid2, cid3, cid4})) {
0866                 REQUIRE(timeline->getClipTrackId(cid) == tid);
0867                 REQUIRE(timeline->getClipPosition(cid) == start + i * length);
0868                 REQUIRE(timeline->getClipPlaytime(cid) == length);
0869                 i++;
0870             }
0871         };
0872         state(tid1, 7);
0873 
0874         auto check_undo = [&](int target, int tid, int oldTid) {
0875             state(tid, target);
0876             undoStack->undo();
0877             state(oldTid, 7);
0878             undoStack->redo();
0879             state(tid, target);
0880             undoStack->undo();
0881             state(oldTid, 7);
0882         };
0883 
0884         REQUIRE(timeline->requestClipMove(cid1, tid1, 6));
0885         qDebug() << "state1";
0886         state(tid1, 6);
0887         undoStack->undo();
0888         state(tid1, 7);
0889         undoStack->redo();
0890         state(tid1, 6);
0891         REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
0892         qDebug() << "state2";
0893         state(tid1, 0);
0894         undoStack->undo();
0895         state(tid1, 6);
0896         undoStack->redo();
0897         state(tid1, 0);
0898         undoStack->undo();
0899         state(tid1, 6);
0900         undoStack->undo();
0901         state(tid1, 7);
0902 
0903         REQUIRE(timeline->requestClipMove(cid3, tid1, 1 + 2 * length));
0904         qDebug() << "state3";
0905         check_undo(1, tid1, tid1);
0906 
0907         REQUIRE(timeline->requestClipMove(cid4, tid1, 4 + 3 * length));
0908         qDebug() << "state4";
0909         check_undo(4, tid1, tid1);
0910 
0911         REQUIRE(timeline->requestClipMove(cid4, tid1, 11 + 3 * length));
0912         qDebug() << "state5";
0913         check_undo(11, tid1, tid1);
0914 
0915         REQUIRE(timeline->requestClipMove(cid2, tid1, 13 + length));
0916         qDebug() << "state6";
0917         check_undo(13, tid1, tid1);
0918 
0919         REQUIRE(timeline->requestClipMove(cid1, tid1, 20));
0920         qDebug() << "state7";
0921         check_undo(20, tid1, tid1);
0922 
0923         REQUIRE(timeline->requestClipMove(cid4, tid1, 7 + 4 * length));
0924         qDebug() << "state8";
0925         check_undo(length + 7, tid1, tid1);
0926 
0927         REQUIRE(timeline->requestClipMove(cid2, tid1, 7 + 2 * length));
0928         qDebug() << "state9";
0929         check_undo(length + 7, tid1, tid1);
0930 
0931         REQUIRE(timeline->requestClipMove(cid1, tid1, 7 + length));
0932         qDebug() << "state10";
0933         check_undo(length + 7, tid1, tid1);
0934 
0935         REQUIRE(timeline->requestClipMove(cid2, tid2, 8 + length));
0936         qDebug() << "state11";
0937         check_undo(8, tid2, tid1);
0938     }
0939 
0940     SECTION("Group move to unavailable track")
0941     {
0942         REQUIRE(timeline->requestClipMove(cid1, tid1, 10));
0943         REQUIRE(timeline->requestClipMove(cid2, tid2, 12));
0944         REQUIRE(timeline->requestClipsGroup({cid1, cid2}));
0945         auto state = [&]() {
0946             REQUIRE(timeline->checkConsistency());
0947             REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
0948             REQUIRE(timeline->getTrackClipsCount(tid2) == 1);
0949             REQUIRE(timeline->getClipTrackId(cid1) == tid1);
0950             REQUIRE(timeline->getClipTrackId(cid2) == tid2);
0951         };
0952         state();
0953 
0954         // Moving clips on an unavailable track will do a same track move
0955         REQUIRE(timeline->requestClipMove(cid2, tid1, 10));
0956         REQUIRE(timeline->getClipPosition(cid1) == 8);
0957         REQUIRE(timeline->getClipPosition(cid2) == 10);
0958         state();
0959         REQUIRE(timeline->requestClipMove(cid2, tid1, 100));
0960         REQUIRE(timeline->getClipPosition(cid1) == 98);
0961         REQUIRE(timeline->getClipPosition(cid2) == 100);
0962         state();
0963         REQUIRE(timeline->requestClipMove(cid1, tid3, 100));
0964         REQUIRE(timeline->getClipPosition(cid1) == 100);
0965         REQUIRE(timeline->getClipPosition(cid2) == 102);
0966         state();
0967     }
0968 
0969     SECTION("Group move with non-consecutive track ids")
0970     {
0971         int tid5 = TrackModel::construct(timeline);
0972         int cid6 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly);
0973         Q_UNUSED(cid6);
0974         int tid6 = TrackModel::construct(timeline);
0975         REQUIRE(tid5 + 1 != tid6);
0976 
0977         REQUIRE(timeline->requestClipMove(cid1, tid5, 10));
0978         REQUIRE(timeline->requestClipMove(cid2, tid5, length + 10));
0979         REQUIRE(timeline->requestClipsGroup({cid1, cid2}));
0980         auto state = [&](int t) {
0981             REQUIRE(timeline->checkConsistency());
0982             REQUIRE(timeline->getTrackClipsCount(t) == 2);
0983             REQUIRE(timeline->getClipTrackId(cid1) == t);
0984             REQUIRE(timeline->getClipTrackId(cid2) == t);
0985             REQUIRE(timeline->getClipPosition(cid1) == 10);
0986             REQUIRE(timeline->getClipPosition(cid2) == 10 + length);
0987         };
0988         state(tid5);
0989         REQUIRE(timeline->requestClipMove(cid1, tid6, 10));
0990         state(tid6);
0991     }
0992 
0993     SECTION("Creation and movement of AV groups")
0994     {
0995         int tid6b = TrackModel::construct(timeline, -1, -1, QString(), true);
0996         int tid6 = TrackModel::construct(timeline, -1, -1, QString(), true);
0997         int tid5 = TrackModel::construct(timeline);
0998         int tid5b = TrackModel::construct(timeline);
0999         auto state0 = [&]() {
1000             REQUIRE(timeline->checkConsistency());
1001             REQUIRE(timeline->getTrackClipsCount(tid5) == 0);
1002             REQUIRE(timeline->getTrackClipsCount(tid6) == 0);
1003         };
1004         state0();
1005         QString binId3 = createProducerWithSound(pCore->getProjectProfile(), binModel);
1006 
1007         int cid6 = -1;
1008         // Setup insert stream data
1009         QMap<int, QString> audioInfo;
1010         audioInfo.insert(1, QStringLiteral("stream1"));
1011         timeline->m_binAudioTargets = audioInfo;
1012         REQUIRE(timeline->requestClipInsertion(binId3, tid5, 3, cid6, true, true, false));
1013         int cid7 = timeline->m_groups->getSplitPartner(cid6);
1014 
1015         auto check_group = [&]() {
1016             // we check that the av group was correctly created
1017             REQUIRE(timeline->getGroupElements(cid6) == std::unordered_set<int>({cid6, cid7}));
1018             int g1 = timeline->m_groups->getDirectAncestor(cid6);
1019             REQUIRE(timeline->m_groups->getDirectChildren(g1) == std::unordered_set<int>({cid6, cid7}));
1020             REQUIRE(timeline->m_groups->getType(g1) == GroupType::AVSplit);
1021         };
1022 
1023         auto state = [&](int pos) {
1024             REQUIRE(timeline->checkConsistency());
1025             REQUIRE(timeline->getTrackClipsCount(tid5) == 1);
1026             REQUIRE(timeline->getTrackClipsCount(tid6) == 1);
1027             REQUIRE(timeline->getClipTrackId(cid6) == tid5);
1028             REQUIRE(timeline->getClipTrackId(cid7) == tid6);
1029             REQUIRE(timeline->getClipPosition(cid6) == pos);
1030             REQUIRE(timeline->getClipPosition(cid7) == pos);
1031             REQUIRE(timeline->getClipPtr(cid6)->clipState() == PlaylistState::VideoOnly);
1032             REQUIRE(timeline->getClipPtr(cid7)->clipState() == PlaylistState::AudioOnly);
1033             check_group();
1034         };
1035         state(3);
1036         undoStack->undo();
1037         state0();
1038         undoStack->redo();
1039         state(3);
1040 
1041         // test deletion + undo after selection
1042         REQUIRE(timeline->requestSetSelection({cid6}));
1043         REQUIRE(timeline->getCurrentSelection() == std::unordered_set<int>{cid6, cid7});
1044 
1045         REQUIRE(timeline->requestItemDeletion(cid6, true));
1046         state0();
1047         undoStack->undo();
1048         state(3);
1049         undoStack->redo();
1050         state0();
1051         undoStack->undo();
1052         state(3);
1053 
1054         // simple translation on the right
1055         REQUIRE(timeline->requestClipMove(cid6, tid5, 10, true, true, true));
1056 
1057         state(10);
1058         undoStack->undo();
1059         state(3);
1060         undoStack->redo();
1061         state(10);
1062 
1063         // simple translation on the left, moving the audio clip this time
1064         REQUIRE(timeline->requestClipMove(cid7, tid6, 1, true, true, true));
1065         state(1);
1066         undoStack->undo();
1067         state(10);
1068         undoStack->redo();
1069         state(1);
1070 
1071         // change track, moving video
1072         REQUIRE(timeline->requestClipMove(cid6, tid5b, 7, true, true, true));
1073         auto state2 = [&](int pos) {
1074             REQUIRE(timeline->checkConsistency());
1075             REQUIRE(timeline->getTrackClipsCount(tid5b) == 1);
1076             REQUIRE(timeline->getTrackClipsCount(tid6b) == 1);
1077             REQUIRE(timeline->getClipTrackId(cid6) == tid5b);
1078             REQUIRE(timeline->getClipTrackId(cid7) == tid6b);
1079             REQUIRE(timeline->getClipPosition(cid6) == pos);
1080             REQUIRE(timeline->getClipPosition(cid7) == pos);
1081             REQUIRE(timeline->getClipPtr(cid6)->clipState() == PlaylistState::VideoOnly);
1082             REQUIRE(timeline->getClipPtr(cid7)->clipState() == PlaylistState::AudioOnly);
1083             check_group();
1084         };
1085         state2(7);
1086         undoStack->undo();
1087         state(1);
1088         undoStack->redo();
1089         state2(7);
1090 
1091         // change track, moving audio
1092         REQUIRE(timeline->requestClipMove(cid7, tid6b, 2, true, true, true));
1093         state2(2);
1094         undoStack->undo();
1095         state2(7);
1096         undoStack->redo();
1097         state2(2);
1098 
1099         undoStack->undo();
1100         undoStack->undo();
1101         state(1);
1102     }
1103 
1104     SECTION("Clip clone")
1105     {
1106         int cid6 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly);
1107         int l = timeline->getClipPlaytime(cid6);
1108         REQUIRE(timeline->requestItemResize(cid6, l - 3, true, true, -1) == l - 3);
1109         REQUIRE(timeline->requestItemResize(cid6, l - 7, false, true, -1) == l - 7);
1110 
1111         int newId;
1112 
1113         std::function<bool(void)> undo = []() { return true; };
1114         std::function<bool(void)> redo = []() { return true; };
1115         REQUIRE(TimelineFunctions::cloneClip(timeline, cid6, newId, PlaylistState::VideoOnly, undo, redo));
1116         REQUIRE(timeline->m_allClips[cid6]->binId() == timeline->m_allClips[newId]->binId());
1117         // TODO check effects
1118     }
1119     pCore->projectManager()->closeCurrentDocument(false, false);
1120 }
1121 
1122 TEST_CASE("Check id unicity", "[ClipModel]")
1123 {
1124     auto binModel = pCore->projectItemModel();
1125     binModel->clean();
1126     std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
1127 
1128     // Here we do some trickery to enable testing.
1129     // We mock the project class so that the undoStack function returns our undoStack
1130     KdenliveDoc document(undoStack);
1131 
1132     // We also mock timeline object to spy few functions and mock others
1133     pCore->projectManager()->m_project = &document;
1134     TimelineItemModel tim(document.uuid(), undoStack);
1135     Mock<TimelineItemModel> timMock(tim);
1136     auto timeline = std::shared_ptr<TimelineItemModel>(&timMock.get(), [](...) {});
1137     TimelineItemModel::finishConstruct(timeline);
1138 
1139     pCore->projectManager()->testSetActiveDocument(&document, timeline);
1140 
1141     RESET(timMock);
1142 
1143     QString binId = createProducer(pCore->getProjectProfile(), "red", binModel);
1144 
1145     std::vector<int> track_ids;
1146     std::unordered_set<int> all_ids;
1147 
1148     std::bernoulli_distribution coin(0.5);
1149 
1150     const int nbr = 20;
1151 
1152     for (int i = 0; i < nbr; i++) {
1153         if (coin(g)) {
1154             int tid = TrackModel::construct(timeline);
1155             REQUIRE(all_ids.count(tid) == 0);
1156             all_ids.insert(tid);
1157             track_ids.push_back(tid);
1158             REQUIRE(timeline->getTracksCount() == int(track_ids.size()));
1159         } else {
1160             int cid = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly);
1161             REQUIRE(all_ids.count(cid) == 0);
1162             all_ids.insert(cid);
1163             REQUIRE(timeline->getClipsCount() == int(all_ids.size() - track_ids.size()));
1164         }
1165     }
1166 
1167     REQUIRE(timeline->checkConsistency());
1168     REQUIRE(all_ids.size() == nbr);
1169     REQUIRE(all_ids.size() != track_ids.size());
1170     pCore->projectManager()->closeCurrentDocument(false, false);
1171 }
1172 
1173 TEST_CASE("Undo and Redo", "[ClipModel]")
1174 {
1175     auto binModel = pCore->projectItemModel();
1176     binModel->clean();
1177     std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
1178 
1179     // Here we do some trickery to enable testing.
1180     // We mock the project class so that the undoStack function returns our undoStack
1181     KdenliveDoc document(undoStack);
1182 
1183     // We also mock timeline object to spy few functions and mock others
1184     pCore->projectManager()->m_project = &document;
1185     TimelineItemModel tim(document.uuid(), undoStack);
1186     Mock<TimelineItemModel> timMock(tim);
1187     auto timeline = std::shared_ptr<TimelineItemModel>(&timMock.get(), [](...) {});
1188     TimelineItemModel::finishConstruct(timeline);
1189     pCore->projectManager()->testSetActiveDocument(&document, timeline);
1190 
1191     RESET(timMock);
1192 
1193     QString binId = createProducer(pCore->getProjectProfile(), "red", binModel);
1194     QString binId2 = createProducer(pCore->getProjectProfile(), "blue", binModel);
1195 
1196     int cid1 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly);
1197     int tid1 = TrackModel::construct(timeline);
1198     int tid2 = TrackModel::construct(timeline);
1199     int cid2 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly);
1200 
1201     int length = 20;
1202     unsigned long nclips = timeline->m_allClips.size();
1203 
1204     SECTION("requestCreateClip")
1205     {
1206         // an invalid clip id shouldn't get created
1207         {
1208             int temp;
1209             Fun undo = []() { return true; };
1210             Fun redo = []() { return true; };
1211             REQUIRE_FALSE(timeline->requestClipCreation("impossible bin id", temp, PlaylistState::VideoOnly, 1, 1., false, undo, redo));
1212         }
1213 
1214         auto state0 = [&]() {
1215             REQUIRE(timeline->checkConsistency());
1216             REQUIRE(timeline->m_allClips.size() == nclips);
1217         };
1218         state0();
1219 
1220         QString binId3 = createProducer(pCore->getProjectProfile(), "green", binModel);
1221         int cid3;
1222         {
1223             Fun undo = []() { return true; };
1224             Fun redo = []() { return true; };
1225             REQUIRE(timeline->requestClipCreation(binId3, cid3, PlaylistState::VideoOnly, 1, 1., false, undo, redo));
1226             pCore->pushUndo(undo, redo, QString());
1227         }
1228 
1229         auto state1 = [&]() {
1230             REQUIRE(timeline->checkConsistency());
1231             REQUIRE(timeline->m_allClips.size() == nclips + 1);
1232             REQUIRE(timeline->getClipPlaytime(cid3) == length);
1233             REQUIRE(timeline->getClipTrackId(cid3) == -1);
1234         };
1235         state1();
1236 
1237         QString binId4 = binId3 + "/1/10";
1238         int cid4;
1239         {
1240             Fun undo = []() { return true; };
1241             Fun redo = []() { return true; };
1242             REQUIRE(timeline->requestClipCreation(binId4, cid4, PlaylistState::VideoOnly, 1, 1., false, undo, redo));
1243             pCore->pushUndo(undo, redo, QString());
1244         }
1245 
1246         auto state2 = [&]() {
1247             REQUIRE(timeline->checkConsistency());
1248             REQUIRE(timeline->m_allClips.size() == nclips + 2);
1249             REQUIRE(timeline->getClipPlaytime(cid4) == 10);
1250             REQUIRE(timeline->getClipTrackId(cid4) == -1);
1251             auto inOut = std::pair<int, int>({1, 10});
1252             REQUIRE(timeline->m_allClips.at(cid4)->getInOut() == inOut);
1253             REQUIRE(timeline->getClipPlaytime(cid3) == length);
1254             REQUIRE(timeline->getClipTrackId(cid3) == -1);
1255         };
1256         state2();
1257         undoStack->undo();
1258         state1();
1259         undoStack->undo();
1260         state0();
1261         undoStack->redo();
1262         state1();
1263         undoStack->redo();
1264         state2();
1265     }
1266 
1267     SECTION("requestInsertClip")
1268     {
1269         auto state0 = [&]() {
1270             REQUIRE(timeline->checkConsistency());
1271             REQUIRE(timeline->m_allClips.size() == nclips);
1272         };
1273         state0();
1274 
1275         QString binId3 = createProducer(pCore->getProjectProfile(), "green", binModel);
1276         int cid3;
1277         REQUIRE(timeline->requestClipInsertion(binId3, tid1, 12, cid3, true));
1278 
1279         auto state1 = [&]() {
1280             REQUIRE(timeline->checkConsistency());
1281             REQUIRE(timeline->m_allClips.size() == nclips + 1);
1282             REQUIRE(timeline->getClipPlaytime(cid3) == length);
1283             REQUIRE(timeline->getClipTrackId(cid3) == tid1);
1284             REQUIRE(timeline->getClipPosition(cid3) == 12);
1285         };
1286         state1();
1287 
1288         QString binId4 = binId3 + "/1/10";
1289         int cid4;
1290         REQUIRE(timeline->requestClipInsertion(binId4, tid2, 17, cid4, true));
1291 
1292         auto state2 = [&]() {
1293             REQUIRE(timeline->checkConsistency());
1294             REQUIRE(timeline->m_allClips.size() == nclips + 2);
1295             REQUIRE(timeline->getClipPlaytime(cid4) == 10);
1296             REQUIRE(timeline->getClipTrackId(cid4) == tid2);
1297             REQUIRE(timeline->getClipPosition(cid4) == 17);
1298             auto inOut = std::pair<int, int>({1, 10});
1299             REQUIRE(timeline->m_allClips.at(cid4)->getInOut() == inOut);
1300             REQUIRE(timeline->getClipPlaytime(cid3) == length);
1301             REQUIRE(timeline->getClipTrackId(cid3) == tid1);
1302             REQUIRE(timeline->getClipPosition(cid3) == 12);
1303         };
1304         state2();
1305         undoStack->undo();
1306         state1();
1307         undoStack->undo();
1308         state0();
1309         undoStack->redo();
1310         state1();
1311         undoStack->redo();
1312         state2();
1313     }
1314     int init_index = undoStack->index();
1315 
1316     SECTION("Basic move undo")
1317     {
1318         REQUIRE(timeline->requestClipMove(cid1, tid1, 5));
1319         REQUIRE(timeline->checkConsistency());
1320         REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
1321         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
1322         REQUIRE(timeline->getClipPosition(cid1) == 5);
1323         REQUIRE(undoStack->index() == init_index + 1);
1324         CHECK_INSERT(Once);
1325 
1326         REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
1327         REQUIRE(timeline->checkConsistency());
1328         REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
1329         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
1330         REQUIRE(timeline->getClipPosition(cid1) == 0);
1331         REQUIRE(undoStack->index() == init_index + 2);
1332         // Move on same track does not trigger insert/remove row
1333         CHECK_MOVE(0);
1334 
1335         undoStack->undo();
1336         REQUIRE(timeline->checkConsistency());
1337         REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
1338         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
1339         REQUIRE(timeline->getClipPosition(cid1) == 5);
1340         REQUIRE(undoStack->index() == init_index + 1);
1341         CHECK_MOVE(0);
1342 
1343         undoStack->redo();
1344         REQUIRE(timeline->checkConsistency());
1345         REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
1346         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
1347         REQUIRE(timeline->getClipPosition(cid1) == 0);
1348         REQUIRE(undoStack->index() == init_index + 2);
1349         CHECK_MOVE(0);
1350 
1351         undoStack->undo();
1352         REQUIRE(timeline->checkConsistency());
1353         REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
1354         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
1355         REQUIRE(timeline->getClipPosition(cid1) == 5);
1356         REQUIRE(undoStack->index() == init_index + 1);
1357         CHECK_MOVE(0);
1358 
1359         REQUIRE(timeline->requestClipMove(cid1, tid1, 2 * length));
1360         REQUIRE(timeline->checkConsistency());
1361         REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
1362         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
1363         REQUIRE(timeline->getClipPosition(cid1) == 2 * length);
1364         REQUIRE(undoStack->index() == init_index + 2);
1365         CHECK_MOVE(0);
1366 
1367         undoStack->undo();
1368         REQUIRE(timeline->checkConsistency());
1369         REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
1370         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
1371         REQUIRE(timeline->getClipPosition(cid1) == 5);
1372         REQUIRE(undoStack->index() == init_index + 1);
1373         CHECK_MOVE(0);
1374 
1375         undoStack->redo();
1376         REQUIRE(timeline->checkConsistency());
1377         REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
1378         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
1379         REQUIRE(timeline->getClipPosition(cid1) == 2 * length);
1380         REQUIRE(undoStack->index() == init_index + 2);
1381         CHECK_MOVE(0);
1382 
1383         undoStack->undo();
1384         CHECK_MOVE(0);
1385         undoStack->undo();
1386         REQUIRE(timeline->checkConsistency());
1387         REQUIRE(timeline->getTrackClipsCount(tid1) == 0);
1388         REQUIRE(timeline->getClipTrackId(cid1) == -1);
1389         REQUIRE(undoStack->index() == init_index);
1390         CHECK_REMOVE(Once);
1391     }
1392 
1393     SECTION("Basic resize orphan clip undo")
1394     {
1395         REQUIRE(timeline->getClipPlaytime(cid2) == length);
1396 
1397         REQUIRE(timeline->requestItemResize(cid2, length - 5, true) == length - 5);
1398         REQUIRE(undoStack->index() == init_index + 1);
1399         REQUIRE(timeline->getClipPlaytime(cid2) == length - 5);
1400 
1401         REQUIRE(timeline->requestItemResize(cid2, length - 10, false) == length - 10);
1402         REQUIRE(undoStack->index() == init_index + 2);
1403         REQUIRE(timeline->getClipPlaytime(cid2) == length - 10);
1404 
1405         REQUIRE(timeline->requestItemResize(cid2, length, false) == -1);
1406         REQUIRE(undoStack->index() == init_index + 2);
1407         REQUIRE(timeline->getClipPlaytime(cid2) == length - 10);
1408 
1409         undoStack->undo();
1410         REQUIRE(undoStack->index() == init_index + 1);
1411         REQUIRE(timeline->getClipPlaytime(cid2) == length - 5);
1412 
1413         undoStack->redo();
1414         REQUIRE(undoStack->index() == init_index + 2);
1415         REQUIRE(timeline->getClipPlaytime(cid2) == length - 10);
1416 
1417         undoStack->undo();
1418         REQUIRE(undoStack->index() == init_index + 1);
1419         REQUIRE(timeline->getClipPlaytime(cid2) == length - 5);
1420 
1421         undoStack->undo();
1422         REQUIRE(undoStack->index() == init_index);
1423         REQUIRE(timeline->getClipPlaytime(cid2) == length);
1424     }
1425     SECTION("Basic resize inserted clip undo")
1426     {
1427         REQUIRE(timeline->getClipPlaytime(cid2) == length);
1428 
1429         auto check = [&](int pos, int l) {
1430             REQUIRE(timeline->checkConsistency());
1431             REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
1432             REQUIRE(timeline->getClipTrackId(cid2) == tid1);
1433             REQUIRE(timeline->getClipPlaytime(cid2) == l);
1434             REQUIRE(timeline->getClipPosition(cid2) == pos);
1435         };
1436         REQUIRE(timeline->requestClipMove(cid2, tid1, 5));
1437         INFO("Test 1");
1438         check(5, length);
1439         REQUIRE(undoStack->index() == init_index + 1);
1440 
1441         REQUIRE(timeline->requestItemResize(cid2, length - 5, true) == length - 5);
1442         INFO("Test 2");
1443         check(5, length - 5);
1444         REQUIRE(undoStack->index() == init_index + 2);
1445 
1446         REQUIRE(timeline->requestItemResize(cid2, length - 10, false) == length - 10);
1447         INFO("Test 3");
1448         check(10, length - 10);
1449         REQUIRE(undoStack->index() == init_index + 3);
1450 
1451         REQUIRE(timeline->requestItemResize(cid2, length, false) == -1);
1452         INFO("Test 4");
1453         check(10, length - 10);
1454         REQUIRE(undoStack->index() == init_index + 3);
1455 
1456         undoStack->undo();
1457         INFO("Test 5");
1458         check(5, length - 5);
1459         REQUIRE(undoStack->index() == init_index + 2);
1460 
1461         undoStack->redo();
1462         INFO("Test 6");
1463         check(10, length - 10);
1464         REQUIRE(undoStack->index() == init_index + 3);
1465 
1466         undoStack->undo();
1467         INFO("Test 7");
1468         check(5, length - 5);
1469         REQUIRE(undoStack->index() == init_index + 2);
1470 
1471         undoStack->undo();
1472         INFO("Test 8");
1473         check(5, length);
1474         REQUIRE(undoStack->index() == init_index + 1);
1475     }
1476     SECTION("Clip Insertion Undo")
1477     {
1478         QString binId3 = createProducer(pCore->getProjectProfile(), "red", binModel);
1479 
1480         REQUIRE(timeline->requestClipMove(cid1, tid1, 5));
1481         auto state1 = [&]() {
1482             REQUIRE(timeline->checkConsistency());
1483             REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
1484             REQUIRE(timeline->getClipTrackId(cid1) == tid1);
1485             REQUIRE(timeline->getClipPosition(cid1) == 5);
1486             REQUIRE(undoStack->index() == init_index + 1);
1487         };
1488         state1();
1489 
1490         int cid3;
1491         REQUIRE_FALSE(timeline->requestClipInsertion(binId3, tid1, 5, cid3));
1492         state1();
1493 
1494         REQUIRE_FALSE(timeline->requestClipInsertion(binId3, tid1, 6, cid3));
1495         state1();
1496 
1497         REQUIRE(timeline->requestClipInsertion(binId3, tid1, 5 + length, cid3));
1498         auto state2 = [&]() {
1499             REQUIRE(timeline->checkConsistency());
1500             REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
1501             REQUIRE(timeline->getClipTrackId(cid1) == tid1);
1502             REQUIRE(timeline->getClipTrackId(cid3) == tid1);
1503             REQUIRE(timeline->getClipPosition(cid1) == 5);
1504             REQUIRE(timeline->getClipPosition(cid3) == 5 + length);
1505             REQUIRE(timeline->m_allClips[cid3]->isValid());
1506             REQUIRE(undoStack->index() == init_index + 2);
1507         };
1508         state2();
1509 
1510         REQUIRE(timeline->requestClipMove(cid3, tid1, 10 + length));
1511         auto state3 = [&]() {
1512             REQUIRE(timeline->checkConsistency());
1513             REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
1514             REQUIRE(timeline->getClipTrackId(cid1) == tid1);
1515             REQUIRE(timeline->getClipTrackId(cid3) == tid1);
1516             REQUIRE(timeline->getClipPosition(cid1) == 5);
1517             REQUIRE(timeline->getClipPosition(cid3) == 10 + length);
1518             REQUIRE(undoStack->index() == init_index + 3);
1519         };
1520         state3();
1521 
1522         REQUIRE(timeline->requestItemResize(cid3, 1, true) == 1);
1523         auto state4 = [&]() {
1524             REQUIRE(timeline->checkConsistency());
1525             REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
1526             REQUIRE(timeline->getClipTrackId(cid1) == tid1);
1527             REQUIRE(timeline->getClipTrackId(cid3) == tid1);
1528             REQUIRE(timeline->getClipPosition(cid1) == 5);
1529             REQUIRE(timeline->getClipPlaytime(cid3) == 1);
1530             REQUIRE(timeline->getClipPosition(cid3) == 10 + length);
1531             REQUIRE(undoStack->index() == init_index + 4);
1532         };
1533         state4();
1534 
1535         undoStack->undo();
1536         state3();
1537 
1538         undoStack->undo();
1539         state2();
1540 
1541         undoStack->undo();
1542         state1();
1543 
1544         undoStack->redo();
1545         state2();
1546 
1547         undoStack->redo();
1548         state3();
1549 
1550         undoStack->redo();
1551         state4();
1552 
1553         undoStack->undo();
1554         state3();
1555 
1556         undoStack->undo();
1557         state2();
1558 
1559         undoStack->undo();
1560         state1();
1561     }
1562 
1563     SECTION("Clip Deletion undo")
1564     {
1565         REQUIRE(timeline->requestClipMove(cid1, tid1, 5));
1566         auto state1 = [&]() {
1567             REQUIRE(timeline->checkConsistency());
1568             REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
1569             REQUIRE(timeline->getClipTrackId(cid1) == tid1);
1570             REQUIRE(timeline->getClipPosition(cid1) == 5);
1571             REQUIRE(undoStack->index() == init_index + 1);
1572         };
1573         state1();
1574 
1575         int nbClips = timeline->getClipsCount();
1576         REQUIRE(timeline->requestItemDeletion(cid1));
1577         auto state2 = [&]() {
1578             REQUIRE(timeline->checkConsistency());
1579             REQUIRE(timeline->getTrackClipsCount(tid1) == 0);
1580             REQUIRE(timeline->getClipsCount() == nbClips - 1);
1581             REQUIRE(undoStack->index() == init_index + 2);
1582         };
1583         state2();
1584 
1585         undoStack->undo();
1586         state1();
1587 
1588         undoStack->redo();
1589         state2();
1590 
1591         undoStack->undo();
1592         state1();
1593     }
1594 
1595     SECTION("Select then delete")
1596     {
1597         REQUIRE(timeline->requestClipMove(cid1, tid1, 5));
1598         REQUIRE(timeline->requestClipMove(cid2, tid2, 1));
1599         auto state1 = [&]() {
1600             REQUIRE(timeline->checkConsistency());
1601             REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
1602             REQUIRE(timeline->getClipTrackId(cid1) == tid1);
1603             REQUIRE(timeline->getClipPosition(cid1) == 5);
1604             REQUIRE(timeline->getTrackClipsCount(tid2) == 1);
1605             REQUIRE(timeline->getClipTrackId(cid2) == tid2);
1606             REQUIRE(timeline->getClipPosition(cid2) == 1);
1607         };
1608         state1();
1609 
1610         REQUIRE(timeline->requestSetSelection({cid1, cid2}));
1611         int nbClips = timeline->getClipsCount();
1612         REQUIRE(timeline->requestItemDeletion(cid1));
1613         auto state2 = [&]() {
1614             REQUIRE(timeline->checkConsistency());
1615             REQUIRE(timeline->getTrackClipsCount(tid1) == 0);
1616             REQUIRE(timeline->getTrackClipsCount(tid2) == 0);
1617             REQUIRE(timeline->getClipsCount() == nbClips - 2);
1618         };
1619         state2();
1620 
1621         undoStack->undo();
1622         state1();
1623 
1624         undoStack->redo();
1625         state2();
1626 
1627         undoStack->undo();
1628         state1();
1629     }
1630 
1631     SECTION("Track insertion undo")
1632     {
1633         std::map<int, int> orig_trackPositions, final_trackPositions;
1634         for (const auto &it : timeline->m_iteratorTable) {
1635             int track = it.first;
1636             int pos = timeline->getTrackPosition(track);
1637             orig_trackPositions[track] = pos;
1638             if (pos >= 1) pos++;
1639             final_trackPositions[track] = pos;
1640         }
1641         auto checkPositions = [&](const std::map<int, int> &pos) {
1642             for (const auto &p : pos) {
1643                 REQUIRE(timeline->getTrackPosition(p.first) == p.second);
1644             }
1645         };
1646         checkPositions(orig_trackPositions);
1647         int new_tid;
1648         REQUIRE(timeline->requestTrackInsertion(1, new_tid));
1649         checkPositions(final_trackPositions);
1650 
1651         undoStack->undo();
1652         checkPositions(orig_trackPositions);
1653 
1654         undoStack->redo();
1655         checkPositions(final_trackPositions);
1656 
1657         undoStack->undo();
1658         checkPositions(orig_trackPositions);
1659     }
1660 
1661     SECTION("Track deletion undo")
1662     {
1663         int nb_clips = timeline->getClipsCount();
1664         int nb_tracks = timeline->getTracksCount();
1665         REQUIRE(timeline->requestClipMove(cid1, tid1, 5));
1666         auto state1 = [&]() {
1667             REQUIRE(timeline->checkConsistency());
1668             REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
1669             REQUIRE(timeline->getClipTrackId(cid1) == tid1);
1670             REQUIRE(timeline->getClipPosition(cid1) == 5);
1671             REQUIRE(undoStack->index() == init_index + 1);
1672             REQUIRE(timeline->getClipsCount() == nb_clips);
1673             REQUIRE(timeline->getTracksCount() == nb_tracks);
1674         };
1675         state1();
1676 
1677         REQUIRE(timeline->requestTrackDeletion(tid1));
1678         REQUIRE(timeline->getClipsCount() == nb_clips - 1);
1679         REQUIRE(timeline->getTracksCount() == nb_tracks - 1);
1680 
1681         undoStack->undo();
1682         state1();
1683 
1684         undoStack->redo();
1685         REQUIRE(timeline->getClipsCount() == nb_clips - 1);
1686         REQUIRE(timeline->getTracksCount() == nb_tracks - 1);
1687 
1688         undoStack->undo();
1689         state1();
1690     }
1691 
1692     unsigned long clipCount = timeline->m_allClips.size();
1693     SECTION("Clip creation and resize")
1694     {
1695         int cid6;
1696         auto state0 = [&]() {
1697             REQUIRE(timeline->m_allClips.size() == clipCount);
1698             REQUIRE(timeline->checkConsistency());
1699         };
1700         state0();
1701 
1702         {
1703             std::function<bool(void)> undo = []() { return true; };
1704             std::function<bool(void)> redo = []() { return true; };
1705             REQUIRE(timeline->requestClipCreation(binId, cid6, PlaylistState::VideoOnly, 1, 1., false, undo, redo));
1706             pCore->pushUndo(undo, redo, QString());
1707         }
1708         int l = timeline->getClipPlaytime(cid6);
1709 
1710         auto state1 = [&]() {
1711             REQUIRE(timeline->m_allClips.size() == clipCount + 1);
1712             REQUIRE(timeline->isClip(cid6));
1713             REQUIRE(timeline->getClipTrackId(cid6) == -1);
1714             REQUIRE(timeline->getClipPlaytime(cid6) == l);
1715         };
1716         state1();
1717 
1718         {
1719             std::function<bool(void)> undo = []() { return true; };
1720             std::function<bool(void)> redo = []() { return true; };
1721             int size = l - 5;
1722             REQUIRE(timeline->requestItemResize(cid6, size, true, true, undo, redo, false));
1723             pCore->pushUndo(undo, redo, QString());
1724         }
1725         auto state2 = [&]() {
1726             REQUIRE(timeline->m_allClips.size() == clipCount + 1);
1727             REQUIRE(timeline->isClip(cid6));
1728             REQUIRE(timeline->getClipTrackId(cid6) == -1);
1729             REQUIRE(timeline->getClipPlaytime(cid6) == l - 5);
1730         };
1731         state2();
1732 
1733         {
1734             std::function<bool(void)> undo = []() { return true; };
1735             std::function<bool(void)> redo = []() { return true; };
1736             REQUIRE(timeline->requestClipMove(cid6, tid1, 7, true, true, true, true, undo, redo));
1737             pCore->pushUndo(undo, redo, QString());
1738         }
1739         auto state3 = [&]() {
1740             REQUIRE(timeline->m_allClips.size() == clipCount + 1);
1741             REQUIRE(timeline->isClip(cid6));
1742             REQUIRE(timeline->getClipTrackId(cid6) == tid1);
1743             REQUIRE(timeline->getClipPosition(cid6) == 7);
1744             REQUIRE(timeline->getClipPlaytime(cid6) == l - 5);
1745         };
1746         state3();
1747 
1748         {
1749             std::function<bool(void)> undo = []() { return true; };
1750             std::function<bool(void)> redo = []() { return true; };
1751             int size = l - 6;
1752             REQUIRE(timeline->requestItemResize(cid6, size, false, true, undo, redo, false));
1753             pCore->pushUndo(undo, redo, QString());
1754         }
1755         auto state4 = [&]() {
1756             REQUIRE(timeline->m_allClips.size() == clipCount + 1);
1757             REQUIRE(timeline->isClip(cid6));
1758             REQUIRE(timeline->getClipTrackId(cid6) == tid1);
1759             REQUIRE(timeline->getClipPosition(cid6) == 8);
1760             REQUIRE(timeline->getClipPlaytime(cid6) == l - 6);
1761         };
1762         state4();
1763 
1764         undoStack->undo();
1765         state3();
1766         undoStack->undo();
1767         state2();
1768         undoStack->undo();
1769         state1();
1770         undoStack->undo();
1771         state0();
1772         undoStack->redo();
1773         state1();
1774         undoStack->redo();
1775         state2();
1776         undoStack->redo();
1777         state3();
1778         undoStack->redo();
1779         state4();
1780     }
1781     pCore->projectManager()->closeCurrentDocument(false, false);
1782 }
1783 
1784 TEST_CASE("Snapping", "[Snapping]")
1785 {
1786     auto binModel = pCore->projectItemModel();
1787     binModel->clean();
1788     std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
1789 
1790     // Here we do some trickery to enable testing.
1791     // We mock the project class so that the undoStack function returns our undoStack
1792     KdenliveDoc document(undoStack);
1793     Mock<KdenliveDoc> docMock(document);
1794 
1795     // We also mock timeline object to spy few functions and mock others
1796     pCore->projectManager()->m_project = &document;
1797     TimelineItemModel tim(document.uuid(), undoStack);
1798     Mock<TimelineItemModel> timMock(tim);
1799     auto timeline = std::shared_ptr<TimelineItemModel>(&timMock.get(), [](...) {});
1800     TimelineItemModel::finishConstruct(timeline);
1801     pCore->projectManager()->testSetActiveDocument(&document, timeline);
1802 
1803     RESET(timMock);
1804 
1805     QString binId = createProducer(pCore->getProjectProfile(), "red", binModel, 50);
1806     QString binId2 = createProducer(pCore->getProjectProfile(), "blue", binModel);
1807 
1808     int tid1 = TrackModel::construct(timeline);
1809     int cid1 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly);
1810     int tid2 = TrackModel::construct(timeline);
1811     int cid2 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly);
1812 
1813     int length = timeline->getClipPlaytime(cid1);
1814     int length2 = timeline->getClipPlaytime(cid2);
1815     SECTION("getBlankSizeNearClip")
1816     {
1817         REQUIRE(timeline->requestClipMove(cid1, tid1, 0));
1818 
1819         // before
1820         REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, false) == 0);
1821         // after
1822         REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, true) == INT_MAX);
1823         REQUIRE(timeline->requestClipMove(cid1, tid1, 10));
1824         // before
1825         REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, false) == 10);
1826         // after
1827         REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, true) == INT_MAX);
1828         REQUIRE(timeline->requestClipMove(cid2, tid1, 25 + length));
1829         // before
1830         REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, false) == 10);
1831         REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid2, false) == 15);
1832         // after
1833         REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, true) == 15);
1834         REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid2, true) == INT_MAX);
1835 
1836         REQUIRE(timeline->requestClipMove(cid2, tid1, 10 + length));
1837         // before
1838         REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, false) == 10);
1839         REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid2, false) == 0);
1840         // after
1841         REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, true) == 0);
1842         REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid2, true) == INT_MAX);
1843     }
1844     SECTION("Snap move to a single clip")
1845     {
1846         int beg = 30;
1847         // in the absence of other clips, a valid move shouldn't be modified
1848         for (int snap = -1; snap <= 5; ++snap) {
1849             REQUIRE(timeline->suggestClipMove(cid2, tid2, beg, -1, snap).at(0) == beg);
1850             REQUIRE(timeline->suggestClipMove(cid2, tid2, beg + length, -1, snap).at(0) == beg + length);
1851             REQUIRE(timeline->checkConsistency());
1852         }
1853 
1854         // We add a clip in first track to create snap points
1855         REQUIRE(timeline->requestClipMove(cid1, tid1, beg));
1856 
1857         // Now a clip in second track should snap to beginning
1858         auto check_snap = [&](int pos, int perturb, int snap) {
1859             if (snap >= perturb) {
1860                 REQUIRE(timeline->suggestClipMove(cid2, tid2, pos + perturb, -1, snap).at(0) == pos);
1861                 REQUIRE(timeline->suggestClipMove(cid2, tid2, pos - perturb, -1, snap).at(0) == pos);
1862             } else {
1863                 REQUIRE(timeline->suggestClipMove(cid2, tid2, pos + perturb, -1, snap).at(0) == pos + perturb);
1864                 REQUIRE(timeline->suggestClipMove(cid2, tid2, pos - perturb, -1, snap).at(0) == pos - perturb);
1865             }
1866         };
1867         for (int snap = -1; snap <= 5; ++snap) {
1868             for (int perturb = 0; perturb <= 6; ++perturb) {
1869                 // snap to beginning
1870                 check_snap(beg, perturb, snap);
1871                 check_snap(beg + length, perturb, snap);
1872                 // snap to end
1873                 check_snap(beg - length2, perturb, snap);
1874                 check_snap(beg + length - length2, perturb, snap);
1875                 REQUIRE(timeline->checkConsistency());
1876             }
1877         }
1878 
1879         // Same test, but now clip is moved in position 0 first
1880         REQUIRE(timeline->requestClipMove(cid2, tid2, 0));
1881         for (int snap = -1; snap <= 5; ++snap) {
1882             for (int perturb = 0; perturb <= 6; ++perturb) {
1883                 // snap to beginning
1884                 check_snap(beg, perturb, snap);
1885                 check_snap(beg + length, perturb, snap);
1886                 // snap to end
1887                 check_snap(beg - length2, perturb, snap);
1888                 check_snap(beg + length - length2, perturb, snap);
1889                 REQUIRE(timeline->checkConsistency());
1890             }
1891         }
1892     }
1893     pCore->projectManager()->closeCurrentDocument(false, false);
1894 }
1895 
1896 TEST_CASE("Operations under locked tracks", "[Locked]")
1897 {
1898 
1899     QString aCompo;
1900     // Look for a compo
1901     QVector<QPair<QString, QString>> transitions = TransitionsRepository::get()->getNames();
1902     for (const auto &trans : qAsConst(transitions)) {
1903         if (TransitionsRepository::get()->isComposition(trans.first)) {
1904             aCompo = trans.first;
1905             break;
1906         }
1907     }
1908     REQUIRE(!aCompo.isEmpty());
1909 
1910     auto binModel = pCore->projectItemModel();
1911     binModel->clean();
1912     std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
1913 
1914     // Here we do some trickery to enable testing.
1915     // We mock the project class so that the undoStack function returns our undoStack
1916     KdenliveDoc document(undoStack);
1917     Mock<KdenliveDoc> docMock(document);
1918 
1919     // We also mock timeline object to spy few functions and mock others
1920     pCore->projectManager()->m_project = &document;
1921     TimelineItemModel tim(document.uuid(), undoStack);
1922     Mock<TimelineItemModel> timMock(tim);
1923     auto timeline = std::shared_ptr<TimelineItemModel>(&timMock.get(), [](...) {});
1924     TimelineItemModel::finishConstruct(timeline);
1925     pCore->projectManager()->testSetActiveDocument(&document, timeline);
1926 
1927     Fake(Method(timMock, adjustAssetRange));
1928 
1929     // This is faked to allow to count calls
1930     Fake(Method(timMock, _beginInsertRows));
1931     Fake(Method(timMock, _beginRemoveRows));
1932     Fake(Method(timMock, _endInsertRows));
1933     Fake(Method(timMock, _endRemoveRows));
1934 
1935     QString binId = createProducer(pCore->getProjectProfile(), "red", binModel);
1936     QString binId3 = createProducerWithSound(pCore->getProjectProfile(), binModel);
1937 
1938     int tid1, tid2, tid3;
1939     REQUIRE(timeline->requestTrackInsertion(-1, tid1));
1940     REQUIRE(timeline->requestTrackInsertion(-1, tid2));
1941     REQUIRE(timeline->requestTrackInsertion(-1, tid3));
1942 
1943     RESET(timMock);
1944 
1945     SECTION("Locked track can't receive insertion")
1946     {
1947         timeline->setTrackLockedState(tid1, true);
1948         REQUIRE(timeline->getTrackById(tid1)->isLocked());
1949         REQUIRE(timeline->getClipsCount() == 0);
1950         REQUIRE(timeline->checkConsistency());
1951         int cid1 = -1;
1952         REQUIRE_FALSE(timeline->requestClipInsertion(binId, tid1, 2, cid1));
1953         REQUIRE(timeline->getClipsCount() == 0);
1954         REQUIRE(timeline->checkConsistency());
1955         REQUIRE(cid1 == -1);
1956 
1957         // now unlock and check that insertion becomes possible again
1958         timeline->setTrackLockedState(tid1, false);
1959         REQUIRE_FALSE(timeline->getTrackById(tid1)->isLocked());
1960         REQUIRE(timeline->getClipsCount() == 0);
1961         REQUIRE(timeline->checkConsistency());
1962         REQUIRE(timeline->requestClipInsertion(binId, tid1, 2, cid1));
1963         REQUIRE(timeline->getClipsCount() == 1);
1964         REQUIRE(timeline->checkConsistency());
1965         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
1966         REQUIRE(timeline->getClipPosition(cid1) == 2);
1967     }
1968     SECTION("Can't move clip on locked track")
1969     {
1970         int cid1 = -1;
1971         REQUIRE(timeline->requestClipInsertion(binId, tid1, 2, cid1));
1972         REQUIRE(timeline->getClipsCount() == 1);
1973         REQUIRE(timeline->checkConsistency());
1974         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
1975         REQUIRE(timeline->getClipPosition(cid1) == 2);
1976         // not yet locked, move should work
1977         REQUIRE(timeline->requestClipMove(cid1, tid1, 4));
1978         REQUIRE(timeline->getClipPosition(cid1) == 4);
1979         REQUIRE(timeline->checkConsistency());
1980         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
1981 
1982         timeline->setTrackLockedState(tid1, true);
1983         REQUIRE(timeline->getTrackById(tid1)->isLocked());
1984         REQUIRE(timeline->checkConsistency());
1985         REQUIRE(timeline->getClipsCount() == 1);
1986         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
1987         REQUIRE(timeline->getClipPosition(cid1) == 4);
1988 
1989         REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, 6));
1990 
1991         REQUIRE(timeline->getTrackById(tid1)->isLocked());
1992         REQUIRE(timeline->checkConsistency());
1993         REQUIRE(timeline->getClipsCount() == 1);
1994         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
1995         REQUIRE(timeline->getClipPosition(cid1) == 4);
1996 
1997         // unlock, move should work again
1998         timeline->setTrackLockedState(tid1, false);
1999         REQUIRE_FALSE(timeline->getTrackById(tid1)->isLocked());
2000         REQUIRE(timeline->checkConsistency());
2001         REQUIRE(timeline->requestClipMove(cid1, tid1, 6));
2002         REQUIRE(timeline->getClipsCount() == 1);
2003         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
2004         REQUIRE(timeline->getClipPosition(cid1) == 6);
2005         REQUIRE(timeline->checkConsistency());
2006     }
2007     SECTION("Can't move composition on locked track")
2008     {
2009         int compo = CompositionModel::construct(timeline, aCompo, QString());
2010         timeline->setTrackLockedState(tid1, true);
2011         REQUIRE(timeline->getTrackById(tid1)->isLocked());
2012         REQUIRE(timeline->checkConsistency());
2013 
2014         REQUIRE(timeline->getCompositionTrackId(compo) == -1);
2015         REQUIRE(timeline->getTrackCompositionsCount(tid1) == 0);
2016         int pos = 10;
2017         REQUIRE_FALSE(timeline->requestCompositionMove(compo, tid1, pos));
2018         REQUIRE(timeline->checkConsistency());
2019         REQUIRE(timeline->getCompositionTrackId(compo) == -1);
2020         REQUIRE(timeline->getTrackCompositionsCount(tid1) == 0);
2021 
2022         // unlock to be able to insert
2023         timeline->setTrackLockedState(tid1, false);
2024         REQUIRE_FALSE(timeline->getTrackById(tid1)->isLocked());
2025         REQUIRE(timeline->checkConsistency());
2026         REQUIRE(timeline->requestCompositionMove(compo, tid1, pos));
2027         REQUIRE(timeline->checkConsistency());
2028         REQUIRE(timeline->getCompositionTrackId(compo) == tid1);
2029         REQUIRE(timeline->getTrackCompositionsCount(tid1) == 1);
2030         REQUIRE(timeline->getCompositionPosition(compo) == pos);
2031 
2032         // relock
2033         timeline->setTrackLockedState(tid1, true);
2034         REQUIRE(timeline->getTrackById(tid1)->isLocked());
2035         REQUIRE(timeline->checkConsistency());
2036         REQUIRE_FALSE(timeline->requestCompositionMove(compo, tid1, pos + 10));
2037         REQUIRE(timeline->checkConsistency());
2038         REQUIRE(timeline->getCompositionTrackId(compo) == tid1);
2039         REQUIRE(timeline->getTrackCompositionsCount(tid1) == 1);
2040         REQUIRE(timeline->getCompositionPosition(compo) == pos);
2041     }
2042     SECTION("Can't resize clip on locked track")
2043     {
2044         int cid1 = -1;
2045         REQUIRE(timeline->requestClipInsertion(binId, tid1, 2, cid1));
2046         REQUIRE(timeline->getClipsCount() == 1);
2047 
2048         auto check = [&](int l) {
2049             REQUIRE(timeline->checkConsistency());
2050             REQUIRE(timeline->getClipTrackId(cid1) == tid1);
2051             REQUIRE(timeline->getClipPosition(cid1) == 2);
2052             REQUIRE(timeline->getClipPlaytime(cid1) == l);
2053         };
2054         check(20);
2055 
2056         // not yet locked, resize should work
2057         REQUIRE(timeline->requestItemResize(cid1, 18, true) == 18);
2058         check(18);
2059 
2060         // lock
2061         timeline->setTrackLockedState(tid1, true);
2062         REQUIRE(timeline->getTrackById(tid1)->isLocked());
2063         check(18);
2064         REQUIRE(timeline->requestItemResize(cid1, 17, true) == -1);
2065         check(18);
2066         REQUIRE(timeline->requestItemResize(cid1, 17, false) == -1);
2067         check(18);
2068         REQUIRE(timeline->requestItemResize(cid1, 19, true) == -1);
2069         check(18);
2070         REQUIRE(timeline->requestItemResize(cid1, 19, false) == -1);
2071         check(18);
2072 
2073         // unlock, resize should work again
2074         timeline->setTrackLockedState(tid1, false);
2075         REQUIRE_FALSE(timeline->getTrackById(tid1)->isLocked());
2076         check(18);
2077         REQUIRE(timeline->requestItemResize(cid1, 17, true) == 17);
2078         check(17);
2079     }
2080     SECTION("Can't resize composition on locked track")
2081     {
2082         int compo = CompositionModel::construct(timeline, aCompo, QString());
2083         REQUIRE(timeline->requestCompositionMove(compo, tid1, 2));
2084         REQUIRE(timeline->requestItemResize(compo, 20, true) == 20);
2085 
2086         auto check = [&](int l) {
2087             REQUIRE(timeline->checkConsistency());
2088             REQUIRE(timeline->getCompositionsCount() == 1);
2089             REQUIRE(timeline->getCompositionTrackId(compo) == tid1);
2090             REQUIRE(timeline->getCompositionPosition(compo) == 2);
2091             REQUIRE(timeline->getCompositionPlaytime(compo) == l);
2092         };
2093         check(20);
2094 
2095         // not yet locked, resize should work
2096         REQUIRE(timeline->requestItemResize(compo, 18, true) == 18);
2097         check(18);
2098 
2099         // lock
2100         timeline->setTrackLockedState(tid1, true);
2101         REQUIRE(timeline->getTrackById(tid1)->isLocked());
2102         check(18);
2103         REQUIRE(timeline->requestItemResize(compo, 17, true) == -1);
2104         check(18);
2105         REQUIRE(timeline->requestItemResize(compo, 17, false) == -1);
2106         check(18);
2107         REQUIRE(timeline->requestItemResize(compo, 19, true) == -1);
2108         check(18);
2109         REQUIRE(timeline->requestItemResize(compo, 19, false) == -1);
2110         check(18);
2111 
2112         // unlock, resize should work again
2113         timeline->setTrackLockedState(tid1, false);
2114         REQUIRE_FALSE(timeline->getTrackById(tid1)->isLocked());
2115         check(18);
2116         REQUIRE(timeline->requestItemResize(compo, 17, true) == 17);
2117         check(17);
2118     }
2119     SECTION("Can't remove clip contained in locked track")
2120     {
2121         std::function<bool(void)> undo = []() { return true; };
2122         std::function<bool(void)> redo = []() { return true; };
2123 
2124         // insert a clip to the track
2125         int cid1 = -1;
2126         REQUIRE(timeline->requestClipInsertion(binId, tid1, 2, cid1));
2127         REQUIRE(timeline->getClipsCount() == 1);
2128         REQUIRE(timeline->checkConsistency());
2129         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
2130         REQUIRE(timeline->getClipPosition(cid1) == 2);
2131 
2132         // lock the track
2133         timeline->setTrackLockedState(tid1, true);
2134         REQUIRE(timeline->getTrackById(tid1)->isLocked());
2135         REQUIRE(timeline->checkConsistency());
2136         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
2137 
2138         // try to delete bin clip, this should not work
2139         REQUIRE_FALSE(binModel->requestBinClipDeletion(binModel->getClipByBinID(binId), undo, redo));
2140         REQUIRE(timeline->checkConsistency());
2141         REQUIRE(timeline->getClipsCount() == 1);
2142         REQUIRE(timeline->getClipTrackId(cid1) == tid1);
2143         REQUIRE(timeline->getClipPosition(cid1) == 2);
2144 
2145         // unlock track, bin clip deletion should work now
2146         timeline->setTrackLockedState(tid1, false);
2147         REQUIRE_FALSE(timeline->getTrackById(tid1)->isLocked());
2148         REQUIRE(binModel->requestBinClipDeletion(binModel->getClipByBinID(binId), undo, redo));
2149         REQUIRE(timeline->checkConsistency());
2150         REQUIRE(timeline->getClipsCount() == 0);
2151         REQUIRE(timeline->checkConsistency());
2152     }
2153 
2154     pCore->projectManager()->closeCurrentDocument(false, false);
2155 }
2156 
2157 TEST_CASE("New KdenliveDoc activeTrack", "KdenliveDoc")
2158 {
2159     auto binModel = pCore->projectItemModel();
2160     binModel->clean();
2161 
2162     std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
2163     QUndoGroup *undoGroup = new QUndoGroup();
2164     undoGroup->addStack(undoStack.get());
2165     const QMap<QString, QString> emptyMap{};
2166 
2167     // Bug 442545: KdenliveDoc created with 0 video tracks causes a crash at
2168     // save time because the document's activeTrack was set to an out-of-range
2169     // position.
2170 
2171     SECTION("0 video tracks")
2172     {
2173         // Create document
2174         KdenliveDoc doc(undoStack, {0, 2});
2175         pCore->projectManager()->m_project = &doc;
2176         QDateTime documentDate = QDateTime::currentDateTime();
2177         pCore->projectManager()->updateTimeline(false, QString(), QString(), documentDate, 0);
2178         QMap<QUuid, QString> allSequences = binModel->getAllSequenceClips();
2179         const QString firstSeqId = allSequences.value(doc.uuid());
2180         pCore->projectManager()->openTimeline(firstSeqId, doc.uuid());
2181         auto timeline = doc.getTimeline(doc.uuid());
2182         pCore->projectManager()->m_activeTimelineModel = timeline;
2183         pCore->projectManager()->testSetActiveDocument(&doc, timeline);
2184         // since there are only 2 tracks, the activeTrack position should be 0 or 1
2185         CHECK(doc.getDocumentProperty("activeTrack").toInt() >= 0);
2186         CHECK(doc.getDocumentProperty("activeTrack").toInt() < 2);
2187         pCore->projectManager()->closeCurrentDocument(false, false);
2188     }
2189 
2190     SECTION("both audio and video tracks")
2191     {
2192         KdenliveDoc doc(undoStack, {2, 2});
2193         pCore->projectManager()->m_project = &doc;
2194         QDateTime documentDate = QDateTime::currentDateTime();
2195         pCore->projectManager()->updateTimeline(false, QString(), QString(), documentDate, 0);
2196         QMap<QUuid, QString> allSequences = binModel->getAllSequenceClips();
2197         const QString firstSeqId = allSequences.value(doc.uuid());
2198         pCore->projectManager()->openTimeline(firstSeqId, doc.uuid());
2199         auto timeline = doc.getTimeline(doc.uuid());
2200         pCore->projectManager()->m_activeTimelineModel = timeline;
2201         pCore->projectManager()->testSetActiveDocument(&doc, timeline);
2202 
2203         CHECK(doc.getSequenceProperty(doc.uuid(), "activeTrack").toInt() >= 0);
2204         CHECK(doc.getSequenceProperty(doc.uuid(), "activeTrack").toInt() < 4);
2205         // because video tracks come after audio tracks, videoTarget position
2206         // should also be after the audio tracks
2207         CHECK(doc.getSequenceProperty(doc.uuid(), "videoTarget").toInt() > 1);
2208         CHECK(doc.getSequenceProperty(doc.uuid(), "videoTarget").toInt() < 4);
2209 
2210         CHECK(doc.getSequenceProperty(doc.uuid(), "audioTarget").toInt() >= 0);
2211         CHECK(doc.getSequenceProperty(doc.uuid(), "audioTarget").toInt() < 2);
2212         pCore->projectManager()->closeCurrentDocument(false, false);
2213     }
2214 
2215     SECTION("0 audio tracks")
2216     {
2217         KdenliveDoc doc(undoStack, {2, 0});
2218         pCore->projectManager()->m_project = &doc;
2219         QDateTime documentDate = QDateTime::currentDateTime();
2220         pCore->projectManager()->updateTimeline(false, QString(), QString(), documentDate, 0);
2221         QMap<QUuid, QString> allSequences = binModel->getAllSequenceClips();
2222         const QString firstSeqId = allSequences.value(doc.uuid());
2223         pCore->projectManager()->openTimeline(firstSeqId, doc.uuid());
2224         auto timeline = doc.getTimeline(doc.uuid());
2225         pCore->projectManager()->m_activeTimelineModel = timeline;
2226         pCore->projectManager()->testSetActiveDocument(&doc, timeline);
2227 
2228         CHECK(doc.getSequenceProperty(doc.uuid(), "activeTrack").toInt() >= 0);
2229         CHECK(doc.getSequenceProperty(doc.uuid(), "activeTrack").toInt() < 2);
2230         CHECK(doc.getSequenceProperty(doc.uuid(), "videoTarget").toInt() >= 0);
2231         CHECK(doc.getSequenceProperty(doc.uuid(), "videoTarget").toInt() < 2);
2232         pCore->projectManager()->closeCurrentDocument(false, false);
2233     }
2234 }