NotificationTests.m 42 KB

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