An amazing project that generates micro reports from tournament results
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1153 lines
41 KiB

////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMTestCase.h"
#import "RLMRealmConfiguration_Private.h"
@interface NotificationTests : RLMTestCase
@property (nonatomic, strong) RLMNotificationToken *token;
@property (nonatomic) bool called;
@end
@implementation NotificationTests
- (void)setUp {
@autoreleasepool {
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
for (int i = 0; i < 10; ++i)
[IntObject createInDefaultRealmWithValue:@[@(i)]];
}];
}
_token = [self.query addNotificationBlock:^(RLMResults *results, __unused RLMCollectionChange *change, NSError *error) {
XCTAssertNotNil(results);
XCTAssertNil(error);
self.called = true;
CFRunLoopStop(CFRunLoopGetCurrent());
}];
CFRunLoopRun();
}
- (void)tearDown {
[_token invalidate];
[super tearDown];
}
- (RLMResults *)query {
return [IntObject objectsWhere:@"intCol > 0 AND intCol < 5"];
}
- (void)runAndWaitForNotification:(void (^)(RLMRealm *))block {
_called = false;
[self waitForNotification:RLMRealmDidChangeNotification realm:RLMRealm.defaultRealm block:^{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
block(realm);
}];
}];
}
- (void)expectNotification:(void (^)(RLMRealm *))block {
[self runAndWaitForNotification:block];
XCTAssertTrue(_called);
}
- (void)expectNoNotification:(void (^)(RLMRealm *))block {
[self runAndWaitForNotification:block];
XCTAssertFalse(_called);
}
- (void)testInsertObjectMatchingQuery {
[self expectNotification:^(RLMRealm *realm) {
[IntObject createInRealm:realm withValue:@[@3]];
}];
}
- (void)testInsertObjectNotMatchingQuery {
[self expectNoNotification:^(RLMRealm *realm) {
[IntObject createInRealm:realm withValue:@[@10]];
}];
}
- (void)testModifyObjectMatchingQuery {
[self expectNotification:^(RLMRealm *realm) {
[[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@4 forKey:@"intCol"];
}];
}
- (void)testModifyObjectToNoLongerMatchQuery {
[self expectNotification:^(RLMRealm *realm) {
[[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@5 forKey:@"intCol"];
}];
}
- (void)testModifyObjectNotMatchingQuery {
[self expectNoNotification:^(RLMRealm *realm) {
[[IntObject objectsInRealm:realm where:@"intCol = 5"] setValue:@6 forKey:@"intCol"];
}];
}
- (void)testModifyObjectToMatchQuery {
[self expectNotification:^(RLMRealm *realm) {
[[IntObject objectsInRealm:realm where:@"intCol = 5"] setValue:@4 forKey:@"intCol"];
}];
}
- (void)testDeleteObjectMatchingQuery {
[self expectNotification:^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]];
}];
}
- (void)testDeleteObjectNotMatchingQuery {
[self expectNoNotification:^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 5"]];
}];
[self expectNoNotification:^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]];
}];
}
- (void)testNonMatchingObjectMovedToIndexOfMatchingRowAndMadeMatching {
[self expectNotification:^(RLMRealm *realm) {
// Make the last object match the query
[[[IntObject allObjectsInRealm:realm] lastObject] setIntCol:3];
// Move the now-matching object over a previously matching object
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]];
}];
}
- (void)testSuppressCollectionNotification {
_called = false;
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
[realm deleteAllObjects];
[realm commitWriteTransactionWithoutNotifying:@[_token] error:nil];
// Add a new callback that we can wait for, as we can't wait for a
// notification to not be delivered
RLMNotificationToken *token = [self.query addNotificationBlock:^(__unused RLMResults *results,
__unused RLMCollectionChange *change,
__unused NSError *error) {
CFRunLoopStop(CFRunLoopGetCurrent());
}];
CFRunLoopRun();
[token invalidate];
XCTAssertFalse(_called);
}
- (void)testSuppressRealmNotification {
RLMRealm *realm = [RLMRealm defaultRealm];
RLMNotificationToken *token = [realm addNotificationBlock:^(__unused RLMNotification notification, __unused RLMRealm *realm) {
XCTFail(@"should not have been called");
}];
[realm beginWriteTransaction];
[realm deleteAllObjects];
[realm commitWriteTransactionWithoutNotifying:@[token] error:nil];
// local realm notifications are called synchronously so no need to wait for anything
[token invalidate];
}
- (void)testSuppressRealmNotificationForWrongRealm {
RLMRealm *otherRealm = [self realmWithTestPath];
RLMNotificationToken *token = [otherRealm addNotificationBlock:^(__unused RLMNotification notification, __unused RLMRealm *realm) {
XCTFail(@"should not have been called");
}];
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
XCTAssertThrows([realm commitWriteTransactionWithoutNotifying:@[token] error:nil]);
[realm cancelWriteTransaction];
[token invalidate];
}
- (void)testSuppressCollectionNotificationForWrongRealm {
// Test with the token's realm not in a write transaction
RLMRealm *otherRealm = [self realmWithTestPath];
[otherRealm beginWriteTransaction];
XCTAssertThrows([otherRealm commitWriteTransactionWithoutNotifying:@[_token] error:nil]);
// and in a write transaction
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
XCTAssertThrows([otherRealm commitWriteTransactionWithoutNotifying:@[_token] error:nil]);
[realm cancelWriteTransaction];
[otherRealm cancelWriteTransaction];
}
@end
@interface SortedNotificationTests : NotificationTests
@end
@implementation SortedNotificationTests
- (RLMResults *)query {
return [[IntObject objectsWhere:@"intCol > 0 AND intCol < 5"]
sortedResultsUsingKeyPath:@"intCol" ascending:NO];
}
- (void)testMoveMatchingObjectDueToDeletionOfNonMatchingObject {
[self expectNoNotification:^(RLMRealm *realm) {
// Make a matching object be the last row
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol >= 5"]];
// Delete a non-last, non-match row so that a matched row is moved
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]];
}];
}
- (void)testMultipleMovesOfSingleRow {
[self expectNotification:^(RLMRealm *realm) {
[realm deleteObjects:[IntObject allObjectsInRealm:realm]];
[IntObject createInRealm:realm withValue:@[@10]];
[IntObject createInRealm:realm withValue:@[@10]];
[IntObject createInRealm:realm withValue:@[@3]];
}];
[self expectNoNotification:^(RLMRealm *realm) {
RLMResults *objects = [IntObject allObjectsInRealm:realm];
[realm deleteObject:objects[1]];
[realm deleteObject:objects[0]];
}];
}
@end
@protocol ChangesetTestCase
- (RLMResults *)query;
- (void)prepare;
@end
@interface NSIndexPath (TableViewHelpers)
@property (nonatomic, readonly) NSInteger section;
@property (nonatomic, readonly) NSInteger row;
@end
@implementation NSIndexPath (TableViewHelpers)
- (NSInteger)section {
return [self indexAtPosition:0];
}
- (NSInteger)row {
return [self indexAtPosition:1];
}
@end
static RLMCollectionChange *getChange(RLMTestCase<ChangesetTestCase> *self, void (^block)(RLMRealm *)) {
[self prepare];
__block bool first = true;
RLMResults *query = [self query];
__block RLMCollectionChange *changes;
id token = [query addNotificationBlock:^(RLMResults *results, RLMCollectionChange *c, NSError *error) {
XCTAssertNotNil(results);
XCTAssertNil(error);
changes = c;
XCTAssertTrue(first == !changes);
first = false;
CFRunLoopStop(CFRunLoopGetCurrent());
}];
CFRunLoopRun();
[self waitForNotification:RLMRealmDidChangeNotification realm:RLMRealm.defaultRealm block:^{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
block(realm);
}];
}];
[(RLMNotificationToken *)token invalidate];
token = nil;
return changes;
}
static void ExpectChange(id self, NSArray *deletions, NSArray *insertions, NSArray *modifications, void (^block)(RLMRealm *)) {
RLMCollectionChange *changes = getChange(self, block);
XCTAssertNotNil(changes);
if (!changes) {
return;
}
XCTAssertEqualObjects(deletions, changes.deletions);
XCTAssertEqualObjects(insertions, changes.insertions);
XCTAssertEqualObjects(modifications, changes.modifications);
NSInteger section = __LINE__;
NSArray *deletionPaths = [changes deletionsInSection:section];
NSArray *insertionPaths = [changes insertionsInSection:section + 1];
NSArray *modificationPaths = [changes modificationsInSection:section + 2];
XCTAssert(deletionPaths.count == 0 || [deletionPaths[0] section] == section);
XCTAssert(insertionPaths.count == 0 || [insertionPaths[0] section] == section + 1);
XCTAssert(modificationPaths.count == 0 || [modificationPaths[0] section] == section + 2);
XCTAssertEqualObjects(deletions, [deletionPaths valueForKey:@"row"]);
XCTAssertEqualObjects(insertions, [insertionPaths valueForKey:@"row"]);
XCTAssertEqualObjects(modifications, [modificationPaths valueForKey:@"row"]);
}
#define ExpectNoChange(self, block) XCTAssertNil(getChange((self), (block)))
@interface ChangesetTests : RLMTestCase <ChangesetTestCase>
@end
@implementation ChangesetTests
- (void)prepare {
@autoreleasepool {
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm deleteAllObjects];
for (int i = 0; i < 10; ++i) {
IntObject *io = [IntObject createInDefaultRealmWithValue:@[@(i)]];
[ArrayPropertyObject createInDefaultRealmWithValue:@[@"", @[], @[io]]];
}
}];
}
}
- (RLMResults *)query {
return [IntObject objectsWhere:@"intCol > 0 AND intCol < 5"];
}
- (void)testDeleteMultiple {
ExpectNoChange(self, ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 4"]];
});
ExpectNoChange(self, ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 4"]];
});
ExpectNoChange(self, ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 5"]];
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]];
});
ExpectChange(self, @[@1, @2, @3], @[], @[], ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 1"]];
});
ExpectChange(self, @[@1, @2, @3], @[], @[], ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]];
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 3"]];
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]];
});
ExpectChange(self, @[@1, @2, @3], @[], @[], ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]];
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 3"]];
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]];
});
ExpectChange(self, @[@3], @[@0], @[], ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 4"]];
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol < 1"]];
});
ExpectChange(self, @[@0, @1, @2, @3], @[], @[], ^(RLMRealm *realm) {
[realm deleteObjects:[[IntObject allObjectsInRealm:realm] valueForKey:@"self"]];
});
}
- (void)testDeleteNewlyInsertedRowMatchingQuery {
ExpectNoChange(self, ^(RLMRealm *realm) {
[IntObject createInRealm:realm withValue:@[@3]];
[realm deleteObject:[IntObject allObjectsInRealm:realm].lastObject];
});
}
- (void)testInsertObjectMatchingQuery {
ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) {
[IntObject createInRealm:realm withValue:@[@3]];
});
}
- (void)testInsertObjectNotMatchingQuery {
ExpectNoChange(self, ^(RLMRealm *realm) {
[IntObject createInRealm:realm withValue:@[@5]];
});
}
- (void)testInsertBothMatchingAndNonMatching {
ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) {
[IntObject createInRealm:realm withValue:@[@5]];
[IntObject createInRealm:realm withValue:@[@3]];
});
}
- (void)testInsertMultipleMatching {
ExpectChange(self, @[], @[@4, @5], @[], ^(RLMRealm *realm) {
[IntObject createInRealm:realm withValue:@[@5]];
[IntObject createInRealm:realm withValue:@[@3]];
[IntObject createInRealm:realm withValue:@[@5]];
[IntObject createInRealm:realm withValue:@[@2]];
});
}
- (void)testModifyObjectMatchingQuery {
ExpectChange(self, @[], @[], @[@2], ^(RLMRealm *realm) {
[[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@4 forKey:@"intCol"];
});
}
- (void)testModifyObjectToNoLongerMatchQuery {
ExpectChange(self, @[@2], @[], @[], ^(RLMRealm *realm) {
[[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@5 forKey:@"intCol"];
});
}
- (void)testModifyObjectNotMatchingQuery {
ExpectNoChange(self, ^(RLMRealm *realm) {
[[IntObject objectsInRealm:realm where:@"intCol = 5"] setValue:@6 forKey:@"intCol"];
});
}
- (void)testModifyObjectToMatchQuery {
ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) {
[[IntObject objectsInRealm:realm where:@"intCol = 5"] setValue:@4 forKey:@"intCol"];
});
}
- (void)testModifyObjectShiftedByDeletion {
ExpectChange(self, @[@1], @[], @[@2], ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]];
[[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@4 forKey:@"intCol"];
});
}
- (void)testDeleteObjectMatchingQuery {
ExpectChange(self, @[@0], @[], @[], ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 1"]];
});
ExpectChange(self, @[@3], @[], @[], ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]];
});
}
- (void)testDeleteNonMatchingBeforeMatches {
ExpectNoChange(self, ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]];
});
}
- (void)testDeleteNonMatchingAfterMatches {
ExpectNoChange(self, ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 5"]];
});
}
- (void)testMoveMatchingObjectDueToDeletionOfNonMatchingObject {
ExpectChange(self, @[@3], @[@0], @[], ^(RLMRealm *realm) {
// Make a matching object be the last row
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol >= 5"]];
// Delete a non-last, non-match row so that a matched row is moved
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]];
});
}
- (void)testNonMatchingObjectMovedToIndexOfMatchingRowAndMadeMatching {
ExpectChange(self, @[@1], @[@1], @[], ^(RLMRealm *realm) {
// Make the last object match the query
[[[IntObject allObjectsInRealm:realm] lastObject] setIntCol:3];
// Move the now-matching object over a previously matching object
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]];
});
}
- (void)testExcludingChangesFromSkippedTransaction {
[self prepare];
__block bool first = true;
RLMResults *query = [self query];
__block RLMCollectionChange *changes;
RLMNotificationToken *token = [query addNotificationBlock:^(RLMResults *results, RLMCollectionChange *c, NSError *error) {
XCTAssertNotNil(results);
XCTAssertNil(error);
changes = c;
XCTAssertTrue(first || changes);
first = false;
CFRunLoopStop(CFRunLoopGetCurrent());
}];
CFRunLoopRun();
[query.realm beginWriteTransaction];
[IntObject createInRealm:query.realm withValue:@[@3]];
[query.realm commitWriteTransactionWithoutNotifying:@[token] error:nil];
[self waitForNotification:RLMRealmDidChangeNotification realm:RLMRealm.defaultRealm block:^{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[IntObject createInRealm:realm withValue:@[@3]];
}];
}];
[token invalidate];
token = nil;
XCTAssertNotNil(changes);
// Should only have the row inserted in the background transaction
XCTAssertEqualObjects(@[@5], changes.insertions);
}
@end
@interface LinkViewChangesetTests : RLMTestCase <ChangesetTestCase>
@end
@implementation LinkViewChangesetTests
- (void)prepare {
@autoreleasepool {
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm deleteAllObjects];
for (int i = 0; i < 10; ++i) {
[IntObject createInDefaultRealmWithValue:@[@(i)]];
}
[ArrayPropertyObject createInDefaultRealmWithValue:@[@"", @[], [IntObject allObjectsInRealm:realm]]];
}];
}
}
- (RLMResults *)query {
return [[[ArrayPropertyObject.allObjects firstObject] intArray]
objectsWhere:@"intCol > 0 AND intCol < 5"];
}
- (void)testDeleteMultiple {
ExpectNoChange(self, ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 4"]];
});
ExpectNoChange(self, ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 5"]];
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]];
});
ExpectChange(self, @[@1, @2, @3], @[], @[], ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 1"]];
});
ExpectChange(self, @[@1, @2, @3], @[], @[], ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]];
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 3"]];
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]];
});
ExpectChange(self, @[@1, @2, @3], @[], @[], ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]];
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 3"]];
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]];
});
ExpectNoChange(self, ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 4"]];
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol < 1"]];
});
}
- (void)testModifyObjectMatchingQuery {
ExpectChange(self, @[], @[], @[@2], ^(RLMRealm *realm) {
[[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@4 forKey:@"intCol"];
});
}
- (void)testModifyObjectToNoLongerMatchQuery {
ExpectChange(self, @[@2], @[], @[], ^(RLMRealm *realm) {
[[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@5 forKey:@"intCol"];
});
}
- (void)testModifyObjectNotMatchingQuery {
ExpectNoChange(self, ^(RLMRealm *realm) {
[[IntObject objectsInRealm:realm where:@"intCol = 5"] setValue:@6 forKey:@"intCol"];
});
}
- (void)testModifyObjectToMatchQuery {
ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) {
[[IntObject objectsInRealm:realm where:@"intCol = 5"] setValue:@4 forKey:@"intCol"];
});
}
- (void)testDeleteObjectMatchingQuery {
ExpectChange(self, @[@0], @[], @[], ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 1"]];
});
ExpectChange(self, @[@3], @[], @[], ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]];
});
}
- (void)testDeleteNonMatchingBeforeMatches {
ExpectNoChange(self, ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]];
});
}
- (void)testDeleteNonMatchingAfterMatches {
ExpectNoChange(self, ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 5"]];
});
}
- (void)testMoveMatchingObjectDueToDeletionOfNonMatchingObject {
ExpectNoChange(self, ^(RLMRealm *realm) {
// Make a matching object be the last row
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol >= 5"]];
// Delete a non-last, non-match row so that a matched row is moved
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]];
});
}
- (void)testNonMatchingObjectMovedToIndexOfMatchingRowAndMadeMatching {
ExpectChange(self, @[@1], @[@3], @[], ^(RLMRealm *realm) {
// Make the last object match the query
[[[IntObject allObjectsInRealm:realm] lastObject] setIntCol:3];
// Move the now-matching object over a previously matching object
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]];
});
}
- (void)testDeleteNewlyInsertedRowMatchingQuery {
ExpectNoChange(self, ^(RLMRealm *realm) {
RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
[array addObject:[IntObject createInRealm:realm withValue:@[@3]]];
[realm deleteObject:[IntObject allObjectsInRealm:realm].lastObject];
});
}
- (void)testInsertObjectMatchingQuery {
ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) {
RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
[array addObject:[IntObject createInRealm:realm withValue:@[@3]]];
});
}
- (void)testInsertObjectNotMatchingQuery {
ExpectNoChange(self, ^(RLMRealm *realm) {
RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
[array addObject:[IntObject createInRealm:realm withValue:@[@5]]];
});
}
- (void)testInsertBothMatchingAndNonMatching {
ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) {
RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
[array addObject:[IntObject createInRealm:realm withValue:@[@5]]];
[array addObject:[IntObject createInRealm:realm withValue:@[@3]]];
});
}
- (void)testInsertMultipleMatching {
ExpectChange(self, @[], @[@4, @5], @[], ^(RLMRealm *realm) {
RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
[array addObject:[IntObject createInRealm:realm withValue:@[@5]]];
[array addObject:[IntObject createInRealm:realm withValue:@[@3]]];
[array addObject:[IntObject createInRealm:realm withValue:@[@5]]];
[array addObject:[IntObject createInRealm:realm withValue:@[@2]]];
});
}
- (void)testInsertAtIndex {
ExpectChange(self, @[], @[@0], @[], ^(RLMRealm *realm) {
RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
IntObject *io = [IntObject createInRealm:realm withValue:@[@3]];
[array insertObject:io atIndex:0];
});
ExpectChange(self, @[], @[@0], @[], ^(RLMRealm *realm) {
RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
IntObject *io = [IntObject createInRealm:realm withValue:@[@3]];
[array insertObject:io atIndex:1];
});
ExpectChange(self, @[], @[@1], @[], ^(RLMRealm *realm) {
RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
IntObject *io = [IntObject createInRealm:realm withValue:@[@3]];
[array insertObject:io atIndex:2];
});
ExpectNoChange(self, ^(RLMRealm *realm) {
RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
IntObject *io = [IntObject createInRealm:realm withValue:@[@5]];
[array insertObject:io atIndex:2];
});
}
- (void)testExchangeObjects {
// adjacent swap: one move, since second is redundant
// ExpectChange(self, @[@1, @0], @[], @[], ^(RLMRealm *realm) {
// RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
// [array exchangeObjectAtIndex:1 withObjectAtIndex:2];
// });
// non-adjacent: two moves needed
// ExpectChange(self, @[@0, @2], ^(RLMRealm *realm) {
// RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
// [array exchangeObjectAtIndex:1 withObjectAtIndex:3];
// });
}
- (void)testRemoveFromArray {
ExpectChange(self, @[@0], @[], @[], ^(RLMRealm *realm) {
RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
[array removeObjectAtIndex:1];
});
ExpectNoChange(self, ^(RLMRealm *realm) {
RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
[array removeObjectAtIndex:0];
});
}
- (void)testClearArray {
ExpectChange(self, @[@0, @1, @2, @3], @[], @[], ^(RLMRealm *realm) {
RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
[array removeAllObjects];
});
}
- (void)testDeleteArray {
ExpectChange(self, @[@0, @1, @2, @3], @[], @[], ^(RLMRealm *realm) {
[realm deleteObjects:[ArrayPropertyObject allObjectsInRealm:realm]];
});
}
- (void)testModifyObjectShiftedByInsertsAndDeletions {
ExpectChange(self, @[@1], @[], @[@2], ^(RLMRealm *realm) {
[realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]];
[[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@4 forKey:@"intCol"];
});
ExpectChange(self, @[], @[@0], @[@3], ^(RLMRealm *realm) {
RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
[array insertObject:[IntObject createInRealm:realm withValue:@[@3]] atIndex:0];
[[IntObject objectsInRealm:realm where:@"intCol = 4"] setValue:@3 forKey:@"intCol"];
});
}
@end
@interface LinkedObjectChangesetTests : RLMTestCase <ChangesetTestCase>
@end
@implementation LinkedObjectChangesetTests
- (void)prepare {
@autoreleasepool {
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm deleteAllObjects];
for (int i = 0; i < 5; ++i) {
[LinkStringObject createInRealm:realm
withValue:@[[StringObject createInRealm:realm
withValue:@[@""]]]];
}
}];
}
}
- (RLMResults *)query {
return LinkStringObject.allObjects;
}
- (void)testDeleteLinkedObject {
ExpectChange(self, @[], @[], @[@3], ^(RLMRealm *realm) {
[realm deleteObject:[StringObject allObjectsInRealm:realm][3]];
});
}
- (void)testModifyLinkedObject {
ExpectChange(self, @[], @[], @[@3], ^(RLMRealm *realm) {
[[StringObject allObjectsInRealm:realm][3] setStringCol:@"a"];
});
}
- (void)testInsertUnlinkedObject {
ExpectNoChange(self, ^(RLMRealm *realm) {
[StringObject createInRealm:realm withValue:@[@""]];
});
}
- (void)testTableClearFollowedByInsertsAndDeletes {
ExpectChange(self, @[], @[], @[@0, @1, @2, @3, @4], ^(RLMRealm *realm) {
[realm deleteObjects:[StringObject allObjectsInRealm:realm]];
[StringObject createInRealm:realm withValue:@[@""]];
[realm deleteObject:[StringObject createInRealm:realm withValue:@[@""]]];
});
}
@end
@interface LinkingObjectsChangesetTests : RLMTestCase <ChangesetTestCase>
@end
@implementation LinkingObjectsChangesetTests
- (void)prepare {
@autoreleasepool {
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm deleteAllObjects];
PersonObject *child = [PersonObject createInDefaultRealmWithValue:@[ @"Child", @0 ]];
for (int i = 0; i < 10; ++i) {
// It takes a village to raise a child…
NSString *name = [NSString stringWithFormat:@"Parent %d", i];
[PersonObject createInDefaultRealmWithValue:@[ name, @(25 + i), @[ child ]]];
}
}];
}
}
- (RLMResults *)query {
return [[PersonObject.allObjects firstObject] parents];
}
- (void)testDeleteOneLinkingObject {
ExpectChange(self, @[@5], @[], @[], ^(RLMRealm *realm) {
[realm deleteObjects:[PersonObject objectsInRealm:realm where:@"age == 30"]];
});
}
- (void)testDeleteSomeLinkingObjects {
ExpectChange(self, @[@2, @8, @9], @[], @[], ^(RLMRealm *realm) {
[realm deleteObjects:[PersonObject objectsInRealm:realm where:@"age > 32"]];
[realm deleteObjects:[PersonObject objectsInRealm:realm where:@"age == 27"]];
});
}
- (void)testDeleteAllLinkingObjects {
ExpectChange(self, @[@0, @1, @2, @3, @4, @5, @6, @7, @8, @9], @[], @[], ^(RLMRealm *realm) {
[realm deleteObjects:[PersonObject objectsInRealm:realm where:@"age > 20"]];
});
}
- (void)testDeleteAll {
ExpectChange(self, @[@0, @1, @2, @3, @4, @5, @6, @7, @8, @9], @[], @[], ^(RLMRealm *realm) {
[realm deleteObjects:[PersonObject allObjectsInRealm:realm]];
});
}
- (void)testUnlinkOne {
ExpectChange(self, @[@4], @[], @[], ^(RLMRealm *realm) {
PersonObject *parent = [[PersonObject objectsInRealm:realm where:@"age == 29"] firstObject];
[parent.children removeAllObjects];
});
}
- (void)testUnlinkAll {
ExpectChange(self, @[@0, @1, @2, @3, @4, @5, @6, @7, @8, @9], @[], @[], ^(RLMRealm *realm) {
for (PersonObject *parent in [PersonObject objectsInRealm:realm where:@"age > 20"])
[parent.children removeAllObjects];
});
}
- (void)testAddNewParent {
ExpectChange(self, @[], @[@10], @[], ^(RLMRealm *realm) {
PersonObject *child = [[PersonObject objectsInRealm:realm where:@"children.@count == 0"] firstObject];
[PersonObject createInDefaultRealmWithValue:@[ @"New parent", @40, @[ child ]]];
});
}
- (void)testAddDuplicateParent {
ExpectChange(self, @[], @[@10], @[@7], ^(RLMRealm *realm) {
PersonObject *parent = [[PersonObject objectsInRealm:realm where:@"age == 32"] firstObject];
[parent.children addObject:[parent.children firstObject]];
});
}
- (void)testModifyParent {
ExpectChange(self, @[], @[], @[@3], ^(RLMRealm *realm) {
PersonObject *parent = [[PersonObject objectsInRealm:realm where:@"age == 28"] firstObject];
parent.age = parent.age + 1;
});
}
@end
@interface AllTypesWithPrimaryKey : RLMObject
@property BOOL boolCol;
@property int intCol;
@property float floatCol;
@property double doubleCol;
@property NSString *stringCol;
@property NSData *binaryCol;
@property NSDate *dateCol;
@property bool cBoolCol;
@property int64_t longCol;
@property StringObject *objectCol;
@property (nonatomic) int pk;
@end
@implementation AllTypesWithPrimaryKey
+ (NSString *)primaryKey { return @"pk"; }
@end
// clang thinks the tests below have retain cycles because `_obj` could retain
// the block passed to addNotificationBlock (but it doesn't)
#pragma clang diagnostic ignored "-Warc-retain-cycles"
@interface ObjectNotifierTests : RLMTestCase
@end
@implementation ObjectNotifierTests {
NSArray *_initialValues;
NSArray *_values;
NSArray<NSString *> *_propertyNames;
AllTypesObject *_obj;
}
- (void)setUp {
NSDate *now = [NSDate date];
StringObject *so = [[StringObject alloc] init];
so.stringCol = @"string";
_initialValues = @[@YES, @1, @1.1f, @1.11, @"string",
[NSData dataWithBytes:"a" length:1], now, @YES, @11, NSNull.null];
_values = @[@NO, @2, @2.2f, @2.22, @"string2", [NSData dataWithBytes:"b" length:1],
[now dateByAddingTimeInterval:1], @NO, @22, so];
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
_obj = [AllTypesObject createInRealm:realm withValue:_initialValues];
[realm commitWriteTransaction];
_propertyNames = [_obj.objectSchema.properties valueForKey:@"name"];
}
- (void)tearDown {
_values = nil;
_initialValues = nil;
_obj = nil;
[super tearDown];
}
- (void)testDeleteObservedObject {
XCTestExpectation *expectation = [self expectationWithDescription:@""];
RLMNotificationToken *token = [_obj addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) {
XCTAssertTrue(deleted);
XCTAssertNil(error);
XCTAssertNil(changes);
[expectation fulfill];
}];
RLMRealm *realm = _obj.realm;
[realm beginWriteTransaction];
[realm deleteObject:_obj];
[realm commitWriteTransaction];
[self waitForExpectationsWithTimeout:2.0 handler:nil];
[token invalidate];
}
- (void)testChangeAllPropertyTypes {
__block NSUInteger i = 0;
__block XCTestExpectation *expectation = nil;
RLMNotificationToken *token = [_obj addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) {
XCTAssertFalse(deleted);
XCTAssertNil(error);
XCTAssertEqual(changes.count, 1U);
RLMPropertyChange *prop = changes[0];
XCTAssertEqualObjects(prop.name, _propertyNames[i]);
XCTAssertNil(prop.previousValue);
if ([prop.name isEqualToString:@"objectCol"]) {
XCTAssertTrue([prop.value isEqualToObject:_values[i]],
@"%d: %@ %@", (int)i, prop.value, _values[i]);
}
else {
XCTAssertEqualObjects(prop.value, _values[i]);
}
[expectation fulfill];
}];
for (i = 0; i < _values.count; ++i) {
expectation = [self expectationWithDescription:@""];
[_obj.realm beginWriteTransaction];
_obj[_propertyNames[i]] = _values[i];
[_obj.realm commitWriteTransaction];
[self waitForExpectationsWithTimeout:2.0 handler:nil];
}
[token invalidate];
}
- (void)testChangeAllPropertyTypesFromBackground {
__block NSUInteger i = 0;
RLMNotificationToken *token = [_obj addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) {
XCTAssertFalse(deleted);
XCTAssertNil(error);
XCTAssertEqual(changes.count, 1U);
RLMPropertyChange *prop = changes[0];
XCTAssertEqualObjects(prop.name, _propertyNames[i]);
if ([prop.name isEqualToString:@"objectCol"]) {
XCTAssertNil(prop.previousValue);
XCTAssertNotNil(prop.value);
}
else {
XCTAssertEqualObjects(prop.previousValue, _initialValues[i]);
XCTAssertEqualObjects(prop.value, _values[i]);
}
}];
for (i = 0; i < _values.count; ++i) {
[self dispatchAsyncAndWait:^{
RLMRealm *realm = [RLMRealm defaultRealm];
AllTypesObject *obj = [[AllTypesObject allObjectsInRealm:realm] firstObject];
[realm beginWriteTransaction];
obj[_propertyNames[i]] = _values[i];
[realm commitWriteTransaction];
}];
[_obj.realm refresh];
}
[token invalidate];
}
- (void)testChangeAllPropertyTypesInSingleTransaction {
XCTestExpectation *expectation = [self expectationWithDescription:@""];
RLMNotificationToken *token = [_obj addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) {
XCTAssertFalse(deleted);
XCTAssertNil(error);
XCTAssertEqual(changes.count, _values.count);
NSUInteger i = 0;
for (RLMPropertyChange *prop in changes) {
XCTAssertEqualObjects(prop.name, _propertyNames[i]);
if ([prop.name isEqualToString:@"objectCol"]) {
XCTAssertTrue([prop.value isEqualToObject:_values[i]]);
}
else {
XCTAssertEqualObjects(prop.value, _values[i]);
}
++i;
}
[expectation fulfill];
}];
[_obj.realm beginWriteTransaction];
for (NSUInteger i = 0; i < _values.count; ++i) {
_obj[_propertyNames[i]] = _values[i];
}
[_obj.realm commitWriteTransaction];
[self waitForExpectationsWithTimeout:2.0 handler:nil];
[token invalidate];
}
- (void)testMultipleObjectNotifiers {
[_obj.realm beginWriteTransaction];
AllTypesObject *obj2 = [AllTypesObject createInRealm:_obj.realm withValue:_obj];
[_obj.realm commitWriteTransaction];
XCTestExpectation *expectation = [self expectationWithDescription:@""];
__block NSUInteger calls = 0;
id block = ^(BOOL deleted, NSArray<RLMPropertyChange *> *changes, NSError *error) {
XCTAssertFalse(deleted);
XCTAssertNil(error);
XCTAssertEqual(changes.count, 1U);
XCTAssertEqualObjects(changes[0].name, @"intCol");
XCTAssertEqualObjects(changes[0].previousValue, @1);
XCTAssertEqualObjects(changes[0].value, @2);
if (++calls == 2) {
[expectation fulfill];
}
};
RLMNotificationToken *token1 = [_obj addNotificationBlock:block];
RLMNotificationToken *token2 = [_obj addNotificationBlock:block];
RLMNotificationToken *token3 = [obj2 addNotificationBlock:^(__unused BOOL deletd,
__unused NSArray<RLMPropertyChange *> *changes,
__unused NSError *error) {
XCTFail(@"notification block for wrong object called");
}];
[self dispatchAsync:^{
RLMRealm *realm = [RLMRealm defaultRealm];
AllTypesObject *obj = [[AllTypesObject allObjectsInRealm:realm] firstObject];
[realm beginWriteTransaction];
obj.intCol = 2;
[realm commitWriteTransaction];
}];
[self waitForExpectationsWithTimeout:2.0 handler:nil];
[token1 invalidate];
[token2 invalidate];
[token3 invalidate];
}
- (void)testArrayPropertiesMerelyReportModification {
[_obj.realm beginWriteTransaction];
ArrayOfAllTypesObject *array = [ArrayOfAllTypesObject createInRealm:_obj.realm withValue:@[@[]]];
[_obj.realm commitWriteTransaction];
XCTestExpectation *expectation = [self expectationWithDescription:@""];
RLMNotificationToken *token = [array addNotificationBlock:^(BOOL deleted, NSArray<RLMPropertyChange *> *changes, NSError *error) {
XCTAssertFalse(deleted);
XCTAssertNil(error);
XCTAssertEqual(changes.count, 1U);
XCTAssertEqualObjects(changes[0].name, @"array");
XCTAssertNil(changes[0].previousValue);
XCTAssertNil(changes[0].value);
[expectation fulfill];
}];
[self dispatchAsync:^{
RLMRealm *realm = [RLMRealm defaultRealm];
ArrayOfAllTypesObject *obj = [[ArrayOfAllTypesObject allObjectsInRealm:realm] firstObject];
[realm beginWriteTransaction];
[obj.array addObject:[[AllTypesObject allObjectsInRealm:realm] firstObject]];
[realm commitWriteTransaction];
}];
[self waitForExpectationsWithTimeout:2.0 handler:nil];
[token invalidate];
}
- (void)testDiffedUpdatesOnlyNotifyForPropertiesWhichActuallyChanged {
NSMutableArray *values = [_initialValues mutableCopy];
[values addObject:@1];
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
[realm addObject:_values.lastObject];
AllTypesWithPrimaryKey *obj = [AllTypesWithPrimaryKey createInRealm:realm withValue:values];
[realm commitWriteTransaction];
__block NSUInteger i = 0;
__block XCTestExpectation *expectation = nil;
RLMNotificationToken *token = [obj addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) {
XCTAssertFalse(deleted);
XCTAssertNil(error);
XCTAssertEqual(changes.count, 1U);
RLMPropertyChange *prop = changes[0];
XCTAssertEqualObjects(prop.name, _propertyNames[i]);
XCTAssertNil(prop.previousValue);
if ([prop.name isEqualToString:@"objectCol"]) {
XCTAssertTrue([prop.value isEqualToObject:_values[i]],
@"%d: %@ %@", (int)i, prop.value, _values[i]);
}
else {
XCTAssertEqualObjects(prop.value, _values[i]);
}
[expectation fulfill];
}];
for (i = 0; i < _values.count; ++i) {
expectation = [self expectationWithDescription:@""];
[realm beginWriteTransaction];
values[i] = _values[i];
[AllTypesWithPrimaryKey createOrUpdateModifiedInRealm:realm withValue:values];
[realm commitWriteTransaction];
[self waitForExpectationsWithTimeout:2.0 handler:nil];
}
[token invalidate];
}
@end