@applicaster/zapp-react-native-ui-components 15.1.4 → 16.0.0-alpha.1042942411

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 (202) hide show
  1. package/Components/BackgroundImage/index.ts +1 -8
  2. package/Components/BaseFocusable/index.ios.ts +12 -2
  3. package/Components/Cell/FocusableWrapper.tsx +3 -0
  4. package/Components/Cell/TvOSCellComponent.tsx +6 -3
  5. package/Components/CellRendererResolver/index.ts +1 -1
  6. package/Components/Focusable/Focusable.tsx +4 -2
  7. package/Components/Focusable/FocusableTvOS.tsx +18 -1
  8. package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +1 -0
  9. package/Components/FocusableGroup/FocusableTvOS.tsx +30 -1
  10. package/Components/GeneralContentScreen/GeneralContentScreenHookAdapter.tsx +39 -0
  11. package/Components/GeneralContentScreen/__tests__/GeneralContentScreenHookAdapter.test.tsx +64 -0
  12. package/Components/GeneralContentScreen/__tests__/HookContentFocusGroup.web.test.tsx +91 -0
  13. package/Components/GeneralContentScreen/hookAdapter/__tests__/networkService.test.ts +74 -0
  14. package/Components/GeneralContentScreen/hookAdapter/__tests__/runInBackground.test.ts +139 -0
  15. package/Components/GeneralContentScreen/hookAdapter/__tests__/validationHelper.test.ts +124 -0
  16. package/Components/GeneralContentScreen/hookAdapter/logger.ts +6 -0
  17. package/Components/GeneralContentScreen/hookAdapter/networkService.ts +53 -0
  18. package/Components/GeneralContentScreen/hookAdapter/runInBackground.ts +48 -0
  19. package/Components/GeneralContentScreen/hookAdapter/validationHelper.ts +72 -0
  20. package/Components/GeneralContentScreen/hookFocus/index.tsx +13 -0
  21. package/Components/GeneralContentScreen/hookFocus/index.web.tsx +69 -0
  22. package/Components/GeneralContentScreen/index.ts +2 -0
  23. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
  24. package/Components/HandlePlayable/HandlePlayable.tsx +13 -8
  25. package/Components/Layout/TV/NavBarContainer.tsx +1 -10
  26. package/Components/Layout/TV/ScreenContainer.tsx +2 -6
  27. package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +7 -12
  28. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +7 -12
  29. package/Components/Layout/TV/index.tsx +3 -4
  30. package/Components/Layout/TV/index.web.tsx +3 -4
  31. package/Components/LinkHandler/LinkHandler.tsx +2 -2
  32. package/Components/MasterCell/CONFIG_BUILDER_TO_REACT_COMPONENT.md +144 -0
  33. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +27 -48
  34. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/model.test.ts +80 -0
  35. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +213 -0
  36. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/selectors.test.ts +45 -0
  37. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/style.test.ts +49 -0
  38. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +165 -0
  39. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +405 -0
  40. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/index.ts +1 -0
  41. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/model.ts +47 -0
  42. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +198 -0
  43. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/selectors.ts +26 -0
  44. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/style.ts +29 -0
  45. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/types.ts +37 -0
  46. package/Components/MasterCell/DefaultComponents/BorderContainerView/__tests__/index.test.tsx +217 -46
  47. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +50 -119
  48. package/Components/MasterCell/DefaultComponents/Button.tsx +50 -58
  49. package/Components/MasterCell/DefaultComponents/ButtonContainerView/components/HorizontalSeparator.tsx +8 -0
  50. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.android.tv.tsx +58 -0
  51. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tsx +15 -0
  52. package/Components/MasterCell/DefaultComponents/{tv/ButtonContainerView/index.tsx → ButtonContainerView/index.tv.tsx} +3 -11
  53. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.web.ts +1 -0
  54. package/Components/MasterCell/DefaultComponents/ButtonContainerView/types.ts +40 -0
  55. package/Components/MasterCell/DefaultComponents/CellBadge/__tests__/__snapshots__/index.test.tsx.snap +18 -0
  56. package/Components/MasterCell/DefaultComponents/CellBadge/__tests__/index.test.tsx +90 -0
  57. package/Components/MasterCell/DefaultComponents/CellBadge/index.tsx +13 -14
  58. package/Components/MasterCell/DefaultComponents/DataProvider/index.tsx +163 -0
  59. package/Components/MasterCell/DefaultComponents/FocusableView/index.android.tsx +2 -23
  60. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -22
  61. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +8 -2
  62. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +11 -3
  63. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +9 -1
  64. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +15 -14
  65. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -3
  66. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +1 -2
  67. package/Components/MasterCell/DefaultComponents/PressableView.tsx +54 -0
  68. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +46 -50
  69. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +47 -39
  70. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
  71. package/Components/MasterCell/DefaultComponents/SecondaryImage/utils.ts +19 -6
  72. package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +15 -0
  73. package/Components/MasterCell/DefaultComponents/Text/index.tsx +45 -15
  74. package/Components/MasterCell/DefaultComponents/UIKitAdapters/Badge.tsx +49 -0
  75. package/Components/MasterCell/DefaultComponents/__tests__/ActionButton.test.tsx +122 -0
  76. package/Components/MasterCell/DefaultComponents/__tests__/Button.test.tsx +186 -0
  77. package/Components/MasterCell/DefaultComponents/__tests__/DataProvider.test.tsx +141 -0
  78. package/Components/MasterCell/DefaultComponents/__tests__/DynamicBadge.test.tsx +156 -0
  79. package/Components/MasterCell/DefaultComponents/__tests__/__snapshots__/ActionButton.test.tsx.snap +60 -0
  80. package/Components/MasterCell/DefaultComponents/__tests__/text.test.tsx +196 -67
  81. package/Components/MasterCell/DefaultComponents/index.ts +13 -3
  82. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +189 -0
  83. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +33 -0
  84. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +33 -0
  85. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +159 -0
  86. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +18 -0
  87. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabel.ts +67 -0
  88. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +39 -0
  89. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +402 -0
  90. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +141 -0
  91. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +354 -0
  92. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/helpers.ts +105 -0
  93. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +129 -0
  94. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +123 -0
  95. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +238 -0
  96. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Asset.ts +4 -18
  97. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Button.ts +24 -73
  98. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TextLabelsContainer.ts +37 -18
  99. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TvActionButton.tsx +27 -0
  100. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +89 -0
  101. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/renderedTree.test.tsx +231 -0
  102. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +47 -48
  103. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +115 -29
  104. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/insertButtonsBetweenLabels.test.ts +45 -13
  105. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +101 -144
  106. package/Components/MasterCell/MappingFunctions/index.js +3 -2
  107. package/Components/MasterCell/README.md +4 -0
  108. package/Components/MasterCell/__tests__/__snapshots__/dataAdapter.test.js.snap +24 -0
  109. package/Components/MasterCell/__tests__/configInflater.test.js +1 -0
  110. package/Components/MasterCell/__tests__/elementMapper.test.js +46 -0
  111. package/Components/MasterCell/contexts/PressedStateContext.ts +3 -0
  112. package/Components/MasterCell/dataAdapter.ts +4 -1
  113. package/Components/MasterCell/elementMapper.tsx +54 -7
  114. package/Components/MasterCell/hooks/index.ts +2 -0
  115. package/Components/MasterCell/hooks/usePressedState.ts +4 -0
  116. package/Components/MasterCell/index.tsx +20 -4
  117. package/Components/MasterCell/utils/__tests__/cloneChildrenWithIds.test.tsx +43 -0
  118. package/Components/MasterCell/utils/__tests__/useFilterChildren.test.tsx +80 -0
  119. package/Components/MasterCell/utils/index.ts +82 -16
  120. package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +33 -69
  121. package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +25 -47
  122. package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +122 -39
  123. package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +671 -0
  124. package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +257 -88
  125. package/Components/ModalComponent/AudioPlayer/Components/index.ts +6 -4
  126. package/Components/ModalComponent/BottomSheetDragArea.tsx +33 -0
  127. package/Components/ModalComponent/BottomSheetModalContent.tsx +17 -10
  128. package/Components/ModalComponent/__tests__/BottomSheetDragArea.test.tsx +74 -0
  129. package/Components/OfflineHandler/__tests__/__snapshots__/index.test.tsx.snap +4 -85
  130. package/Components/OfflineHandler/__tests__/index.test.tsx +101 -32
  131. package/Components/OfflineHandler/hooks.ts +123 -0
  132. package/Components/OfflineHandler/index.tsx +51 -73
  133. package/Components/OfflineHandler/utils/index.ts +1 -13
  134. package/Components/PlayerContainer/PlayerContainer.tsx +6 -5
  135. package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +2 -4
  136. package/Components/PreloaderWrapper/index.tsx +1 -1
  137. package/Components/River/ComponentsMap/ComponentsMap.tsx +8 -21
  138. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  139. package/Components/River/RefreshControl.tsx +19 -105
  140. package/Components/River/River.tsx +9 -82
  141. package/Components/River/TV/River.tsx +31 -14
  142. package/Components/River/TV/index.tsx +8 -4
  143. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +30 -0
  144. package/Components/River/TV/utils/index.ts +4 -0
  145. package/Components/River/TV/withFocusableGroupForContent.tsx +71 -0
  146. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +4 -0
  147. package/Components/River/__tests__/componentsMap.test.js +38 -0
  148. package/Components/River/hooks/__tests__/usePullToRefresh.test.ts +132 -0
  149. package/Components/River/hooks/index.ts +1 -0
  150. package/Components/River/hooks/usePullToRefresh.ts +51 -0
  151. package/Components/Screen/TV/index.web.tsx +4 -2
  152. package/Components/Screen/__tests__/Screen.test.tsx +65 -42
  153. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +68 -44
  154. package/Components/Screen/hooks.ts +2 -3
  155. package/Components/Screen/index.tsx +2 -3
  156. package/Components/Screen/orientationHandler.ts +3 -3
  157. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +5 -3
  158. package/Components/ScreenResolver/index.tsx +9 -5
  159. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +1 -1
  160. package/Components/ScreenRevealManager/ScreenRevealManager.ts +40 -8
  161. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +86 -69
  162. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +1 -1
  163. package/Components/Tabs/TabContent.tsx +7 -4
  164. package/Components/TopCutoffOverlay/__tests__/TopCutoffOverlay.test.tsx +201 -0
  165. package/Components/TopCutoffOverlay/hooks/__tests__/useMarginTop.test.ts +130 -0
  166. package/Components/TopCutoffOverlay/hooks/index.ts +1 -0
  167. package/Components/TopCutoffOverlay/hooks/useMarginTop.ts +59 -0
  168. package/Components/TopCutoffOverlay/index.tsx +55 -0
  169. package/Components/Transitioner/index.js +3 -3
  170. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +5 -5
  171. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
  172. package/Components/VideoModal/utils.ts +12 -10
  173. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
  174. package/Components/Viewport/ViewportAware/index.tsx +16 -7
  175. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  176. package/Components/default-cell-renderer/viewTrees/mobile/index.ts +0 -3
  177. package/Contexts/CachedDimensionsContext/__tests__/index.test.ts +154 -0
  178. package/Contexts/ScreenContext/index.tsx +25 -18
  179. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  180. package/Decorators/Analytics/index.tsx +6 -5
  181. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +1 -0
  182. package/Decorators/ConfigurationWrapper/const.ts +1 -0
  183. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
  184. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  185. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  186. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
  187. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
  188. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +80 -0
  189. package/Helpers/DataSourceHelper/index.ts +19 -0
  190. package/package.json +5 -5
  191. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +0 -135
  192. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/types.ts +0 -25
  193. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +0 -112
  194. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +0 -107
  195. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +0 -153
  196. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +0 -67
  197. package/Components/OfflineHandler/NotificationView/utils.ts +0 -34
  198. package/Components/River/TV/withTVEventHandler.tsx +0 -36
  199. package/Helpers/DataSourceHelper/index.js +0 -19
  200. /package/Components/BackgroundImage/{BackgroundImage.tv.android.tsx → BackgroundImage.android.tv.tsx} +0 -0
  201. /package/Components/BackgroundImage/{BackgroundImage.tv.ios.tsx → BackgroundImage.ios.tv.tsx} +0 -0
  202. /package/Components/BackgroundImage/{BackgroundImage.tv.web.tsx → BackgroundImage.tsx} +0 -0
