NotificationTests.m 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2015 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. #import "RLMRealmConfiguration_Private.h"
  20. @interface NotificationTests : RLMTestCase
  21. @property (nonatomic, strong) RLMNotificationToken *token;
  22. @property (nonatomic) bool called;
  23. @end
  24. @implementation NotificationTests
  25. - (void)setUp {
  26. @autoreleasepool {
  27. RLMRealm *realm = [RLMRealm defaultRealm];
  28. [realm transactionWithBlock:^{
  29. for (int i = 0; i < 10; ++i)
  30. [IntObject createInDefaultRealmWithValue:@[@(i)]];
  31. }];
  32. }
  33. _token = [self.query addNotificationBlock:^(RLMResults *results, __unused RLMCollectionChange *change, NSError *error) {
  34. XCTAssertNotNil(results);
  35. XCTAssertNil(error);
  36. self.called = true;
  37. CFRunLoopStop(CFRunLoopGetCurrent());
  38. }];
  39. CFRunLoopRun();
  40. }
  41. - (void)tearDown {
  42. [_token invalidate];
  43. [super tearDown];
  44. }
  45. - (RLMResults *)query {
  46. return [IntObject objectsWhere:@"intCol > 0 AND intCol < 5"];
  47. }
  48. - (void)runAndWaitForNotification:(void (^)(RLMRealm *))block {
  49. _called = false;
  50. [self waitForNotification:RLMRealmDidChangeNotification realm:RLMRealm.defaultRealm block:^{
  51. RLMRealm *realm = [RLMRealm defaultRealm];
  52. [realm transactionWithBlock:^{
  53. block(realm);
  54. }];
  55. }];
  56. }
  57. - (void)expectNotification:(void (^)(RLMRealm *))block {
  58. [self runAndWaitForNotification:block];
  59. XCTAssertTrue(_called);
  60. }
  61. - (void)expectNoNotification:(void (^)(RLMRealm *))block {
  62. [self runAndWaitForNotification:block];
  63. XCTAssertFalse(_called);
  64. }
  65. - (void)testInsertObjectMatchingQuery {
  66. [self expectNotification:^(RLMRealm *realm) {
  67. [IntObject createInRealm:realm withValue:@[@3]];
  68. }];
  69. }
  70. - (void)testInsertObjectNotMatchingQuery {
  71. [self expectNoNotification:^(RLMRealm *realm) {
  72. [IntObject createInRealm:realm withValue:@[@10]];
  73. }];
  74. }
  75. - (void)testModifyObjectMatchingQuery {
  76. [self expectNotification:^(RLMRealm *realm) {
  77. [[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@4 forKey:@"intCol"];
  78. }];
  79. }
  80. - (void)testModifyObjectToNoLongerMatchQuery {
  81. [self expectNotification:^(RLMRealm *realm) {
  82. [[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@5 forKey:@"intCol"];
  83. }];
  84. }
  85. - (void)testModifyObjectNotMatchingQuery {
  86. [self expectNoNotification:^(RLMRealm *realm) {
  87. [[IntObject objectsInRealm:realm where:@"intCol = 5"] setValue:@6 forKey:@"intCol"];
  88. }];
  89. }
  90. - (void)testModifyObjectToMatchQuery {
  91. [self expectNotification:^(RLMRealm *realm) {
  92. [[IntObject objectsInRealm:realm where:@"intCol = 5"] setValue:@4 forKey:@"intCol"];
  93. }];
  94. }
  95. - (void)testDeleteObjectMatchingQuery {
  96. [self expectNotification:^(RLMRealm *realm) {
  97. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]];
  98. }];
  99. }
  100. - (void)testDeleteObjectNotMatchingQuery {
  101. [self expectNoNotification:^(RLMRealm *realm) {
  102. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 5"]];
  103. }];
  104. [self expectNoNotification:^(RLMRealm *realm) {
  105. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]];
  106. }];
  107. }
  108. - (void)testNonMatchingObjectMovedToIndexOfMatchingRowAndMadeMatching {
  109. [self expectNotification:^(RLMRealm *realm) {
  110. // Make the last object match the query
  111. [[[IntObject allObjectsInRealm:realm] lastObject] setIntCol:3];
  112. // Move the now-matching object over a previously matching object
  113. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]];
  114. }];
  115. }
  116. - (void)testSuppressCollectionNotification {
  117. _called = false;
  118. RLMRealm *realm = [RLMRealm defaultRealm];
  119. [realm beginWriteTransaction];
  120. [realm deleteAllObjects];
  121. [realm commitWriteTransactionWithoutNotifying:@[_token] error:nil];
  122. // Add a new callback that we can wait for, as we can't wait for a
  123. // notification to not be delivered
  124. RLMNotificationToken *token = [self.query addNotificationBlock:^(__unused RLMResults *results,
  125. __unused RLMCollectionChange *change,
  126. __unused NSError *error) {
  127. CFRunLoopStop(CFRunLoopGetCurrent());
  128. }];
  129. CFRunLoopRun();
  130. [token invalidate];
  131. XCTAssertFalse(_called);
  132. }
  133. - (void)testSuppressRealmNotification {
  134. RLMRealm *realm = [RLMRealm defaultRealm];
  135. RLMNotificationToken *token = [realm addNotificationBlock:^(__unused RLMNotification notification, __unused RLMRealm *realm) {
  136. XCTFail(@"should not have been called");
  137. }];
  138. [realm beginWriteTransaction];
  139. [realm deleteAllObjects];
  140. [realm commitWriteTransactionWithoutNotifying:@[token] error:nil];
  141. // local realm notifications are called synchronously so no need to wait for anything
  142. [token invalidate];
  143. }
  144. - (void)testSuppressRealmNotificationForWrongRealm {
  145. RLMRealm *otherRealm = [self realmWithTestPath];
  146. RLMNotificationToken *token = [otherRealm addNotificationBlock:^(__unused RLMNotification notification, __unused RLMRealm *realm) {
  147. XCTFail(@"should not have been called");
  148. }];
  149. RLMRealm *realm = [RLMRealm defaultRealm];
  150. [realm beginWriteTransaction];
  151. XCTAssertThrows([realm commitWriteTransactionWithoutNotifying:@[token] error:nil]);
  152. [realm cancelWriteTransaction];
  153. [token invalidate];
  154. }
  155. - (void)testSuppressCollectionNotificationForWrongRealm {
  156. // Test with the token's realm not in a write transaction
  157. RLMRealm *otherRealm = [self realmWithTestPath];
  158. [otherRealm beginWriteTransaction];
  159. XCTAssertThrows([otherRealm commitWriteTransactionWithoutNotifying:@[_token] error:nil]);
  160. // and in a write transaction
  161. RLMRealm *realm = [RLMRealm defaultRealm];
  162. [realm beginWriteTransaction];
  163. XCTAssertThrows([otherRealm commitWriteTransactionWithoutNotifying:@[_token] error:nil]);
  164. [realm cancelWriteTransaction];
  165. [otherRealm cancelWriteTransaction];
  166. }
  167. @end
  168. @interface SortedNotificationTests : NotificationTests
  169. @end
  170. @implementation SortedNotificationTests
  171. - (RLMResults *)query {
  172. return [[IntObject objectsWhere:@"intCol > 0 AND intCol < 5"]
  173. sortedResultsUsingKeyPath:@"intCol" ascending:NO];
  174. }
  175. - (void)testMoveMatchingObjectDueToDeletionOfNonMatchingObject {
  176. [self expectNoNotification:^(RLMRealm *realm) {
  177. // Make a matching object be the last row
  178. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol >= 5"]];
  179. // Delete a non-last, non-match row so that a matched row is moved
  180. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]];
  181. }];
  182. }
  183. - (void)testMultipleMovesOfSingleRow {
  184. [self expectNotification:^(RLMRealm *realm) {
  185. [realm deleteObjects:[IntObject allObjectsInRealm:realm]];
  186. [IntObject createInRealm:realm withValue:@[@10]];
  187. [IntObject createInRealm:realm withValue:@[@10]];
  188. [IntObject createInRealm:realm withValue:@[@3]];
  189. }];
  190. [self expectNoNotification:^(RLMRealm *realm) {
  191. RLMResults *objects = [IntObject allObjectsInRealm:realm];
  192. [realm deleteObject:objects[1]];
  193. [realm deleteObject:objects[0]];
  194. }];
  195. }
  196. @end
  197. @protocol ChangesetTestCase
  198. - (RLMResults *)query;
  199. - (void)prepare;
  200. @end
  201. @interface NSIndexPath (TableViewHelpers)
  202. @property (nonatomic, readonly) NSInteger section;
  203. @property (nonatomic, readonly) NSInteger row;
  204. @end
  205. @implementation NSIndexPath (TableViewHelpers)
  206. - (NSInteger)section {
  207. return [self indexAtPosition:0];
  208. }
  209. - (NSInteger)row {
  210. return [self indexAtPosition:1];
  211. }
  212. @end
  213. static RLMCollectionChange *getChange(RLMTestCase<ChangesetTestCase> *self, void (^block)(RLMRealm *)) {
  214. [self prepare];
  215. __block bool first = true;
  216. RLMResults *query = [self query];
  217. __block RLMCollectionChange *changes;
  218. id token = [query addNotificationBlock:^(RLMResults *results, RLMCollectionChange *c, NSError *error) {
  219. XCTAssertNotNil(results);
  220. XCTAssertNil(error);
  221. changes = c;
  222. XCTAssertTrue(first == !changes);
  223. first = false;
  224. CFRunLoopStop(CFRunLoopGetCurrent());
  225. }];
  226. CFRunLoopRun();
  227. [self waitForNotification:RLMRealmDidChangeNotification realm:RLMRealm.defaultRealm block:^{
  228. RLMRealm *realm = [RLMRealm defaultRealm];
  229. [realm transactionWithBlock:^{
  230. block(realm);
  231. }];
  232. }];
  233. [(RLMNotificationToken *)token invalidate];
  234. token = nil;
  235. return changes;
  236. }
  237. static void ExpectChange(id self, NSArray *deletions, NSArray *insertions, NSArray *modifications, void (^block)(RLMRealm *)) {
  238. RLMCollectionChange *changes = getChange(self, block);
  239. XCTAssertNotNil(changes);
  240. if (!changes) {
  241. return;
  242. }
  243. XCTAssertEqualObjects(deletions, changes.deletions);
  244. XCTAssertEqualObjects(insertions, changes.insertions);
  245. XCTAssertEqualObjects(modifications, changes.modifications);
  246. NSInteger section = __LINE__;
  247. NSArray *deletionPaths = [changes deletionsInSection:section];
  248. NSArray *insertionPaths = [changes insertionsInSection:section + 1];
  249. NSArray *modificationPaths = [changes modificationsInSection:section + 2];
  250. XCTAssert(deletionPaths.count == 0 || [deletionPaths[0] section] == section);
  251. XCTAssert(insertionPaths.count == 0 || [insertionPaths[0] section] == section + 1);
  252. XCTAssert(modificationPaths.count == 0 || [modificationPaths[0] section] == section + 2);
  253. XCTAssertEqualObjects(deletions, [deletionPaths valueForKey:@"row"]);
  254. XCTAssertEqualObjects(insertions, [insertionPaths valueForKey:@"row"]);
  255. XCTAssertEqualObjects(modifications, [modificationPaths valueForKey:@"row"]);
  256. }
  257. #define ExpectNoChange(self, block) XCTAssertNil(getChange((self), (block)))
  258. @interface ChangesetTests : RLMTestCase <ChangesetTestCase>
  259. @end
  260. @implementation ChangesetTests
  261. - (void)prepare {
  262. @autoreleasepool {
  263. RLMRealm *realm = [RLMRealm defaultRealm];
  264. [realm transactionWithBlock:^{
  265. [realm deleteAllObjects];
  266. for (int i = 0; i < 10; ++i) {
  267. IntObject *io = [IntObject createInDefaultRealmWithValue:@[@(i)]];
  268. [ArrayPropertyObject createInDefaultRealmWithValue:@[@"", @[], @[io]]];
  269. }
  270. }];
  271. }
  272. }
  273. - (RLMResults *)query {
  274. return [IntObject objectsWhere:@"intCol > 0 AND intCol < 5"];
  275. }
  276. - (void)testDeleteMultiple {
  277. ExpectNoChange(self, ^(RLMRealm *realm) {
  278. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 4"]];
  279. });
  280. ExpectNoChange(self, ^(RLMRealm *realm) {
  281. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 4"]];
  282. });
  283. ExpectNoChange(self, ^(RLMRealm *realm) {
  284. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 5"]];
  285. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]];
  286. });
  287. ExpectChange(self, @[@1, @2, @3], @[], @[], ^(RLMRealm *realm) {
  288. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 1"]];
  289. });
  290. ExpectChange(self, @[@1, @2, @3], @[], @[], ^(RLMRealm *realm) {
  291. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]];
  292. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 3"]];
  293. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]];
  294. });
  295. ExpectChange(self, @[@1, @2, @3], @[], @[], ^(RLMRealm *realm) {
  296. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]];
  297. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 3"]];
  298. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]];
  299. });
  300. ExpectChange(self, @[@3], @[@0], @[], ^(RLMRealm *realm) {
  301. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 4"]];
  302. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol < 1"]];
  303. });
  304. ExpectChange(self, @[@0, @1, @2, @3], @[], @[], ^(RLMRealm *realm) {
  305. [realm deleteObjects:[[IntObject allObjectsInRealm:realm] valueForKey:@"self"]];
  306. });
  307. }
  308. - (void)testDeleteNewlyInsertedRowMatchingQuery {
  309. ExpectNoChange(self, ^(RLMRealm *realm) {
  310. [IntObject createInRealm:realm withValue:@[@3]];
  311. [realm deleteObject:[IntObject allObjectsInRealm:realm].lastObject];
  312. });
  313. }
  314. - (void)testInsertObjectMatchingQuery {
  315. ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) {
  316. [IntObject createInRealm:realm withValue:@[@3]];
  317. });
  318. }
  319. - (void)testInsertObjectNotMatchingQuery {
  320. ExpectNoChange(self, ^(RLMRealm *realm) {
  321. [IntObject createInRealm:realm withValue:@[@5]];
  322. });
  323. }
  324. - (void)testInsertBothMatchingAndNonMatching {
  325. ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) {
  326. [IntObject createInRealm:realm withValue:@[@5]];
  327. [IntObject createInRealm:realm withValue:@[@3]];
  328. });
  329. }
  330. - (void)testInsertMultipleMatching {
  331. ExpectChange(self, @[], @[@4, @5], @[], ^(RLMRealm *realm) {
  332. [IntObject createInRealm:realm withValue:@[@5]];
  333. [IntObject createInRealm:realm withValue:@[@3]];
  334. [IntObject createInRealm:realm withValue:@[@5]];
  335. [IntObject createInRealm:realm withValue:@[@2]];
  336. });
  337. }
  338. - (void)testModifyObjectMatchingQuery {
  339. ExpectChange(self, @[], @[], @[@2], ^(RLMRealm *realm) {
  340. [[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@4 forKey:@"intCol"];
  341. });
  342. }
  343. - (void)testModifyObjectToNoLongerMatchQuery {
  344. ExpectChange(self, @[@2], @[], @[], ^(RLMRealm *realm) {
  345. [[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@5 forKey:@"intCol"];
  346. });
  347. }
  348. - (void)testModifyObjectNotMatchingQuery {
  349. ExpectNoChange(self, ^(RLMRealm *realm) {
  350. [[IntObject objectsInRealm:realm where:@"intCol = 5"] setValue:@6 forKey:@"intCol"];
  351. });
  352. }
  353. - (void)testModifyObjectToMatchQuery {
  354. ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) {
  355. [[IntObject objectsInRealm:realm where:@"intCol = 5"] setValue:@4 forKey:@"intCol"];
  356. });
  357. }
  358. - (void)testModifyObjectShiftedByDeletion {
  359. ExpectChange(self, @[@1], @[], @[@2], ^(RLMRealm *realm) {
  360. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]];
  361. [[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@4 forKey:@"intCol"];
  362. });
  363. }
  364. - (void)testDeleteObjectMatchingQuery {
  365. ExpectChange(self, @[@0], @[], @[], ^(RLMRealm *realm) {
  366. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 1"]];
  367. });
  368. ExpectChange(self, @[@3], @[], @[], ^(RLMRealm *realm) {
  369. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]];
  370. });
  371. }
  372. - (void)testDeleteNonMatchingBeforeMatches {
  373. ExpectNoChange(self, ^(RLMRealm *realm) {
  374. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]];
  375. });
  376. }
  377. - (void)testDeleteNonMatchingAfterMatches {
  378. ExpectNoChange(self, ^(RLMRealm *realm) {
  379. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 5"]];
  380. });
  381. }
  382. - (void)testMoveMatchingObjectDueToDeletionOfNonMatchingObject {
  383. ExpectChange(self, @[@3], @[@0], @[], ^(RLMRealm *realm) {
  384. // Make a matching object be the last row
  385. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol >= 5"]];
  386. // Delete a non-last, non-match row so that a matched row is moved
  387. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]];
  388. });
  389. }
  390. - (void)testNonMatchingObjectMovedToIndexOfMatchingRowAndMadeMatching {
  391. ExpectChange(self, @[@1], @[@1], @[], ^(RLMRealm *realm) {
  392. // Make the last object match the query
  393. [[[IntObject allObjectsInRealm:realm] lastObject] setIntCol:3];
  394. // Move the now-matching object over a previously matching object
  395. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]];
  396. });
  397. }
  398. - (void)testExcludingChangesFromSkippedTransaction {
  399. [self prepare];
  400. __block bool first = true;
  401. RLMResults *query = [self query];
  402. __block RLMCollectionChange *changes;
  403. RLMNotificationToken *token = [query addNotificationBlock:^(RLMResults *results, RLMCollectionChange *c, NSError *error) {
  404. XCTAssertNotNil(results);
  405. XCTAssertNil(error);
  406. changes = c;
  407. XCTAssertTrue(first || changes);
  408. first = false;
  409. CFRunLoopStop(CFRunLoopGetCurrent());
  410. }];
  411. CFRunLoopRun();
  412. [query.realm beginWriteTransaction];
  413. [IntObject createInRealm:query.realm withValue:@[@3]];
  414. [query.realm commitWriteTransactionWithoutNotifying:@[token] error:nil];
  415. [self waitForNotification:RLMRealmDidChangeNotification realm:RLMRealm.defaultRealm block:^{
  416. RLMRealm *realm = [RLMRealm defaultRealm];
  417. [realm transactionWithBlock:^{
  418. [IntObject createInRealm:realm withValue:@[@3]];
  419. }];
  420. }];
  421. [token invalidate];
  422. token = nil;
  423. XCTAssertNotNil(changes);
  424. // Should only have the row inserted in the background transaction
  425. XCTAssertEqualObjects(@[@5], changes.insertions);
  426. }
  427. @end
  428. @interface LinkViewChangesetTests : RLMTestCase <ChangesetTestCase>
  429. @end
  430. @implementation LinkViewChangesetTests
  431. - (void)prepare {
  432. @autoreleasepool {
  433. RLMRealm *realm = [RLMRealm defaultRealm];
  434. [realm transactionWithBlock:^{
  435. [realm deleteAllObjects];
  436. for (int i = 0; i < 10; ++i) {
  437. [IntObject createInDefaultRealmWithValue:@[@(i)]];
  438. }
  439. [ArrayPropertyObject createInDefaultRealmWithValue:@[@"", @[], [IntObject allObjectsInRealm:realm]]];
  440. }];
  441. }
  442. }
  443. - (RLMResults *)query {
  444. return [[[ArrayPropertyObject.allObjects firstObject] intArray]
  445. objectsWhere:@"intCol > 0 AND intCol < 5"];
  446. }
  447. - (void)testDeleteMultiple {
  448. ExpectNoChange(self, ^(RLMRealm *realm) {
  449. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 4"]];
  450. });
  451. ExpectNoChange(self, ^(RLMRealm *realm) {
  452. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 5"]];
  453. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]];
  454. });
  455. ExpectChange(self, @[@1, @2, @3], @[], @[], ^(RLMRealm *realm) {
  456. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 1"]];
  457. });
  458. ExpectChange(self, @[@1, @2, @3], @[], @[], ^(RLMRealm *realm) {
  459. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]];
  460. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 3"]];
  461. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]];
  462. });
  463. ExpectChange(self, @[@1, @2, @3], @[], @[], ^(RLMRealm *realm) {
  464. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]];
  465. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 3"]];
  466. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]];
  467. });
  468. ExpectNoChange(self, ^(RLMRealm *realm) {
  469. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol > 4"]];
  470. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol < 1"]];
  471. });
  472. }
  473. - (void)testModifyObjectMatchingQuery {
  474. ExpectChange(self, @[], @[], @[@2], ^(RLMRealm *realm) {
  475. [[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@4 forKey:@"intCol"];
  476. });
  477. }
  478. - (void)testModifyObjectToNoLongerMatchQuery {
  479. ExpectChange(self, @[@2], @[], @[], ^(RLMRealm *realm) {
  480. [[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@5 forKey:@"intCol"];
  481. });
  482. }
  483. - (void)testModifyObjectNotMatchingQuery {
  484. ExpectNoChange(self, ^(RLMRealm *realm) {
  485. [[IntObject objectsInRealm:realm where:@"intCol = 5"] setValue:@6 forKey:@"intCol"];
  486. });
  487. }
  488. - (void)testModifyObjectToMatchQuery {
  489. ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) {
  490. [[IntObject objectsInRealm:realm where:@"intCol = 5"] setValue:@4 forKey:@"intCol"];
  491. });
  492. }
  493. - (void)testDeleteObjectMatchingQuery {
  494. ExpectChange(self, @[@0], @[], @[], ^(RLMRealm *realm) {
  495. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 1"]];
  496. });
  497. ExpectChange(self, @[@3], @[], @[], ^(RLMRealm *realm) {
  498. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 4"]];
  499. });
  500. }
  501. - (void)testDeleteNonMatchingBeforeMatches {
  502. ExpectNoChange(self, ^(RLMRealm *realm) {
  503. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]];
  504. });
  505. }
  506. - (void)testDeleteNonMatchingAfterMatches {
  507. ExpectNoChange(self, ^(RLMRealm *realm) {
  508. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 5"]];
  509. });
  510. }
  511. - (void)testMoveMatchingObjectDueToDeletionOfNonMatchingObject {
  512. ExpectNoChange(self, ^(RLMRealm *realm) {
  513. // Make a matching object be the last row
  514. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol >= 5"]];
  515. // Delete a non-last, non-match row so that a matched row is moved
  516. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 0"]];
  517. });
  518. }
  519. - (void)testNonMatchingObjectMovedToIndexOfMatchingRowAndMadeMatching {
  520. ExpectChange(self, @[@1], @[@3], @[], ^(RLMRealm *realm) {
  521. // Make the last object match the query
  522. [[[IntObject allObjectsInRealm:realm] lastObject] setIntCol:3];
  523. // Move the now-matching object over a previously matching object
  524. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]];
  525. });
  526. }
  527. - (void)testDeleteNewlyInsertedRowMatchingQuery {
  528. ExpectNoChange(self, ^(RLMRealm *realm) {
  529. RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
  530. [array addObject:[IntObject createInRealm:realm withValue:@[@3]]];
  531. [realm deleteObject:[IntObject allObjectsInRealm:realm].lastObject];
  532. });
  533. }
  534. - (void)testInsertObjectMatchingQuery {
  535. ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) {
  536. RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
  537. [array addObject:[IntObject createInRealm:realm withValue:@[@3]]];
  538. });
  539. }
  540. - (void)testInsertObjectNotMatchingQuery {
  541. ExpectNoChange(self, ^(RLMRealm *realm) {
  542. RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
  543. [array addObject:[IntObject createInRealm:realm withValue:@[@5]]];
  544. });
  545. }
  546. - (void)testInsertBothMatchingAndNonMatching {
  547. ExpectChange(self, @[], @[@4], @[], ^(RLMRealm *realm) {
  548. RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
  549. [array addObject:[IntObject createInRealm:realm withValue:@[@5]]];
  550. [array addObject:[IntObject createInRealm:realm withValue:@[@3]]];
  551. });
  552. }
  553. - (void)testInsertMultipleMatching {
  554. ExpectChange(self, @[], @[@4, @5], @[], ^(RLMRealm *realm) {
  555. RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
  556. [array addObject:[IntObject createInRealm:realm withValue:@[@5]]];
  557. [array addObject:[IntObject createInRealm:realm withValue:@[@3]]];
  558. [array addObject:[IntObject createInRealm:realm withValue:@[@5]]];
  559. [array addObject:[IntObject createInRealm:realm withValue:@[@2]]];
  560. });
  561. }
  562. - (void)testInsertAtIndex {
  563. ExpectChange(self, @[], @[@0], @[], ^(RLMRealm *realm) {
  564. RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
  565. IntObject *io = [IntObject createInRealm:realm withValue:@[@3]];
  566. [array insertObject:io atIndex:0];
  567. });
  568. ExpectChange(self, @[], @[@0], @[], ^(RLMRealm *realm) {
  569. RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
  570. IntObject *io = [IntObject createInRealm:realm withValue:@[@3]];
  571. [array insertObject:io atIndex:1];
  572. });
  573. ExpectChange(self, @[], @[@1], @[], ^(RLMRealm *realm) {
  574. RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
  575. IntObject *io = [IntObject createInRealm:realm withValue:@[@3]];
  576. [array insertObject:io atIndex:2];
  577. });
  578. ExpectNoChange(self, ^(RLMRealm *realm) {
  579. RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
  580. IntObject *io = [IntObject createInRealm:realm withValue:@[@5]];
  581. [array insertObject:io atIndex:2];
  582. });
  583. }
  584. - (void)testExchangeObjects {
  585. // adjacent swap: one move, since second is redundant
  586. // ExpectChange(self, @[@1, @0], @[], @[], ^(RLMRealm *realm) {
  587. // RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
  588. // [array exchangeObjectAtIndex:1 withObjectAtIndex:2];
  589. // });
  590. // non-adjacent: two moves needed
  591. // ExpectChange(self, @[@0, @2], ^(RLMRealm *realm) {
  592. // RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
  593. // [array exchangeObjectAtIndex:1 withObjectAtIndex:3];
  594. // });
  595. }
  596. - (void)testRemoveFromArray {
  597. ExpectChange(self, @[@0], @[], @[], ^(RLMRealm *realm) {
  598. RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
  599. [array removeObjectAtIndex:1];
  600. });
  601. ExpectNoChange(self, ^(RLMRealm *realm) {
  602. RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
  603. [array removeObjectAtIndex:0];
  604. });
  605. }
  606. - (void)testClearArray {
  607. ExpectChange(self, @[@0, @1, @2, @3], @[], @[], ^(RLMRealm *realm) {
  608. RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
  609. [array removeAllObjects];
  610. });
  611. }
  612. - (void)testDeleteArray {
  613. ExpectChange(self, @[@0, @1, @2, @3], @[], @[], ^(RLMRealm *realm) {
  614. [realm deleteObjects:[ArrayPropertyObject allObjectsInRealm:realm]];
  615. });
  616. }
  617. - (void)testModifyObjectShiftedByInsertsAndDeletions {
  618. ExpectChange(self, @[@1], @[], @[@2], ^(RLMRealm *realm) {
  619. [realm deleteObjects:[IntObject objectsInRealm:realm where:@"intCol = 2"]];
  620. [[IntObject objectsInRealm:realm where:@"intCol = 3"] setValue:@4 forKey:@"intCol"];
  621. });
  622. ExpectChange(self, @[], @[@0], @[@3], ^(RLMRealm *realm) {
  623. RLMArray *array = [[[ArrayPropertyObject allObjectsInRealm:realm] firstObject] intArray];
  624. [array insertObject:[IntObject createInRealm:realm withValue:@[@3]] atIndex:0];
  625. [[IntObject objectsInRealm:realm where:@"intCol = 4"] setValue:@3 forKey:@"intCol"];
  626. });
  627. }
  628. @end
  629. @interface LinkedObjectChangesetTests : RLMTestCase <ChangesetTestCase>
  630. @end
  631. @implementation LinkedObjectChangesetTests
  632. - (void)prepare {
  633. @autoreleasepool {
  634. RLMRealm *realm = [RLMRealm defaultRealm];
  635. [realm transactionWithBlock:^{
  636. [realm deleteAllObjects];
  637. for (int i = 0; i < 5; ++i) {
  638. [LinkStringObject createInRealm:realm
  639. withValue:@[[StringObject createInRealm:realm
  640. withValue:@[@""]]]];
  641. }
  642. }];
  643. }
  644. }
  645. - (RLMResults *)query {
  646. return LinkStringObject.allObjects;
  647. }
  648. - (void)testDeleteLinkedObject {
  649. ExpectChange(self, @[], @[], @[@3], ^(RLMRealm *realm) {
  650. [realm deleteObject:[StringObject allObjectsInRealm:realm][3]];
  651. });
  652. }
  653. - (void)testModifyLinkedObject {
  654. ExpectChange(self, @[], @[], @[@3], ^(RLMRealm *realm) {
  655. [[StringObject allObjectsInRealm:realm][3] setStringCol:@"a"];
  656. });
  657. }
  658. - (void)testInsertUnlinkedObject {
  659. ExpectNoChange(self, ^(RLMRealm *realm) {
  660. [StringObject createInRealm:realm withValue:@[@""]];
  661. });
  662. }
  663. - (void)testTableClearFollowedByInsertsAndDeletes {
  664. ExpectChange(self, @[], @[], @[@0, @1, @2, @3, @4], ^(RLMRealm *realm) {
  665. [realm deleteObjects:[StringObject allObjectsInRealm:realm]];
  666. [StringObject createInRealm:realm withValue:@[@""]];
  667. [realm deleteObject:[StringObject createInRealm:realm withValue:@[@""]]];
  668. });
  669. }
  670. @end
  671. @interface LinkingObjectsChangesetTests : RLMTestCase <ChangesetTestCase>
  672. @end
  673. @implementation LinkingObjectsChangesetTests
  674. - (void)prepare {
  675. @autoreleasepool {
  676. RLMRealm *realm = [RLMRealm defaultRealm];
  677. [realm transactionWithBlock:^{
  678. [realm deleteAllObjects];
  679. PersonObject *child = [PersonObject createInDefaultRealmWithValue:@[ @"Child", @0 ]];
  680. for (int i = 0; i < 10; ++i) {
  681. // It takes a village to raise a child…
  682. NSString *name = [NSString stringWithFormat:@"Parent %d", i];
  683. [PersonObject createInDefaultRealmWithValue:@[ name, @(25 + i), @[ child ]]];
  684. }
  685. }];
  686. }
  687. }
  688. - (RLMResults *)query {
  689. return [[PersonObject.allObjects firstObject] parents];
  690. }
  691. - (void)testDeleteOneLinkingObject {
  692. ExpectChange(self, @[@5], @[], @[], ^(RLMRealm *realm) {
  693. [realm deleteObjects:[PersonObject objectsInRealm:realm where:@"age == 30"]];
  694. });
  695. }
  696. - (void)testDeleteSomeLinkingObjects {
  697. ExpectChange(self, @[@2, @8, @9], @[], @[], ^(RLMRealm *realm) {
  698. [realm deleteObjects:[PersonObject objectsInRealm:realm where:@"age > 32"]];
  699. [realm deleteObjects:[PersonObject objectsInRealm:realm where:@"age == 27"]];
  700. });
  701. }
  702. - (void)testDeleteAllLinkingObjects {
  703. ExpectChange(self, @[@0, @1, @2, @3, @4, @5, @6, @7, @8, @9], @[], @[], ^(RLMRealm *realm) {
  704. [realm deleteObjects:[PersonObject objectsInRealm:realm where:@"age > 20"]];
  705. });
  706. }
  707. - (void)testDeleteAll {
  708. ExpectChange(self, @[@0, @1, @2, @3, @4, @5, @6, @7, @8, @9], @[], @[], ^(RLMRealm *realm) {
  709. [realm deleteObjects:[PersonObject allObjectsInRealm:realm]];
  710. });
  711. }
  712. - (void)testUnlinkOne {
  713. ExpectChange(self, @[@4], @[], @[], ^(RLMRealm *realm) {
  714. PersonObject *parent = [[PersonObject objectsInRealm:realm where:@"age == 29"] firstObject];
  715. [parent.children removeAllObjects];
  716. });
  717. }
  718. - (void)testUnlinkAll {
  719. ExpectChange(self, @[@0, @1, @2, @3, @4, @5, @6, @7, @8, @9], @[], @[], ^(RLMRealm *realm) {
  720. for (PersonObject *parent in [PersonObject objectsInRealm:realm where:@"age > 20"])
  721. [parent.children removeAllObjects];
  722. });
  723. }
  724. - (void)testAddNewParent {
  725. ExpectChange(self, @[], @[@10], @[], ^(RLMRealm *realm) {
  726. PersonObject *child = [[PersonObject objectsInRealm:realm where:@"children.@count == 0"] firstObject];
  727. [PersonObject createInDefaultRealmWithValue:@[ @"New parent", @40, @[ child ]]];
  728. });
  729. }
  730. - (void)testAddDuplicateParent {
  731. ExpectChange(self, @[], @[@10], @[@7], ^(RLMRealm *realm) {
  732. PersonObject *parent = [[PersonObject objectsInRealm:realm where:@"age == 32"] firstObject];
  733. [parent.children addObject:[parent.children firstObject]];
  734. });
  735. }
  736. - (void)testModifyParent {
  737. ExpectChange(self, @[], @[], @[@3], ^(RLMRealm *realm) {
  738. PersonObject *parent = [[PersonObject objectsInRealm:realm where:@"age == 28"] firstObject];
  739. parent.age = parent.age + 1;
  740. });
  741. }
  742. @end
  743. // clang things the tests below have retain cycles because `_obj` could retain
  744. // the block passed to addNotificationBlock (but it doesn't)
  745. #pragma clang diagnostic ignored "-Warc-retain-cycles"
  746. @interface ObjectNotifierTests : RLMTestCase
  747. @end
  748. @implementation ObjectNotifierTests {
  749. NSArray *_initialValues;
  750. NSArray *_values;
  751. NSArray<NSString *> *_propertyNames;
  752. AllTypesObject *_obj;
  753. }
  754. - (void)setUp {
  755. NSDate *now = [NSDate date];
  756. StringObject *so = [[StringObject alloc] init];
  757. so.stringCol = @"string";
  758. _initialValues = @[@YES, @1, @1.1f, @1.11, @"string",
  759. [NSData dataWithBytes:"a" length:1], now, @YES, @11, NSNull.null];
  760. _values = @[@NO, @2, @2.2f, @2.22, @"string2", [NSData dataWithBytes:"b" length:1],
  761. [now dateByAddingTimeInterval:1], @NO, @22, so];
  762. RLMRealm *realm = [RLMRealm defaultRealm];
  763. [realm beginWriteTransaction];
  764. _obj = [AllTypesObject createInRealm:realm withValue:_initialValues];
  765. [realm commitWriteTransaction];
  766. _propertyNames = [_obj.objectSchema.properties valueForKey:@"name"];
  767. }
  768. - (void)tearDown {
  769. _values = nil;
  770. _initialValues = nil;
  771. _obj = nil;
  772. [super tearDown];
  773. }
  774. - (void)testDeleteObservedObject {
  775. XCTestExpectation *expectation = [self expectationWithDescription:@""];
  776. RLMNotificationToken *token = [_obj addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) {
  777. XCTAssertTrue(deleted);
  778. XCTAssertNil(error);
  779. XCTAssertNil(changes);
  780. [expectation fulfill];
  781. }];
  782. RLMRealm *realm = _obj.realm;
  783. [realm beginWriteTransaction];
  784. [realm deleteObject:_obj];
  785. [realm commitWriteTransaction];
  786. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  787. [token invalidate];
  788. }
  789. - (void)testChangeAllPropertyTypes {
  790. __block NSUInteger i = 0;
  791. __block XCTestExpectation *expectation = nil;
  792. RLMNotificationToken *token = [_obj addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) {
  793. XCTAssertFalse(deleted);
  794. XCTAssertNil(error);
  795. XCTAssertEqual(changes.count, 1U);
  796. RLMPropertyChange *prop = changes[0];
  797. XCTAssertEqualObjects(prop.name, _propertyNames[i]);
  798. XCTAssertNil(prop.previousValue);
  799. if ([prop.name isEqualToString:@"objectCol"]) {
  800. XCTAssertTrue([prop.value isEqualToObject:_values[i]],
  801. @"%d: %@ %@", (int)i, prop.value, _values[i]);
  802. }
  803. else {
  804. XCTAssertEqualObjects(prop.value, _values[i]);
  805. }
  806. [expectation fulfill];
  807. }];
  808. for (i = 0; i < _values.count; ++i) {
  809. expectation = [self expectationWithDescription:@""];
  810. [_obj.realm beginWriteTransaction];
  811. _obj[_propertyNames[i]] = _values[i];
  812. [_obj.realm commitWriteTransaction];
  813. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  814. }
  815. [token invalidate];
  816. }
  817. - (void)testChangeAllPropertyTypesFromBackground {
  818. __block NSUInteger i = 0;
  819. RLMNotificationToken *token = [_obj addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) {
  820. XCTAssertFalse(deleted);
  821. XCTAssertNil(error);
  822. XCTAssertEqual(changes.count, 1U);
  823. RLMPropertyChange *prop = changes[0];
  824. XCTAssertEqualObjects(prop.name, _propertyNames[i]);
  825. if ([prop.name isEqualToString:@"objectCol"]) {
  826. XCTAssertNil(prop.previousValue);
  827. XCTAssertNotNil(prop.value);
  828. }
  829. else {
  830. XCTAssertEqualObjects(prop.previousValue, _initialValues[i]);
  831. XCTAssertEqualObjects(prop.value, _values[i]);
  832. }
  833. }];
  834. for (i = 0; i < _values.count; ++i) {
  835. [self dispatchAsyncAndWait:^{
  836. RLMRealm *realm = [RLMRealm defaultRealm];
  837. AllTypesObject *obj = [[AllTypesObject allObjectsInRealm:realm] firstObject];
  838. [realm beginWriteTransaction];
  839. obj[_propertyNames[i]] = _values[i];
  840. [realm commitWriteTransaction];
  841. }];
  842. [_obj.realm refresh];
  843. }
  844. [token invalidate];
  845. }
  846. - (void)testChangeAllPropertyTypesInSingleTransaction {
  847. XCTestExpectation *expectation = [self expectationWithDescription:@""];
  848. RLMNotificationToken *token = [_obj addNotificationBlock:^(BOOL deleted, NSArray *changes, NSError *error) {
  849. XCTAssertFalse(deleted);
  850. XCTAssertNil(error);
  851. XCTAssertEqual(changes.count, _values.count);
  852. NSUInteger i = 0;
  853. for (RLMPropertyChange *prop in changes) {
  854. XCTAssertEqualObjects(prop.name, _propertyNames[i]);
  855. if ([prop.name isEqualToString:@"objectCol"]) {
  856. XCTAssertTrue([prop.value isEqualToObject:_values[i]]);
  857. }
  858. else {
  859. XCTAssertEqualObjects(prop.value, _values[i]);
  860. }
  861. ++i;
  862. }
  863. [expectation fulfill];
  864. }];
  865. [_obj.realm beginWriteTransaction];
  866. for (NSUInteger i = 0; i < _values.count; ++i) {
  867. _obj[_propertyNames[i]] = _values[i];
  868. }
  869. [_obj.realm commitWriteTransaction];
  870. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  871. [token invalidate];
  872. }
  873. - (void)testMultipleObjectNotifiers {
  874. [_obj.realm beginWriteTransaction];
  875. AllTypesObject *obj2 = [AllTypesObject createInRealm:_obj.realm withValue:_obj];
  876. [_obj.realm commitWriteTransaction];
  877. XCTestExpectation *expectation = [self expectationWithDescription:@""];
  878. __block NSUInteger calls = 0;
  879. id block = ^(BOOL deleted, NSArray<RLMPropertyChange *> *changes, NSError *error) {
  880. XCTAssertFalse(deleted);
  881. XCTAssertNil(error);
  882. XCTAssertEqual(changes.count, 1U);
  883. XCTAssertEqualObjects(changes[0].name, @"intCol");
  884. XCTAssertEqualObjects(changes[0].previousValue, @1);
  885. XCTAssertEqualObjects(changes[0].value, @2);
  886. if (++calls == 2) {
  887. [expectation fulfill];
  888. }
  889. };
  890. RLMNotificationToken *token1 = [_obj addNotificationBlock:block];
  891. RLMNotificationToken *token2 = [_obj addNotificationBlock:block];
  892. RLMNotificationToken *token3 = [obj2 addNotificationBlock:^(__unused BOOL deletd,
  893. __unused NSArray<RLMPropertyChange *> *changes,
  894. __unused NSError *error) {
  895. XCTFail(@"notification block for wrong object called");
  896. }];
  897. [self dispatchAsync:^{
  898. RLMRealm *realm = [RLMRealm defaultRealm];
  899. AllTypesObject *obj = [[AllTypesObject allObjectsInRealm:realm] firstObject];
  900. [realm beginWriteTransaction];
  901. obj.intCol = 2;
  902. [realm commitWriteTransaction];
  903. }];
  904. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  905. [token1 invalidate];
  906. [token2 invalidate];
  907. [token3 invalidate];
  908. }
  909. - (void)testArrayPropertiesMerelyReportModification {
  910. [_obj.realm beginWriteTransaction];
  911. ArrayOfAllTypesObject *array = [ArrayOfAllTypesObject createInRealm:_obj.realm withValue:@[@[]]];
  912. [_obj.realm commitWriteTransaction];
  913. XCTestExpectation *expectation = [self expectationWithDescription:@""];
  914. RLMNotificationToken *token = [array addNotificationBlock:^(BOOL deleted, NSArray<RLMPropertyChange *> *changes, NSError *error) {
  915. XCTAssertFalse(deleted);
  916. XCTAssertNil(error);
  917. XCTAssertEqual(changes.count, 1U);
  918. XCTAssertEqualObjects(changes[0].name, @"array");
  919. XCTAssertNil(changes[0].previousValue);
  920. XCTAssertNil(changes[0].value);
  921. [expectation fulfill];
  922. }];
  923. [self dispatchAsync:^{
  924. RLMRealm *realm = [RLMRealm defaultRealm];
  925. ArrayOfAllTypesObject *obj = [[ArrayOfAllTypesObject allObjectsInRealm:realm] firstObject];
  926. [realm beginWriteTransaction];
  927. [obj.array addObject:[[AllTypesObject allObjectsInRealm:realm] firstObject]];
  928. [realm commitWriteTransaction];
  929. }];
  930. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  931. [token invalidate];
  932. }
  933. @end