ComponentKitTests/TransactionalDataSource/CKDataSourceChangesetVerificationTests.mm (1,184 lines of code) (raw):

/* * Copyright (c) 2014-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ #import <Foundation/Foundation.h> #import <XCTest/XCTest.h> #import <ComponentKit/CKComponentLayout.h> #import <ComponentKit/CKComponentScopeRootFactory.h> #import <ComponentKit/CKDataSourceChangeset.h> #import <ComponentKit/CKDataSourceChangesetModification.h> #import <ComponentKit/CKDataSourceItemInternal.h> #import <ComponentKit/CKDataSourceStateInternal.h> #import <ComponentKit/CKDataSourceChangesetVerification.h> #import <ComponentKitTestHelpers/CKChangesetHelpers.h> #import "CKDataSourceStateTestHelpers.h" @interface CKDataSourceChangesetVerificationTests : XCTestCase @end @implementation CKDataSourceChangesetVerificationTests #pragma mark - Valid changesets - (void)assertEqualChangesetInfoWith:(CKInvalidChangesetInfo)lhs target:(CKInvalidChangesetInfo)rhs { XCTAssertEqual(lhs.operationType, rhs.operationType); XCTAssertEqual(lhs.section, rhs.section); XCTAssertEqual(lhs.item, rhs.item); } - (void)test_emptyChangesetEmptySections { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[]]; CKDataSourceChangeset *changeset = [[CKDataSourceChangesetBuilder dataSourceChangeset] build]; [self assertEqualChangesetInfoWith:kChangeSetValid target:CKIsValidChangesetForState(changeset, state, nil)]; } - (void)test_emptyChangesetNonEmptySections { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"A"), itemWithModel(@"B"), ], @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[CKDataSourceChangesetBuilder dataSourceChangeset] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_nonEmptyChangesetEmptySections { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[]]; CKDataSourceChangeset *changeset = [[[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedSections:[NSIndexSet indexSetWithIndex:0]] withInsertedItems:@{ [NSIndexPath indexPathForItem:0 inSection:0]: @"A", [NSIndexPath indexPathForItem:1 inSection:0]: @"B", }] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_validInsertionIntoEmptySection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedItems:@{ [NSIndexPath indexPathForItem:0 inSection:0]: @"A", [NSIndexPath indexPathForItem:1 inSection:0]: @"B", }] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_validInsertionIntoBeginningOfNonEmptySection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedItems:@{ [NSIndexPath indexPathForItem:0 inSection:0]: @"A", [NSIndexPath indexPathForItem:1 inSection:0]: @"B", }] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_validInsertionIntoMiddleOfNonEmptySection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedItems:@{ [NSIndexPath indexPathForItem:1 inSection:0]: @"A", [NSIndexPath indexPathForItem:2 inSection:0]: @"B", }] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_validInsertionAtEndOfNonEmptySection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedItems:@{ [NSIndexPath indexPathForItem:2 inSection:0]: @"A", [NSIndexPath indexPathForItem:3 inSection:0]: @"B", }] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_validRemovalAtBeginningOfSection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), itemWithModel(@"E"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withRemovedItems:[NSSet setWithArray:@[ [NSIndexPath indexPathForItem:0 inSection:0], ]]] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_validRemovalAtMiddleOfSection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), itemWithModel(@"E"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withRemovedItems:[NSSet setWithArray:@[ [NSIndexPath indexPathForItem:1 inSection:0], ]]] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_validRemovalAtEndOfSection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), itemWithModel(@"E"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withRemovedItems:[NSSet setWithArray:@[ [NSIndexPath indexPathForItem:2 inSection:0], ]]] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_validRemovalOfAllItems { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), itemWithModel(@"E"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withRemovedItems:[NSSet setWithArray:@[ [NSIndexPath indexPathForItem:2 inSection:0], [NSIndexPath indexPathForItem:1 inSection:0], [NSIndexPath indexPathForItem:0 inSection:0], ]]] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_validInsertionOfSectionInEmptySections { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedSections:[NSIndexSet indexSetWithIndex:0]] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_validInsertionOfSectionAtBeginningOfNonEmptySections { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], @[ itemWithModel(@"E"), itemWithModel(@"F"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedSections:[NSIndexSet indexSetWithIndex:0]] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_validInsertionOfSectionInMiddleOfNonEmptySections { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], @[ itemWithModel(@"E"), itemWithModel(@"F"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedSections:[NSIndexSet indexSetWithIndex:1]] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_validInsertionOfSectionAtEndOfNonEmptySections { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], @[ itemWithModel(@"E"), itemWithModel(@"F"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedSections:[NSIndexSet indexSetWithIndex:2]] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_validInsertionOfMultipleSections { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], @[ itemWithModel(@"E"), itemWithModel(@"F"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 2)]] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_removalOfSectionAtBeginningOfSections { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], @[ itemWithModel(@"E"), itemWithModel(@"F"), ], @[ itemWithModel(@"G"), itemWithModel(@"H"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withRemovedSections:[NSIndexSet indexSetWithIndex:0]] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_removalOfSectionAtMiddleOfSections { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], @[ itemWithModel(@"E"), itemWithModel(@"F"), ], @[ itemWithModel(@"G"), itemWithModel(@"H"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withRemovedSections:[NSIndexSet indexSetWithIndex:1]] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_removalOfSectionAtEndOfSections { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], @[ itemWithModel(@"E"), itemWithModel(@"F"), ], @[ itemWithModel(@"G"), itemWithModel(@"H"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withRemovedSections:[NSIndexSet indexSetWithIndex:2]] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_removeMultipleSectionsAtBeginningOfSections { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], @[ itemWithModel(@"E"), itemWithModel(@"F"), ], @[ itemWithModel(@"G"), itemWithModel(@"H"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withRemovedSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_removeMultipleSectionsAtEndOfSections { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], @[ itemWithModel(@"E"), itemWithModel(@"F"), ], @[ itemWithModel(@"G"), itemWithModel(@"H"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withRemovedSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 2)]] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_validUpdate { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withUpdatedItems:@{ [NSIndexPath indexPathForItem:0 inSection:0]: @"A", }] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_validMoveForwardInsideOneSection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withMovedItems:@{ [NSIndexPath indexPathForItem:0 inSection:0]: [NSIndexPath indexPathForItem:1 inSection:0], }] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_validMoveBackwardInsideOneSection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withMovedItems:@{ [NSIndexPath indexPathForItem:1 inSection:0]: [NSIndexPath indexPathForItem:0 inSection:0], }] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_validMoveForwardBetweenSections { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], @[ itemWithModel(@"E"), itemWithModel(@"F"), ], @[ itemWithModel(@"G"), itemWithModel(@"H"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withMovedItems:@{ [NSIndexPath indexPathForItem:0 inSection:0]: [NSIndexPath indexPathForItem:2 inSection:1], }] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_validMoveBackwardBetweenSections { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], @[ itemWithModel(@"E"), itemWithModel(@"F"), ], @[ itemWithModel(@"G"), itemWithModel(@"H"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withMovedItems:@{ [NSIndexPath indexPathForItem:0 inSection:1]: [NSIndexPath indexPathForItem:2 inSection:0], }] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_validMoveBackwardWithOriginalIndexRemoved { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"A"), itemWithModel(@"B"), itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; // Changeset for transition from [A, B, C, D] to [D, A] CKDataSourceChangeset *changeset = [[[[CKDataSourceChangesetBuilder dataSourceChangeset] withMovedItems:@{ [NSIndexPath indexPathForItem:3 inSection:0] : [NSIndexPath indexPathForItem:0 inSection:0], }] withRemovedItems:[NSSet setWithArray:@[ [NSIndexPath indexPathForItem:1 inSection:0], [NSIndexPath indexPathForItem:2 inSection:0], ]]] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_WhenVerifyingMovesWithSectionRemoval_ValidatesFromIndexPathsAgainstOriginalState { const auto state = CKDataSourceTestState(nullptr, nil, 2, 20); const auto changeset = CK::makeChangeset({ .removedSections = { 0, }, .movedItems = { {{1, 1}, {0, 9}}, {{1, 4}, {0, 17}}, {{1, 18}, {0, 13}}, {{1, 19}, {0, 5}}, }, }); [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_WhenVerifyingMovesWithSectionInsertion_ValidatesFromIndexPathsAgainstOriginalState { const auto state = CKDataSourceTestState(nullptr, nil, 1, 20); const auto changeset = CK::makeChangeset({ .movedItems = { {{0, 1}, {1, 9}}, {{0, 4}, {1, 17}}, {{0, 18}, {1, 13}}, {{0, 19}, {1, 5}}, }, .insertedSections = { 0 }, }); [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_WhenVerifyingMovesWithBothSectionInsertionsAndRemovals_ValidatesFromIndexPathsAgainstOriginalState { const auto state = CKDataSourceTestState(nullptr, nil, 2, 20); const auto changeset = CK::makeChangeset({ .removedSections = { 0, }, .movedItems = { {{1, 1}, {1, 9}}, {{1, 4}, {1, 17}}, {{1, 18}, {1, 13}}, {{1, 19}, {1, 5}}, }, .insertedSections = { 0 }, }); [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_WhenVerifyingMovesToNewSection_ValidatesToIndexPathsAgainstFinalState { const auto state = CKDataSourceTestState(nullptr, nil, 2, 2); const auto changeset = CK::makeChangeset({ .movedItems = { {{0, 0}, {1, 0}}, {{1, 0}, {1, 1}}, }, .insertedSections = { 1 }, }); [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_WhenVerifyingMovesWithinSection_ValidatesToIndexPathsAgainstFinalState { const auto state = CKDataSourceTestState(nullptr, nil, 1, 7); const auto changeset = CK::makeChangeset({ .updatedItems = { {{0, 0}, @"Upd"}, {{0, 1}, @"Upd"}, {{0, 2}, @"Upd"}, {{0, 3}, @"Upd"}, {{0, 5}, @"Upd"}, {{0, 6}, @"Upd"}, }, .movedItems = { {{0, 0}, {0, 9}}, {{0, 1}, {0, 10}}, }, .insertedItems = { {{0, 2}, @"New"}, {{0, 3}, @"New"}, {{0, 4}, @"New"}, {{0, 5}, @"New"}, {{0, 6}, @"New"}, {{0, 7}, @"New"}, } }); [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_WhenVerifyingMovesFromDeletedSection { const auto state = CKDataSourceTestState(nullptr, nil, 2, 2); const auto changeset = CK::makeChangeset({ .movedItems = { {{0, 0}, {0, 0}}, {{0, 1}, {0, 1}}, }, .removedSections = { 0 }, }); [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_WhenVerifyingMovesWithinSection { const auto state = CKDataSourceTestState(nullptr, nil, 1, 20); const auto changeset = CK::makeChangeset({ .movedItems = { {{0, 0}, {0, 19}}, {{0, 1}, {0, 20}}, {{0, 2}, {0, 21}}, {{0, 3}, {0, 22}}, {{0, 4}, {0, 23}}, {{0, 5}, {0, 24}}, {{0, 6}, {0, 25}}, {{0, 7}, {0, 26}}, {{0, 8}, {0, 27}}, {{0, 9}, {0, 28}}, {{0, 10}, {0, 29}}, {{0, 11}, {0, 30}}, {{0, 12}, {0, 31}}, {{0, 13}, {0, 32}}, {{0, 14}, {0, 33}}, {{0, 15}, {0, 34}}, {{0, 16}, {0, 35}}, {{0, 17}, {0, 36}}, {{0, 18}, {0, 37}}, {{0, 19}, {0, 38}} }, .insertedItems = { {{0, 0}, @"New"}, {{0, 1}, @"New"}, {{0, 2}, @"New"}, {{0, 3}, @"New"}, {{0, 4}, @"New"}, {{0, 5}, @"New"}, {{0, 6}, @"New"}, {{0, 7}, @"New"}, {{0, 8}, @"New"}, {{0, 9}, @"New"}, {{0, 10}, @"New"}, {{0, 11}, @"New"}, {{0, 12}, @"New"}, {{0, 13}, @"New"}, {{0, 14}, @"New"}, {{0, 15}, @"New"}, {{0, 16}, @"New"}, {{0, 17}, @"New"}, {{0, 18}, @"New"} } }); [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_WhenApplyingPendingModificationsWithMoves_TreatsToIndexPathsAsSpecifiedAfterSectionRemovalsAndInsertions { const auto state = CKDataSourceTestState(nullptr, nil, 5, 1); const auto pendingChangeset = CK::makeChangeset({ .removedSections = { 0, 2 }, .movedItems = { {{3, 0}, {4, 0}}, }, .insertedSections = { 0, 2, 3 }, .insertedItems = { {{0, 0}, @"New"}, {{2, 0}, @"New"}, {{3, 0}, @"New"}, } }); const auto pendingModification = [[CKDataSourceChangesetModification alloc] initWithChangeset:pendingChangeset stateListener:nil userInfo:nil qos:CKDataSourceQOSDefault]; const auto changeset = CK::makeChangeset({ .removedSections = { 0, 3 }, .movedItems = { {{4, 0}, {4, 0}}, }, .insertedSections = { 0, 3 }, .insertedItems = { {{0, 0}, @"New2"}, {{3, 0}, @"New2"}, } }); [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, @[pendingModification]) target:kChangeSetValid]; } #pragma mark - Invalid changesets - (void)test_invalidUpdateInNegativeSection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withUpdatedItems:@{ [NSIndexPath indexPathForItem:1 inSection:-1]: @"A", }] build]; CKInvalidChangesetInfo target = { CKInvalidChangesetOperationTypeUpdate, -1, 1 }; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:target]; } - (void)test_invalidUpdateInNegativeItem { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withUpdatedItems:@{ [NSIndexPath indexPathForItem:-1 inSection:0]: @"A", }] build]; CKInvalidChangesetInfo target = { CKInvalidChangesetOperationTypeUpdate, 0, -1 }; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:target]; } - (void)test_invalidInsertionAtEndOfSection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedItems:@{ [NSIndexPath indexPathForItem:3 inSection:0]: @"A", }] build]; CKInvalidChangesetInfo target = { CKInvalidChangesetOperationTypeInsertRow, 0, 3 }; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:target]; } - (void)test_invalidMultipleInsertionAtEndOfSection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedItems:@{ [NSIndexPath indexPathForItem:2 inSection:0]: @"A", [NSIndexPath indexPathForItem:4 inSection:0]: @"B", [NSIndexPath indexPathForItem:5 inSection:0]: @"E", }] build]; CKInvalidChangesetInfo target = { CKInvalidChangesetOperationTypeInsertRow, 0, 4 }; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:target]; } - (void)test_invalidInsertionInNonExistentSection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedItems:@{ [NSIndexPath indexPathForItem:0 inSection:1]: @"A", }] build]; CKInvalidChangesetInfo target = { CKInvalidChangesetOperationTypeInsertRow, 1, 0 }; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:target]; } - (void)test_invalidRemovalInValidSection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withRemovedItems:[NSSet setWithArray:@[ [NSIndexPath indexPathForItem:2 inSection:0], ]]] build]; CKInvalidChangesetInfo target = { CKInvalidChangesetOperationTypeRemoveRow, 0, 2 }; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:target]; } - (void)test_invalidRemovalInNonExistentSection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withRemovedItems:[NSSet setWithArray:@[ [NSIndexPath indexPathForItem:0 inSection:1], ]]] build]; CKInvalidChangesetInfo target = { CKInvalidChangesetOperationTypeRemoveRow, 1, 0 }; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:target]; } - (void)test_invalidInsertionOfSection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedSections:[NSIndexSet indexSetWithIndex:2]] build]; CKInvalidChangesetInfo target = { CKInvalidChangesetOperationTypeInsertSection, 2, -1 }; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:target]; } - (void)test_invalidRemovalOfSection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withRemovedSections:[NSIndexSet indexSetWithIndex:2]] build]; CKInvalidChangesetInfo target = { CKInvalidChangesetOperationTypeRemoveSection, 2, -1 }; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:target]; } - (void)test_invalidUpdateWithinExistingSection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withUpdatedItems:@{ [NSIndexPath indexPathForItem:2 inSection:0]: @"A", }] build]; CKInvalidChangesetInfo target = { CKInvalidChangesetOperationTypeUpdate, 0, 2 }; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:target]; } - (void)test_invalidUpdateWithinNonExistentSection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withUpdatedItems:@{ [NSIndexPath indexPathForItem:0 inSection:1]: @"A", }] build]; CKInvalidChangesetInfo target = { CKInvalidChangesetOperationTypeUpdate, 1, 0 }; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:target]; } - (void)test_moveWithInvalidOriginIndexPathInExistingSection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withMovedItems:@{ [NSIndexPath indexPathForItem:2 inSection:0]: [NSIndexPath indexPathForItem:0 inSection:0], }] build]; CKInvalidChangesetInfo target = { CKInvalidChangesetOperationTypeMoveRow, 0, 2 }; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:target]; } - (void)test_moveWithInvalidDestinationIndexPathInExistingSection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withMovedItems:@{ [NSIndexPath indexPathForItem:0 inSection:0]: [NSIndexPath indexPathForItem:2 inSection:0], }] build]; CKInvalidChangesetInfo target = { CKInvalidChangesetOperationTypeMoveRow, 0, 2 }; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:target]; } - (void)test_moveWithInvalidOriginSection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withMovedItems:@{ [NSIndexPath indexPathForItem:0 inSection:1]: [NSIndexPath indexPathForItem:0 inSection:0], }] build]; CKInvalidChangesetInfo target = { CKInvalidChangesetOperationTypeMoveRow, 1, -1 }; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:target]; } - (void)test_moveWithInvalidDestinationSection { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[ @[ itemWithModel(@"C"), itemWithModel(@"D"), ], ]]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withMovedItems:@{ [NSIndexPath indexPathForItem:0 inSection:0]: [NSIndexPath indexPathForItem:0 inSection:1], }] build]; CKInvalidChangesetInfo target = { CKInvalidChangesetOperationTypeMoveRow, 1, -1 }; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:target]; } #pragma mark - More complicated situations - (void)test_validInitialSectionInsertionsWithItemInsertions { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[]]; CKDataSourceChangeset *changeset = [[[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]] withInsertedItems:@{ [NSIndexPath indexPathForItem:0 inSection:0]: @"A1", [NSIndexPath indexPathForItem:1 inSection:0]: @"B1", [NSIndexPath indexPathForItem:0 inSection:1]: @"A2", [NSIndexPath indexPathForItem:1 inSection:1]: @"B2", }] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, nil) target:kChangeSetValid]; } - (void)test_validInitialSectionInsertionsInPendingAsynchronousModificationWithItemInsertions { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[]]; NSArray<id<CKDataSourceStateModifying>> *pendingAsynchronousModifications = @[ // Insert section 0 and section 1 [[CKDataSourceChangesetModification alloc] initWithChangeset: [[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]] build] stateListener:nil userInfo:nil qos:CKDataSourceQOSDefault], ]; CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedItems:@{ [NSIndexPath indexPathForItem:0 inSection:0]: @"A1", [NSIndexPath indexPathForItem:1 inSection:0]: @"B1", [NSIndexPath indexPathForItem:0 inSection:1]: @"A2", [NSIndexPath indexPathForItem:1 inSection:1]: @"B2", }] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, pendingAsynchronousModifications) target:kChangeSetValid]; } - (void)test_validChangesetAppliedToValidPendingAsynchronousModifications { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[]]; NSArray<id<CKDataSourceStateModifying>> *pendingAsynchronousModifications = @[ // Insert section 0 [[CKDataSourceChangesetModification alloc] initWithChangeset: [[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedSections:[NSIndexSet indexSetWithIndex:0]] build] stateListener:nil userInfo:nil qos:CKDataSourceQOSDefault], // Insert two items into section 0 [[CKDataSourceChangesetModification alloc] initWithChangeset: [[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedItems:@{ [NSIndexPath indexPathForItem:0 inSection:0]: @"A1", [NSIndexPath indexPathForItem:1 inSection:0]: @"B1", }] build] stateListener:nil userInfo:nil qos:CKDataSourceQOSDefault], // Insert section 1 [[CKDataSourceChangesetModification alloc] initWithChangeset: [[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedSections:[NSIndexSet indexSetWithIndex:1]] build] stateListener:nil userInfo:nil qos:CKDataSourceQOSDefault], // Insert two items into section 1 [[CKDataSourceChangesetModification alloc] initWithChangeset: [[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedItems:@{ [NSIndexPath indexPathForItem:0 inSection:1]: @"A2", [NSIndexPath indexPathForItem:1 inSection:1]: @"B2", }] build] stateListener:nil userInfo:nil qos:CKDataSourceQOSDefault], ]; // Remove first item from each section CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withRemovedItems:[NSSet setWithArray:@[ [NSIndexPath indexPathForItem:0 inSection:0], [NSIndexPath indexPathForItem:0 inSection:1], ]]] build]; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, pendingAsynchronousModifications) target:kChangeSetValid]; } - (void)test_invalidChangesetAppliedToValidPendingAsynchronousModifications { CKDataSourceState *state = [[CKDataSourceState alloc] initWithConfiguration:nil sections:@[]]; NSArray<id<CKDataSourceStateModifying>> *pendingAsynchronousModifications = @[ // Insert section 0 [[CKDataSourceChangesetModification alloc] initWithChangeset: [[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedSections:[NSIndexSet indexSetWithIndex:0]] build] stateListener:nil userInfo:nil qos:CKDataSourceQOSDefault], // Insert two items into section 0 [[CKDataSourceChangesetModification alloc] initWithChangeset: [[[CKDataSourceChangesetBuilder dataSourceChangeset] withInsertedItems:@{ [NSIndexPath indexPathForItem:0 inSection:0]: @"A1", [NSIndexPath indexPathForItem:1 inSection:0]: @"B1", }] build] stateListener:nil userInfo:nil qos:CKDataSourceQOSDefault], ]; // Remove first item from section 1 CKDataSourceChangeset *changeset = [[[CKDataSourceChangesetBuilder dataSourceChangeset] withRemovedItems:[NSSet setWithArray:@[ [NSIndexPath indexPathForItem:0 inSection:0], [NSIndexPath indexPathForItem:0 inSection:1], ]]] build]; CKInvalidChangesetInfo target = { CKInvalidChangesetOperationTypeRemoveRow, 1, 0 }; [self assertEqualChangesetInfoWith:CKIsValidChangesetForState(changeset, state, pendingAsynchronousModifications) target:target]; } static const CKInvalidChangesetInfo kChangeSetValid = { CKInvalidChangesetOperationTypeNone, -1, -1}; static CKDataSourceItem *itemWithModel(id model) { return [[CKDataSourceItem alloc] initWithRootLayout:{} model:model scopeRoot:CKComponentScopeRootWithDefaultPredicates(nil, nil) boundsAnimation:{}]; } @end