@pomwik/afterdb-react 0.0.0-stage → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGES-FROM-UPSTREAM.md +57 -0
  2. package/LICENSE +177 -0
  3. package/NOTICE +9 -0
  4. package/README.md +10 -2
  5. package/dist/RealmContext.d.ts +153 -0
  6. package/dist/RealmContext.d.ts.map +1 -0
  7. package/dist/RealmProvider.d.ts +95 -0
  8. package/dist/RealmProvider.d.ts.map +1 -0
  9. package/dist/cachedCollection.d.ts +58 -0
  10. package/dist/cachedCollection.d.ts.map +1 -0
  11. package/dist/cachedObject.d.ts +48 -0
  12. package/dist/cachedObject.d.ts.map +1 -0
  13. package/dist/helpers.d.ts +22 -0
  14. package/dist/helpers.d.ts.map +1 -0
  15. package/dist/index.cjs +2818 -0
  16. package/dist/index.cjs.map +1 -0
  17. package/dist/index.d.ts +100 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +2809 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/tsconfig.types.tsbuildinfo +1 -0
  22. package/dist/types.d.ts +64 -0
  23. package/dist/types.d.ts.map +1 -0
  24. package/dist/useObject.d.ts +30 -0
  25. package/dist/useObject.d.ts.map +1 -0
  26. package/dist/useQuery.d.ts +31 -0
  27. package/dist/useQuery.d.ts.map +1 -0
  28. package/dist/useRealm.d.ts +12 -0
  29. package/dist/useRealm.d.ts.map +1 -0
  30. package/package.json +117 -4
  31. package/src/RealmContext.ts +196 -0
  32. package/src/RealmProvider.tsx +251 -0
  33. package/src/__tests__/RealmProvider.test.tsx +583 -0
  34. package/src/__tests__/createRealmTestContext.ts +68 -0
  35. package/src/__tests__/helpers.ts +35 -0
  36. package/src/__tests__/mocks.ts +154 -0
  37. package/src/__tests__/profileHook.tsx +76 -0
  38. package/src/__tests__/setup.ts +22 -0
  39. package/src/__tests__/tsconfig.json +14 -0
  40. package/src/__tests__/useObjectHook.test.tsx +158 -0
  41. package/src/__tests__/useObjectRender.test.tsx +630 -0
  42. package/src/__tests__/useQueryHook.test.tsx +209 -0
  43. package/src/__tests__/useQueryRender.test.tsx +463 -0
  44. package/src/cachedCollection.ts +210 -0
  45. package/src/cachedObject.ts +164 -0
  46. package/src/helpers.ts +60 -0
  47. package/src/index.tsx +130 -0
  48. package/src/runtime.d.ts +31 -0
  49. package/src/types.ts +92 -0
  50. package/src/useObject.tsx +227 -0
  51. package/src/useQuery.tsx +205 -0
  52. package/src/useRealm.tsx +41 -0
