@applicaster/zapp-react-native-ui-components 15.0.0-rc.14 → 15.0.0-rc.140

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 (197) hide show
  1. package/Components/AnimatedInOut/index.tsx +69 -26
  2. package/Components/BaseFocusable/index.ios.ts +12 -2
  3. package/Components/Cell/Cell.tsx +14 -3
  4. package/Components/Cell/CellWithFocusable.tsx +9 -0
  5. package/Components/Cell/FocusableWrapper.tsx +3 -0
  6. package/Components/Cell/TvOSCellComponent.tsx +25 -6
  7. package/Components/Focusable/Focusable.tsx +4 -2
  8. package/Components/Focusable/FocusableTvOS.tsx +18 -1
  9. package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +1 -0
  10. package/Components/FocusableGroup/FocusableTvOS.tsx +32 -1
  11. package/Components/GeneralContentScreen/GeneralContentScreen.tsx +39 -28
  12. package/Components/GeneralContentScreen/__tests__/GeneralContentScreen.test.tsx +104 -0
  13. package/Components/GeneralContentScreen/utils/__tests__/getScreenDataSource.test.ts +19 -0
  14. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
  15. package/Components/GeneralContentScreen/utils/getScreenDataSource.ts +9 -0
  16. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +22 -6
  17. package/Components/HandlePlayable/HandlePlayable.tsx +33 -94
  18. package/Components/HandlePlayable/const.ts +3 -0
  19. package/Components/HandlePlayable/utils.ts +105 -0
  20. package/Components/HookRenderer/HookRenderer.tsx +40 -10
  21. package/Components/HookRenderer/__tests__/HookRenderer.test.tsx +60 -0
  22. package/Components/Layout/TV/LayoutBackground.tsx +5 -2
  23. package/Components/Layout/TV/NavBarContainer.tsx +1 -10
  24. package/Components/Layout/TV/ScreenContainer.tsx +2 -6
  25. package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +7 -12
  26. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +7 -12
  27. package/Components/Layout/TV/index.tsx +3 -4
  28. package/Components/Layout/TV/index.web.tsx +3 -4
  29. package/Components/LinkHandler/LinkHandler.tsx +2 -2
  30. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/model.test.ts +80 -0
  31. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +187 -0
  32. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/selectors.test.ts +45 -0
  33. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/style.test.ts +49 -0
  34. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/model.ts +47 -0
  35. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +170 -0
  36. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/selectors.ts +26 -0
  37. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/style.ts +29 -0
  38. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/types.ts +37 -0
  39. package/Components/MasterCell/DefaultComponents/BorderContainerView/__tests__/index.test.tsx +16 -1
  40. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +30 -2
  41. package/Components/MasterCell/DefaultComponents/Button.tsx +0 -15
  42. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +5 -1
  43. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +11 -3
  44. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +9 -1
  45. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +15 -14
  46. package/Components/MasterCell/DefaultComponents/LiveImage/__tests__/prepareEntry.test.ts +352 -0
  47. package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +136 -0
  48. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +43 -22
  49. package/Components/MasterCell/DefaultComponents/PressableView.tsx +261 -0
  50. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +40 -39
  51. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/Image.test.tsx +95 -0
  52. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +86 -0
  53. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/index.test.ts +141 -0
  54. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
  55. package/Components/MasterCell/DefaultComponents/SecondaryImage/index.ts +1 -1
  56. package/Components/MasterCell/DefaultComponents/Text/index.tsx +10 -14
  57. package/Components/MasterCell/DefaultComponents/index.ts +2 -0
  58. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +42 -0
  59. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +127 -0
  60. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ButtonContainerView.ts +23 -0
  61. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +16 -0
  62. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabel.ts +67 -0
  63. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +32 -0
  64. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +195 -0
  65. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +140 -0
  66. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +222 -0
  67. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/helpers.ts +105 -0
  68. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +104 -0
  69. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +118 -0
  70. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +73 -0
  71. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +86 -0
  72. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +35 -48
  73. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +115 -29
  74. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +39 -144
  75. package/Components/MasterCell/elementMapper.tsx +1 -0
  76. package/Components/MasterCell/hoc/__tests__/withAsyncRender.test.tsx +219 -0
  77. package/Components/MasterCell/hoc/withAsyncRender.tsx +9 -7
  78. package/Components/MasterCell/index.tsx +2 -0
  79. package/Components/MasterCell/utils/__tests__/resolveColor.test.js +82 -3
  80. package/Components/MasterCell/utils/index.ts +61 -31
  81. package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +102 -87
  82. package/Components/MeasurmentsPortal/__tests__/MeasurementsPortal.test.tsx +355 -0
  83. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +17 -9
  84. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +16 -8
  85. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +2 -2
  86. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +17 -18
  87. package/Components/OfflineHandler/NotificationView/utils.ts +34 -0
  88. package/Components/OfflineHandler/__tests__/index.test.tsx +27 -18
  89. package/Components/PlayerContainer/PlayerContainer.tsx +43 -64
  90. package/Components/PlayerImageBackground/index.tsx +3 -22
  91. package/Components/PreloaderWrapper/__tests__/index.test.tsx +26 -0
  92. package/Components/PreloaderWrapper/index.tsx +15 -0
  93. package/Components/River/ComponentsMap/ComponentsMap.tsx +18 -16
  94. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  95. package/Components/River/RefreshControl.tsx +19 -82
  96. package/Components/River/River.tsx +9 -82
  97. package/Components/River/RiverItem.tsx +26 -20
  98. package/Components/River/TV/River.tsx +31 -14
  99. package/Components/River/TV/index.tsx +8 -4
  100. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +30 -0
  101. package/Components/River/TV/utils/index.ts +4 -0
  102. package/Components/River/TV/withFocusableGroupForContent.tsx +71 -0
  103. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +2 -0
  104. package/Components/River/__tests__/componentsMap.test.js +38 -0
  105. package/Components/River/hooks/__tests__/usePullToRefresh.test.ts +132 -0
  106. package/Components/River/hooks/index.ts +1 -0
  107. package/Components/River/hooks/usePullToRefresh.ts +51 -0
  108. package/Components/Screen/TV/index.web.tsx +4 -2
  109. package/Components/Screen/__tests__/Screen.test.tsx +66 -42
  110. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +68 -44
  111. package/Components/Screen/hooks.ts +75 -6
  112. package/Components/Screen/index.tsx +9 -4
  113. package/Components/Screen/navigationHandler.ts +49 -24
  114. package/Components/Screen/orientationHandler.ts +10 -13
  115. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +46 -0
  116. package/Components/ScreenFeedLoader/__tests__/ScreenFeedLoader.test.tsx +94 -0
  117. package/Components/ScreenFeedLoader/index.ts +1 -0
  118. package/Components/ScreenResolver/__tests__/screenResolver.test.js +24 -0
  119. package/Components/ScreenResolver/hooks/index.ts +3 -0
  120. package/Components/ScreenResolver/hooks/useGetComponent.ts +15 -0
  121. package/Components/ScreenResolver/hooks/useScreenComponentResolver.tsx +90 -0
  122. package/Components/ScreenResolver/index.tsx +15 -111
  123. package/Components/ScreenResolver/utils/__tests__/getScreenTypeProps.test.ts +45 -0
  124. package/Components/ScreenResolver/utils/getScreenTypeProps.ts +43 -0
  125. package/Components/ScreenResolver/utils/index.ts +1 -0
  126. package/Components/ScreenResolver/withDefaultScreenContext.tsx +16 -0
  127. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +25 -0
  128. package/Components/ScreenResolverFeedProvider/__tests__/ScreenResolverFeedProvider.test.tsx +44 -0
  129. package/Components/ScreenResolverFeedProvider/index.ts +1 -0
  130. package/Components/ScreenRevealManager/ScreenRevealManager.ts +40 -8
  131. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +86 -69
  132. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +44 -26
  133. package/Components/Tabs/TV/Tabs.tsx +20 -3
  134. package/Components/Tabs/TabContent.tsx +7 -4
  135. package/Components/TopCutoffOverlay/hooks/__tests__/useMarginTop.test.ts +130 -0
  136. package/Components/TopCutoffOverlay/hooks/index.ts +1 -0
  137. package/Components/TopCutoffOverlay/hooks/useMarginTop.ts +59 -0
  138. package/Components/TopCutoffOverlay/index.tsx +55 -0
  139. package/Components/Transitioner/Scene.tsx +10 -3
  140. package/Components/Transitioner/index.js +3 -3
  141. package/Components/VideoLive/LiveImageManager.ts +199 -54
  142. package/Components/VideoLive/PlayerLiveImageComponent.tsx +31 -33
  143. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +2 -17
  144. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +1 -0
  145. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +118 -171
  146. package/Components/VideoModal/ModalAnimation/index.ts +2 -13
  147. package/Components/VideoModal/ModalAnimation/utils.ts +1 -327
  148. package/Components/VideoModal/PlayerWrapper.tsx +14 -88
  149. package/Components/VideoModal/VideoModal.tsx +1 -5
  150. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -0
  151. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
  152. package/Components/VideoModal/hooks/useModalSize.ts +10 -5
  153. package/Components/VideoModal/playerWrapperStyle.ts +70 -0
  154. package/Components/VideoModal/playerWrapperUtils.ts +91 -0
  155. package/Components/VideoModal/utils.ts +19 -9
  156. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
  157. package/Components/Viewport/ViewportAware/index.tsx +16 -7
  158. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  159. package/Components/ZappUIComponent/index.tsx +12 -6
  160. package/Components/default-cell-renderer/viewTrees/mobile/index.ts +0 -3
  161. package/Components/index.js +1 -1
  162. package/Contexts/ScreenContext/__tests__/index.test.tsx +57 -0
  163. package/Contexts/ScreenContext/index.tsx +71 -19
  164. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  165. package/Contexts/ZappHookModalContext/index.tsx +37 -61
  166. package/Contexts/ZappPipesContext/ZappPipesContextFactory.tsx +18 -7
  167. package/Contexts/index.ts +0 -2
  168. package/Decorators/Analytics/index.tsx +6 -5
  169. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +1 -0
  170. package/Decorators/ConfigurationWrapper/const.ts +1 -0
  171. package/Decorators/ZappPipesDataConnector/ResolverSelector.tsx +25 -7
  172. package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +212 -5
  173. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
  174. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  175. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  176. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
  177. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
  178. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +80 -0
  179. package/Helpers/DataSourceHelper/index.ts +19 -0
  180. package/events/index.ts +3 -0
  181. package/events/scrollEndReached.ts +15 -0
  182. package/index.d.ts +7 -0
  183. package/package.json +6 -5
  184. package/Components/MasterCell/DefaultComponents/Text/utils/__tests__/withAdjustedLineHeight.test.ts +0 -46
  185. package/Components/MasterCell/DefaultComponents/Text/utils/index.ts +0 -21
  186. package/Components/PlayerContainer/ErrorDisplay/ErrorDisplay.tsx +0 -57
  187. package/Components/PlayerContainer/ErrorDisplay/index.ts +0 -9
  188. package/Components/River/TV/withTVEventHandler.tsx +0 -27
  189. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +0 -60
  190. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +0 -417
  191. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.web.tsx +0 -294
  192. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +0 -176
  193. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.web.tsx +0 -93
  194. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +0 -500
  195. package/Components/VideoModal/ModalAnimation/__tests__/getMoveUpValue.test.ts +0 -108
  196. package/Helpers/DataSourceHelper/index.js +0 -19
  197. /package/Components/HookRenderer/{index.tsx → index.ts} +0 -0
