LinkingObjectsTests.mm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  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)testLinkingObjectsOnFrozenObject {
  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. [realm commitWriteTransaction];
  78. PersonObject *frozenHannah = hannah.freeze;
  79. PersonObject *frozenMark = mark.freeze;
  80. XCTAssertEqualObjects(asArray(frozenHannah.parents), (@[frozenMark]));
  81. [realm beginWriteTransaction];
  82. PersonObject *diane = [PersonObject createInRealm:realm withValue:@[@"Diane", @29, @[hannah]]];
  83. [realm commitWriteTransaction];
  84. PersonObject *frozenHannah2 = hannah.freeze;
  85. PersonObject *frozenMark2 = mark.freeze;
  86. PersonObject *frozenDiane = diane.freeze;
  87. XCTAssertEqualObjects(asArray(frozenHannah.parents), (@[frozenMark]));
  88. XCTAssertEqualObjects(asArray(frozenHannah2.parents), (@[frozenMark2, frozenDiane]));
  89. [realm beginWriteTransaction];
  90. [realm deleteObject:hannah];
  91. [realm commitWriteTransaction];
  92. XCTAssertEqualObjects(asArray(frozenHannah.parents), (@[frozenMark]));
  93. XCTAssertEqualObjects(asArray(frozenHannah2.parents), (@[frozenMark2, frozenDiane]));
  94. }
  95. - (void)testFilteredLinkingObjects {
  96. NSArray *(^asArray)(id) = ^(id arrayLike) {
  97. return [arrayLike valueForKeyPath:@"self"];
  98. };
  99. RLMRealm *realm = [self realmWithTestPath];
  100. [realm beginWriteTransaction];
  101. PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[ @"Hannah", @0 ]];
  102. PersonObject *mark = [PersonObject createInRealm:realm withValue:@[ @"Mark", @30, @[ hannah ]]];
  103. PersonObject *diane = [PersonObject createInRealm:realm withValue:@[ @"Diane", @29, @[ hannah ]]];
  104. RLMLinkingObjects *hannahsParents = hannah.parents;
  105. // Three separate queries so that accessing a property on one doesn't invalidate testing of other properties.
  106. RLMResults *resultsA = [hannahsParents objectsWhere:@"age > 25"];
  107. RLMResults *resultsB = [hannahsParents objectsWhere:@"age > 25"];
  108. RLMResults *resultsC = [hannahsParents objectsWhere:@"age > 25"];
  109. [mark.children removeAllObjects];
  110. [realm commitWriteTransaction];
  111. XCTAssertEqual(resultsA.count, 1u);
  112. XCTAssertEqual(NSNotFound, [resultsB indexOfObjectWhere:@"name = 'Mark'"]);
  113. XCTAssertEqualObjects(asArray(resultsC), (@[ diane ]));
  114. }
  115. - (void)testNotificationSentInitially {
  116. RLMRealm *realm = [self realmWithTestPath];
  117. [realm beginWriteTransaction];
  118. PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[ @"Hannah", @0 ]];
  119. PersonObject *mark = [PersonObject createInRealm:realm withValue:@[ @"Mark", @30, @[ hannah ]]];
  120. [realm commitWriteTransaction];
  121. id expectation = [self expectationWithDescription:@""];
  122. RLMNotificationToken *token = [hannah.parents addNotificationBlock:^(RLMResults *linkingObjects, RLMCollectionChange *change, NSError *error) {
  123. XCTAssertEqualObjects([linkingObjects valueForKeyPath:@"self"], (@[ mark ]));
  124. XCTAssertNil(change);
  125. XCTAssertNil(error);
  126. [expectation fulfill];
  127. }];
  128. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  129. [token invalidate];
  130. }
  131. - (void)testNotificationSentAfterCommit {
  132. RLMRealm *realm = self.realmWithTestPath;
  133. [realm beginWriteTransaction];
  134. PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[ @"Hannah", @0 ]];
  135. [realm commitWriteTransaction];
  136. __block bool first = true;
  137. __block id expectation = [self expectationWithDescription:@""];
  138. RLMNotificationToken *token = [hannah.parents addNotificationBlock:^(RLMResults *linkingObjects, RLMCollectionChange *change, NSError *error) {
  139. XCTAssertNotNil(linkingObjects);
  140. XCTAssert(first ? !change : !!change);
  141. XCTAssertNil(error);
  142. first = false;
  143. [expectation fulfill];
  144. }];
  145. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  146. expectation = [self expectationWithDescription:@""];
  147. [self dispatchAsyncAndWait:^{
  148. RLMRealm *realm = self.realmWithTestPath;
  149. [realm transactionWithBlock:^{
  150. [PersonObject createInRealm:realm withValue:@[ @"Mark", @30, [PersonObject objectsInRealm:realm where:@"name == 'Hannah'"] ]];
  151. }];
  152. }];
  153. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  154. [token invalidate];
  155. }
  156. - (void)testNotificationNotSentForUnrelatedChange {
  157. RLMRealm *realm = self.realmWithTestPath;
  158. [realm beginWriteTransaction];
  159. PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[ @"Hannah", @0 ]];
  160. [realm commitWriteTransaction];
  161. id expectation = [self expectationWithDescription:@""];
  162. RLMNotificationToken *token = [hannah.parents addNotificationBlock:^(RLMResults *, RLMCollectionChange *, NSError *) {
  163. // will throw if it's incorrectly called a second time due to the
  164. // unrelated write transaction
  165. [expectation fulfill];
  166. }];
  167. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  168. // All notification blocks are called as part of a single runloop event, so
  169. // waiting for this one also waits for the above one to get a chance to run
  170. [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{
  171. [self dispatchAsyncAndWait:^{
  172. [self.realmWithTestPath transactionWithBlock:^{ }];
  173. }];
  174. }];
  175. [token invalidate];
  176. }
  177. - (void)testNotificationSentOnlyForActualRefresh {
  178. RLMRealm *realm = self.realmWithTestPath;
  179. [realm beginWriteTransaction];
  180. PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[ @"Hannah", @0 ]];
  181. [realm commitWriteTransaction];
  182. __block id expectation = [self expectationWithDescription:@""];
  183. RLMNotificationToken *token = [hannah.parents addNotificationBlock:^(RLMResults *linkingObjects, RLMCollectionChange *, NSError *error) {
  184. XCTAssertNotNil(linkingObjects);
  185. XCTAssertNil(error);
  186. // will throw if it's called a second time before we create the new
  187. // expectation object immediately before manually refreshing
  188. [expectation fulfill];
  189. }];
  190. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  191. // Turn off autorefresh, so the background commit should not result in a notification
  192. realm.autorefresh = NO;
  193. // All notification blocks are called as part of a single runloop event, so
  194. // waiting for this one also waits for the above one to get a chance to run
  195. [self waitForNotification:RLMRealmRefreshRequiredNotification realm:realm block:^{
  196. [self dispatchAsyncAndWait:^{
  197. RLMRealm *realm = self.realmWithTestPath;
  198. [realm transactionWithBlock:^{
  199. [PersonObject createInRealm:realm withValue:@[ @"Mark", @30, [PersonObject objectsInRealm:realm where:@"name == 'Hannah'"] ]];
  200. }];
  201. }];
  202. }];
  203. expectation = [self expectationWithDescription:@""];
  204. [realm refresh];
  205. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  206. [token invalidate];
  207. }
  208. - (void)testDeletingObjectWithNotificationsRegistered {
  209. RLMRealm *realm = self.realmWithTestPath;
  210. [realm beginWriteTransaction];
  211. PersonObject *hannah = [PersonObject createInRealm:realm withValue:@[ @"Hannah", @0 ]];
  212. PersonObject *mark = [PersonObject createInRealm:realm withValue:@[ @"Mark", @30, @[ hannah ]]];
  213. [realm commitWriteTransaction];
  214. __block id expectation = [self expectationWithDescription:@""];
  215. RLMNotificationToken *token = [hannah.parents addNotificationBlock:^(RLMResults *linkingObjects, RLMCollectionChange *, NSError *error) {
  216. XCTAssertNotNil(linkingObjects);
  217. XCTAssertNil(error);
  218. [expectation fulfill];
  219. }];
  220. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  221. [realm beginWriteTransaction];
  222. [realm deleteObject:mark];
  223. [realm commitWriteTransaction];
  224. [token invalidate];
  225. }
  226. - (void)testRenamedProperties {
  227. RLMRealm *realm = self.realmWithTestPath;
  228. [realm beginWriteTransaction];
  229. auto obj1 = [RenamedProperties1 createInRealm:realm withValue:@[@1, @"a"]];
  230. auto obj2 = [RenamedProperties2 createInRealm:realm withValue:@[@2, @"b"]];
  231. auto link = [LinkToRenamedProperties1 createInRealm:realm withValue:@[obj1, obj2, @[obj1, obj1]]];
  232. [realm commitWriteTransaction];
  233. XCTAssertEqualObjects(obj1.linking1.objectClassName, @"LinkToRenamedProperties1");
  234. XCTAssertEqualObjects(obj1.linking2.objectClassName, @"LinkToRenamedProperties2");
  235. XCTAssertTrue([obj1.linking1[0] isEqualToObject:link]);
  236. XCTAssertTrue([obj2.linking2[0] isEqualToObject:link]);
  237. }
  238. @end