stream-chat-react-native-core 9.7.0 → 9.7.1-beta.2
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.
- package/lib/commonjs/components/Message/utils/measureInWindow.js +51 -8
- package/lib/commonjs/components/Message/utils/measureInWindow.js.map +1 -1
- package/lib/commonjs/components/UIComponents/PortalWhileClosingView.js +6 -13
- package/lib/commonjs/components/UIComponents/PortalWhileClosingView.js.map +1 -1
- package/lib/commonjs/mock-builders/DB/mock.js +2 -0
- package/lib/commonjs/mock-builders/DB/mock.js.map +1 -1
- package/lib/commonjs/utils/isRNVersionGreaterThanOrEqualTo.js +22 -0
- package/lib/commonjs/utils/isRNVersionGreaterThanOrEqualTo.js.map +1 -0
- package/lib/commonjs/utils/react-native-constants.js +7 -0
- package/lib/commonjs/utils/react-native-constants.js.map +1 -0
- package/lib/commonjs/version.json +1 -1
- package/lib/module/components/Message/utils/measureInWindow.js +51 -8
- package/lib/module/components/Message/utils/measureInWindow.js.map +1 -1
- package/lib/module/components/UIComponents/PortalWhileClosingView.js +6 -13
- package/lib/module/components/UIComponents/PortalWhileClosingView.js.map +1 -1
- package/lib/module/mock-builders/DB/mock.js +2 -0
- package/lib/module/mock-builders/DB/mock.js.map +1 -1
- package/lib/module/utils/isRNVersionGreaterThanOrEqualTo.js +22 -0
- package/lib/module/utils/isRNVersionGreaterThanOrEqualTo.js.map +1 -0
- package/lib/module/utils/react-native-constants.js +7 -0
- package/lib/module/utils/react-native-constants.js.map +1 -0
- package/lib/module/version.json +1 -1
- package/lib/typescript/components/Message/utils/measureInWindow.d.ts +12 -2
- package/lib/typescript/components/Message/utils/measureInWindow.d.ts.map +1 -1
- package/lib/typescript/components/UIComponents/PortalWhileClosingView.d.ts.map +1 -1
- package/lib/typescript/utils/isRNVersionGreaterThanOrEqualTo.d.ts +9 -0
- package/lib/typescript/utils/isRNVersionGreaterThanOrEqualTo.d.ts.map +1 -0
- package/lib/typescript/utils/react-native-constants.d.ts +8 -0
- package/lib/typescript/utils/react-native-constants.d.ts.map +1 -0
- package/package.json +10 -10
- package/src/__tests__/offline-support/offline-feature.tsx +43 -34
- package/src/components/Message/utils/__tests__/measureInWindow.test.ts +217 -0
- package/src/components/Message/utils/measureInWindow.ts +69 -6
- package/src/components/MessageInput/__tests__/__snapshots__/AttachButton.test.tsx.snap +15 -0
- package/src/components/MessageInput/__tests__/__snapshots__/SendButton.test.tsx.snap +10 -0
- package/src/components/MessageList/__tests__/__snapshots__/ScrollToBottomButton.test.tsx.snap +1 -0
- package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap +6 -0
- package/src/components/UIComponents/PortalWhileClosingView.tsx +9 -14
- package/src/mock-builders/DB/mock.ts +2 -0
- package/src/utils/__tests__/isRNVersionGreaterThanOrEqualTo.test.ts +60 -0
- package/src/utils/isRNVersionGreaterThanOrEqualTo.ts +27 -0
- package/src/utils/react-native-constants.ts +10 -0
- package/src/version.json +1 -1
|
@@ -257,8 +257,8 @@ export const Generic = () => {
|
|
|
257
257
|
} as ChannelFilters;
|
|
258
258
|
const sort: ChannelSort = { last_updated: 1 };
|
|
259
259
|
|
|
260
|
-
const renderComponent = () =>
|
|
261
|
-
render(
|
|
260
|
+
const renderComponent = async () => {
|
|
261
|
+
const result = render(
|
|
262
262
|
<Chat client={chatClient} enableOfflineSupport>
|
|
263
263
|
<WithComponents overrides={{ ChannelPreview: ChannelPreviewComponent }}>
|
|
264
264
|
<ChannelList filters={filters} sort={sort} swipeActionsEnabled={false} />
|
|
@@ -266,6 +266,15 @@ export const Generic = () => {
|
|
|
266
266
|
</Chat>,
|
|
267
267
|
);
|
|
268
268
|
|
|
269
|
+
await waitFor(async () => {
|
|
270
|
+
const tablesInDb = (await BetterSqlite.getTables()) as Array<{ name: string }>;
|
|
271
|
+
const tableNamesInDB = tablesInDb.map((table) => table.name);
|
|
272
|
+
expect(Object.keys(tables).every((name) => tableNamesInDB.includes(name))).toBe(true);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
return result;
|
|
276
|
+
};
|
|
277
|
+
|
|
269
278
|
const expectCIDsOnUIToBeInDB = async (
|
|
270
279
|
queryAllByLabelText: typeof screen.queryAllByLabelText,
|
|
271
280
|
) => {
|
|
@@ -365,7 +374,7 @@ export const Generic = () => {
|
|
|
365
374
|
|
|
366
375
|
it('should store filter-sort query and cids on ChannelList in channelQueries table', async () => {
|
|
367
376
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
368
|
-
renderComponent();
|
|
377
|
+
await renderComponent();
|
|
369
378
|
|
|
370
379
|
await act(() => dispatchConnectionChangedEvent(chatClient, false));
|
|
371
380
|
// await waiter();
|
|
@@ -381,7 +390,7 @@ export const Generic = () => {
|
|
|
381
390
|
it('should store channels and its state in tables', async () => {
|
|
382
391
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
383
392
|
|
|
384
|
-
renderComponent();
|
|
393
|
+
await renderComponent();
|
|
385
394
|
|
|
386
395
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
387
396
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
@@ -400,7 +409,7 @@ export const Generic = () => {
|
|
|
400
409
|
useMockedApis(chatClient, [queryChannelsApi([emptyChannel])]);
|
|
401
410
|
jest.spyOn(chatClient, 'hydrateActiveChannels');
|
|
402
411
|
|
|
403
|
-
renderComponent();
|
|
412
|
+
await renderComponent();
|
|
404
413
|
|
|
405
414
|
await waitFor(async () => {
|
|
406
415
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
@@ -415,7 +424,7 @@ export const Generic = () => {
|
|
|
415
424
|
it('should add a new message to database', async () => {
|
|
416
425
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
417
426
|
|
|
418
|
-
renderComponent();
|
|
427
|
+
await renderComponent();
|
|
419
428
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
420
429
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
421
430
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -444,7 +453,7 @@ export const Generic = () => {
|
|
|
444
453
|
it('should correctly handle multiple new messages and add them to the database', async () => {
|
|
445
454
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
446
455
|
|
|
447
|
-
renderComponent();
|
|
456
|
+
await renderComponent();
|
|
448
457
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
449
458
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
450
459
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -506,7 +515,7 @@ export const Generic = () => {
|
|
|
506
515
|
it('should correctly handle multiple new messages from our own user', async () => {
|
|
507
516
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
508
517
|
|
|
509
|
-
renderComponent();
|
|
518
|
+
await renderComponent();
|
|
510
519
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
511
520
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
512
521
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -568,7 +577,7 @@ export const Generic = () => {
|
|
|
568
577
|
it('should add a new channel and a new message to database from notification event', async () => {
|
|
569
578
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
570
579
|
|
|
571
|
-
renderComponent();
|
|
580
|
+
await renderComponent();
|
|
572
581
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
573
582
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
574
583
|
await waitFor(() => {
|
|
@@ -609,7 +618,7 @@ export const Generic = () => {
|
|
|
609
618
|
it('should update a message in database', async () => {
|
|
610
619
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
611
620
|
|
|
612
|
-
renderComponent();
|
|
621
|
+
await renderComponent();
|
|
613
622
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
614
623
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
615
624
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -637,7 +646,7 @@ export const Generic = () => {
|
|
|
637
646
|
it('should remove the channel from DB when user is removed as member', async () => {
|
|
638
647
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
639
648
|
|
|
640
|
-
renderComponent();
|
|
649
|
+
await renderComponent();
|
|
641
650
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
642
651
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
643
652
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -671,7 +680,7 @@ export const Generic = () => {
|
|
|
671
680
|
it('should remove the channel from DB if the channel is deleted', async () => {
|
|
672
681
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
673
682
|
|
|
674
|
-
renderComponent();
|
|
683
|
+
await renderComponent();
|
|
675
684
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
676
685
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
677
686
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -705,7 +714,7 @@ export const Generic = () => {
|
|
|
705
714
|
it('should correctly mark the channel as hidden in the db', async () => {
|
|
706
715
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
707
716
|
|
|
708
|
-
renderComponent();
|
|
717
|
+
await renderComponent();
|
|
709
718
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
710
719
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
711
720
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -739,7 +748,7 @@ export const Generic = () => {
|
|
|
739
748
|
it('should correctly mark the channel as visible if it was hidden before in the db', async () => {
|
|
740
749
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
741
750
|
|
|
742
|
-
renderComponent();
|
|
751
|
+
await renderComponent();
|
|
743
752
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
744
753
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
745
754
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -800,7 +809,7 @@ export const Generic = () => {
|
|
|
800
809
|
it('should add the channel to DB when user is added as member', async () => {
|
|
801
810
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
802
811
|
|
|
803
|
-
renderComponent();
|
|
812
|
+
await renderComponent();
|
|
804
813
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
805
814
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
806
815
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -838,7 +847,7 @@ export const Generic = () => {
|
|
|
838
847
|
it('should remove the channel messages from DB when channel is truncated', async () => {
|
|
839
848
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
840
849
|
|
|
841
|
-
renderComponent();
|
|
850
|
+
await renderComponent();
|
|
842
851
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
843
852
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
844
853
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -874,7 +883,7 @@ export const Generic = () => {
|
|
|
874
883
|
it('should truncate the correct messages if channel.truncated arrives with truncated_at', async () => {
|
|
875
884
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
876
885
|
|
|
877
|
-
renderComponent();
|
|
886
|
+
await renderComponent();
|
|
878
887
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
879
888
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
880
889
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -926,7 +935,7 @@ export const Generic = () => {
|
|
|
926
935
|
it('should gracefully handle a truncated_at date before each message', async () => {
|
|
927
936
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
928
937
|
|
|
929
|
-
renderComponent();
|
|
938
|
+
await renderComponent();
|
|
930
939
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
931
940
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
932
941
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -962,7 +971,7 @@ export const Generic = () => {
|
|
|
962
971
|
it('should gracefully handle a truncated_at date after each message', async () => {
|
|
963
972
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
964
973
|
|
|
965
|
-
renderComponent();
|
|
974
|
+
await renderComponent();
|
|
966
975
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
967
976
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
968
977
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -1004,7 +1013,7 @@ export const Generic = () => {
|
|
|
1004
1013
|
|
|
1005
1014
|
it('should add a reaction to DB when a new reaction is added', async () => {
|
|
1006
1015
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
1007
|
-
renderComponent();
|
|
1016
|
+
await renderComponent();
|
|
1008
1017
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
1009
1018
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
1010
1019
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -1049,7 +1058,7 @@ export const Generic = () => {
|
|
|
1049
1058
|
|
|
1050
1059
|
it('should correctly add multiple reactions to the DB', async () => {
|
|
1051
1060
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
1052
|
-
renderComponent();
|
|
1061
|
+
await renderComponent();
|
|
1053
1062
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
1054
1063
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
1055
1064
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -1136,7 +1145,7 @@ export const Generic = () => {
|
|
|
1136
1145
|
|
|
1137
1146
|
it('should gracefully handle multiple reaction.new events of the same type for the same user', async () => {
|
|
1138
1147
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
1139
|
-
renderComponent();
|
|
1148
|
+
await renderComponent();
|
|
1140
1149
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
1141
1150
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
1142
1151
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -1205,7 +1214,7 @@ export const Generic = () => {
|
|
|
1205
1214
|
it('should remove a reaction from DB when reaction is deleted', async () => {
|
|
1206
1215
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
1207
1216
|
|
|
1208
|
-
renderComponent();
|
|
1217
|
+
await renderComponent();
|
|
1209
1218
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
1210
1219
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
1211
1220
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -1259,7 +1268,7 @@ export const Generic = () => {
|
|
|
1259
1268
|
it('should update a reaction in DB when reaction is updated', async () => {
|
|
1260
1269
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
1261
1270
|
|
|
1262
|
-
renderComponent();
|
|
1271
|
+
await renderComponent();
|
|
1263
1272
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
1264
1273
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
1265
1274
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -1296,7 +1305,7 @@ export const Generic = () => {
|
|
|
1296
1305
|
|
|
1297
1306
|
it('should correctly upsert reactions when enforce_unique is true', async () => {
|
|
1298
1307
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
1299
|
-
renderComponent();
|
|
1308
|
+
await renderComponent();
|
|
1300
1309
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
1301
1310
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
1302
1311
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -1396,7 +1405,7 @@ export const Generic = () => {
|
|
|
1396
1405
|
|
|
1397
1406
|
it('should also update the corresponding message.reaction_groups with reaction.new', async () => {
|
|
1398
1407
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
1399
|
-
renderComponent();
|
|
1408
|
+
await renderComponent();
|
|
1400
1409
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
1401
1410
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
1402
1411
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -1455,7 +1464,7 @@ export const Generic = () => {
|
|
|
1455
1464
|
|
|
1456
1465
|
it('should also update the corresponding message.reaction_groups with reaction.updated', async () => {
|
|
1457
1466
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
1458
|
-
renderComponent();
|
|
1467
|
+
await renderComponent();
|
|
1459
1468
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
1460
1469
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
1461
1470
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -1512,7 +1521,7 @@ export const Generic = () => {
|
|
|
1512
1521
|
|
|
1513
1522
|
it('should also update the corresponding message.reaction_groups with reaction.deleted', async () => {
|
|
1514
1523
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
1515
|
-
renderComponent();
|
|
1524
|
+
await renderComponent();
|
|
1516
1525
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
1517
1526
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
1518
1527
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -1569,7 +1578,7 @@ export const Generic = () => {
|
|
|
1569
1578
|
|
|
1570
1579
|
it('should add a member to DB when a new member is added to channel', async () => {
|
|
1571
1580
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
1572
|
-
renderComponent();
|
|
1581
|
+
await renderComponent();
|
|
1573
1582
|
|
|
1574
1583
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
1575
1584
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
@@ -1592,7 +1601,7 @@ export const Generic = () => {
|
|
|
1592
1601
|
it('should remove a member from DB when a member is removed from channel', async () => {
|
|
1593
1602
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
1594
1603
|
|
|
1595
|
-
renderComponent();
|
|
1604
|
+
await renderComponent();
|
|
1596
1605
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
1597
1606
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
1598
1607
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -1614,7 +1623,7 @@ export const Generic = () => {
|
|
|
1614
1623
|
it('should update the member in DB when a member of a channel is updated', async () => {
|
|
1615
1624
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
1616
1625
|
|
|
1617
|
-
renderComponent();
|
|
1626
|
+
await renderComponent();
|
|
1618
1627
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
1619
1628
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
1620
1629
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -1641,7 +1650,7 @@ export const Generic = () => {
|
|
|
1641
1650
|
it('should update the channel data in DB when a channel is updated', async () => {
|
|
1642
1651
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
1643
1652
|
|
|
1644
|
-
renderComponent();
|
|
1653
|
+
await renderComponent();
|
|
1645
1654
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
1646
1655
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
1647
1656
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -1667,7 +1676,7 @@ export const Generic = () => {
|
|
|
1667
1676
|
it('should update reads in DB when channel is read', async () => {
|
|
1668
1677
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
1669
1678
|
|
|
1670
|
-
renderComponent();
|
|
1679
|
+
await renderComponent();
|
|
1671
1680
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
1672
1681
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
1673
1682
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -1715,7 +1724,7 @@ export const Generic = () => {
|
|
|
1715
1724
|
it('should update reads in DB when a channel is marked as unread', async () => {
|
|
1716
1725
|
useMockedApis(chatClient, [queryChannelsApi(channels)]);
|
|
1717
1726
|
|
|
1718
|
-
renderComponent();
|
|
1727
|
+
await renderComponent();
|
|
1719
1728
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
1720
1729
|
await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true));
|
|
1721
1730
|
await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy());
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { Dimensions, Platform, View } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import type { EdgeInsets } from 'react-native-safe-area-context';
|
|
4
|
+
|
|
5
|
+
import { isMeasuredRectBogus, measureInWindow } from '../measureInWindow';
|
|
6
|
+
|
|
7
|
+
// `measureInWindow` is globally mocked in jest-setup so other suites don't hit native
|
|
8
|
+
// measurement; this suite exercises the real implementation.
|
|
9
|
+
jest.unmock('../measureInWindow');
|
|
10
|
+
|
|
11
|
+
// measureInWindow reads `isRN86OrGreater` as a live binding at call time, so mocking the constants
|
|
12
|
+
// module with a mutable flag lets us exercise both the "RN handles the inset" and "we compensate"
|
|
13
|
+
// branches without reimporting. (Flag is `mock`-prefixed as jest requires for values referenced
|
|
14
|
+
// inside a mock factory.)
|
|
15
|
+
let mockIsRN86OrGreater = false;
|
|
16
|
+
jest.mock('../../../../utils/react-native-constants', () => ({
|
|
17
|
+
__esModule: true,
|
|
18
|
+
get isRN86OrGreater() {
|
|
19
|
+
return mockIsRN86OrGreater;
|
|
20
|
+
},
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
// screen 400x800 => bogus threshold is |x| > 800 and |y| > 1600 (2x each)
|
|
24
|
+
const SCREEN = { fontScale: 1, height: 800, scale: 2, width: 400 };
|
|
25
|
+
const INSETS: EdgeInsets = { bottom: 10, left: 0, right: 0, top: 24 };
|
|
26
|
+
|
|
27
|
+
const setPlatform = (os: typeof Platform.OS) => {
|
|
28
|
+
Object.defineProperty(Platform, 'OS', { configurable: true, get: () => os });
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type MeasureInWindowTuple = [number, number, number, number];
|
|
32
|
+
type MeasureTuple = [number, number, number, number, number, number];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Builds a fake native handle whose `measureInWindow`/`measure` callbacks fire synchronously
|
|
36
|
+
* (as Fabric does). Omitting `measure` produces a handle without the method, exercising the
|
|
37
|
+
* "no fallback available" branch.
|
|
38
|
+
*/
|
|
39
|
+
const makeNode = ({
|
|
40
|
+
measure,
|
|
41
|
+
measureInWindow: miw,
|
|
42
|
+
}: {
|
|
43
|
+
measure?: MeasureTuple;
|
|
44
|
+
measureInWindow: MeasureInWindowTuple;
|
|
45
|
+
}): { current: View | null } => {
|
|
46
|
+
const handle: Record<string, unknown> = {
|
|
47
|
+
measureInWindow: (cb: (...args: MeasureInWindowTuple) => void) => cb(...miw),
|
|
48
|
+
};
|
|
49
|
+
if (measure) {
|
|
50
|
+
handle.measure = (cb: (...args: MeasureTuple) => void) => cb(...measure);
|
|
51
|
+
}
|
|
52
|
+
return { current: handle as unknown as View };
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
describe('isMeasuredRectBogus', () => {
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
jest.spyOn(Dimensions, 'get').mockReturnValue(SCREEN);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
jest.restoreAllMocks();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('accepts a normal on-screen rect', () => {
|
|
65
|
+
expect(isMeasuredRectBogus(50, 300, 200, 40)).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('accepts a rect within the 2x margin (e.g. partially off-screen)', () => {
|
|
69
|
+
expect(isMeasuredRectBogus(50, 1500, 200, 40)).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it.each([
|
|
73
|
+
['NaN x', [NaN, 10, 20, 20]],
|
|
74
|
+
['NaN y', [10, NaN, 20, 20]],
|
|
75
|
+
['Infinity x', [Infinity, 10, 20, 20]],
|
|
76
|
+
['-Infinity y', [10, -Infinity, 20, 20]],
|
|
77
|
+
['NaN width', [10, 10, NaN, 20]],
|
|
78
|
+
['Infinity height', [10, 10, 20, Infinity]],
|
|
79
|
+
] as [string, MeasureInWindowTuple][])('flags non-finite values: %s', (_label, [x, y, w, h]) => {
|
|
80
|
+
expect(isMeasuredRectBogus(x, y, w, h)).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it.each([
|
|
84
|
+
['zero width', [10, 10, 0, 40]],
|
|
85
|
+
['zero height', [10, 10, 40, 0]],
|
|
86
|
+
['negative width', [10, 10, -5, 40]],
|
|
87
|
+
['negative height', [10, 10, 40, -5]],
|
|
88
|
+
] as [string, MeasureInWindowTuple][])(
|
|
89
|
+
'flags non-positive dimensions: %s',
|
|
90
|
+
(_label, [x, y, w, h]) => {
|
|
91
|
+
expect(isMeasuredRectBogus(x, y, w, h)).toBe(true);
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
it.each([
|
|
96
|
+
['huge y (the FLAG_LAYOUT_NO_LIMITS failure)', [50, 29000, 40, 36]],
|
|
97
|
+
['huge x', [29000, 300, 40, 36]],
|
|
98
|
+
['huge negative x', [-2000, 300, 40, 36]],
|
|
99
|
+
['huge negative y', [50, -2000, 40, 36]],
|
|
100
|
+
] as [string, MeasureInWindowTuple][])(
|
|
101
|
+
'flags coordinates beyond 2x the screen: %s',
|
|
102
|
+
(_label, [x, y, w, h]) => {
|
|
103
|
+
expect(isMeasuredRectBogus(x, y, w, h)).toBe(true);
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
it('treats the exact 2x boundary as valid and just past it as bogus', () => {
|
|
108
|
+
// bounds are 800 (x) and 1600 (y); the check uses strict `>`
|
|
109
|
+
expect(isMeasuredRectBogus(800, 1600, 40, 36)).toBe(false);
|
|
110
|
+
expect(isMeasuredRectBogus(801, 300, 40, 36)).toBe(true);
|
|
111
|
+
expect(isMeasuredRectBogus(50, 1601, 40, 36)).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('trusts the measurement when screen dimensions are unavailable', () => {
|
|
115
|
+
jest.spyOn(Dimensions, 'get').mockReturnValue({ ...SCREEN, height: 0, width: 0 });
|
|
116
|
+
expect(isMeasuredRectBogus(99999, 99999, 40, 36)).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('measureInWindow', () => {
|
|
121
|
+
const originalOS = Platform.OS;
|
|
122
|
+
|
|
123
|
+
beforeEach(() => {
|
|
124
|
+
jest.spyOn(Dimensions, 'get').mockReturnValue(SCREEN);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
afterEach(() => {
|
|
128
|
+
jest.restoreAllMocks();
|
|
129
|
+
setPlatform(originalOS);
|
|
130
|
+
mockIsRN86OrGreater = false;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('rejects when the node is not mounted', async () => {
|
|
134
|
+
await expect(measureInWindow({ current: null }, INSETS)).rejects.toThrow(/native handle/);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('healthy measurement (primary path)', () => {
|
|
138
|
+
it('resolves with the window rect unchanged on iOS', async () => {
|
|
139
|
+
setPlatform('ios');
|
|
140
|
+
const node = makeNode({ measureInWindow: [10, 300, 200, 40] });
|
|
141
|
+
await expect(measureInWindow(node, INSETS)).resolves.toEqual({
|
|
142
|
+
h: 40,
|
|
143
|
+
w: 200,
|
|
144
|
+
x: 10,
|
|
145
|
+
y: 300,
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('compensates by insets.top on Android below RN 0.86', async () => {
|
|
150
|
+
setPlatform('android');
|
|
151
|
+
mockIsRN86OrGreater = false;
|
|
152
|
+
const node = makeNode({ measureInWindow: [10, 300, 200, 40] });
|
|
153
|
+
await expect(measureInWindow(node, INSETS)).resolves.toEqual({
|
|
154
|
+
h: 40,
|
|
155
|
+
w: 200,
|
|
156
|
+
x: 10,
|
|
157
|
+
y: 324,
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('does not compensate on Android from RN 0.86 onward (RN corrects the inset natively)', async () => {
|
|
162
|
+
setPlatform('android');
|
|
163
|
+
mockIsRN86OrGreater = true;
|
|
164
|
+
const node = makeNode({ measureInWindow: [10, 300, 200, 40] });
|
|
165
|
+
await expect(measureInWindow(node, INSETS)).resolves.toEqual({
|
|
166
|
+
h: 40,
|
|
167
|
+
w: 200,
|
|
168
|
+
x: 10,
|
|
169
|
+
y: 300,
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('bogus measurement (measure() fallback)', () => {
|
|
175
|
+
it('falls back to root-relative pageX/pageY', async () => {
|
|
176
|
+
setPlatform('ios');
|
|
177
|
+
const node = makeNode({
|
|
178
|
+
measure: [0, 0, 64, 36, 12, 717],
|
|
179
|
+
measureInWindow: [28903, 29088, 64, 36],
|
|
180
|
+
});
|
|
181
|
+
await expect(measureInWindow(node, INSETS)).resolves.toEqual({ h: 36, w: 64, x: 12, y: 717 });
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('does not add insets.top to the fallback, since pageY already accounts for it', async () => {
|
|
185
|
+
setPlatform('android');
|
|
186
|
+
const node = makeNode({
|
|
187
|
+
measure: [0, 0, 64, 36, 12, 717],
|
|
188
|
+
measureInWindow: [28903, 29088, 64, 36],
|
|
189
|
+
});
|
|
190
|
+
await expect(measureInWindow(node, INSETS)).resolves.toEqual({ h: 36, w: 64, x: 12, y: 717 });
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('returns the compensated window rect when no measure() fallback is available (RN < 0.86)', async () => {
|
|
194
|
+
setPlatform('android');
|
|
195
|
+
mockIsRN86OrGreater = false;
|
|
196
|
+
const node = makeNode({ measureInWindow: [28903, 29088, 64, 36] });
|
|
197
|
+
await expect(measureInWindow(node, INSETS)).resolves.toEqual({
|
|
198
|
+
h: 36,
|
|
199
|
+
w: 64,
|
|
200
|
+
x: 28903,
|
|
201
|
+
y: 29112,
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('returns the uncompensated window rect when no measure() fallback is available (RN >= 0.86)', async () => {
|
|
206
|
+
setPlatform('android');
|
|
207
|
+
mockIsRN86OrGreater = true;
|
|
208
|
+
const node = makeNode({ measureInWindow: [28903, 29088, 64, 36] });
|
|
209
|
+
await expect(measureInWindow(node, INSETS)).resolves.toEqual({
|
|
210
|
+
h: 36,
|
|
211
|
+
w: 64,
|
|
212
|
+
x: 28903,
|
|
213
|
+
y: 29088,
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
});
|
|
@@ -1,20 +1,83 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Platform, View } from 'react-native';
|
|
2
|
+
import { Dimensions, Platform, View } from 'react-native';
|
|
3
3
|
import { EdgeInsets } from 'react-native-safe-area-context';
|
|
4
4
|
|
|
5
|
+
import { isRN86OrGreater } from '../../../utils/react-native-constants';
|
|
6
|
+
|
|
7
|
+
type MeasuredRect = { x: number; y: number; w: number; h: number };
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* On Android, `measureInWindow` historically did not account for the top inset, so we added it
|
|
11
|
+
* ourselves. React Native 0.86 fixed this upstream as part of its edge-to-edge rework and now
|
|
12
|
+
* returns topInset corrected window coordinates. On >= 0.86 adding it again double counts the
|
|
13
|
+
* inset and offsets every measured view down by `insets.top`, so we only compensate on Android
|
|
14
|
+
* running React Native below 0.86.
|
|
15
|
+
*/
|
|
16
|
+
const androidTopInsetCompensation = (insets: EdgeInsets): number =>
|
|
17
|
+
Platform.OS === 'android' && !isRN86OrGreater ? insets.top : 0;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* How many screen lengths away from the origin a measured coordinate may fall before we treat it as
|
|
21
|
+
* bogus. Real targets (a view currently on screen) always measure well within one screen and the failure
|
|
22
|
+
* mode we guard against is off by tens of screens, so a generous multiplier cleanly separates the two
|
|
23
|
+
* with no risk of false positives on legitimate values.
|
|
24
|
+
*/
|
|
25
|
+
const SCREEN_BOUND_MULTIPLIER = 2;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* `measureInWindow` can return wildly out of bounds coordinates on Android when the native window has
|
|
29
|
+
* been forced edge-to-edge behind React Native's back (i.e a library setting `FLAG_LAYOUT_NO_LIMITS`,
|
|
30
|
+
* as `react-native-system-navigation-bar` does for a transparent nav bar) while RN's own edge-to-edge
|
|
31
|
+
* (`edgeToEdgeEnabled`) is off. In that state the window relative measurement is corrupted and would
|
|
32
|
+
* return a position of the View far offscreen.
|
|
33
|
+
*/
|
|
34
|
+
export const isMeasuredRectBogus = (x: number, y: number, w: number, h: number): boolean => {
|
|
35
|
+
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(w) || !Number.isFinite(h)) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
if (w <= 0 || h <= 0) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
const { width, height } = Dimensions.get('screen');
|
|
42
|
+
if (width <= 0 || height <= 0) {
|
|
43
|
+
// can't reason about bounds, trust the measurement
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return (
|
|
47
|
+
Math.abs(x) > width * SCREEN_BOUND_MULTIPLIER || Math.abs(y) > height * SCREEN_BOUND_MULTIPLIER
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
5
51
|
export const measureInWindow = (
|
|
6
52
|
node: React.RefObject<View | null>,
|
|
7
53
|
insets: EdgeInsets,
|
|
8
|
-
): Promise<
|
|
54
|
+
): Promise<MeasuredRect> => {
|
|
9
55
|
return new Promise((resolve, reject) => {
|
|
10
56
|
const handle = node.current;
|
|
11
|
-
if (!handle)
|
|
57
|
+
if (!handle) {
|
|
12
58
|
return reject(
|
|
13
59
|
new Error('The native handle could not be found while invoking measureInWindow.'),
|
|
14
60
|
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
handle.measureInWindow((x, y, w, h) => {
|
|
64
|
+
if (!isMeasuredRectBogus(x, y, w, h)) {
|
|
65
|
+
resolve({ h, w, x, y: y + androidTopInsetCompensation(insets) });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// If `measureInWindow` returned an out of bounds rect, fallback to `measure()`, whose
|
|
70
|
+
// `pageX`/`pageY` are relative to the app root and are the same coordinate space as the window.
|
|
71
|
+
// They will stays correct when the window frame has been mutated out from under
|
|
72
|
+
// React Native.
|
|
73
|
+
if (typeof handle.measure !== 'function') {
|
|
74
|
+
resolve({ h, w, x, y: y + androidTopInsetCompensation(insets) });
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
15
77
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
78
|
+
handle.measure((_x, _y, width, height, pageX, pageY) => {
|
|
79
|
+
resolve({ h: height, w: width, x: pageX, y: pageY });
|
|
80
|
+
});
|
|
81
|
+
});
|
|
19
82
|
});
|
|
20
83
|
};
|