@applicaster/zapp-react-native-ui-components 14.0.0-rc.9 → 15.0.0-rc.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 (163) hide show
  1. package/Components/AnimatedInOut/index.tsx +5 -3
  2. package/Components/AudioPlayer/index.tsx +15 -0
  3. package/Components/AudioPlayer/mobile/Layout.tsx +66 -0
  4. package/Components/AudioPlayer/{__tests__/__snapshots__/audioPlayer.test.js.snap → mobile/__tests__/__snapshots__/audioPlayerMobileLayout.test.js.snap} +2 -2
  5. package/Components/AudioPlayer/mobile/__tests__/audioPlayerMobileLayout.test.js +18 -0
  6. package/Components/AudioPlayer/mobile/index.tsx +18 -0
  7. package/Components/AudioPlayer/{Artwork.tsx → tv/Artwork.tsx} +3 -2
  8. package/Components/AudioPlayer/{Channel.tsx → tv/Channel.tsx} +7 -7
  9. package/Components/AudioPlayer/tv/Layout.tsx +168 -0
  10. package/Components/AudioPlayer/{Runtime.tsx → tv/Runtime.tsx} +7 -1
  11. package/Components/AudioPlayer/{Summary.tsx → tv/Summary.tsx} +6 -2
  12. package/Components/AudioPlayer/{Title.tsx → tv/Title.tsx} +6 -2
  13. package/Components/AudioPlayer/{__tests__ → tv/__tests__}/__snapshots__/Runtime.test.js.snap +2 -2
  14. package/Components/AudioPlayer/tv/__tests__/__snapshots__/audioPlayer.test.js.snap +164 -0
  15. package/Components/AudioPlayer/tv/__tests__/__snapshots__/channel.test.js.snap +19 -0
  16. package/Components/AudioPlayer/{__tests__ → tv/__tests__}/__snapshots__/summary.test.js.snap +1 -2
  17. package/Components/AudioPlayer/{__tests__ → tv/__tests__}/__snapshots__/title.test.js.snap +1 -2
  18. package/Components/AudioPlayer/{__tests__ → tv/__tests__}/audioPlayer.test.js +7 -3
  19. package/Components/AudioPlayer/{helpers.tsx → tv/helpers.tsx} +11 -5
  20. package/Components/AudioPlayer/{AudioPlayer.tsx → tv/index.tsx} +17 -58
  21. package/Components/AudioPlayer/types.ts +40 -0
  22. package/Components/BaseFocusable/index.tsx +23 -12
  23. package/Components/Cell/Cell.tsx +91 -64
  24. package/Components/Cell/CellWithFocusable.tsx +3 -0
  25. package/Components/Cell/__tests__/CellWIthFocusable.test.js +3 -2
  26. package/Components/Cell/index.js +7 -3
  27. package/Components/ComponentResolver/index.ts +1 -1
  28. package/Components/FeedLoader/FeedLoader.tsx +7 -16
  29. package/Components/FeedLoader/FeedLoaderHOC.tsx +21 -0
  30. package/Components/FeedLoader/index.js +2 -8
  31. package/Components/Focusable/Focusable.tsx +12 -3
  32. package/Components/Focusable/FocusableTvOS.tsx +5 -5
  33. package/Components/Focusable/FocusableiOS.tsx +2 -2
  34. package/Components/Focusable/Touchable.tsx +5 -3
  35. package/Components/Focusable/__tests__/index.android.test.tsx +3 -0
  36. package/Components/Focusable/index.android.tsx +19 -11
  37. package/Components/Focusable/index.tsx +1 -1
  38. package/Components/FocusableGroup/FocusableTvOS.tsx +1 -1
  39. package/Components/FocusableList/FocusableItem.tsx +4 -3
  40. package/Components/FocusableList/FocusableListItemWrapper.tsx +2 -1
  41. package/Components/FocusableList/hooks/useCellState.android.ts +13 -3
  42. package/Components/FocusableList/index.tsx +20 -9
  43. package/Components/FreezeWithCallback/__tests__/index.test.tsx +67 -43
  44. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +42 -59
  45. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +13 -10
  46. package/Components/HandlePlayable/HandlePlayable.tsx +25 -9
  47. package/Components/HookRenderer/HookRenderer.tsx +5 -1
  48. package/Components/Layout/TV/LayoutBackground.tsx +1 -1
  49. package/Components/Layout/TV/__tests__/index.test.tsx +0 -1
  50. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +6 -2
  51. package/Components/MasterCell/DefaultComponents/Button.tsx +1 -1
  52. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -39
  53. package/Components/MasterCell/DefaultComponents/Image/hoc/withDimensions.tsx +1 -1
  54. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +1 -1
  55. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +65 -17
  56. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/Image.test.tsx +21 -3
  57. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +6 -3
  58. package/Components/MasterCell/DefaultComponents/Text/index.tsx +26 -6
  59. package/Components/MasterCell/DefaultComponents/__tests__/image.test.js +10 -10
  60. package/Components/MasterCell/DefaultComponents/__tests__/text.test.tsx +18 -18
  61. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/__tests__/index.test.tsx +10 -10
  62. package/Components/MasterCell/elementMapper.tsx +1 -2
  63. package/Components/MasterCell/index.tsx +1 -1
  64. package/Components/MasterCell/utils/behaviorProvider.ts +82 -14
  65. package/Components/MasterCell/utils/index.ts +11 -5
  66. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +13 -18
  67. package/Components/OfflineHandler/__tests__/__snapshots__/index.test.tsx.snap +9 -0
  68. package/Components/OfflineHandler/__tests__/index.test.tsx +26 -35
  69. package/Components/PlayerContainer/ErrorDisplay/index.ts +1 -1
  70. package/Components/PlayerContainer/PlayerContainer.tsx +46 -33
  71. package/Components/PlayerContainer/ProgramInfo/index.tsx +1 -1
  72. package/Components/PlayerContainer/index.ts +1 -1
  73. package/Components/PlayerImageBackground/index.tsx +1 -1
  74. package/Components/River/ComponentsMap/ComponentsMap.tsx +0 -1
  75. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +378 -0
  76. package/Components/River/ComponentsMap/hooks/useLoadingState.ts +2 -2
  77. package/Components/River/RefreshControl.tsx +11 -17
  78. package/Components/River/RiverItem.tsx +3 -0
  79. package/Components/River/TV/River.tsx +2 -17
  80. package/Components/River/TV/index.tsx +3 -1
  81. package/Components/River/TV/withPipesV1DataLoader.tsx +43 -0
  82. package/Components/River/TV/withRiverDataLoader.tsx +17 -0
  83. package/Components/River/TV/withTVEventHandler.tsx +1 -1
  84. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +2 -0
  85. package/Components/River/__tests__/river.test.js +12 -26
  86. package/Components/River/index.tsx +1 -1
  87. package/Components/Screen/__tests__/Screen.test.tsx +28 -29
  88. package/Components/Screen/__tests__/navigationHandler.test.ts +133 -22
  89. package/Components/Screen/navigationHandler.ts +20 -2
  90. package/Components/ScreenResolver/index.tsx +15 -0
  91. package/Components/ScreenRevealManager/ScreenRevealManager.ts +76 -0
  92. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +107 -0
  93. package/Components/ScreenRevealManager/__tests__/withScreenRevealManager.test.tsx +96 -0
  94. package/Components/ScreenRevealManager/index.ts +1 -0
  95. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +79 -0
  96. package/Components/Tabs/TV/Tabs.android.tsx +1 -3
  97. package/Components/Tabs/Tabs.tsx +2 -3
  98. package/Components/TextInputTv/__tests__/__snapshots__/TextInputTv.test.js.snap +13 -0
  99. package/Components/TextInputTv/index.tsx +11 -0
  100. package/Components/Touchable/__tests__/__snapshots__/touchable.test.tsx.snap +34 -0
  101. package/Components/Touchable/__tests__/touchable.test.tsx +12 -17
  102. package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +15 -9
  103. package/Components/VideoLive/animationUtils.ts +3 -3
  104. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +3 -9
  105. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.web.tsx +294 -0
  106. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.web.tsx +93 -0
  107. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +73 -29
  108. package/Components/VideoModal/PlayerDetails.tsx +24 -2
  109. package/Components/VideoModal/PlayerWrapper.tsx +26 -142
  110. package/Components/VideoModal/VideoModal.tsx +3 -17
  111. package/Components/VideoModal/__tests__/PlayerDetails.test.tsx +5 -5
  112. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -7
  113. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +44 -240
  114. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +9 -1
  115. package/Components/VideoModal/hooks/index.ts +0 -2
  116. package/Components/VideoModal/hooks/useDelayedPlayerDetails.ts +40 -15
  117. package/Components/VideoModal/hooks/useModalSize.ts +18 -2
  118. package/Components/VideoModal/hooks/utils/__tests__/showDetails.test.ts +2 -2
  119. package/Components/VideoModal/hooks/utils/index.ts +4 -0
  120. package/Components/VideoModal/utils.ts +6 -0
  121. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +12 -16
  122. package/Components/Viewport/ViewportTracker/__tests__/viewportTracker.test.js +84 -24
  123. package/Components/Viewport/VisibilitySensor/VisibilitySensor.tsx +3 -3
  124. package/Components/default-cell-renderer/viewTrees/tv/DefaultCell/index.ts +3 -3
  125. package/Contexts/CellFocusedStateContext/index.tsx +27 -0
  126. package/Contexts/ConfigutaionContext/__tests__/ConfigurationProvider.test.tsx +3 -3
  127. package/Contexts/ScreenContext/index.tsx +46 -6
  128. package/Decorators/ConfigurationWrapper/__tests__/withConfigurationProvider.test.tsx +3 -3
  129. package/Decorators/ConfigurationWrapper/withConfigurationProvider.tsx +2 -2
  130. package/Decorators/RiverFeedLoader/__tests__/__snapshots__/riverFeedLoader.test.tsx.snap +221 -209
  131. package/Decorators/RiverFeedLoader/__tests__/riverFeedLoader.test.tsx +14 -16
  132. package/Decorators/RiverFeedLoader/__tests__/utils.test.ts +0 -20
  133. package/Decorators/RiverFeedLoader/index.tsx +22 -4
  134. package/Decorators/RiverFeedLoader/utils/index.ts +0 -18
  135. package/Decorators/RiverResolver/__tests__/riverResolver.test.tsx +3 -6
  136. package/Decorators/ZappPipesDataConnector/ResolverSelector.tsx +25 -0
  137. package/Decorators/ZappPipesDataConnector/__tests__/NullFeedResolver.test.tsx +78 -0
  138. package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +205 -0
  139. package/Decorators/ZappPipesDataConnector/__tests__/StaticFeedResolver.test.tsx +251 -0
  140. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +368 -0
  141. package/Decorators/ZappPipesDataConnector/__tests__/utils.test.ts +39 -0
  142. package/Decorators/ZappPipesDataConnector/index.tsx +26 -293
  143. package/Decorators/ZappPipesDataConnector/resolvers/NullFeedResolver.tsx +25 -0
  144. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +87 -0
  145. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +266 -0
  146. package/Decorators/ZappPipesDataConnector/types.ts +29 -0
  147. package/Decorators/ZappPipesDataConnector/utils/mongoFilter.ts +738 -0
  148. package/Decorators/ZappPipesDataConnector/utils/useFilter.tsx +157 -0
  149. package/events/index.ts +3 -0
  150. package/package.json +5 -10
  151. package/Components/AudioPlayer/AudioPlayerLayout.tsx +0 -202
  152. package/Components/AudioPlayer/__tests__/__snapshots__/audioPlayerLayout.test.js.snap +0 -66
  153. package/Components/AudioPlayer/__tests__/__snapshots__/channel.test.js.snap +0 -28
  154. package/Components/AudioPlayer/__tests__/audioPlayerLayout.test.js +0 -26
  155. package/Components/AudioPlayer/index.ts +0 -1
  156. package/Components/River/__tests__/__snapshots__/river.test.js.snap +0 -27
  157. package/Components/VideoModal/hooks/useBackgroundColor.ts +0 -10
  158. /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/Runtime.test.js +0 -0
  159. /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/__snapshots__/artWork.test.js.snap +0 -0
  160. /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/artWork.test.js +0 -0
  161. /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/channel.test.js +0 -0
  162. /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/summary.test.js +0 -0
  163. /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/title.test.js +0 -0