@@ -139,7 +139,13 @@ jest.mock(
139
139
  })
140
140
  );
141
141
 
142
+ jest.mock("@applicaster/zapp-react-native-ui-components/events", () => ({
143
+ ...jest.requireActual("@applicaster/zapp-react-native-ui-components/events"),
144
+ emitScrollEndReached: jest.fn(),
145
+ }));
146
+
142
147
  const { View } = require("react-native");
148
+ const events = require("@applicaster/zapp-react-native-ui-components/events");
143
149
  const { ComponentsMap } = require("../ComponentsMap/ComponentsMap");
144
150
  const theme = require("./theme-mock.json");
145
151
 
@@ -190,4 +196,36 @@ describe("componentsMap", () => {
190
196
 
191
197
  expect(toJSON()).toMatchSnapshot();
192
198
  });
199
+
200
+ it("calls emitScrollEndReached when onScroll was called and isScreenWrappedInContainer is false", () => {
201
+ themeSpy = jest
202
+ .spyOn(themeUtils, "useTheme")
203
+ .mockImplementation(() => () => theme);
204
+
205
+ events.emitScrollEndReached.mockClear();
206
+
207
+ const { getByTestId } = render(
208
+ <Provider store={store}>
209
+ <ComponentsMap
210
+ {...props}
211
+ isScreenWrappedInContainer={false}
212
+ feed={{ entry: [] }}
213
+ />
214
+ </Provider>
215
+ );
216
+
217
+ const flatList = getByTestId("components-map-flat-list");
218
+
219
+ flatList.props.onScroll({
220
+ nativeEvent: {
221
+ contentOffset: { y: 0 },
222
+ layoutMeasurement: { height: 100 },
223
+ contentSize: { height: 200 },
224
+ },
225
+ });
226
+
227
+ flatList.props.onEndReached();
228
+
229
+ expect(events.emitScrollEndReached).toHaveBeenCalledTimes(1);
230
+ });
193
231
  });