@@ -0,0 +1,630 @@
1
+ ////////////////////////////////////////////////////////////////////////////
2
+ //
3
+ // Copyright 2022 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
+
19
+ import { act, fireEvent, render, waitFor } from "@testing-library/react-native";
20
+ import React, { useEffect, useState } from "react";
21
+ import { FlatList, ListRenderItem, Text, TextInput, TouchableHighlight, View } from "react-native";
22
+ import Realm from "realm";
23
+
24
+ import { createUseObject } from "../useObject";
25
+ import { randomRealmPath } from "./helpers";
26
+
27
+ export class ListItem extends Realm.Object {
28
+ id!: Realm.BSON.ObjectId;
29
+ name!: string;
30
+ lists!: Realm.List<List>;
31
+
32
+ static schema: Realm.ObjectSchema = {
33
+ name: "ListItem",
34
+ properties: {
35
+ id: "objectId",
36
+ name: "string",
37
+ lists: {
38
+ type: "linkingObjects",
39
+ objectType: "List",
40
+ property: "items",
41
+ },
42
+ },
43
+ primaryKey: "id",
44
+ };
45
+ }
46
+
47
+ export class List extends Realm.Object {
48
+ id!: Realm.BSON.ObjectId;
49
+ title!: string;
50
+ items!: Realm.List<ListItem>;
51
+ favoriteItem?: ListItem;
52
+ tags!: Realm.List<string>;
53
+
54
+ static schema: Realm.ObjectSchema = {
55
+ name: "List",
56
+ properties: {
57
+ id: "objectId",
58
+ title: "string",
59
+ items: "ListItem[]",
60
+ favoriteItem: "ListItem",
61
+ tags: "string[]",
62
+ },
63
+ primaryKey: "id",
64
+ };
65
+ }
66
+
67
+ const configuration: Realm.Configuration = {
68
+ schema: [List, ListItem],
69
+ deleteRealmIfMigrationNeeded: true,
70
+ path: randomRealmPath(),
71
+ };
72
+
73
+ // TODO: It would be better not to have to rely on this, but at the moment I see no other alternatives
74
+ function forceSynchronousNotifications(realm: Realm) {
75
+ // Starting a transaction will force all listeners to advance to the latest version
76
+ // and deliver notifications. We don't need to commit the transaction for this to work.
77
+ realm.beginTransaction();
78
+ realm.cancelTransaction();
79
+ }
80
+
81
+ const itemRenderCounter = jest.fn();
82
+ const listRenderCounter = jest.fn();
83
+ const objectChangeCounter = jest.fn();
84
+ const listChangeCounter = jest.fn();
85
+
86
+ const testRealm: Realm = new Realm(configuration);
87
+
88
+ const testCollection = [...new Array(100)].map(() => {
89
+ const id = new Realm.BSON.ObjectId();
90
+ return { id, name: id.toHexString() };
91
+ });
92
+
93
+ const useRealm = () => {
94
+ return testRealm;
95
+ };
96
+
97
+ const useObject = createUseObject(useRealm);
98
+
99
+ // setting renderItems to false will invoke `useObject` but not actual display the data
100
+ // this is to test that the changes to the object is still trigger a rerender, even when not displayed
101
+ // see https://github.com/realm/realm-js/issues/5185
102
+ const App = ({ renderItems = true, targetPrimaryKey = parentObjectId }) => {
103
+ return (
104
+ <SetupComponent>
105
+ <TestComponent testID="testContainer" renderItems={renderItems} targetPrimaryKey={targetPrimaryKey} />
106
+ </SetupComponent>
107
+ );
108
+ };
109
+
110
+ const parentObjectId = new Realm.BSON.ObjectId();
111
+
112
+ const SetupComponent = ({ children }: { children: JSX.Element }): JSX.Element | null => {
113
+ const realm = useRealm();
114
+
115
+ /*
116
+ useEffect(() => {
117
+ return () => {
118
+ realm.close();
119
+ };
120
+ }, [realm]);
121
+ */
122
+
123
+ const [setupComplete, setSetupComplete] = useState(false);
124
+ useEffect(() => {
125
+ realm.write(() => {
126
+ realm.deleteAll();
127
+ });
128
+ setSetupComplete(true);
129
+ }, [realm]);
130
+
131
+ if (!setupComplete) {
132
+ return null;
133
+ }
134
+
135
+ return children;
136
+ };
137
+
138
+ const Item: React.FC<{ item: ListItem }> = React.memo(({ item }) => {
139
+ itemRenderCounter();
140
+ const realm = useRealm();
141
+ const idString = item.id.toHexString();
142
+
143
+ return (
144
+ <View testID={`result${idString}`}>
145
+ <View testID={`name${idString}`}>
146
+ <Text>{item.name}</Text>
147
+ </View>
148
+ <TextInput
149
+ testID={`input${idString}`}
150
+ value={item.name}
151
+ onChangeText={(text) => {
152
+ realm.write(() => {
153
+ item.name = text;
154
+ });
155
+ }}
156
+ ></TextInput>
157
+ <TouchableHighlight
158
+ testID={`deleteButton${idString}`}
159
+ onPress={() => {
160
+ realm.write(() => {
161
+ realm.delete(item);
162
+ });
163
+ }}
164
+ >
165
+ <Text>Delete</Text>
166
+ </TouchableHighlight>
167
+ </View>
168
+ );
169
+ });
170
+
171
+ const keyExtractor = (item: ListItem) => `${item.id}`;
172
+
173
+ const renderItem: ListRenderItem<ListItem> = ({ item }) => <Item item={item} />;
174
+
175
+ const ItemList: React.FC<{ list: Realm.List<ListItem> }> = React.memo(({ list }) => {
176
+ const [counter, setCounter] = useState(0);
177
+
178
+ // This useEffect is to test that the list object reference is not changing when
179
+ // the component is re-rendered.
180
+ useEffect(() => {
181
+ listChangeCounter();
182
+ }, [list]);
183
+
184
+ return (
185
+ <>
186
+ <FlatList data={list} keyExtractor={keyExtractor} renderItem={renderItem} />
187
+ <TouchableHighlight
188
+ testID={`rerenderListButton`}
189
+ onPress={() => {
190
+ setCounter(counter + 1);
191
+ }}
192
+ >
193
+ <Text>Rerender</Text>
194
+ </TouchableHighlight>
195
+ </>
196
+ );
197
+ });
198
+
199
+ const TestComponent: React.FC<{ testID?: string; renderItems?: boolean; targetPrimaryKey: Realm.BSON.ObjectId }> = ({
200
+ testID,
201
+ renderItems,
202
+ targetPrimaryKey,
203
+ }) => {
204
+ const list = useObject(List, targetPrimaryKey);
205
+ const realm = useRealm();
206
+ const [counter, setCounter] = useState(0);
207
+
208
+ // This useEffect is to test that the list object reference is not changing when
209
+ // the component is re-rendered.
210
+ useEffect(() => {
211
+ objectChangeCounter();
212
+ }, [list]);
213
+
214
+ if (list === null) {
215
+ return <View testID={testID} />;
216
+ }
217
+
218
+ listRenderCounter();
219
+
220
+ const listIdString = list.id.toHexString();
221
+
222
+ return (
223
+ <View testID={testID}>
224
+ <View testID="list">{renderItems && <ItemList list={list.items} />}</View>
225
+ <View testID={`list${listIdString}`}>
226
+ <View testID={`listTitle${listIdString}`}>
227
+ <Text>{list.title}</Text>
228
+ </View>
229
+ <TextInput
230
+ testID={`listTitleInput${listIdString}`}
231
+ value={list.title}
232
+ onChangeText={(text) => {
233
+ realm.write(() => {
234
+ list.title = text;
235
+ });
236
+ }}
237
+ ></TextInput>
238
+ <TouchableHighlight
239
+ testID={`deleteListButton${listIdString}`}
240
+ onPress={() => {
241
+ realm.write(() => {
242
+ realm.delete(list);
243
+ });
244
+ }}
245
+ >
246
+ <Text>Delete</Text>
247
+ </TouchableHighlight>
248
+ </View>
249
+ {list?.favoriteItem && (
250
+ <View testID={`favoriteItem-${list?.favoriteItem.name}`}>
251
+ <Text>{list?.favoriteItem.name}</Text>
252
+ </View>
253
+ )}
254
+ <Text>{list?.tags[0]}</Text>
255
+ <TouchableHighlight
256
+ testID={`rerenderObjectButton`}
257
+ onPress={() => {
258
+ setCounter(counter + 1);
259
+ }}
260
+ >
261
+ <Text>Rerender</Text>
262
+ </TouchableHighlight>
263
+ </View>
264
+ );
265
+ };
266
+
267
+ async function setupTest() {
268
+ const { getByTestId, getByText, debug, rerender } = render(<App />);
269
+ await waitFor(() => getByTestId("testContainer"));
270
+
271
+ // In order to test that `useObject` brings the non-existing object into view when it's created,
272
+ // we do the creation after the app is rendered.
273
+ testRealm.write(() => {
274
+ testRealm.create(List, { id: parentObjectId, title: "List", items: testCollection });
275
+ });
276
+
277
+ const object = testRealm.objectForPrimaryKey(List, parentObjectId);
278
+ if (!object) throw new Error("Object not found in Realm");
279
+ await waitFor(() => getByTestId("list"));
280
+ const collection = object.items;
281
+
282
+ expect(listRenderCounter).toHaveBeenCalledTimes(1);
283
+ expect(itemRenderCounter).toHaveBeenCalledTimes(10);
284
+
285
+ return { getByTestId, getByText, debug, object, collection, rerender };
286
+ }
287
+
288
+ describe("useObject: rendering objects with a Realm.List property", () => {
289
+ afterEach(() => {
290
+ listRenderCounter.mockClear();
291
+ itemRenderCounter.mockClear();
292
+ objectChangeCounter.mockClear();
293
+ listChangeCounter.mockClear();
294
+ });
295
+
296
+ afterAll(() => {
297
+ testRealm.close();
298
+ });
299
+
300
+ describe("rendering single object", () => {
301
+ it("renders an object in one render cycle", async () => {
302
+ const { getByTestId } = render(<App />);
303
+
304
+ // In order to test that `useObject` brings the non-existing object into view when it's created,
305
+ // we do the creation after the app is rendered.
306
+ testRealm.write(() => {
307
+ testRealm.create(List, { id: parentObjectId, title: "List", items: testCollection });
308
+ });
309
+
310
+ await waitFor(() => getByTestId("list"));
311
+
312
+ expect(listRenderCounter).toHaveBeenCalledTimes(1);
313
+ });
314
+
315
+ it("only re-renders the changed object when a property changes", async () => {
316
+ const { getByTestId, getByText, object } = await setupTest();
317
+
318
+ const idString = object.id.toHexString();
319
+
320
+ const titleElement = getByTestId(`listTitle${idString}`);
321
+ const inputComponent = getByTestId(`listTitleInput${idString}`);
322
+
323
+ expect(titleElement).toHaveTextContent("List");
324
+ expect(listRenderCounter).toHaveBeenCalledTimes(1);
325
+
326
+ fireEvent.changeText(inputComponent, "ChangedList");
327
+
328
+ await waitFor(() => getByText("ChangedList"));
329
+
330
+ expect(titleElement).toHaveTextContent("ChangedList");
331
+ expect(listRenderCounter).toHaveBeenCalledTimes(2);
332
+ });
333
+
334
+ it("it nullifies the object when it is deleted", async () => {
335
+ const { getByTestId, object } = await setupTest();
336
+
337
+ const idString = object.id.toHexString();
338
+
339
+ const deleteButton = getByTestId(`deleteListButton${idString}`);
340
+
341
+ fireEvent.press(deleteButton);
342
+
343
+ await act(async () => {
344
+ forceSynchronousNotifications(testRealm);
345
+ });
346
+
347
+ expect(object.isValid()).toBe(false);
348
+
349
+ expect(testRealm.objectForPrimaryKey(List, parentObjectId)).toBe(null);
350
+
351
+ const testContainer = getByTestId("testContainer");
352
+ expect(testContainer).toBeEmptyElement();
353
+
354
+ // List is now gone, so it wasn't detected as a render
355
+ expect(listRenderCounter).toHaveBeenCalledTimes(1);
356
+ });
357
+
358
+ it("test changes to linked object", async () => {
359
+ const { getByTestId } = await setupTest();
360
+ const object = testRealm.objectForPrimaryKey(List, parentObjectId);
361
+ if (!object) throw new Error("Object not found in Realm");
362
+
363
+ await act(async () => {
364
+ testRealm.write(() => {
365
+ object.favoriteItem = object.items[0];
366
+ });
367
+ forceSynchronousNotifications(testRealm);
368
+ });
369
+
370
+ expect(getByTestId(`favoriteItem-${object.items[0].name}`)).toBeTruthy();
371
+
372
+ await act(async () => {
373
+ testRealm.write(() => {
374
+ object.favoriteItem = object.items[1];
375
+ });
376
+ forceSynchronousNotifications(testRealm);
377
+ });
378
+
379
+ expect(getByTestId(`favoriteItem-${object.items[1].name}`)).toBeTruthy();
380
+
381
+ await act(async () => {
382
+ testRealm.write(() => {
383
+ object.items[1].name = "apple";
384
+ });
385
+ forceSynchronousNotifications(testRealm);
386
+ });
387
+
388
+ expect(getByTestId(`favoriteItem-${object.items[1].name}`)).toBeTruthy();
389
+
390
+ // We should only have re-rendered once, as only the last change actually modified an item
391
+ expect(itemRenderCounter).toHaveBeenCalledTimes(11);
392
+ });
393
+
394
+ it("renders a different list if the target primary key changes", async () => {
395
+ const { rerender, getByTestId } = await setupTest();
396
+
397
+ const secondListId = new Realm.BSON.ObjectId();
398
+
399
+ testRealm.write(() => {
400
+ testRealm.create(List, { id: secondListId, title: "Second List", items: [] });
401
+ });
402
+
403
+ rerender(<App targetPrimaryKey={secondListId} />);
404
+
405
+ let titleElement = getByTestId(`listTitle${secondListId.toHexString()}`);
406
+ expect(titleElement).toHaveTextContent("Second List");
407
+
408
+ const thirdListId = new Realm.BSON.ObjectId();
409
+
410
+ testRealm.write(() => {
411
+ testRealm.create(List, { id: thirdListId, title: "Third List", items: [] });
412
+ });
413
+
414
+ rerender(<App targetPrimaryKey={thirdListId} />);
415
+
416
+ titleElement = getByTestId(`listTitle${thirdListId.toHexString()}`);
417
+ expect(titleElement).toHaveTextContent("Third List");
418
+
419
+ // Render the old id
420
+ rerender(<App targetPrimaryKey={secondListId} />);
421
+
422
+ await waitFor(() => getByTestId(`listTitle${secondListId.toHexString()}`));
423
+ titleElement = getByTestId(`listTitle${secondListId.toHexString()}`);
424
+ expect(titleElement).toHaveTextContent("Second List");
425
+ });
426
+ it("will return the same reference when state changes", async () => {
427
+ const { getByTestId } = await setupTest();
428
+
429
+ // Force a rerender
430
+ const rerenderButton = getByTestId("rerenderObjectButton");
431
+
432
+ // We expect the object to be re-rendered 3 times, once for the initial render, once for the
433
+ // object itself coming into existence, and once adding list of items to the list
434
+ const expectedCount = 3;
435
+
436
+ // Update the state twice to ensure the object reference is the same and we don't have a false positive
437
+ fireEvent.press(rerenderButton);
438
+ expect(objectChangeCounter).toHaveBeenCalledTimes(expectedCount);
439
+
440
+ fireEvent.press(rerenderButton);
441
+ expect(objectChangeCounter).toHaveBeenCalledTimes(expectedCount);
442
+ });
443
+ it("will rerender when the realm database is cleared", async () => {
444
+ await setupTest();
445
+
446
+ const initialCount = 2;
447
+
448
+ expect(objectChangeCounter).toHaveBeenCalledTimes(initialCount);
449
+
450
+ await act(async () => {
451
+ testRealm.write(() => {
452
+ testRealm.deleteAll();
453
+ });
454
+ forceSynchronousNotifications(testRealm);
455
+ });
456
+
457
+ expect(objectChangeCounter).toHaveBeenCalledTimes(initialCount + 1);
458
+ });
459
+ });
460
+
461
+ describe("rendering objects with a Realm.List property", () => {
462
+ it("renders each visible item in the list once", async () => {
463
+ const { getByTestId } = render(<App />);
464
+
465
+ testRealm.write(() => {
466
+ testRealm.create(List, { id: parentObjectId, title: "List", items: testCollection });
467
+ });
468
+
469
+ await waitFor(() => getByTestId("list"));
470
+
471
+ expect(itemRenderCounter).toHaveBeenCalledTimes(10);
472
+ });
473
+
474
+ it("only re-renders the changed item when a list item's data changes", async () => {
475
+ const { getByTestId, getByText, collection } = await setupTest();
476
+
477
+ const id = collection[0].id;
478
+
479
+ const idString = id.toHexString();
480
+
481
+ const nameElement = getByTestId(`name${idString}`);
482
+ const input = getByTestId(`input${idString}`);
483
+
484
+ expect(nameElement).toHaveTextContent(idString);
485
+
486
+ fireEvent.changeText(input, "apple");
487
+
488
+ await waitFor(() => getByText("apple"));
489
+
490
+ expect(nameElement).toHaveTextContent("apple");
491
+ expect(itemRenderCounter).toHaveBeenCalledTimes(11);
492
+ });
493
+
494
+ // This is a restriction of the current implementation as we do not know the
495
+ // id of a deleted item, ideally this would only re-render once
496
+ it("re-renders every visible item in the list when an item is deleted", async () => {
497
+ const { getByTestId, collection } = await setupTest();
498
+
499
+ const firstItem = collection[0];
500
+ const id = firstItem.id;
501
+
502
+ const idString = id.toHexString();
503
+
504
+ const nextVisible = collection[10];
505
+
506
+ const deleteButton = getByTestId(`deleteButton${idString}`);
507
+ const nameElement = getByTestId(`name${idString}`);
508
+
509
+ expect(nameElement).toHaveTextContent(idString);
510
+ expect(itemRenderCounter).toHaveBeenCalledTimes(10);
511
+
512
+ fireEvent.press(deleteButton);
513
+
514
+ const nextIdString = nextVisible.id.toHexString();
515
+
516
+ await waitFor(() => getByTestId(`name${nextIdString}`));
517
+
518
+ expect(itemRenderCounter).toHaveBeenCalledTimes(20);
519
+ });
520
+
521
+ it("only re-renders the changed item when a visible list item is modified without a UI interaction", async () => {
522
+ const { collection } = await setupTest();
523
+ testRealm.write(() => {
524
+ collection[0].name = "apple";
525
+ });
526
+
527
+ // Force Realm listeners to fire rather than waiting for the text "apple"
528
+ // to appear, in order to match how the subsequent "does not re-render"
529
+ // test works.
530
+ await act(async () => {
531
+ forceSynchronousNotifications(testRealm);
532
+ });
533
+
534
+ expect(itemRenderCounter).toHaveBeenCalledTimes(11);
535
+ });
536
+
537
+ it("only renders the new item when a list item is added", async () => {
538
+ const { collection } = await setupTest();
539
+ testRealm.write(() => {
540
+ collection.unshift(testRealm.create(ListItem, { id: new Realm.BSON.ObjectId(), name: "apple" }));
541
+ });
542
+
543
+ // Force Realm listeners to fire rather than waiting for the text "apple"
544
+ // to appear, in order to match how the subsequent "does not re-render"
545
+ // test works.
546
+ await act(async () => {
547
+ forceSynchronousNotifications(testRealm);
548
+ });
549
+
550
+ expect(itemRenderCounter).toHaveBeenCalledTimes(11);
551
+ });
552
+
553
+ it("does not re-render if an invisible list item is modified without a UI interaction", async () => {
554
+ const { collection } = await setupTest();
555
+
556
+ testRealm.write(() => {
557
+ const lastIndex = collection.length - 1;
558
+ collection[lastIndex].name = "apple";
559
+ });
560
+
561
+ await act(async () => {
562
+ forceSynchronousNotifications(testRealm);
563
+ });
564
+
565
+ expect(itemRenderCounter).toHaveBeenCalledTimes(10);
566
+ });
567
+
568
+ it("test that primitive lists render correctly", async () => {
569
+ const { object } = await setupTest();
570
+
571
+ testRealm.write(() => {
572
+ const tags = ["apple", "banana", "cherry", "durian", "eggplant"];
573
+ object.tags.push(...tags);
574
+ });
575
+
576
+ await act(async () => {
577
+ forceSynchronousNotifications(testRealm);
578
+ });
579
+
580
+ // no assertion here, just checking that the test doesn't crash
581
+ });
582
+
583
+ it("re-renders the list even if the list items have not been rendered", async () => {
584
+ const { getByTestId } = render(<App renderItems={false} />);
585
+
586
+ const list = testRealm.write(() => {
587
+ return testRealm.create(List, { id: parentObjectId, title: "List" });
588
+ });
589
+
590
+ await waitFor(() => getByTestId("list"));
591
+
592
+ expect(listRenderCounter).toHaveBeenCalledTimes(1);
593
+
594
+ testRealm.write(() => {
595
+ list.items.push(testRealm.create(ListItem, testCollection[0]));
596
+ });
597
+
598
+ await act(async () => {
599
+ forceSynchronousNotifications(testRealm);
600
+ });
601
+
602
+ expect(listRenderCounter).toHaveBeenCalledTimes(2);
603
+
604
+ testRealm.write(() => {
605
+ list.items.push(testRealm.create(ListItem, testCollection[1]));
606
+ });
607
+
608
+ await act(async () => {
609
+ forceSynchronousNotifications(testRealm);
610
+ });
611
+ expect(listRenderCounter).toHaveBeenCalledTimes(3);
612
+ });
613
+ });
614
+ it("will return the same reference when state changes", async () => {
615
+ const { getByTestId } = await setupTest();
616
+
617
+ // Force a rerender
618
+ const rerenderButton = getByTestId("rerenderListButton");
619
+
620
+ // We expect the object to be re-rendered 2 times, once for the initial render
621
+ // and once for adding list of items to the list
622
+ const expectedCount = 2;
623
+
624
+ // Update the state twice to ensure the object reference is the same and we don't have a false positive
625
+ fireEvent.press(rerenderButton);
626
+ expect(objectChangeCounter).toHaveBeenCalledTimes(expectedCount);
627
+ fireEvent.press(rerenderButton);
628
+ expect(objectChangeCounter).toHaveBeenCalledTimes(expectedCount);
629
+ });
630
+ });