@@ -1,7 +1,6 @@
1
1
  import * as React from "react";
2
2
  import { useEffect, useReducer } from "react";
3
- // @ts-ignore
4
- import { TVMenuControl, View, ViewStyle } from "react-native";
3
+ import { View, ViewStyle } from "react-native";
5
4
  import * as R from "ramda";
6
5
  import uuid from "uuid/v4";
7
6
  import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager";
@@ -62,6 +61,11 @@ import {
62
61
  useModalAnimationContext,
63
62
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
64
63
 
64
+ import {
65
+ PlayerNativeCommandTypes,
66
+ PlayerNativeSendCommand,
67
+ } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/playerNativeCommand";
68
+
65
69
  type Props = {
66
70
  Player: React.ComponentType<any>;
67
71
  PlayerLoadingView?: React.ComponentType<any>; // 👀 we are not receiving this prop
@@ -88,7 +92,7 @@ export const VideoModalMode = {
88
92
  MAXIMIZED: "MAXIMIZED",
89
93
  MINIMIZED: "MINIMIZED",
90
94
  FULLSCREEN: "FULLSCREEN",
91
- };
95
+ } as const;
92
96
 
93
97
  export type PlayNextData = {
94
98
  state: PlayNextState;
@@ -127,7 +131,7 @@ const webStyles = {
127
131
  playerScreen: {
128
132
  flex: 1,
129
133
  height: "100vh",
130
- background: "black",
134
+ backgroundColor: "black",
131
135
  },
132
136
  playerWrapper: {
133
137
  height: "100%",
@@ -135,9 +139,6 @@ const webStyles = {
135
139
  },
136
140
  inlineRiver: {
137
141
  height: INLINE_CONTAINER_CONTENT_HEIGHT,
138
-
139
- borderWidth: 4,
140
- borderColor: "yellow",
141
142
  },
142
143
  };
143
144
 
@@ -148,7 +149,6 @@ const nativeStyles = {
148
149
  },
149
150
  playerScreen: {
150
151
  flex: 1,
151
- backgroundColor: "black",
152
152
  overflow: "hidden",
153
153
  },
154
154
  playerWrapper: {
@@ -263,9 +263,15 @@ const PlayerContainerComponent = (props: Props) => {
263
263
  return;
264
264
  }
265
265
 
266
+ // send command to clear and stop player
267
+ PlayerNativeSendCommand(
268
+ PlayerNativeCommandTypes.clearPlayerData,
269
+ state.playerId
270
+ );
271
+
266
272
  showNavBar(true);
267
273
  navigator.goBack();
268
- }, [isModal, navigator.goBack, showNavBar]);
274
+ }, [isModal, navigator.goBack, state.playerId, showNavBar]);
269
275
 
