@applicaster/zapp-react-native-ui-components 15.0.0-rc.99 → 15.1.0-alpha.4285753931

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 (131) hide show
  1. package/Components/BackgroundImage/BackgroundImage.tv.android.tsx +28 -0
  2. package/Components/BackgroundImage/BackgroundImage.tv.ios.tsx +24 -0
  3. package/Components/BackgroundImage/BackgroundImage.tv.web.tsx +42 -0
  4. package/Components/BackgroundImage/index.ts +8 -0
  5. package/Components/BaseFocusable/index.ios.ts +2 -12
  6. package/Components/Cell/FocusableWrapper.tsx +0 -3
  7. package/Components/Cell/TvOSCellComponent.tsx +0 -5
  8. package/Components/CellRendererResolver/index.ts +1 -1
  9. package/Components/ComponentResolver/__tests__/componentResolver.test.js +1 -1
  10. package/Components/Focusable/Focusable.tsx +2 -4
  11. package/Components/Focusable/FocusableTvOS.tsx +1 -18
  12. package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +0 -1
  13. package/Components/FocusableGroup/FocusableTvOS.tsx +12 -37
  14. package/Components/GeneralContentScreen/GeneralContentScreen.tsx +39 -28
  15. package/Components/GeneralContentScreen/__tests__/GeneralContentScreen.test.tsx +104 -0
  16. package/Components/GeneralContentScreen/utils/__tests__/getScreenDataSource.test.ts +19 -0
  17. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
  18. package/Components/GeneralContentScreen/utils/getScreenDataSource.ts +9 -0
  19. package/Components/HandlePlayable/HandlePlayable.tsx +24 -42
  20. package/Components/HandlePlayable/utils.ts +31 -0
  21. package/Components/HookRenderer/HookRenderer.tsx +40 -10
  22. package/Components/HookRenderer/__tests__/HookRenderer.test.tsx +60 -0
  23. package/Components/Layout/FullWidthRow.tsx +38 -0
  24. package/Components/Layout/TV/ScreenContainer.tsx +11 -2
  25. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +5 -1
  26. package/Components/Layout/TV/index.tsx +6 -6
  27. package/Components/Layout/TV/index.web.tsx +6 -6
  28. package/Components/LinkHandler/LinkHandler.tsx +2 -2
  29. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +10 -4
  30. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +1 -5
  31. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +3 -11
  32. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +1 -9
  33. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +14 -15
  34. package/Components/MasterCell/DefaultComponents/LiveImage/__tests__/prepareEntry.test.ts +352 -0
  35. package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +136 -0
  36. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +34 -16
  37. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +6 -7
  38. package/Components/MasterCell/DefaultComponents/Text/index.tsx +2 -6
  39. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +2 -6
  40. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +11 -233
  41. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +15 -19
  42. package/Components/Navigator/StackNavigator.tsx +6 -0
  43. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +2 -2
  44. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +18 -17
  45. package/Components/OfflineHandler/__tests__/index.test.tsx +18 -27
  46. package/Components/PlayerContainer/PlayerContainer.tsx +13 -31
  47. package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +286 -0
  48. package/Components/PreloaderWrapper/__tests__/index.test.tsx +26 -0
  49. package/Components/PreloaderWrapper/index.tsx +15 -0
  50. package/Components/River/ComponentsMap/ComponentsMap.tsx +3 -4
  51. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  52. package/Components/River/RefreshControl.tsx +9 -3
  53. package/Components/River/RiverItem.tsx +26 -20
  54. package/Components/River/TV/River.tsx +14 -31
  55. package/Components/River/TV/index.tsx +4 -8
  56. package/Components/River/TV/withTVEventHandler.tsx +36 -0
  57. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +0 -16
  58. package/Components/River/__tests__/componentsMap.test.js +0 -38
  59. package/Components/Screen/TV/hooks/__tests__/useAfterPaint.test.ts +60 -0
  60. package/Components/Screen/TV/hooks/index.ts +2 -0
  61. package/Components/Screen/TV/hooks/useAfterPaint.ts +23 -0
  62. package/Components/Screen/TV/index.web.tsx +18 -11
  63. package/Components/Screen/__tests__/Screen.test.tsx +43 -65
  64. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +44 -68
  65. package/Components/Screen/hooks.ts +76 -5
  66. package/Components/Screen/index.tsx +10 -3
  67. package/Components/Screen/orientationHandler.ts +3 -3
  68. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +46 -0
  69. package/Components/ScreenFeedLoader/__tests__/ScreenFeedLoader.test.tsx +94 -0
  70. package/Components/ScreenFeedLoader/index.ts +1 -0
  71. package/Components/ScreenResolver/__tests__/screenResolver.test.js +24 -0
  72. package/Components/ScreenResolver/hooks/index.ts +3 -0
  73. package/Components/ScreenResolver/hooks/useGetComponent.ts +15 -0
  74. package/Components/ScreenResolver/hooks/useScreenComponentResolver.tsx +90 -0
  75. package/Components/ScreenResolver/index.tsx +9 -115
  76. package/Components/ScreenResolver/utils/__tests__/getScreenTypeProps.test.ts +45 -0
  77. package/Components/ScreenResolver/utils/getScreenTypeProps.ts +43 -0
  78. package/Components/ScreenResolver/utils/index.ts +1 -0
  79. package/Components/ScreenResolver/withDefaultScreenContext.tsx +16 -0
  80. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +25 -0
  81. package/Components/ScreenResolverFeedProvider/__tests__/ScreenResolverFeedProvider.test.tsx +44 -0
  82. package/Components/ScreenResolverFeedProvider/index.ts +1 -0
  83. package/Components/ScreenRevealManager/Overlay.tsx +34 -0
  84. package/Components/ScreenRevealManager/ScreenRevealManager.ts +8 -40
  85. package/Components/ScreenRevealManager/__tests__/Overlay.test.tsx +88 -0
  86. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +69 -86
  87. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +10 -18
  88. package/Components/Tabs/TabContent.tsx +4 -7
  89. package/Components/Transitioner/Scene.tsx +9 -15
  90. package/Components/Transitioner/index.js +3 -3
  91. package/Components/VideoLive/LiveImageManager.ts +209 -53
  92. package/Components/VideoLive/PlayerLiveImageComponent.tsx +32 -32
  93. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +2 -17
  94. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +5 -5
  95. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +7 -15
  96. package/Components/VideoModal/utils.ts +16 -13
  97. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  98. package/Components/ZappFrameworkComponents/BarView/BarView.tsx +12 -5
  99. package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +2 -2
  100. package/Components/ZappUIComponent/index.tsx +12 -6
  101. package/Components/index.js +1 -1
  102. package/Contexts/CachedDimensionsContext/index.ts +17 -2
  103. package/Contexts/ScreenContext/__tests__/index.test.tsx +57 -0
  104. package/Contexts/ScreenContext/index.tsx +64 -26
  105. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  106. package/Contexts/ZappPipesContext/ZappPipesContextFactory.tsx +18 -7
  107. package/Decorators/Analytics/index.tsx +5 -6
  108. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +0 -1
  109. package/Decorators/ConfigurationWrapper/const.ts +0 -1
  110. package/Decorators/ZappPipesDataConnector/ResolverSelector.tsx +25 -7
  111. package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +212 -5
  112. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  113. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  114. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
  115. package/Helpers/ComponentCellSelectionHelper/index.js +0 -6
  116. package/Helpers/DataSourceHelper/index.js +19 -0
  117. package/Helpers/index.js +7 -40
  118. package/package.json +5 -5
  119. package/Components/Layout/TV/LayoutBackground.tsx +0 -31
  120. package/Components/MasterCell/DefaultComponents/Text/utils/__tests__/withAdjustedLineHeight.test.ts +0 -46
  121. package/Components/MasterCell/DefaultComponents/Text/utils/index.ts +0 -21
  122. package/Components/PlayerContainer/ErrorDisplay/ErrorDisplay.tsx +0 -57
  123. package/Components/PlayerContainer/ErrorDisplay/index.ts +0 -9
  124. package/Components/PlayerContainer/useRestrictMobilePlayback.tsx +0 -101
  125. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +0 -30
  126. package/Components/River/TV/utils/index.ts +0 -4
  127. package/Components/River/TV/withFocusableGroupForContent.tsx +0 -71
  128. package/Helpers/Analytics/index.js +0 -95
  129. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +0 -80
  130. package/Helpers/DataSourceHelper/index.ts +0 -19
  131. /package/Components/HookRenderer/{index.tsx → index.ts} +0 -0