@@ -0,0 +1,132 @@
1
+ import { act, renderHook } from "@testing-library/react-native";
2
+ import { refreshCoordinator } from "@applicaster/zapp-react-native-utils/refreshUtils/RefreshCoordinator";
3
+
4
+ import { usePullToRefresh } from "../usePullToRefresh";
5
+
6
+ jest.mock(
7
+ "@applicaster/zapp-react-native-utils/refreshUtils/RefreshCoordinator",
8
+ () => ({
9
+ refreshCoordinator: {
10
+ triggerRefresh: jest.fn(),
11
+ },
12
+ })
13
+ );
14
+
15
+ jest.useFakeTimers();
16
+
17
+ describe("usePullToRefresh", () => {
18
+ const screenId = "test-screen";
19
+ const components = [{ id: "comp1" }, { id: "comp2" }] as any;
20
+
21
+ beforeEach(() => {
22
+ jest.clearAllMocks();
23
+ });
24
+
25
+ it("should initialize with refreshing=false", () => {
26
+ const { result } = renderHook(() => usePullToRefresh(screenId, components));
27
+
28
+ expect(result.current.refreshing).toBe(false);
29
+ });
30
+
31
+ it("should trigger refresh and set refreshing=true", () => {
32
+ const { result } = renderHook(() => usePullToRefresh(screenId, components));
33
+
34
+ act(() => {
35
+ result.current.onRefresh();
36
+ });
37
+
38
+ expect(result.current.refreshing).toBe(true);
39
+
40
+ expect(refreshCoordinator.triggerRefresh).toHaveBeenCalledWith(
41
+ components,
42
+ screenId
43
+ );
44
+ });
45
+
46
+ it("should set refreshing=false after spinner duration", () => {
47
+ const { result } = renderHook(() => usePullToRefresh(screenId, components));
48
+
49
+ act(() => {
50
+ result.current.onRefresh();
51
+ });
52
+
53
+ expect(result.current.refreshing).toBe(true);
54
+
55
+ act(() => {
56
+ jest.advanceTimersByTime(1500);
57
+ });
58
+
59
+ expect(result.current.refreshing).toBe(false);
60
+ });
61
+
62
+ it("should not stop refreshing before spinner duration", () => {
63
+ const { result } = renderHook(() => usePullToRefresh(screenId, components));
64
+
65
+ act(() => {
66
+ result.current.onRefresh();
67
+ });
68
+
69
+ act(() => {
70
+ jest.advanceTimersByTime(1000);
71
+ });
72
+
73
+ expect(result.current.refreshing).toBe(true);
74
+ });
75
+
76
+ it("should clear timeout on unmount", () => {
77
+ const clearTimeoutSpy = jest.spyOn(global, "clearTimeout");
78
+
79
+ const { result, unmount } = renderHook(() =>
80
+ usePullToRefresh(screenId, components)
81
+ );
82
+
83
+ act(() => {
84
+ result.current.onRefresh();
85
+ });
86
+
87
+ unmount();
88
+
89
+ expect(clearTimeoutSpy).toHaveBeenCalled();
90
+ });
91
+
92
+ it("should handle multiple refresh calls correctly", () => {
93
+ const { result } = renderHook(() => usePullToRefresh(screenId, components));
94
+
95
+ act(() => {
96
+ result.current.onRefresh();
97
+ result.current.onRefresh();
98
+ });
99
+
100
+ expect(refreshCoordinator.triggerRefresh).toHaveBeenCalledTimes(2);
101
+ expect(result.current.refreshing).toBe(true);
102
+
103
+ act(() => {
104
+ jest.advanceTimersByTime(1500);
105
+ });
106
+
107
+ expect(result.current.refreshing).toBe(false);
108
+ });
109
+
110
+ it("should use latest props in callback", () => {
111
+ const { result, rerender } = renderHook(
112
+ ({ screenId, components }) => usePullToRefresh(screenId, components),
113
+ {
114
+ initialProps: { screenId, components },
115
+ }
116
+ );
117
+
118
+ const newComponents = [{ id: "new-comp" }] as any;
119
+ const newScreenId = "new-screen";
120
+
121
+ rerender({ screenId: newScreenId, components: newComponents });
122
+
123
+ act(() => {
124
+ result.current.onRefresh();
125
+ });
126
+
127
+ expect(refreshCoordinator.triggerRefresh).toHaveBeenCalledWith(
128
+ newComponents,
129
+ newScreenId
130
+ );
131
+ });
132
+ });
@@ -0,0 +1 @@
1
+ export { usePullToRefresh } from "./usePullToRefresh";
@@ -0,0 +1,51 @@
1
+ import { useState, useCallback, useRef, useEffect } from "react";
2
+ import { refreshCoordinator } from "@applicaster/zapp-react-native-utils/refreshUtils/RefreshCoordinator";
3
+
4
+ const SPINNER_DURATION_MS = 1500;
5
+
6
+ /**
7
+ * Pull-to-refresh hook.
8
+ *
9
+ * Triggers a refresh for all screen components via RefreshCoordinator.
10
+ * Each component's UrlFeedResolver already subscribes to refresh$ events
11
+ * and calls reloadData() when triggered — so this hook only needs to:
12
+ * 1. Push events into the refresh bus
13
+ * 2. Show a fixed-duration spinner as UX feedback
14
+ *
15
+ * Data updates arrive reactively via Redux (silentRefresh: true keeps
16
+ * old data visible while loading).
17
+ */
18
+ export const usePullToRefresh = (
19
+ screenId: string,
20
+ components: ZappUIComponent[] = []
21
+ ) => {
22
+ const [refreshing, setRefreshing] = useState(false);
23
+ const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
24
+
25
+ // Cleanup timer on unmount
26
+ useEffect(() => {
27
+ return () => {
28
+ if (timerRef.current) {
29
+ clearTimeout(timerRef.current);
30
+ }
31
+ };
32
+ }, []);
33
+
34
+ const onRefresh = useCallback(() => {
35
+ setRefreshing(true);
36
+ refreshCoordinator.triggerRefresh(components, screenId);
37
+
38
+ // Spinner is UX feedback for the gesture.
39
+ // Data updates arrive reactively via Redux (silentRefresh: true).
40
+ if (timerRef.current) {
41
+ clearTimeout(timerRef.current);
42
+ }
43
+
44
+ timerRef.current = setTimeout(
45
+ () => setRefreshing(false),
46
+ SPINNER_DURATION_MS
47
+ );
48
+ }, [components, screenId]);
49
+
50
+ return { refreshing, onRefresh };
51
+ };
@@ -9,9 +9,10 @@ import {
9
9
  import {
10
10
  useIsScreenActive,
11
11
  useNavigation,
12
+ useRivers,
12
13
  } from "@applicaster/zapp-react-native-utils/reactHooks";
13
14
  import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
14
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
15
+ import { usePlugins } from "@applicaster/zapp-react-native-redux/hooks";
15
16
  import {
16
17
  useNavbarState,
17
18
  useScreenBackgroundColor,
@@ -102,7 +103,8 @@ export const Screen = ({ route, Components }: Props) => {
102
103
  }
103
104
 
104
105
  const navigator = useNavigation();
105
- const { plugins = [], rivers = [] } = usePickFromState(["plugins", "rivers"]);
106
+ const plugins = usePlugins();
107
+ const rivers = useRivers();
106
108
 
107
109
  const pathAttributes = getPathAttributes({ pathname: route });
108
110
  const routeState = navigator.getStackForPathname(route);
@@ -1,18 +1,15 @@
1
1
  import * as React from "react";
2
2
  import { View } from "react-native";
3
- import { render } from "@testing-library/react-native";
3
+ import { renderWithProviders } from "@applicaster/zapp-react-native-utils/testUtils";
4
4
 
5
5
  const Mocked_RouteManager = jest.fn((props) => (
6
6
  <View testID="routeManager" {...props} />
7
7
  ));
8
8
 
9
- const mock_navBarVisibleFlag = true;
10
-
11
- const mockIsNavBarVisible = jest.fn(() => mock_navBarVisibleFlag);
12
-
13
9
  const mockIsOrientationCompatible = jest.fn(() => true);
14
10
 
15
11
  jest.mock("react-native-safe-area-context", () => ({
12
+ ...jest.requireActual("react-native-safe-area-context"),
16
13
  useSafeAreaInsets: () => ({ top: 44 }),
17
14
  useSafeAreaFrame: () => ({ x: 0, y: 0, width: 375, height: 812 }),
18
15
  }));
@@ -36,12 +33,14 @@ jest.mock(
36
33
  );
37
34
 
38
35
  jest.mock("@applicaster/zapp-react-native-utils/analyticsUtils", () => ({
36
+ ...jest.requireActual("@applicaster/zapp-react-native-utils/analyticsUtils"),
39
37
  useAnalytics: jest.fn(() => ({
40
38
  sendScreenEvent: jest.fn(),
41
39
  })),
42
40
  }));
43
41
 
44
42
  jest.mock("@applicaster/zapp-react-native-utils/theme", () => ({
43
+ ...jest.requireActual("@applicaster/zapp-react-native-utils/theme"),
45
44
  useTheme: jest.fn(() => ({
46
45
  app_background_color: "blue",
47
46
  })),
@@ -78,21 +77,44 @@ jest.mock(
78
77
  })
79
78
  );
80
79
 
81
- jest.mock("@applicaster/zapp-react-native-utils/reactHooks/navigation", () => ({
82
- isNavBarVisible: mockIsNavBarVisible,
83
- useIsScreenActive: jest.fn().mockReturnValue(true),
84
- useNavigation: jest.fn(() => ({
85
- canGoBack: () => false,
86
- currentRoute: "/river/testId",
87
- activeRiver: { id: "testId" },
88
- screenData: { id: "testId" },
89
- data: { screen: { id: "testId" } },
90
- })),
91
- useRoute: jest.fn(() => ({
92
- pathname: "/river/testId",
93
- screenData: { id: "testId" },
94
- })),
95
- }));
80
+ jest.mock(
81
+ "@applicaster/zapp-react-native-utils/reactHooks/navigation/useNavigation",
82
+ () => ({
83
+ ...jest.requireActual(
84
+ "@applicaster/zapp-react-native-utils/reactHooks/navigation/useNavigation"
85
+ ),
86
+ useNavigation: jest.fn(() => ({
87
+ canGoBack: () => false,
88
+ currentRoute: "/river/testId",
89
+ activeRiver: { id: "testId" },
90
+ screenData: { id: "testId" },
91
+ data: { screen: { id: "testId" } },
92
+ })),
93
+ })
94
+ );
95
+
96
+ jest.mock(
97
+ "@applicaster/zapp-react-native-utils/reactHooks/navigation/useIsScreenActive",
98
+ () => ({
99
+ ...jest.requireActual(
100
+ "@applicaster/zapp-react-native-utils/reactHooks/navigation/useIsScreenActive"
101
+ ),
102
+ useIsScreenActive: jest.fn().mockReturnValue(true),
103
+ })
104
+ );
105
+
106
+ jest.mock(
107
+ "@applicaster/zapp-react-native-utils/reactHooks/navigation/useRoute",
108
+ () => ({
109
+ ...jest.requireActual(
110
+ "@applicaster/zapp-react-native-utils/reactHooks/navigation/useRoute"
111
+ ),
112
+ useRoute: jest.fn(() => ({
113
+ pathname: "/river/testId",
114
+ screenData: { id: "testId" },
115
+ })),
116
+ })
117
+ );
96
118
 
97
119
  jest.mock("@applicaster/zapp-react-native-utils/reactHooks", () => ({
98
120
  ...jest.requireActual("@applicaster/zapp-react-native-utils/reactHooks"),
@@ -113,26 +135,6 @@ jest.mock("@applicaster/zapp-react-native-utils/reactHooks", () => ({
113
135
  useIsTablet: jest.fn(() => false),
114
136
  }));
115
137
 
116
- jest.mock("@applicaster/zapp-react-native-redux/hooks", () => {
117
- const View = jest.requireActual("react-native").View;
118
-
119
- return {
120
- ...jest.requireActual("@applicaster/zapp-react-native-redux/hooks"),
121
- usePickFromState: () => ({
122
- plugins: [
123
- {
124
- name: "Offline Plugin",
125
- identifier: "offline-experience",
126
- type: "general",
127
- module: {
128
- OfflineFallbackScreen: ({ children }) => <View>{children}</View>, // eslint-disable-line
129
- },
130
- },
131
- ],
132
- }),
133
- };
134
- });
135
-
136
138
  const {
137
139
  allowedOrientationsForScreen,
138
140
  getOrientation,
@@ -152,6 +154,19 @@ const screenProps = {
152
154
 
153
155
  const { Screen } = require("..");
154
156
 
157
+ const store = {
158
+ plugins: [
159
+ {
160
+ name: "Offline Plugin",
161
+ identifier: "offline-experience",
162
+ type: "general",
163
+ module: {
164
+ OfflineFallbackScreen: ({ children }) => <View>{children}</View>, // eslint-disable-line
165
+ },
166
+ },
167
+ ],
168
+ };
169
+
155
170
  describe("<Screen Component />", () => {
156
171
  beforeEach(() => {
157
172
  allowedOrientationsForScreen.mockClear();
@@ -161,14 +176,22 @@ describe("<Screen Component />", () => {
161
176
 
162
177
  describe("when the navbar should show", () => {
163
178
  it("renders correctly", () => {
164
- const { toJSON } = render(<Screen {...screenProps} />);
179
+ const { toJSON } = renderWithProviders(
180
+ <Screen {...screenProps} />,
181
+ store
182
+ );
183
+
165
184
  expect(toJSON()).toMatchSnapshot();
166
185
  });
167
186
  });
168
187
 
169
188
  describe("when the navbar should be hidden", () => {
170
189
  it("renders correctly", () => {
171
- const { toJSON } = render(<Screen {...screenProps} />);
190
+ const { toJSON } = renderWithProviders(
191
+ <Screen {...screenProps} />,
192
+ store
193
+ );
194
+
172
195
  expect(toJSON()).toMatchSnapshot();
173
196
  });
174
197
  });
@@ -1,67 +1,91 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`<Screen Component /> when the navbar should be hidden renders correctly 1`] = `
4
- <View
5
- importantForAccessibility="yes"
4
+ <RNCSafeAreaProvider
5
+ onInsetsChange={[Function]}
6
6
  style={
7
- {
8
- "backgroundColor": "blue",
9
- "flex": 1,
10
- "paddingTop": 0,
11
- }
7
+ [
8
+ {
9
+ "flex": 1,
10
+ },
11
+ undefined,
12
+ ]
12
13
  }
13
14
  >
14
15
  <View
15
- hasMenu={false}
16
- id="/river/testId"
17
- pathname="/river/testId"
18
- selected="testId"
19
- testID="navBar"
20
- title="Test Title"
21
- />
22
- <View>
16
+ importantForAccessibility="yes"
17
+ style={
18
+ {
19
+ "backgroundColor": "blue",
20
+ "flex": 1,
21
+ "paddingTop": 0,
22
+ }
23
+ }
24
+ >
23
25
  <View
26
+ hasMenu={false}
27
+ id="/river/testId"
24
28
  pathname="/river/testId"
25
- screenData={
26
- {
27
- "id": "testId",
28
- }
29
- }
30
- testID="routeManager"
29
+ selected="testId"
30
+ testID="navBar"
31
+ title="Test Title"
31
32
  />
33
+ <View>
34
+ <View
35
+ pathname="/river/testId"
36
+ screenData={
37
+ {
38
+ "id": "testId",
39
+ }
40
+ }
41
+ testID="routeManager"
42
+ />
43
+ </View>
32
44
  </View>
33
- </View>
45
+ </RNCSafeAreaProvider>
34
46
  `;
35
47
 
36
48
  exports[`<Screen Component /> when the navbar should show renders correctly 1`] = `
37
- <View
38
- importantForAccessibility="yes"
49
+ <RNCSafeAreaProvider
50
+ onInsetsChange={[Function]}
39
51
  style={
40
- {
41
- "backgroundColor": "blue",
42
- "flex": 1,
43
- "paddingTop": 0,
44
- }
52
+ [
53
+ {
54
+ "flex": 1,
55
+ },
56
+ undefined,
57
+ ]
45
58
  }
46
59
  >
47
60
  <View
48
- hasMenu={false}
49
- id="/river/testId"
50
- pathname="/river/testId"
51
- selected="testId"
52
- testID="navBar"
53
- title="Test Title"
54
- />
55
- <View>
61
+ importantForAccessibility="yes"
62
+ style={
63
+ {
64
+ "backgroundColor": "blue",
65
+ "flex": 1,
66
+ "paddingTop": 0,
67
+ }
68
+ }
69
+ >
56
70
  <View
71
+ hasMenu={false}
72
+ id="/river/testId"
57
73
  pathname="/river/testId"
58
- screenData={
59
- {
60
- "id": "testId",
61
- }
62
- }
63
- testID="routeManager"
74
+ selected="testId"
75
+ testID="navBar"
76
+ title="Test Title"
64
77
  />
78
+ <View>
79
+ <View
80
+ pathname="/river/testId"
81
+ screenData={
82
+ {
83
+ "id": "testId",
84
+ }
85
+ }
86
+ testID="routeManager"
87
+ />
88
+ </View>
65
89
  </View>
66
- </View>
90
+ </RNCSafeAreaProvider>
67
91
  `;
@@ -1,4 +1,4 @@
1
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
1
+ import { useAppData } from "@applicaster/zapp-react-native-redux/hooks";
2
2
  import {
3
3
  useGetScreenOrientation,
4
4
  isOrientationCompatible,
@@ -95,8 +95,7 @@ export const useWaitForValidOrientation = () => {
95
95
 
96
96
  const isTablet = useIsTablet();
97
97
 
98
- const { appData } = usePickFromState(["appData"]);
99
- const isTabletPortrait = appData?.isTabletPortrait;
98
+ const { isTabletPortrait } = useAppData();
100
99
 
101
100
  const layoutData = useMemo(
102
101
  () => ({ isTablet, isTabletPortrait, width: screenWidth, height }),
@@ -1,8 +1,7 @@
1
1
  /// <reference types="@applicaster/applicaster-types" />
2
2
  import React from "react";
3
3
  import { AccessibilityInfo, findNodeHandle, View } from "react-native";
4
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
5
-
4
+ import { usePlugins } from "@applicaster/zapp-react-native-redux/hooks";
6
5
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
7
6
  import { getComponentModule } from "@applicaster/zapp-react-native-utils/pluginUtils";
8
7
  import {
@@ -44,7 +43,7 @@ type Props = {
44
43
  export function Screen(_props: Props) {
45
44
  const theme = useTheme<BaseThemePropertiesMobile>();
46
45
  const navigation = useNavigation();
47
- const { plugins } = usePickFromState(["plugins"]);
46
+ const plugins = usePlugins();
48
47
 
49
48
  // Gets the data for the current screen configuration
50
49
  const currentScreenData = useCurrentScreenData();
@@ -7,7 +7,7 @@ import {
7
7
  useGetScreenOrientation,
8
8
  } from "@applicaster/zapp-react-native-utils/appUtils/orientationHelper";
9
9
  import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
10
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
10
+ import { usePlugins, useAppData } from "@applicaster/zapp-react-native-redux";
11
11
  import { findPluginByType } from "@applicaster/zapp-react-native-utils/pluginUtils";
12
12
  import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
13
13
  import { zappHookModalStore } from "../../Contexts/ZappHookModalContext";
@@ -63,8 +63,8 @@ export function useNewOrientationForScreenData({
63
63
  }: OrientationHookArgs) {
64
64
  const isTablet = useIsTablet();
65
65
 
66
- const { appData, plugins } = usePickFromState(["appData", "plugins"]);
67
- const isTabletPortrait = appData?.isTabletPortrait;
66
+ const plugins = usePlugins();
67
+ const { isTabletPortrait } = useAppData();
68
68
  const isLandscapeTablet = isTablet && !isTabletPortrait;
69
69
 
70
70
  const screenOrientation = useGetScreenOrientation(screenData);
@@ -8,9 +8,11 @@ import { componentsLogger } from "../../Helpers/logger";
8
8
  const logger = componentsLogger.addSubsystem("ScreenFeedLoader");
9
9
 
10
10
  /** Loads and provides `feedData` and store to */
11
- export const ScreenFeedLoader: React.FC<
12
- React.PropsWithChildren<{ id: string; feedData: any }>
13
- > = ({ id, feedData, children }) => {
11
+ export const ScreenFeedLoader: React.FC<{
12
+ id: string;
13
+ feedData: any;
14
+ children: React.ReactElement | null;
15
+ }> = ({ id, feedData, children }) => {
14
16
  const { source: feedUrl, mapping } = feedData;
15
17
 
16
18
  const { data, loading, error } = useFeedLoader({
@@ -1,10 +1,18 @@
1
1
  import * as React from "react";
2
2
 
3
3
  import { componentsLogger } from "../../Helpers/logger";
4
+
4
5
  import { useScreenAnalytics } from "@applicaster/zapp-react-native-utils/analyticsUtils/helpers/hooks";
6
+
7
+ import { ScreenResultCallback } from "@applicaster/zapp-react-native-utils/zappFrameworkUtils/HookCallback/callbackNavigationAction";
5
8
  import { useScreenComponentResolver } from "./hooks/useScreenComponentResolver";
6
9
  import { withDefaultScreenContext } from "./withDefaultScreenContext";
7
10
 
11
+ export enum PresentationType {
12
+ Standalone = "Standalone",
13
+ Hook = "Hook",
14
+ }
15
+
8
16
  const logger = componentsLogger.addSubsystem("ScreenResolver");
9
17
 
10
18
  type Props = {
@@ -14,6 +22,7 @@ type Props = {
14
22
  feedId?: string;
15
23
  feedTitle?: string;
16
24
  focused?: boolean;
25
+ resultCallback?: ScreenResultCallback;
17
26
  parentFocus?: {
18
27
  nextFocusDown?: React.Ref<any>;
19
28
  nextFocusRight?: React.Ref<any>;
@@ -23,11 +32,6 @@ type Props = {
23
32
  groupId?: string;
24
33
  };
25
34
 
26
- export enum PresentationType {
27
- Standalone = "Standalone",
28
- Hook = "Hook",
29
- }
30
-
31
35
  export function ScreenResolverComponent(props: Props) {
32
36
  const { screenType } = props;
33
37
  const component = useScreenComponentResolver(screenType, props);
@@ -8,7 +8,7 @@ export const ScreenResolverFeedProvider = ({
8
8
  useFeedData,
9
9
  }: {
10
10
  id: string;
11
- children: React.ReactNode;
11
+ children: React.ReactElement | null;
12
12
  useFeedData: (id: string) => Option<ZappDataSource>;
13
13
  }) => {
14
14
  const feedData = useFeedData(id);