270
276
  const playEntry = (entry) => navigator.replaceTop(entry, { mode });
271
277
 
@@ -393,13 +399,17 @@ const PlayerContainerComponent = (props: Props) => {
393
399
  }
394
400
  };
395
401
 
396
- const playerRemoteHandler = (event, isLanguageOverlayVisible) => {
397
- const { eventType } = event;
402
+ const playerRemoteHandler = React.useCallback(
403
+ (isLanguageOverlayVisible = false) =>
404
+ (event) => {
405
+ const { eventType } = event;
398
406
 
399
- if (!isLanguageOverlayVisible && eventType === "menu") {
400
- close();
401
- }
402
- };
407
+ if (!isLanguageOverlayVisible && eventType === "menu") {
408
+ close();
409
+ }
410
+ },
411
+ [close]
412
+ );
403
413
 
404
414
  // Effects
405
415
  useEffect(() => {
@@ -413,7 +423,10 @@ const PlayerContainerComponent = (props: Props) => {
413
423
  id: playerContainerId,
414
424
  listener: {
415
425
  onVideoEnd,
416
- onError,
426
+ onError: (err: Error) => {
427
+ // Adapt Error to the expected shape for onError
428
+ onError({ error: err });
429
+ },
417
430
  onLoad,
418
431
  onPlayerClose: close,
419
432
  onPlayerDetached: close,
@@ -512,16 +525,6 @@ const PlayerContainerComponent = (props: Props) => {
512
525
  }
513
526
  }, [isAudioContent]);
514
527
 
515
- // Needs to handle back button on Apple TV
516
- // https://github.com/facebook/react-native/issues/18930
517
- useEffect(() => {
518
- TVMenuControl?.enableTVMenuKey();
519
-
520
- return () => {
521
- TVMenuControl?.disableTVMenuKey();
522
- };
523
- }, []);
524
-
525
528
  useEffect(() => {
526
529
  playerEvent("source_changed", { item });
527
530
 
@@ -568,8 +571,9 @@ const PlayerContainerComponent = (props: Props) => {
568
571
  const isInlineTV = isInlineTVUtil(screenData);
569
572
 
570
573
  const inline =
571
- [VideoModalMode.MAXIMIZED, VideoModalMode.MINIMIZED].includes(mode) ||
572
- isInlineTV;
574
+ [VideoModalMode.MAXIMIZED, VideoModalMode.MINIMIZED].includes(
575
+ mode as any
576
+ ) || isInlineTV;
573
577
 
574
578
  const value = React.useMemo(
575
579
  () => ({ playerId: state.playerId }),
@@ -590,7 +594,11 @@ const PlayerContainerComponent = (props: Props) => {
590
594
  );
591
595
  }
592
596
 
593
- if (screen_background_color && mode !== VideoModalMode.FULLSCREEN) {
597
+ if (
598
+ screen_background_color &&
599
+ mode !== VideoModalMode.FULLSCREEN &&
600
+ isTV()
601
+ ) {
594
602
  updatedStyles.playerScreen.backgroundColor = screen_background_color;
595
603
  }
596
604
 
@@ -620,6 +628,8 @@ const PlayerContainerComponent = (props: Props) => {
620
628
  playNextData,
621
629
  };
622
630
 
631
+ const pointerEventsProp = mode === "MINIMIZED" ? "box-none" : "auto";
632
+
623
633
  return (
624
634
  <PlayerStateContext.Provider value={value}>
625
635
  <PlayerContainerContextProvider
@@ -630,9 +640,9 @@ const PlayerContainerComponent = (props: Props) => {
630
640
  <PlayerContainerContext.Consumer>
631
641
  {(context) => (
632
642
  <TVEventHandlerComponent
633
- tvEventHandler={(_component, event) =>
634
- playerRemoteHandler(event, context.isLanguageOverlayVisible)
635
- }
643
+ tvEventHandler={playerRemoteHandler(
644
+ context.isLanguageOverlayVisible
645
+ )}
636
646
  >
637
647
  <FocusableGroup
638
648
  id={FocusableGroupMainContainerId}
@@ -640,14 +650,17 @@ const PlayerContainerComponent = (props: Props) => {
640
650
  preferredFocus
641
651
  shouldUsePreferredFocus
642
652
  groupId={groupId}
653
+ pointerEvents={pointerEventsProp}
643
654
  >
644
655
  {/* Video player and components */}
645
656
  <View
646
657
  style={styles.playerScreen}
647
658
  testID={"player-screen-container"}
659
+ pointerEvents={pointerEventsProp}
648
660
  >
649
661
  {/* Player container */}
650
662
  <View
663
+ pointerEvents={pointerEventsProp}
651
664
  style={[
652
665
  styles.playerWrapper,
653
666
  // eslint-disable-next-line react-native/no-inline-styles, react-native/no-color-literals
@@ -719,7 +732,7 @@ const PlayerContainerComponent = (props: Props) => {
719
732
  key={item.id}
720
733
  groupId={FocusableGroupMainContainerId}
721
734
  cellTapAction={onCellTap}
722
- extraAnchorPointYOffset={-600}
735
+ extraAnchorPointYOffset={0}
723
736
  isScreenWrappedInContainer={true}
724
737
  containerHeight={styles.inlineRiver.height}
725
738
  componentsMapExtraProps={{
@@ -1,4 +1,4 @@
1
- import { connectToStore } from "@applicaster/zapp-react-native-redux";
1
+ import { connectToStore } from "@applicaster/zapp-react-native-redux/utils/connectToStore";
2
2
  import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
3
3
  import { styleKeys } from "@applicaster/zapp-react-native-utils/styleKeysUtils";
4
4
  import { transformColorCode as fixColorHexCode } from "@applicaster/zapp-react-native-utils/transform";
@@ -1,6 +1,6 @@
1
1
  import * as R from "ramda";
2
2
 
3
- import { connectToStore } from "@applicaster/zapp-react-native-redux";
3
+ import { connectToStore } from "@applicaster/zapp-react-native-redux/utils/connectToStore";
4
4
  import { loadPipesData } from "@applicaster/zapp-react-native-redux/ZappPipes";
5
5
 
6
6
  import { PlayerContainer as PlayerContainerComponent } from "./PlayerContainer";
@@ -28,7 +28,7 @@ const PlayerImageBackgroundComponent = ({
28
28
  defaultImageDimensions,
29
29
  }: Props) => {
30
30
  const source = React.useMemo(
31
- () => ({ uri: imageSrcFromMediaItem(entry, imageKey) }),
31
+ () => ({ uri: imageSrcFromMediaItem(entry, [imageKey]) }),
32
32
  [imageKey, entry]
33
33
  );
34
34
 
@@ -286,7 +286,6 @@ function ComponentsMapComponent(props: Props) {
286
286
  initialNumToRender={3}
287
287
  maxToRenderPerBatch={10}
288
288
  windowSize={12}
289
- listKey={riverId}
290
289
  keyExtractor={keyExtractor}
291
290
  renderItem={renderRiverItem}
292
291
  data={riverComponents}
@@ -0,0 +1,378 @@
1
+ import { renderHook, act } from "@testing-library/react-hooks";
2
+ import { BehaviorSubject } from "rxjs";
3
+ import { useLoadingState } from "../useLoadingState";
4
+
5
+ jest.mock(
6
+ "@applicaster/zapp-react-native-utils/reactHooks/state/useRefWithInitialValue",
7
+ () => ({
8
+ useRefWithInitialValue: jest.fn((initializer) => ({
9
+ current: initializer(),
10
+ })),
11
+ })
12
+ );
13
+
14
+ describe("useLoadingState", () => {
15
+ let onLoadDone: jest.Mock;
16
+
17
+ beforeEach(() => {
18
+ onLoadDone = jest.fn();
19
+ jest.clearAllMocks();
20
+ });
21
+
22
+ describe("initialization", () => {
23
+ it("should initialize with correct default state for zero components", () => {
24
+ const { result } = renderHook(() => useLoadingState(0, onLoadDone));
25
+
26
+ const initialState = result.current.loadingState.getValue();
27
+
28
+ expect(initialState).toEqual({
29
+ index: -1,
30
+ done: true,
31
+ waitForAllComponents: false,
32
+ });
33
+
34
+ expect(result.current.shouldShowLoadingError).toBe(false);
35
+ });
36
+
37
+ it("should initialize with correct default state for multiple components", () => {
38
+ const { result } = renderHook(() => useLoadingState(3, onLoadDone));
39
+
40
+ const initialState = result.current.loadingState.getValue();
41
+
42
+ expect(initialState).toEqual({
43
+ index: -1,
44
+ done: false,
45
+ waitForAllComponents: false,
46
+ });
47
+
48
+ expect(result.current.shouldShowLoadingError).toBe(false);
49
+ });
50
+
51
+ it("should return a BehaviorSubject for loadingState", () => {
52
+ const { result } = renderHook(() => useLoadingState(3, onLoadDone));
53
+
54
+ expect(result.current.loadingState).toBeInstanceOf(BehaviorSubject);
55
+ });
56
+ });
57
+
58
+ describe("arePreviousComponentsLoaded", () => {
59
+ it("should return true for index 0 (first component)", () => {
60
+ const { result } = renderHook(() => useLoadingState(3, onLoadDone));
61
+
62
+ expect(result.current.arePreviousComponentsLoaded(0)).toBe(true);
63
+ });
64
+
65
+ it("should return false when previous components are not loaded", () => {
66
+ const { result } = renderHook(() => useLoadingState(3, onLoadDone));
67
+
68
+ expect(result.current.arePreviousComponentsLoaded(1)).toBe(false);
69
+ expect(result.current.arePreviousComponentsLoaded(2)).toBe(false);
70
+ });
71
+
72
+ it("should return true when all previous components are loaded", () => {
73
+ const { result } = renderHook(() => useLoadingState(3, onLoadDone));
74
+
75
+ act(() => {
76
+ result.current.onLoadFinished(0);
77
+ });
78
+
79
+ expect(result.current.arePreviousComponentsLoaded(1)).toBe(true);
80
+ expect(result.current.arePreviousComponentsLoaded(2)).toBe(false);
81
+
82
+ act(() => {
83
+ result.current.onLoadFinished(1);
84
+ });
85
+
86
+ expect(result.current.arePreviousComponentsLoaded(2)).toBe(true);
87
+ });
88
+ });
89
+
90
+ describe("onLoadFinished", () => {
91
+ it("should update component state and loading state when component finishes loading", () => {
92
+ const { result } = renderHook(() => useLoadingState(3, onLoadDone));
93
+
94
+ act(() => {
95
+ result.current.onLoadFinished(0);
96
+ });
97
+
98
+ const state = result.current.loadingState.getValue();
99
+ expect(state.index).toBe(0);
100
+ expect(state.done).toBe(false);
101
+ });
102
+
103
+ it("should update index to highest loaded component", () => {
104
+ const { result } = renderHook(() => useLoadingState(3, onLoadDone));
105
+
106
+ act(() => {
107
+ result.current.onLoadFinished(2);
108
+ });
109
+
110
+ let state = result.current.loadingState.getValue();
111
+ expect(state.index).toBe(2);
112
+
113
+ act(() => {
114
+ result.current.onLoadFinished(1);
115
+ });
116
+
117
+ state = result.current.loadingState.getValue();
118
+ expect(state.index).toBe(2); // Should remain 2, not decrease to 1
119
+ });
120
+
121
+ it("should mark as done when all components are loaded", () => {
122
+ const { result } = renderHook(() => useLoadingState(2, onLoadDone));
123
+
124
+ act(() => {
125
+ result.current.onLoadFinished(0);
126
+ });
127
+
128
+ let state = result.current.loadingState.getValue();
129
+ expect(state.done).toBe(true); // True because arePreviousComponentsLoaded(1) returns true when component 0 is loaded
130
+ expect(onLoadDone).toHaveBeenCalledTimes(1);
131
+
132
+ act(() => {
133
+ result.current.onLoadFinished(1);
134
+ });
135
+
136
+ state = result.current.loadingState.getValue();
137
+ expect(state.done).toBe(true);
138
+ expect(onLoadDone).toHaveBeenCalledTimes(2); // Called again
139
+ });
140
+
141
+ it("should call onLoadDone when count is 0", () => {
142
+ const { result } = renderHook(() => useLoadingState(0, onLoadDone));
143
+
144
+ const state = result.current.loadingState.getValue();
145
+ expect(state.done).toBe(true);
146
+ // onLoadDone is not called on initialization for count 0, only when all components are loaded via dispatch
147
+ });
148
+
149
+ it("should handle loading components out of order", () => {
150
+ const { result } = renderHook(() => useLoadingState(3, onLoadDone));
151
+
152
+ // Load component 2 first
153
+ act(() => {
154
+ result.current.onLoadFinished(2);
155
+ });
156
+
157
+ let state = result.current.loadingState.getValue();
158
+ expect(state.done).toBe(false);
159
+
160
+ // Load component 0
161
+ act(() => {
162
+ result.current.onLoadFinished(0);
163
+ });
164
+
165
+ state = result.current.loadingState.getValue();
166
+ expect(state.done).toBe(false);
167
+
168
+ // Load component 1 - should complete loading
169
+ act(() => {
170
+ result.current.onLoadFinished(1);
171
+ });
172
+
173
+ state = result.current.loadingState.getValue();
174
+ expect(state.done).toBe(true);
175
+ expect(onLoadDone).toHaveBeenCalledTimes(1);
176
+ });
177
+
178
+ it("should call onLoadDone again on subsequent dispatches", () => {
179
+ const { result } = renderHook(() => useLoadingState(2, onLoadDone));
180
+
181
+ act(() => {
182
+ result.current.onLoadFinished(0);
183
+ result.current.onLoadFinished(1);
184
+ });
185
+
186
+ expect(onLoadDone).toHaveBeenCalledTimes(2); // Called for each dispatch when done is true
187
+
188
+ // Try loading again - onLoadDone will be called again because dispatch runs again
189
+ act(() => {
190
+ result.current.onLoadFinished(0);
191
+ });
192
+
193
+ expect(onLoadDone).toHaveBeenCalledTimes(3); // Will be called again
194
+ });
195
+ });
196
+
197
+ describe("onLoadFailed", () => {
198
+ it("should treat failed components as loaded when SHOULD_FAIL_ON_COMPONENT_LOADING is false", () => {
199
+ const { result } = renderHook(() => useLoadingState(2, onLoadDone));
200
+
201
+ const error = new Error("Load failed");
202
+
203
+ act(() => {
204
+ result.current.onLoadFailed({ error, index: 0 });
205
+ });
206
+
207
+ const state = result.current.loadingState.getValue();
208
+ expect(state.index).toBe(0);
209
+ expect(result.current.shouldShowLoadingError).toBe(false);
210
+ });
211
+
212
+ it("should complete loading when all components fail", () => {
213
+ const { result } = renderHook(() => useLoadingState(2, onLoadDone));
214
+
215
+ const error = new Error("Load failed");
216
+
217
+ act(() => {
218
+ result.current.onLoadFailed({ error, index: 0 });
219
+ result.current.onLoadFailed({ error, index: 1 });
220
+ });
221
+
222
+ const state = result.current.loadingState.getValue();
223
+ expect(state.done).toBe(true);
224
+ expect(onLoadDone).toHaveBeenCalledTimes(2); // Called for each failed component
225
+ });
226
+
227
+ it("should handle mixed success and failure", () => {
228
+ const { result } = renderHook(() => useLoadingState(3, onLoadDone));
229
+
230
+ const error = new Error("Load failed");
231
+
232
+ act(() => {
233
+ result.current.onLoadFinished(0);
234
+ result.current.onLoadFailed({ error, index: 1 });
235
+ result.current.onLoadFinished(2);
236
+ });
237
+
238
+ const state = result.current.loadingState.getValue();
239
+ expect(state.done).toBe(true);
240
+ expect(onLoadDone).toHaveBeenCalledTimes(2); // Called when all components 0,1,2 are handled
241
+ });
242
+ });
243
+
244
+ describe("loading state observable", () => {
245
+ it("should emit state changes through BehaviorSubject", () => {
246
+ const { result } = renderHook(() => useLoadingState(3, onLoadDone)); // Use 3 components so loading component 0 doesn't complete everything
247
+ const mockSubscriber = jest.fn();
248
+
249
+ result.current.loadingState.subscribe(mockSubscriber);
250
+
251
+ act(() => {
252
+ result.current.onLoadFinished(0);
253
+ });
254
+
255
+ // Should have been called twice: initial state + update
256
+ expect(mockSubscriber).toHaveBeenCalledTimes(2);
257
+
258
+ expect(mockSubscriber).toHaveBeenLastCalledWith({
259
+ index: 0,
260
+ done: false, // Will be false because we need components 1 and 2 as well
261
+ waitForAllComponents: false,
262
+ });
263
+ });
264
+
265
+ it("should preserve waitForAllComponents flag in state updates", () => {
266
+ const { result } = renderHook(() => useLoadingState(2, onLoadDone));
267
+
268
+ act(() => {
269
+ result.current.onLoadFinished(0);
270
+ });
271
+
272
+ const state = result.current.loadingState.getValue();
273
+ expect(state.waitForAllComponents).toBe(false);
274
+ });
275
+ });
276
+
277
+ describe("memoization", () => {
278
+ it("should return stable references for functions", () => {
279
+ const { result, rerender } = renderHook(() =>
280
+ useLoadingState(2, onLoadDone)
281
+ );
282
+
283
+ const firstRender = {
284
+ onLoadFinished: result.current.onLoadFinished,
285
+ onLoadFailed: result.current.onLoadFailed,
286
+ arePreviousComponentsLoaded: result.current.arePreviousComponentsLoaded,
287
+ };
288
+
289
+ rerender();
290
+
291
+ expect(result.current.onLoadFinished).toBe(firstRender.onLoadFinished);
292
+ expect(result.current.onLoadFailed).toBe(firstRender.onLoadFailed);
293
+
294
+ expect(result.current.arePreviousComponentsLoaded).toBe(
295
+ firstRender.arePreviousComponentsLoaded
296
+ );
297
+ });
298
+
299
+ it("should return stable function references (current behavior)", () => {
300
+ const { result, rerender } = renderHook(
301
+ ({ onLoadDone }) => useLoadingState(2, onLoadDone),
302
+ { initialProps: { onLoadDone } }
303
+ );
304
+
305
+ const firstResult = result.current;
306
+
307
+ const newOnLoadDone = jest.fn();
308
+ rerender({ onLoadDone: newOnLoadDone });
309
+
310
+ // Functions should remain the same due to empty dependency arrays (this is the current behavior)
311
+ expect(result.current.onLoadFinished).toBe(firstResult.onLoadFinished);
312
+ expect(result.current.onLoadFailed).toBe(firstResult.onLoadFailed);
313
+ });
314
+ });
315
+
316
+ describe("edge cases", () => {
317
+ it("should handle duplicate load finished calls gracefully", () => {
318
+ const { result } = renderHook(() => useLoadingState(2, onLoadDone));
319
+
320
+ act(() => {
321
+ result.current.onLoadFinished(0);
322
+ result.current.onLoadFinished(0); // Duplicate call
323
+ });
324
+
325
+ const state = result.current.loadingState.getValue();
326
+ expect(state.index).toBe(0);
327
+ expect(state.done).toBe(true); // True because loading component 0 makes it done in a 2-component setup
328
+ });
329
+
330
+ it("should handle loading index greater than component count", () => {
331
+ const { result } = renderHook(() => useLoadingState(2, onLoadDone));
332
+
333
+ act(() => {
334
+ result.current.onLoadFinished(5); // Index out of bounds
335
+ });
336
+
337
+ const state = result.current.loadingState.getValue();
338
+ expect(state.index).toBe(5);
339
+ expect(state.done).toBe(false); // Should not be done as not all components loaded
340
+ });
341
+
342
+ it("should handle negative indices", () => {
343
+ const { result } = renderHook(() => useLoadingState(2, onLoadDone));
344
+
345
+ act(() => {
346
+ result.current.onLoadFinished(-1);
347
+ });
348
+
349
+ const state = result.current.loadingState.getValue();
350
+ expect(state.index).toBe(-1); // Should remain -1
351
+ });
352
+ });
353
+
354
+ describe("component count changes", () => {
355
+ it("should handle changing component count", () => {
356
+ const { result, rerender } = renderHook(
357
+ ({ count }) => useLoadingState(count, onLoadDone),
358
+ { initialProps: { count: 2 } }
359
+ );
360
+
361
+ act(() => {
362
+ result.current.onLoadFinished(0);
363
+ });
364
+
365
+ // Change count
366
+ rerender({ count: 3 });
367
+
368
+ // The hook should work with the new count
369
+ act(() => {
370
+ result.current.onLoadFinished(1);
371
+ result.current.onLoadFinished(2);
372
+ });
373
+
374
+ const state = result.current.loadingState.getValue();
375
+ expect(state.done).toBe(true);
376
+ });
377
+ });
378
+ });
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import { isNil, set, lensIndex, T, slice } from "ramda";
2
+ import { isNil, set, lensIndex, slice } from "ramda";
3
3
  import { BehaviorSubject } from "rxjs";
4
4
  import { useRefWithInitialValue } from "@applicaster/zapp-react-native-utils/reactHooks/state/useRefWithInitialValue";
5
5
 
@@ -53,7 +53,7 @@ export const useLoadingState = (
53
53
 
54
54
  const componentsBefore = slice(0, index, componentStateRef.current);
55
55
 
56
- return componentsBefore.every(T);
56
+ return componentsBefore.every(Boolean);
57
57
  }, []);
58
58
 
59
59
  const dispatch = React.useCallback(({ payload }) => {
@@ -10,11 +10,10 @@ import { useLocalizedStrings } from "@applicaster/zapp-react-native-utils/locali
10
10
  import { useAnalytics } from "@applicaster/zapp-react-native-utils/analyticsUtils";
11
11
  import { useSendAnalyticsEventWithFunction } from "@applicaster/zapp-react-native-utils/analyticsUtils/helpers/hooks";
12
12
  import { useCurrentScreenData } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useCurrentScreenData";
13
- import { loadPipesData } from "@applicaster/zapp-react-native-redux/ZappPipes";
14
- import { useDispatch } from "react-redux";
15
13
  import { useShallow } from "zustand/react/shallow";
16
14
  import { useScreenContextV2 } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
17
15
  import { useSafeAreaInsets } from "react-native-safe-area-context";
16
+ import { useLoadPipesDataDispatch } from "@applicaster/zapp-react-native-utils/reactHooks";
18
17
 
19
18
  const BRIGHTNESS_THRESHOLD = 160;
20
19
  const ABOVE_DEFAULT_COLOR = "gray";
@@ -61,38 +60,33 @@ export const usePullToRefresh = (
61
60
  ) => {
62
61
  const isPipesV1 = !!pullToRefreshPipesV1RefreshingStateUpdater;
63
62
 
64
- const dispatch = useDispatch();
65
-
66
63
  const [refreshing, setRefreshing] = React.useState(false);
67
64
 
68
65
  const feeds: string[] =
69
66
  riverComponents?.map(R.path(["data", "source"])).filter((feed) => !!feed) ??
70
67
  [];
71
68
 
72
- const screenData = useCurrentScreenData();
73
-
74
69
  const feedsLength = feeds.length;
75
70
 
76
71
  const [requestsCompletedCounter, setRequestsCompletedCounter] =
77
72
  React.useState(0);
78
73
 
74
+ const loadPipesDataDispatcher = useLoadPipesDataDispatch();
75
+
79
76
  React.useEffect(() => {
80
77
  // will not work for pipes v1 on 1st level screens
81
78
  if (refreshing && !isPipesV1) {
82
79
  feeds.forEach((feed) => {
83
- dispatch(
84
- loadPipesData(feed, {
85
- silentRefresh: true,
86
- clearCache: true,
87
- callback: () => {
88
- setRequestsCompletedCounter(R.inc);
89
- },
90
- riverId: screenData.id,
91
- })
92
- );
80
+ loadPipesDataDispatcher(feed, {
81
+ silentRefresh: true,
82
+ clearCache: true,
83
+ callback: () => {
84
+ setRequestsCompletedCounter(R.inc);
85
+ },
86
+ });
93
87
  });
94
88
  }
95
- }, [refreshing, isPipesV1]);
89
+ }, [refreshing, isPipesV1, feeds, loadPipesDataDispatcher]);
96
90
 
97
91
  React.useEffect(() => {
98
92
  if (requestsCompletedCounter === feedsLength) {