@@ -1,173 +1,68 @@
1
- import * as R from "ramda";
2
-
1
+ import memoizee from "memoizee";
3
2
  import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
4
- import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
3
+ import { getEnabledButtonSlots } from "../../../ActionButtonsCore/selectors";
4
+ import {
5
+ insertBetweenLabelContainers,
6
+ insertBetweenLabels,
7
+ } from "../../../ActionButtonsCore/placement";
5
8
 
6
9
  export const getButtonsCount = (
7
10
  configuration: Record<string, unknown>,
8
11
  prefix: string
9
12
  ): number => {
10
- const button1Key = `${prefix}_button_1_button_enabled`;
11
- const button2Key = `${prefix}_button_2_button_enabled`;
12
- const button3Key = `${prefix}_button_3_button_enabled`;
13
-
14
- return R.toPairs(configuration).filter(
15
- ([key, value]) =>
16
- [button1Key, button2Key, button3Key].includes(key) && value
17
- ).length;
13
+ return getEnabledButtonSlots(configuration, `${prefix}_button`).length;
18
14
  };
19
15
 
20
16
  export const getPluginIdentifier = (
21
17
  configuration: Record<string, unknown>,
22
18
  prefix: string,
23
- index: number
24
- ): string => {
25
- const match = `${prefix}_button_\\d_assign_action`;
26
- const re = new RegExp(match);
27
-
28
- const rejectNils = R.compose(R.path([index]), R.filter(isNotNil));
29
-
30
- return rejectNils(
31
- R.toPairs(configuration)
32
- .filter(([key]) => R.startsWith(`${prefix}_button`, key))
33
- .map(([key, value]) => {
34
- const matched = key.match(re);
35
-
36
- return matched ? value : undefined;
37
- })
38
- );
39
- };
40
-
41
- type Label = {
42
- name: string;
43
- };
19
+ slot: number
20
+ ): string | undefined => {
21
+ const value = configuration[
22
+ `${prefix}_button_${slot}_assign_action`
23
+ ] as string;
44
24
 
45
- type LabelContainer = {
46
- elements: Array<Label>;
25
+ return value == null || value === "" ? undefined : value;
47
26
  };
48
27
 
49
- type LabelExtraContainer = {
50
- elements: Array<LabelContainer>;
51
- };
52
-
53
- const withButtons = (labelName, buttons, labels) => {
54
- return labels.reduce((acc, label) => {
55
- if (label.name === labelName) {
56
- return [...acc, label, buttons];
57
- }
58
-
59
- return [...acc, label];
60
- }, []);
61
- };
62
-
63
- const withButtonsInContainer = (labelName, buttons, views) => {
64
- return views.map((view) => {
65
- return {
66
- ...view,
67
- elements: view.elements.reduce((acc, label) => {
68
- if (label.name === labelName) {
69
- return [...acc, label, buttons];
70
- }
71
-
72
- return [...acc, label];
73
- }, []),
74
- };
75
- });
76
- };
28
+ export const memoizedGetPluginIdentifier = memoizee(getPluginIdentifier);
77
29
 
78
30
  export const insertButtonsBetweenLabels = (
79
31
  configuration: Record<string, unknown>,
80
32
  buttons,
81
- labels: Label[] = []
82
- ) => {
83
- if (R.isNil(buttons)) {
84
- return labels;
85
- }
86
-
87
- const position = R.pathOr(
88
- undefined,
89
- ["tv_buttons_container_position"],
90
- configuration
33
+ labels = []
34
+ ) =>
35
+ insertBetweenLabels(
36
+ {
37
+ position: configuration.tv_buttons_container_position as
38
+ | string
39
+ | undefined,
40
+ allowOnTop: true,
41
+ appendWhenMissing: true,
42
+ },
43
+ buttons,
44
+ labels
91
45
  );
92
46
 
93
- if (position === "on_top") {
94
- return [buttons, ...labels];
95
- }
96
-
97
- const labelsWithButtons = withButtons(position, buttons, labels);
98
-
99
- if (R.equals(labelsWithButtons, labels)) {
100
- // put buttons after all labels by default
101
- return [...labels, buttons];
102
- }
103
-
104
- return labelsWithButtons;
105
- };
106
-
107
47
  export const insertButtonsBetweenLabelContainers = (
108
48
  configuration: Record<string, unknown>,
109
49
  buttons,
110
- labelContainers: Array<LabelExtraContainer> = []
111
- ) => {
112
- if (R.isNil(buttons)) {
113
- return labelContainers;
114
- }
115
-
116
- if (R.isEmpty(labelContainers)) {
117
- return [buttons];
118
- }
119
-
120
- const position = R.pathOr(
121
- undefined,
122
- ["tv_buttons_container_position"],
123
- configuration
50
+ labelContainers = []
51
+ ) =>
52
+ insertBetweenLabelContainers(
53
+ {
54
+ position: configuration.tv_buttons_container_position as
55
+ | string
56
+ | undefined,
57
+ allowOnTop: true,
58
+ appendWhenMissing: true,
59
+ },
60
+ buttons,
61
+ labelContainers
124
62
  );
125
63
 
126
- if (position === "on_top") {
127
- const firstContainerLabels = R.lensPath([0, "elements"]);
128
-
129
- return R.over(firstContainerLabels, R.prepend(buttons), labelContainers);
130
- }
131
-
132
- const labelContainersWithButtons = labelContainers.map((labelContainer) => {
133
- return {
134
- ...labelContainer,
135
- elements: withButtonsInContainer(
136
- position,
137
- buttons,
138
- labelContainer.elements
139
- ),
140
- };
141
- });
142
-
143
- if (R.equals(labelContainersWithButtons, labelContainers)) {
144
- // put buttons in last container
145
- const lastContainerLabels = R.lensPath([
146
- labelContainers.length - 1,
147
- "elements",
148
- ]);
149
-
150
- return R.over(lastContainerLabels, R.append(buttons), labelContainers);
151
- }
152
-
153
- return labelContainersWithButtons;
154
- };
155
-
156
- export const mapSelfAlignment = (horizontalPosition) => {
157
- switch (horizontalPosition) {
158
- case "left":
159
- return "flex-start";
160
- case "right":
161
- return "flex-end";
162
- case "center":
163
- return "center";
164
- default:
165
- return "flex-start";
166
- }
167
- };
168
-
169
64
  export const getTextTransform = (textTransform: string) => {
170
- if (R.isNil(textTransform) || R.isEmpty(textTransform)) {
65
+ if (textTransform == null || textTransform === "") {
171
66
  return "none";
172
67
  }
173
68
 
@@ -19,6 +19,7 @@ const focusableTypes = new Set([
19
19
  "View",
20
20
  "ButtonContainerView",
21
21
  "FocusableView",
22
+ "PressableView",
22
23
  "CollapsibleTextContainer",
23
24
  "BorderContainerView",
24
25
  ]);
@@ -0,0 +1,219 @@
1
+ import React from "react";
2
+ import { render } from "@testing-library/react-native";
3
+ import { Text, View } from "react-native";
4
+ import { withAsyncRenderHOC } from "../withAsyncRender";
5
+
6
+ describe("withAsyncRenderHOC", () => {
7
+ // Test component that requires onAsyncRender
8
+ const TestComponent = ({
9
+ onAsyncRender,
10
+ text,
11
+ }: {
12
+ onAsyncRender: () => void;
13
+ text?: string;
14
+ }) => {
15
+ React.useEffect(() => {
16
+ onAsyncRender();
17
+ }, [onAsyncRender]);
18
+
19
+ return (
20
+ <View>
21
+ <Text>{text || "Test"}</Text>
22
+ </View>
23
+ );
24
+ };
25
+
26
+ it("should wrap component and provide onAsyncRender callback", () => {
27
+ const WrappedComponent = withAsyncRenderHOC(TestComponent);
28
+
29
+ const wrapper = render(<WrappedComponent text="Hello" />);
30
+
31
+ expect(wrapper.toJSON()).not.toBeNull();
32
+ expect(wrapper.getByText("Hello")).toBeDefined();
33
+ });
34
+
35
+ it("should call emitAsyncElementRegistrate on mount", () => {
36
+ const mockRegistrate = jest.fn();
37
+ const WrappedComponent = withAsyncRenderHOC(TestComponent);
38
+
39
+ render(<WrappedComponent emitAsyncElementRegistrate={mockRegistrate} />);
40
+
41
+ expect(mockRegistrate).toHaveBeenCalledTimes(1);
42
+ });
43
+
44
+ it("should call emitAsyncElementLayout when onAsyncRender is called", () => {
45
+ const mockLayout = jest.fn();
46
+
47
+ const ComponentWithAsyncCall = ({
48
+ onAsyncRender,
49
+ }: {
50
+ onAsyncRender: () => void;
51
+ }) => {
52
+ return (
53
+ <View onLayout={onAsyncRender}>
54
+ <Text>Async Component</Text>
55
+ </View>
56
+ );
57
+ };
58
+
59
+ const WrappedComponent = withAsyncRenderHOC(ComponentWithAsyncCall);
60
+
61
+ const wrapper = render(
62
+ <WrappedComponent emitAsyncElementLayout={mockLayout} />
63
+ );
64
+
65
+ // Trigger onLayout
66
+ const view = wrapper.UNSAFE_getByType(View);
67
+ view.props.onLayout();
68
+
69
+ expect(mockLayout).toHaveBeenCalled();
70
+ });
71
+
72
+ it("should return unsubscribe function from emitAsyncElementRegistrate", () => {
73
+ const mockUnsubscribe = jest.fn();
74
+ const mockRegistrate = jest.fn(() => mockUnsubscribe);
75
+ const WrappedComponent = withAsyncRenderHOC(TestComponent);
76
+
77
+ const { unmount } = render(
78
+ <WrappedComponent emitAsyncElementRegistrate={mockRegistrate} />
79
+ );
80
+
81
+ expect(mockRegistrate).toHaveBeenCalledTimes(1);
82
+
83
+ // Unmount should trigger cleanup
84
+ unmount();
85
+
86
+ expect(mockUnsubscribe).toHaveBeenCalled();
87
+ });
88
+
89
+ it("should work without emitAsyncElementRegistrate prop", () => {
90
+ const WrappedComponent = withAsyncRenderHOC(TestComponent);
91
+
92
+ const wrapper = render(<WrappedComponent text="No Registration" />);
93
+
94
+ expect(wrapper.toJSON()).not.toBeNull();
95
+ expect(wrapper.getByText("No Registration")).toBeDefined();
96
+ });
97
+
98
+ it("should work without emitAsyncElementLayout prop", () => {
99
+ const ComponentWithLayout = ({
100
+ onAsyncRender,
101
+ }: {
102
+ onAsyncRender: () => void;
103
+ }) => {
104
+ return (
105
+ <View onLayout={onAsyncRender}>
106
+ <Text>Test Layout</Text>
107
+ </View>
108
+ );
109
+ };
110
+
111
+ const WrappedComponent = withAsyncRenderHOC(ComponentWithLayout);
112
+
113
+ const wrapper = render(<WrappedComponent />);
114
+
115
+ // Should not throw when calling onLayout without emitAsyncElementLayout
116
+ const view = wrapper.UNSAFE_getByType(View);
117
+ expect(() => view.props.onLayout()).not.toThrow();
118
+ });
119
+
120
+ it("should pass through all other props to wrapped component", () => {
121
+ const ComponentWithProps = ({
122
+ customProp,
123
+ anotherProp,
124
+ }: {
125
+ onAsyncRender: () => void;
126
+ customProp: string;
127
+ anotherProp: number;
128
+ }) => {
129
+ return (
130
+ <View>
131
+ <Text>{customProp}</Text>
132
+ <Text>{anotherProp}</Text>
133
+ </View>
134
+ );
135
+ };
136
+
137
+ const WrappedComponent = withAsyncRenderHOC(ComponentWithProps);
138
+
139
+ const wrapper = render(
140
+ <WrappedComponent customProp="custom value" anotherProp={42} />
141
+ );
142
+
143
+ expect(wrapper.getByText("custom value")).toBeDefined();
144
+ expect(wrapper.getByText("42")).toBeDefined();
145
+ });
146
+
147
+ it("should call emitAsyncElementLayout multiple times if onAsyncRender is called multiple times", () => {
148
+ const mockLayout = jest.fn();
149
+
150
+ const ComponentWithMultipleCalls = ({
151
+ onAsyncRender,
152
+ }: {
153
+ onAsyncRender: () => void;
154
+ }) => {
155
+ return (
156
+ <View>
157
+ <View onLayout={onAsyncRender} testID="view1">
158
+ <Text>First</Text>
159
+ </View>
160
+ <View onLayout={onAsyncRender} testID="view2">
161
+ <Text>Second</Text>
162
+ </View>
163
+ </View>
164
+ );
165
+ };
166
+
167
+ const WrappedComponent = withAsyncRenderHOC(ComponentWithMultipleCalls);
168
+
169
+ const wrapper = render(
170
+ <WrappedComponent emitAsyncElementLayout={mockLayout} />
171
+ );
172
+
173
+ // Trigger both onLayout callbacks
174
+ const view1 = wrapper.getByTestId("view1");
175
+ const view2 = wrapper.getByTestId("view2");
176
+
177
+ view1.props.onLayout();
178
+ view2.props.onLayout();
179
+
180
+ expect(mockLayout).toHaveBeenCalledTimes(2);
181
+ });
182
+
183
+ it("should handle components that use onAsyncRender in effects", () => {
184
+ const mockLayout = jest.fn();
185
+
186
+ const ComponentWithEffect = ({
187
+ onAsyncRender,
188
+ }: {
189
+ onAsyncRender: () => void;
190
+ }) => {
191
+ React.useEffect(() => {
192
+ // Simulate async operation completing
193
+ const timer = setTimeout(() => {
194
+ onAsyncRender();
195
+ }, 0);
196
+
197
+ return () => clearTimeout(timer);
198
+ }, [onAsyncRender]);
199
+
200
+ return (
201
+ <View>
202
+ <Text>Effect Component</Text>
203
+ </View>
204
+ );
205
+ };
206
+
207
+ const WrappedComponent = withAsyncRenderHOC(ComponentWithEffect);
208
+
209
+ render(<WrappedComponent emitAsyncElementLayout={mockLayout} />);
210
+
211
+ // The effect should have been triggered
212
+ return new Promise((resolve) => {
213
+ setTimeout(() => {
214
+ expect(mockLayout).toHaveBeenCalled();
215
+ resolve(true);
216
+ }, 10);
217
+ });
218
+ });
219
+ });
@@ -6,12 +6,12 @@ type withAsyncRenderHOCProps = {
6
6
  emitAsyncElementLayout?: () => void;
7
7
  };
8
8
 
9
- type withAsyncRenderHOCT = (
10
- Component: React.ComponentType<{ onAsyncRender: () => void }>
11
- ) => React.FC<withAsyncRenderHOCProps>;
12
-
13
- export const withAsyncRenderHOC: withAsyncRenderHOCT = (Component) => {
14
- const WithAsyncRender = (props: withAsyncRenderHOCProps) => {
9
+ export const withAsyncRenderHOC = <P extends { onAsyncRender: () => void }>(
10
+ Component: React.ComponentType<P>
11
+ ): React.FC<Omit<P, "onAsyncRender"> & withAsyncRenderHOCProps> => {
12
+ const WithAsyncRender = (
13
+ props: Omit<P, "onAsyncRender"> & withAsyncRenderHOCProps
14
+ ) => {
15
15
  const { emitAsyncElementRegistrate = noop, emitAsyncElementLayout = noop } =
16
16
  props;
17
17
 
@@ -27,7 +27,9 @@ export const withAsyncRenderHOC: withAsyncRenderHOCT = (Component) => {
27
27
  }
28
28
  }, [emitAsyncElementLayout]);
29
29
 
30
- return <Component {...props} onAsyncRender={onAsyncRender} />;
30
+ return (
31
+ <Component {...(props as unknown as P)} onAsyncRender={onAsyncRender} />
32
+ );
31
33
  };
32
34
 
33
35
  return WithAsyncRender;
@@ -103,6 +103,8 @@ export function masterCellBuilder({
103
103
  wrapperRef,
104
104
  cellUUID,
105
105
  skipButtons,
106
+ hasFocusableInside,
107
+ entry: item,
106
108
  })
107
109
  );
108
110
 
@@ -32,14 +32,16 @@ describe("resolveColor", () => {
32
32
  color: "invalid_path",
33
33
  };
34
34
 
35
- expect(resolveColor(entry, style)).toEqual(style);
35
+ expect(resolveColor(entry, style)).toEqual({
36
+ color: undefined,
37
+ });
36
38
 
37
39
  expect(loggerSpy).toHaveBeenCalledWith(
38
40
  expect.objectContaining({
39
41
  message: "Cannot resolve property invalid_path from the entry.",
40
42
  data: {
43
+ colorFromProp: "invalid_path",
41
44
  configurationValue: "invalid_path",
42
- entry,
43
45
  },
44
46
  })
45
47
  );
@@ -102,7 +104,9 @@ describe("resolveColor", () => {
102
104
  color: "not.exist.path",
103
105
  };
104
106
 
105
- expect(resolveColor(entry, style)).toEqual(style);
107
+ expect(resolveColor(entry, style)).toEqual({
108
+ color: undefined,
109
+ });
106
110
  });
107
111
 
108
112
  it("not modify style with empty path", () => {
@@ -112,4 +116,79 @@ describe("resolveColor", () => {
112
116
 
113
117
  expect(resolveColor(entry, style)).toEqual(style);
114
118
  });
119
+
120
+ describe("memoization", () => {
121
+ beforeEach(() => {
122
+ // Clear memoization cache before each test
123
+ resolveColor.clear && resolveColor.clear();
124
+ });
125
+
126
+ it("hits cache with same entry and style references", () => {
127
+ const style = { color: "extensions.color" };
128
+
129
+ const result1 = resolveColor(entry, style);
130
+ const result2 = resolveColor(entry, style);
131
+
132
+ expect(result1).toBe(result2); // Same object reference
133
+ });
134
+
135
+ it("hits cache with new references but equal entry/style values", () => {
136
+ const entryClone = {
137
+ extensions: {
138
+ color: "red",
139
+ green_color: "green",
140
+ },
141
+ };
142
+
143
+ const style = { color: "extensions.color" };
144
+ const styleClone = { color: "extensions.color" };
145
+
146
+ const result1 = resolveColor(entry, style);
147
+ const result2 = resolveColor(entryClone, styleClone);
148
+
149
+ expect(result1).toBe(result2);
150
+ });
151
+
152
+ it("misses cache when entry is new object", () => {
153
+ const entry2 = { extensions: { color: "blue" } }; // Same values, different object
154
+ const style = { color: "extensions.color" };
155
+
156
+ const result1 = resolveColor(entry, style);
157
+ const result2 = resolveColor(entry2, style);
158
+
159
+ expect(result1).not.toBe(result2); // Different object references
160
+ });
161
+
162
+ it("misses cache when entry property changes", () => {
163
+ const myEntry = {
164
+ extensions: {
165
+ color: "red",
166
+ green_color: "green",
167
+ },
168
+ };
169
+
170
+ const style = { color: "extensions.color" };
171
+
172
+ const result1 = resolveColor(myEntry, style);
173
+
174
+ myEntry.extensions.color = "blue"; // Change property
175
+ const result2 = resolveColor(myEntry, style);
176
+
177
+ expect(result1).toEqual({ color: "red" });
178
+ expect(result2).toEqual({ color: "blue" });
179
+ expect(result1).not.toBe(result2);
180
+ });
181
+
182
+ it("misses cache when style changes", () => {
183
+ const style1 = { color: "extensions.color" };
184
+ const style2 = { backgroundColor: "extensions.color" };
185
+
186
+ const result1 = resolveColor(entry, style1);
187
+ const result2 = resolveColor(entry, style2);
188
+
189
+ expect(result1).toEqual({ color: "red" });
190
+ expect(result2).toEqual({ backgroundColor: "red" });
191
+ expect(result1).not.toBe(result2);
192
+ });
193
+ });
115
194
  });