LinkingObjectsTests.mm 11 KB

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