LinkingObjectsTests.mm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2016 Realm Inc.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. //
  17. ////////////////////////////////////////////////////////////////////////////
  18. #import "RLMTestCase.h"
  19. @interface LinkingObjectsTests : RLMTestCase
  20. @end
  21. @implementation LinkingObjectsTests
  22. - (void)testBasics {
  23. NSArray *(^asArray)(id) = ^(id arrayLike) {
  24. return [arrayLike valueForKeyPath:@"self"];
  25. };
  26. RLMRealm *realm = [self realmWithTestPath];
  27. [realm beginWriteTransaction];
  28. PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[ @"Hannah", @0 ]];
  29. PersonObject *mark = [PersonObject createInRealm:realm withValue:@[ @"Mark", @30, @[ hannah ]]];
  30. RLMLinkingObjects *hannahsParents = hannah.parents;
  31. XCTAssertEqualObjects(asArray(hannahsParents), (@[ mark ]));
  32. [realm commitWriteTransaction];
  33. XCTAssertEqualObjects(asArray(hannahsParents), (@[ mark ]));
  34. [realm beginWriteTransaction];
  35. PersonObject *diane = [PersonObject createInRealm:realm withValue:@[ @"Diane", @29, @[ hannah ]]];
  36. [realm commitWriteTransaction];
  37. XCTAssertEqualObjects(asArray(hannahsParents), (@[ mark, diane ]));
  38. [realm beginWriteTransaction];
  39. [realm deleteObject:hannah];
  40. [realm commitWriteTransaction];
  41. XCTAssertEqualObjects(asArray(hannahsParents), (@[ ]));
  42. }
  43. - (void)testLinkingObjectsOnUnmanagedObject {
  44. PersonObject *don = [[PersonObject alloc] initWithValue:@[ @"Don", @60, @[] ]];
  45. XCTAssertEqual(0u, don.parents.count);
  46. XCTAssertNil(don.parents.firstObject);
  47. XCTAssertNil(don.parents.lastObject);
  48. for (__unused id parent in don.parents) {
  49. XCTFail(@"Got an item in empty linking objects");
  50. }
  51. XCTAssertEqual(0u, [don.parents sortedResultsUsingKeyPath:@"age" ascending:YES].count);
  52. XCTAssertEqual(0u, [don.parents objectsWhere:@"TRUEPREDICATE"].count);
  53. XCTAssertNil([don.parents minOfProperty:@"age"]);
  54. XCTAssertNil([don.parents maxOfProperty:@"age"]);
  55. XCTAssertEqualObjects(@0, [don.parents sumOfProperty:@"age"]);
  56. XCTAssertNil([don.parents averageOfProperty:@"age"]);
  57. XCTAssertEqualObjects(@[], [don.parents valueForKey:@"age"]);
  58. XCTAssertEqualObjects(@0, [don.parents valueForKeyPath:@"@count"]);
  59. XCTAssertNil([don.parents valueForKeyPath:@"@min.age"]);
  60. XCTAssertNil([don.parents valueForKeyPath:@"@max.age"]);
  61. XCTAssertEqualObjects(@0, [don.parents valueForKeyPath:@"@sum.age"]);
  62. XCTAssertNil([don.parents valueForKeyPath:@"@avg.age"]);
  63. PersonObject *mark = [[PersonObject alloc] initWithValue:@[ @"Mark", @30, @[] ]];
  64. XCTAssertEqual(NSNotFound, [don.parents indexOfObject:mark]);
  65. XCTAssertEqual(NSNotFound, [don.parents indexOfObjectWhere:@"TRUEPREDICATE"]);
  66. RLMAssertThrowsWithReason(([don.parents addNotificationBlock:^(RLMResults *, RLMCollectionChange *, NSError *) { }]),
  67. @"Linking objects notifications are only supported on managed objects.");
  68. }
  69. - (void)testFilteredLinkingObjects {
  70. NSArray *(^asArray)(id) = ^(id arrayLike) {
  71. return [arrayLike valueForKeyPath:@"self"];
  72. };
  73. RLMRealm *realm = [self realmWithTestPath];
  74. [realm beginWriteTransaction];
  75. PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[ @"Hannah", @0 ]];
  76. PersonObject *mark = [PersonObject createInRealm:realm withValue:@[ @"Mark", @30, @[ hannah ]]];
  77. PersonObject *diane = [PersonObject createInRealm:realm withValue:@[ @"Diane", @29, @[ hannah ]]];
  78. RLMLinkingObjects *hannahsParents = hannah.parents;
  79. // Three separate queries so that accessing a property on one doesn't invalidate testing of other properties.
  80. RLMResults *resultsA = [hannahsParents objectsWhere:@"age > 25"];
  81. RLMResults *resultsB = [hannahsParents objectsWhere:@"age > 25"];
  82. RLMResults *resultsC = [hannahsParents objectsWhere:@"age > 25"];
  83. [mark.children removeAllObjects];
  84. [realm commitWriteTransaction];
  85. XCTAssertEqual(resultsA.count, 1u);
  86. XCTAssertEqual(NSNotFound, [resultsB indexOfObjectWhere:@"name = 'Mark'"]);
  87. XCTAssertEqualObjects(asArray(resultsC), (@[ diane ]));
  88. }
  89. - (void)testNotificationSentInitially {
  90. RLMRealm *realm = [self realmWithTestPath];
  91. [realm beginWriteTransaction];
  92. PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[ @"Hannah", @0 ]];
  93. PersonObject *mark = [PersonObject createInRealm:realm withValue:@[ @"Mark", @30, @[ hannah ]]];
  94. [realm commitWriteTransaction];
  95. id expectation = [self expectationWithDescription:@""];
  96. RLMNotificationToken *token = [hannah.parents addNotificationBlock:^(RLMResults *linkingObjects, RLMCollectionChange *change, NSError *error) {
  97. XCTAssertEqualObjects([linkingObjects valueForKeyPath:@"self"], (@[ mark ]));
  98. XCTAssertNil(change);
  99. XCTAssertNil(error);
  100. [expectation fulfill];
  101. }];
  102. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  103. [token invalidate];
  104. }
  105. - (void)testNotificationSentAfterCommit {
  106. RLMRealm *realm = self.realmWithTestPath;
  107. [realm beginWriteTransaction];
  108. PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[ @"Hannah", @0 ]];
  109. [realm commitWriteTransaction];
  110. __block bool first = true;
  111. __block id expectation = [self expectationWithDescription:@""];
  112. RLMNotificationToken *token = [hannah.parents addNotificationBlock:^(RLMResults *linkingObjects, RLMCollectionChange *change, NSError *error) {
  113. XCTAssertNotNil(linkingObjects);
  114. XCTAssert(first ? !change : !!change);
  115. XCTAssertNil(error);
  116. first = false;
  117. [expectation fulfill];
  118. }];
  119. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  120. expectation = [self expectationWithDescription:@""];
  121. [self dispatchAsyncAndWait:^{
  122. RLMRealm *realm = self.realmWithTestPath;
  123. [realm transactionWithBlock:^{
  124. [PersonObject createInRealm:realm withValue:@[ @"Mark", @30, [PersonObject objectsInRealm:realm where:@"name == 'Hannah'"] ]];
  125. }];
  126. }];
  127. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  128. [token invalidate];
  129. }
  130. - (void)testNotificationNotSentForUnrelatedChange {
  131. RLMRealm *realm = self.realmWithTestPath;
  132. [realm beginWriteTransaction];
  133. PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[ @"Hannah", @0 ]];
  134. [realm commitWriteTransaction];
  135. id expectation = [self expectationWithDescription:@""];
  136. RLMNotificationToken *token = [hannah.parents addNotificationBlock:^(RLMResults *, RLMCollectionChange *, NSError *) {
  137. // will throw if it's incorrectly called a second time due to the
  138. // unrelated write transaction
  139. [expectation fulfill];
  140. }];
  141. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  142. // All notification blocks are called as part of a single runloop event, so
  143. // waiting for this one also waits for the above one to get a chance to run
  144. [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{
  145. [self dispatchAsyncAndWait:^{
  146. [self.realmWithTestPath transactionWithBlock:^{ }];
  147. }];
  148. }];
  149. [token invalidate];
  150. }
  151. - (void)testNotificationSentOnlyForActualRefresh {
  152. RLMRealm *realm = self.realmWithTestPath;
  153. [realm beginWriteTransaction];
  154. PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[ @"Hannah", @0 ]];
  155. [realm commitWriteTransaction];
  156. __block id expectation = [self expectationWithDescription:@""];
  157. RLMNotificationToken *token = [hannah.parents addNotificationBlock:^(RLMResults *linkingObjects, RLMCollectionChange *, NSError *error) {
  158. XCTAssertNotNil(linkingObjects);
  159. XCTAssertNil(error);
  160. // will throw if it's called a second time before we create the new
  161. // expectation object immediately before manually refreshing
  162. [expectation fulfill];
  163. }];
  164. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  165. // Turn off autorefresh, so the background commit should not result in a notification
  166. realm.autorefresh = NO;
  167. // All notification blocks are called as part of a single runloop event, so
  168. // waiting for this one also waits for the above one to get a chance to run
  169. [self waitForNotification:RLMRealmRefreshRequiredNotification realm:realm block:^{
  170. [self dispatchAsyncAndWait:^{
  171. RLMRealm *realm = self.realmWithTestPath;
  172. [realm transactionWithBlock:^{
  173. [PersonObject createInRealm:realm withValue:@[ @"Mark", @30, [PersonObject objectsInRealm:realm where:@"name == 'Hannah'"] ]];
  174. }];
  175. }];
  176. }];
  177. expectation = [self expectationWithDescription:@""];
  178. [realm refresh];
  179. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  180. [token invalidate];
  181. }
  182. - (void)testDeletingObjectWithNotificationsRegistered {
  183. RLMRealm *realm = self.realmWithTestPath;
  184. [realm beginWriteTransaction];
  185. PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[ @"Hannah", @0 ]];
  186. PersonObject *mark = [PersonObject createInRealm:realm withValue:@[ @"Mark", @30, @[ hannah ]]];
  187. [realm commitWriteTransaction];
  188. __block id expectation = [self expectationWithDescription:@""];
  189. RLMNotificationToken *token = [hannah.parents addNotificationBlock:^(RLMResults *linkingObjects, RLMCollectionChange *, NSError *error) {
  190. XCTAssertNotNil(linkingObjects);
  191. XCTAssertNil(error);
  192. [expectation fulfill];
  193. }];
  194. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  195. [realm beginWriteTransaction];
  196. [realm deleteObject:mark];
  197. [realm commitWriteTransaction];
  198. [token invalidate];
  199. }
  200. - (void)testRenamedProperties {
  201. RLMRealm *realm = self.realmWithTestPath;
  202. [realm beginWriteTransaction];
  203. auto obj1 = [RenamedProperties1 createInRealm:realm withValue:@[@1, @"a"]];
  204. auto obj2 = [RenamedProperties2 createInRealm:realm withValue:@[@2, @"b"]];
  205. auto link = [LinkToRenamedProperties1 createInRealm:realm withValue:@[obj1, obj2, @[obj1, obj1]]];
  206. [realm commitWriteTransaction];
  207. XCTAssertEqualObjects(obj1.linking1.objectClassName, @"LinkToRenamedProperties1");
  208. XCTAssertEqualObjects(obj1.linking2.objectClassName, @"LinkToRenamedProperties2");
  209. XCTAssertTrue([obj1.linking1[0] isEqualToObject:link]);
  210. XCTAssertTrue([obj2.linking2[0] isEqualToObject:link]);
  211. }
  212. @end