@@ -0,0 +1,286 @@
1
+ import * as React from "react";
2
+ import { render } from "@testing-library/react-native";
3
+
4
+ jest.mock(
5
+ "@applicaster/zapp-react-native-utils/appUtils/playerManager",
6
+ () => ({
7
+ playerManager: {
8
+ invokeHandler: jest.fn(),
9
+ getPluginConfiguration: jest.fn(() => ({})),
10
+ isRegistered: jest.fn(() => true),
11
+ on: jest.fn().mockReturnThis(),
12
+ removeHandler: jest.fn().mockReturnThis(),
13
+ getActivePlayer: jest.fn(() => null),
14
+ closeNativePlayer: jest.fn(),
15
+ },
16
+ })
17
+ );
18
+
19
+ jest.mock("@applicaster/zapp-react-native-utils/reactUtils", () => ({
20
+ isTV: jest.fn(() => false),
21
+ isAndroidTVPlatform: jest.fn(() => false),
22
+ isTvOSPlatform: jest.fn(() => false),
23
+ platformSelect: jest.fn(({ native }) => native),
24
+ }));
25
+
26
+ jest.mock("@applicaster/zapp-react-native-utils/playerUtils", () => ({
27
+ isAudioItem: jest.fn(() => false),
28
+ isInlineTV: jest.fn(() => false),
29
+ }));
30
+
31
+ jest.mock(
32
+ "@applicaster/zapp-react-native-tvos-ui-components/Components/TVEventHandlerComponent",
33
+ () => ({
34
+ TVEventHandlerComponent: ({ children }) => children,
35
+ })
36
+ );
37
+
38
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/utils", () => ({
39
+ usePrevious: jest.fn(),
40
+ }));
41
+
42
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks", () => {
43
+ const navigator = {
44
+ closeVideoModal: jest.fn(),
45
+ minimiseVideoModal: jest.fn(),
46
+ maximiseVideoModal: jest.fn(),
47
+ fullscreenVideoModal: jest.fn(),
48
+ isVideoModalDocked: jest.fn(() => false),
49
+ goBack: jest.fn(),
50
+ replaceTop: jest.fn(),
51
+ push: jest.fn(),
52
+ setPlayNextOverlay: jest.fn(),
53
+ currentRoute: "/playable/entry-1",
54
+ };
55
+
56
+ const backHandlerRef: { current: (() => boolean) | null } = {
57
+ current: null,
58
+ };
59
+
60
+ return {
61
+ useBackHandler: jest.fn((cb) => {
62
+ backHandlerRef.current = cb;
63
+ }),
64
+ useNavigation: jest.fn(() => navigator),
65
+ __navigator: navigator,
66
+ __backHandlerRef: backHandlerRef,
67
+ };
68
+ });
69
+
70
+ jest.mock("@applicaster/zapp-react-native-bridge/QuickBrick", () => ({
71
+ QUICK_BRICK_EVENTS: {
72
+ IDLE_TIMER_DISABLED: "IDLE_TIMER_DISABLED",
73
+ MOVE_APP_TO_BACKGROUND: "MOVE_APP_TO_BACKGROUND",
74
+ },
75
+ sendQuickBrickEvent: jest.fn(),
76
+ }));
77
+
78
+ jest.mock("../ProgramInfo", () => ({
79
+ ProgramInfo: () => null,
80
+ }));
81
+
82
+ jest.mock("../../AudioPlayer", () => ({
83
+ AudioPlayer: () => null,
84
+ }));
85
+
86
+ jest.mock("../logger", () => ({
87
+ log_debug: jest.fn(),
88
+ log_info: jest.fn(),
89
+ log_warning: jest.fn(),
90
+ playerContainerLogger: { error: jest.fn() },
91
+ }));
92
+
93
+ jest.mock(
94
+ "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer",
95
+ () => {
96
+ const player = {
97
+ addListener: jest.fn(),
98
+ removeListener: jest.fn(),
99
+ isPaused: jest.fn(() => false),
100
+ isAd: jest.fn(() => false),
101
+ seekTo: jest.fn(),
102
+ getPluginConfiguration: jest.fn(() => ({})),
103
+ getOverlayObservable: jest.fn(() => ({ getPlayNextEntry: () => null })),
104
+ };
105
+
106
+ return {
107
+ usePlayer: jest.fn(() => player),
108
+ __player: player,
109
+ };
110
+ }
111
+ );
112
+
113
+ jest.mock(
114
+ "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayNextOverlay",
115
+ () => ({
116
+ usePlayNextOverlay: jest.fn(() => null),
117
+ })
118
+ );
119
+
120
+ jest.mock(
121
+ "@applicaster/zapp-react-native-utils/appUtils/playerManager/playerNativeCommand",
122
+ () => ({
123
+ PlayerNativeCommandTypes: { clearPlayerData: "clearPlayerData" },
124
+ PlayerNativeSendCommand: jest.fn(),
125
+ })
126
+ );
127
+
128
+ jest.mock(
129
+ "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext",
130
+ () => ({
131
+ useSetNavbarState: jest.fn(() => ({ setVisible: jest.fn() })),
132
+ })
133
+ );
134
+
135
+ jest.mock(
136
+ "@applicaster/zapp-react-native-utils/reactHooks/screen/useTargetScreenData",
137
+ () => ({
138
+ useTargetScreenData: jest.fn(() => ({ id: "screen-id", styles: {} })),
139
+ })
140
+ );
141
+
142
+ jest.mock("../PlayerContainerContext", () => {
143
+ const ReactActual = require("react");
144
+
145
+ return {
146
+ PlayerContainerContext: ReactActual.createContext({
147
+ isLanguageOverlayVisible: false,
148
+ bottomFocusableId: "bottom",
149
+ showComponentsContainer: false,
150
+ refs: null,
151
+ }),
152
+ PlayerContainerContextProvider: ({ children }) => children,
153
+ };
154
+ });
155
+
156
+ jest.mock(
157
+ "@applicaster/zapp-react-native-ui-components/Components/FocusableGroup",
158
+ () => ({
159
+ FocusableGroup: ({ children }) => children,
160
+ })
161
+ );
162
+
163
+ jest.mock("../WappersView/PlayerFocusableWrapperView", () => ({
164
+ PlayerFocusableWrapperView: ({ children }) => children,
165
+ }));
166
+
167
+ jest.mock("../WappersView/ComponentFocusableWrapperView", () => ({
168
+ ComponentFocusableWrapperView: () => null,
169
+ }));
170
+
171
+ jest.mock("../index", () => ({
172
+ FocusableGroupMainContainerId: "player-container-general",
173
+ }));
174
+
175
+ jest.mock(
176
+ "@applicaster/zapp-react-native-utils/navigationUtils/itemTypeMatchers",
177
+ () => ({
178
+ isPlayable: jest.fn(() => true),
179
+ })
180
+ );
181
+
182
+ jest.mock("../../GeneralContentScreen", () => ({
183
+ GeneralContentScreen: () => null,
184
+ }));
185
+
186
+ jest.mock("@applicaster/zapp-react-native-redux/hooks", () => ({
187
+ usePickFromState: jest.fn(() => ({
188
+ appData: { isTabletPortrait: false },
189
+ })),
190
+ }));
191
+
192
+ import { PlayerContainer, VideoModalMode } from "../PlayerContainer";
193
+
194
+ const { __navigator: mockNavigator, __backHandlerRef: mockBackHandlerRef } =
195
+ jest.requireMock("@applicaster/zapp-react-native-utils/reactHooks");
196
+
197
+ const { playerManager: mockPlayerManager } = jest.requireMock(
198
+ "@applicaster/zapp-react-native-utils/appUtils/playerManager"
199
+ );
200
+
201
+ const { __player: mockPlayer } = jest.requireMock(
202
+ "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer"
203
+ );
204
+
205
+ const Player = React.forwardRef(() => null);
206
+
207
+ const item = {
208
+ id: "entry-1",
209
+ title: "Test entry",
210
+ summary: "",
211
+ content: { src: "https://example.com/video.m3u8" },
212
+ extensions: {},
213
+ } as any;
214
+
215
+ const renderPlayerContainer = (props = {}) =>
216
+ render(
217
+ <PlayerContainer
218
+ Player={Player}
219
+ item={item}
220
+ style={{}}
221
+ loadPipesData={jest.fn()}
222
+ isModal={true}
223
+ mode={VideoModalMode.MAXIMIZED}
224
+ {...props}
225
+ />
226
+ );
227
+
228
+ const pressHardwareBack = () => {
229
+ if (!mockBackHandlerRef.current) {
230
+ throw new Error("back handler was not registered via useBackHandler");
231
+ }
232
+
233
+ return mockBackHandlerRef.current();
234
+ };
235
+
236
+ describe("PlayerContainer hardware back handling", () => {
237
+ beforeEach(() => {
238
+ jest.clearAllMocks();
239
+ mockBackHandlerRef.current = null;
240
+ mockPlayer.getPluginConfiguration.mockReturnValue({});
241
+ mockPlayer.isPaused.mockReturnValue(false);
242
+ mockPlayer.isAd.mockReturnValue(false);
243
+
244
+ mockPlayer.getOverlayObservable.mockReturnValue({
245
+ getPlayNextEntry: () => null,
246
+ });
247
+
248
+ mockPlayerManager.getPluginConfiguration.mockReturnValue({});
249
+ mockPlayerManager.on.mockReturnThis();
250
+ mockPlayerManager.removeHandler.mockReturnThis();
251
+ });
252
+
253
+ it("minimises the video modal on back press when mini player is enabled", () => {
254
+ renderPlayerContainer();
255
+
256
+ const handled = pressHardwareBack();
257
+
258
+ expect(mockNavigator.minimiseVideoModal).toHaveBeenCalled();
259
+ expect(mockNavigator.closeVideoModal).not.toHaveBeenCalled();
260
+ expect(handled).toBe(true);
261
+ });
262
+
263
+ it("closes the video modal on back press when mini player is disabled", () => {
264
+ mockPlayer.getPluginConfiguration.mockReturnValue({
265
+ disable_mini_player_when_inline: true,
266
+ });
267
+
268
+ renderPlayerContainer();
269
+
270
+ const handled = pressHardwareBack();
271
+
272
+ expect(mockNavigator.closeVideoModal).toHaveBeenCalled();
273
+ expect(mockNavigator.minimiseVideoModal).not.toHaveBeenCalled();
274
+ expect(handled).toBe(true);
275
+ });
276
+
277
+ it("does not handle back press when the player is already minimised", () => {
278
+ renderPlayerContainer({ mode: VideoModalMode.MINIMIZED });
279
+
280
+ const handled = pressHardwareBack();
281
+
282
+ expect(handled).toBe(false);
283
+ expect(mockNavigator.minimiseVideoModal).not.toHaveBeenCalled();
284
+ expect(mockNavigator.closeVideoModal).not.toHaveBeenCalled();
285
+ });
286
+ });
@@ -0,0 +1,26 @@
1
+ import React from "react";
2
+ import { Text } from "react-native";
3
+ import { render } from "@testing-library/react-native";
4
+ import { PreloaderWrapper } from "..";
5
+
6
+ describe("PreloaderWrapper", () => {
7
+ it("renders children when preloader is hidden", () => {
8
+ const { getByText } = render(
9
+ <PreloaderWrapper showPreloader={false}>
10
+ <Text>content</Text>
11
+ </PreloaderWrapper>
12
+ );
13
+
14
+ expect(getByText("content")).toBeDefined();
15
+ });
16
+
17
+ it("renders null when preloader is shown", () => {
18
+ const { queryByText } = render(
19
+ <PreloaderWrapper showPreloader>
20
+ <Text>content</Text>
21
+ </PreloaderWrapper>
22
+ );
23
+
24
+ expect(queryByText("content")).toBeNull();
25
+ });
26
+ });
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+
3
+ type PreloaderWrapperProps = {
4
+ showPreloader?: boolean;
5
+ children?: React.ReactNode;
6
+ };
7
+
8
+ export const PreloaderWrapper: React.FC<PreloaderWrapperProps> = ({
9
+ showPreloader = false,
10
+ children,
11
+ }) => {
12
+ return !showPreloader ? children : null;
13
+ };
14
+
15
+ export default PreloaderWrapper;
@@ -281,7 +281,6 @@ function ComponentsMapComponent(props: Props) {
281
281
  >
282
282
  <ViewportTracker>
283
283
  <FlatList
284
- testID="components-map-flat-list"
285
284
  ref={(ref) => {
286
285
  flatListRef.current = ref;
287
286
  }}
@@ -313,10 +312,10 @@ function ComponentsMapComponent(props: Props) {
313
312
  onScrollEndDrag={_onScrollEndDrag}
314
313
  scrollEventThrottle={16}
315
314
  {...scrollViewExtraProps}
315
+ /* When wrapped in a parent ScrollView (e.g. tabs),
316
+ this FlatList doesn't scroll so onEndReached can fire repeatedly;
317
+ skip it here and let the parent ScrollView emit scroll-end instead. */
316
318
  onEndReached={
317
- /* When wrapped in a parent ScrollView (e.g. tabs),
318
- this FlatList doesn't scroll so onEndReached can fire repeatedly;
319
- skip it here and let the parent ScrollView emit scroll-end instead. */
320
319
  isScreenWrappedInContainer
321
320
  ? undefined
322
321
  : () => {
@@ -1,4 +1,4 @@
1
- import { renderHook, act } from "@testing-library/react-native";
1
+ import { renderHook, act } from "@testing-library/react-hooks";
2
2
  import { BehaviorSubject } from "rxjs";
3
3
  import { useLoadingState } from "../useLoadingState";
4
4
 
@@ -5,6 +5,8 @@ import {
5
5
  StyleSheet,
6
6
  } from "react-native";
7
7
  import * as R from "ramda";
8
+ import { path } from "@applicaster/zapp-react-native-utils/utils";
9
+ import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
8
10
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
9
11
  import { useLocalizedStrings } from "@applicaster/zapp-react-native-utils/localizationUtils";
10
12
  import { useAnalytics } from "@applicaster/zapp-react-native-utils/analyticsUtils";
@@ -62,9 +64,13 @@ export const usePullToRefresh = (
62
64
 
63
65
  const [refreshing, setRefreshing] = React.useState(false);
64
66
 
65
- const feeds: string[] =
66
- riverComponents?.map(R.path(["data", "source"])).filter((feed) => !!feed) ??
67
- [];
67
+ const feeds: string[] = React.useMemo(
68
+ () =>
69
+ (riverComponents || [])
70
+ .map((riverComponent) => path(["data", "source"], riverComponent))
71
+ .filter((feed) => !isNilOrEmpty(feed)),
72
+ [riverComponents]
73
+ );
68
74
 
69
75
  const feedsLength = feeds.length;
70
76
 
@@ -14,6 +14,7 @@ import { tvPluginsWithCellRenderer } from "../../const";
14
14
  import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
15
15
  import type { BehaviorSubject } from "rxjs";
16
16
  import { useCallbackActions } from "@applicaster/zapp-react-native-utils/zappFrameworkUtils/HookCallback/useCallbackActions";
17
+ import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
17
18
 
18
19
  export type RiverItemType = {
19
20
  item: ZappUIComponent;
@@ -112,33 +113,38 @@ function RiverItemComponent(props: RiverItemType) {
112
113
  CellRenderer = undefined;
113
114
  }
114
115
 
115
- React.useEffect(() => {
116
- riverLogger.log({
117
- message: "mounting component",
118
- data: { item, feedUrl, Component, CellRenderer },
119
- jsOnly: true,
120
- });
116
+ const isComponentMissing = isNilOrEmpty(Component);
121
117
 
122
- if (!CellRenderer && !isGroup(item)) {
118
+ /**
119
+ * TODO: Move this plugin existence check further up the stack (before ComponentsMap).
120
+ * Filtering items at the list-rendering or data-processing level would prevent
121
+ * mounting RiverItem entirely for missing components.
122
+ */
123
+ React.useEffect(() => {
124
+ if (isComponentMissing) {
123
125
  riverLogger.warning({
124
- message: "Cell Renderer is null - will fallback to default cell",
125
- data: { item, CellRenderer },
126
+ message: `Component ${item.component_type} is null - skipping rendering`,
127
+ });
128
+
129
+ onLoadFinished(index);
130
+ } else {
131
+ riverLogger.log({
132
+ message: "mounting component",
133
+ data: { item, feedUrl, Component, CellRenderer },
126
134
  jsOnly: true,
127
135
  });
136
+
137
+ if (!CellRenderer && !isGroup(item)) {
138
+ riverLogger.warning({
139
+ message: "Cell Renderer is null - will fallback to default cell",
140
+ data: { item, CellRenderer },
141
+ jsOnly: true,
142
+ });
143
+ }
128
144
  }
129
145
  }, []);
130
146
 
131
- if (!readyToBeDisplayed) {
132
- return null;
133
- }
134
-
135
- if (Component === null || typeof Component === "undefined") {
136
- riverLogger.warning({
137
- message: `Component ${item.component_type} is null - skipping rendering`,
138
- });
139
-
140
- onLoadFinished(index);
141
-
147
+ if (!readyToBeDisplayed || isComponentMissing) {
142
148
  return null;
143
149
  }
144
150
 
@@ -2,8 +2,7 @@
2
2
 
3
3
  import * as React from "react";
4
4
  import { Text } from "react-native";
5
-
6
- import { mergeRight } from "@applicaster/zapp-react-native-utils/utils";
5
+ import * as R from "ramda";
7
6
 
8
7
  import { GeneralContentScreen } from "../../GeneralContentScreen";
9
8
  import { ScreenResolver } from "@applicaster/zapp-react-native-ui-components/Components/ScreenResolver";
@@ -14,8 +13,6 @@ import {
14
13
  } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
15
14
  import { useRivers } from "@applicaster/zapp-react-native-utils/reactHooks/state";
16
15
 
17
- import { toStringOrEmpty } from "./utils";
18
-
19
16
  type Props = {
20
17
  screenId: string;
21
18
  screenData: ZappRiver | ZappEntry;
@@ -27,7 +24,6 @@ type Props = {
27
24
  isInsideContainer?: boolean;
28
25
  extraAnchorPointYOffset: number;
29
26
  river?: ZappRiver | ZappEntry;
30
- groupId: string;
31
27
  };
32
28
 
33
29
  export const River = (props: Props) => {
@@ -39,7 +35,6 @@ export const River = (props: Props) => {
39
35
  componentsMapExtraProps,
40
36
  isInsideContainer,
41
37
  extraAnchorPointYOffset,
42
- groupId,
43
38
  } = props;
44
39
 
45
40
  const { title: screenTitle, summary: screenSummary } = useNavbarState();
@@ -56,41 +51,28 @@ export const River = (props: Props) => {
56
51
  [screenId]
57
52
  );
58
53
 
59
- const screenResolverData = React.useMemo(() => {
60
- const extraData = mergeRight(extraProps, screenResolverExtraProps);
61
-
62
- return {
63
- extraData,
64
- screenData: mergeRight(river, { groupId: extraData?.groupId }),
65
- componentsMapExtraProps: mergeRight(componentsMapExtraProps, { groupId }),
66
- };
67
- }, [
68
- extraProps,
69
- screenResolverExtraProps,
70
- river,
71
- componentsMapExtraProps,
72
- groupId,
73
- ]);
54
+ const stringOrEmpty = (value: string | number | undefined): string =>
55
+ R.isNil(value) ? "" : String(value);
74
56
 
75
57
  React.useEffect(() => {
76
58
  if (!isInsideContainer) {
77
- setScreenTitle(toStringOrEmpty(screenData?.title));
78
- setScreenSummary(toStringOrEmpty(screenData?.summary));
59
+ setScreenTitle(stringOrEmpty(screenData?.title));
60
+ setScreenSummary(stringOrEmpty(screenData?.summary));
79
61
  }
80
62
  }, [screenData.id]);
81
63
 
82
64
  React.useEffect(() => {
83
65
  if (feedData && !isInsideContainer) {
84
66
  if (feedData.title && feedData.title !== screenTitle) {
85
- setScreenTitle(toStringOrEmpty(feedData.title));
67
+ setScreenTitle(stringOrEmpty(feedData.title));
86
68
  }
87
69
 
88
70
  if (feedData.summary && feedData.summary !== screenSummary) {
89
- setScreenSummary(toStringOrEmpty(feedData.summary));
71
+ setScreenSummary(stringOrEmpty(feedData.summary));
90
72
  }
91
73
  } else {
92
- setScreenTitle(toStringOrEmpty(screenData?.title));
93
- setScreenSummary(toStringOrEmpty(screenData?.summary));
74
+ setScreenTitle(stringOrEmpty(screenData?.title));
75
+ setScreenSummary(stringOrEmpty(screenData?.summary));
94
76
  }
95
77
  }, [feedData, screenData, screenTitle, screenSummary]);
96
78
 
@@ -104,13 +86,15 @@ export const River = (props: Props) => {
104
86
  }
105
87
 
106
88
  if (river.type !== "general_content") {
89
+ const extraData = { ...R.mergeRight(extraProps, screenResolverExtraProps) };
90
+
107
91
  return (
108
92
  <ScreenResolver
109
93
  screenType={river.type}
110
94
  screenId={screenId}
111
- screenData={screenResolverData.screenData}
112
- componentsMapExtraProps={screenResolverData.componentsMapExtraProps}
113
- {...screenResolverData.extraData}
95
+ screenData={R.merge(river, { groupId: extraData?.groupId })}
96
+ componentsMapExtraProps={componentsMapExtraProps}
97
+ {...extraData}
114
98
  />
115
99
  );
116
100
  }
@@ -122,7 +106,6 @@ export const River = (props: Props) => {
122
106
  isScreenWrappedInContainer={isInsideContainer}
123
107
  extraAnchorPointYOffset={extraAnchorPointYOffset}
124
108
  componentsMapExtraProps={componentsMapExtraProps}
125
- groupId={groupId}
126
109
  />
127
110
  );
128
111
  };
@@ -1,15 +1,11 @@
1
- import { compose, identity } from "@applicaster/zapp-react-native-utils/utils";
2
- import { isTvOSPlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
3
-
1
+ import { compose } from "ramda";
4
2
  import { River as RiverComponent } from "./River";
3
+ import { withTvEventHandler } from "./withTVEventHandler";
5
4
  import { withComponentsMapOffsetContext } from "../../../Contexts/ComponentsMapOffsetContext";
6
5
  import { withRiverDataLoader } from "./withRiverDataLoader";
7
- import { withFocusableGroupForContent } from "./withFocusableGroupForContent";
8
-
9
- const isTVOS = isTvOSPlatform();
10
6
 
11
7
  export const River = compose(
8
+ withTvEventHandler,
12
9
  withComponentsMapOffsetContext,
13
- withRiverDataLoader,
14
- isTVOS ? withFocusableGroupForContent : identity
10
+ withRiverDataLoader
15
11
  )(RiverComponent);
@@ -0,0 +1,36 @@
1
+ /* eslint max-len: off */
2
+
3
+ import React from "react";
4
+ import { TVEventHandlerComponent } from "@applicaster/zapp-react-native-tvos-ui-components/Components/TVEventHandlerComponent";
5
+ import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
6
+ import { useIsStandaloneFullscreen } from "@applicaster/zapp-react-native-utils/reactHooks/screen";
7
+
8
+ export const withTvEventHandler = (Component) => {
9
+ return function WithTVEventHandler(props) {
10
+ const navigator = useNavigation();
11
+
12
+ const isStandaloneFullscreen = useIsStandaloneFullscreen();
13
+
14
+ const remoteHandler = (event) => {
15
+ const { eventType } = event;
16
+
17
+ const canGoBack = navigator.canGoBack();
18
+
19
+ if (eventType === "menu" && isStandaloneFullscreen) {
20
+ navigator.goHome();
21
+
22
+ return;
23
+ }
24
+
25
+ if (eventType === "menu" && canGoBack) {
26
+ navigator.goBack();
27
+ }
28
+ };
29
+
30
+ return (
31
+ <TVEventHandlerComponent tvEventHandler={remoteHandler}>
32
+ <Component {...props} />
33
+ </TVEventHandlerComponent>
34
+ );
35
+ };
36
+ };
@@ -10,11 +10,6 @@ exports[`componentsMap renders renders components map correctly 1`] = `
10
10
  >
11
11
  <View
12
12
  onLayout={[Function]}
13
- style={
14
- {
15
- "flex": 1,
16
- }
17
- }
18
13
  >
19
14
  <RCTScrollView
20
15
  ListFooterComponent={
@@ -155,7 +150,6 @@ exports[`componentsMap renders renders components map correctly 1`] = `
155
150
  }
156
151
  }
157
152
  stickyHeaderIndices={[]}
158
- testID="components-map-flat-list"
159
153
  viewabilityConfigCallbackPairs={[]}
160
154
  windowSize={12}
161
155
  >
@@ -167,11 +161,6 @@ exports[`componentsMap renders renders components map correctly 1`] = `
167
161
  >
168
162
  <View
169
163
  onLayout={[Function]}
170
- style={
171
- {
172
- "flex": 1,
173
- }
174
- }
175
164
  >
176
165
  <View />
177
166
  </View>
@@ -183,11 +172,6 @@ exports[`componentsMap renders renders components map correctly 1`] = `
183
172
  >
184
173
  <View
185
174
  onLayout={[Function]}
186
- style={
187
- {
188
- "flex": 1,
189
- }
190
- }
191
175
  />
192
176
  </View>
193
177
  <View