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