NotificationTests.m 42 KB

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