@applicaster/zapp-react-native-ui-components 15.0.0-rc.12 → 15.0.0-rc.121

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 (159) 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 +47 -0
  6. package/Components/Cell/TvOSCellComponent.tsx +106 -19
  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/BorderContainerView/__tests__/index.test.tsx +16 -1
  31. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +30 -2
  32. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +5 -1
  33. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +11 -3
  34. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +9 -1
  35. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +15 -14
  36. package/Components/MasterCell/DefaultComponents/LiveImage/__tests__/prepareEntry.test.ts +352 -0
  37. package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +136 -0
  38. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +43 -22
  39. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +40 -39
  40. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/Image.test.tsx +95 -0
  41. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +86 -0
  42. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/index.test.ts +141 -0
  43. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
  44. package/Components/MasterCell/DefaultComponents/SecondaryImage/index.ts +1 -1
  45. package/Components/MasterCell/DefaultComponents/Text/index.tsx +8 -8
  46. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +6 -2
  47. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +233 -11
  48. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +19 -15
  49. package/Components/MasterCell/hoc/__tests__/withAsyncRender.test.tsx +219 -0
  50. package/Components/MasterCell/hoc/withAsyncRender.tsx +9 -7
  51. package/Components/MasterCell/index.tsx +2 -0
  52. package/Components/MasterCell/utils/__tests__/resolveColor.test.js +82 -3
  53. package/Components/MasterCell/utils/index.ts +61 -31
  54. package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +102 -87
  55. package/Components/MeasurmentsPortal/__tests__/MeasurementsPortal.test.tsx +355 -0
  56. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +17 -9
  57. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +16 -8
  58. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +2 -2
  59. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +17 -18
  60. package/Components/OfflineHandler/NotificationView/utils.ts +34 -0
  61. package/Components/OfflineHandler/__tests__/index.test.tsx +27 -18
  62. package/Components/PlayerContainer/PlayerContainer.tsx +43 -64
  63. package/Components/PlayerImageBackground/index.tsx +3 -22
  64. package/Components/PreloaderWrapper/__tests__/index.test.tsx +26 -0
  65. package/Components/PreloaderWrapper/index.tsx +15 -0
  66. package/Components/River/ComponentsMap/ComponentsMap.tsx +16 -0
  67. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  68. package/Components/River/RefreshControl.tsx +9 -3
  69. package/Components/River/RiverItem.tsx +26 -20
  70. package/Components/River/TV/River.tsx +31 -14
  71. package/Components/River/TV/index.tsx +8 -4
  72. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +30 -0
  73. package/Components/River/TV/utils/index.ts +4 -0
  74. package/Components/River/TV/withFocusableGroupForContent.tsx +71 -0
  75. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +2 -0
  76. package/Components/River/__tests__/componentsMap.test.js +38 -0
  77. package/Components/Screen/TV/index.web.tsx +4 -2
  78. package/Components/Screen/__tests__/Screen.test.tsx +66 -42
  79. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +68 -44
  80. package/Components/Screen/hooks.ts +75 -6
  81. package/Components/Screen/index.tsx +9 -4
  82. package/Components/Screen/navigationHandler.ts +49 -24
  83. package/Components/Screen/orientationHandler.ts +10 -13
  84. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +46 -0
  85. package/Components/ScreenFeedLoader/__tests__/ScreenFeedLoader.test.tsx +94 -0
  86. package/Components/ScreenFeedLoader/index.ts +1 -0
  87. package/Components/ScreenResolver/__tests__/screenResolver.test.js +24 -0
  88. package/Components/ScreenResolver/hooks/index.ts +3 -0
  89. package/Components/ScreenResolver/hooks/useGetComponent.ts +15 -0
  90. package/Components/ScreenResolver/hooks/useScreenComponentResolver.tsx +90 -0
  91. package/Components/ScreenResolver/index.tsx +15 -111
  92. package/Components/ScreenResolver/utils/__tests__/getScreenTypeProps.test.ts +45 -0
  93. package/Components/ScreenResolver/utils/getScreenTypeProps.ts +43 -0
  94. package/Components/ScreenResolver/utils/index.ts +1 -0
  95. package/Components/ScreenResolver/withDefaultScreenContext.tsx +16 -0
  96. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +25 -0
  97. package/Components/ScreenResolverFeedProvider/__tests__/ScreenResolverFeedProvider.test.tsx +44 -0
  98. package/Components/ScreenResolverFeedProvider/index.ts +1 -0
  99. package/Components/ScreenRevealManager/ScreenRevealManager.ts +40 -8
  100. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +86 -69
  101. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +44 -26
  102. package/Components/Tabs/TV/Tabs.tsx +20 -3
  103. package/Components/Tabs/TabContent.tsx +7 -4
  104. package/Components/Transitioner/Scene.tsx +10 -3
  105. package/Components/Transitioner/index.js +3 -3
  106. package/Components/VideoLive/LiveImageManager.ts +199 -54
  107. package/Components/VideoLive/PlayerLiveImageComponent.tsx +31 -33
  108. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +2 -17
  109. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +1 -0
  110. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +118 -171
  111. package/Components/VideoModal/ModalAnimation/index.ts +2 -13
  112. package/Components/VideoModal/ModalAnimation/utils.ts +1 -327
  113. package/Components/VideoModal/PlayerWrapper.tsx +14 -88
  114. package/Components/VideoModal/VideoModal.tsx +1 -5
  115. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -0
  116. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
  117. package/Components/VideoModal/hooks/useModalSize.ts +10 -5
  118. package/Components/VideoModal/playerWrapperStyle.ts +70 -0
  119. package/Components/VideoModal/playerWrapperUtils.ts +91 -0
  120. package/Components/VideoModal/utils.ts +19 -9
  121. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
  122. package/Components/Viewport/ViewportAware/index.tsx +16 -7
  123. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  124. package/Components/ZappUIComponent/index.tsx +12 -6
  125. package/Components/index.js +1 -1
  126. package/Contexts/ScreenContext/__tests__/index.test.tsx +57 -0
  127. package/Contexts/ScreenContext/index.tsx +71 -19
  128. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  129. package/Contexts/ZappHookModalContext/index.tsx +37 -61
  130. package/Contexts/ZappPipesContext/ZappPipesContextFactory.tsx +18 -7
  131. package/Contexts/index.ts +0 -2
  132. package/Decorators/Analytics/index.tsx +6 -5
  133. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +1 -0
  134. package/Decorators/ConfigurationWrapper/const.ts +1 -0
  135. package/Decorators/ZappPipesDataConnector/ResolverSelector.tsx +25 -7
  136. package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +212 -5
  137. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
  138. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  139. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  140. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
  141. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
  142. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +80 -0
  143. package/Helpers/DataSourceHelper/index.ts +19 -0
  144. package/events/index.ts +3 -0
  145. package/events/scrollEndReached.ts +15 -0
  146. package/index.d.ts +7 -0
  147. package/package.json +6 -5
  148. package/Components/PlayerContainer/ErrorDisplay/ErrorDisplay.tsx +0 -57
  149. package/Components/PlayerContainer/ErrorDisplay/index.ts +0 -9
  150. package/Components/River/TV/withTVEventHandler.tsx +0 -27
  151. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +0 -60
  152. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +0 -417
  153. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.web.tsx +0 -294
  154. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +0 -176
  155. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.web.tsx +0 -93
  156. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +0 -500
  157. package/Components/VideoModal/ModalAnimation/__tests__/getMoveUpValue.test.ts +0 -108
  158. package/Helpers/DataSourceHelper/index.js +0 -19
  159. /package/Components/HookRenderer/{index.tsx → index.ts} +0 -0
@@ -1,9 +1,15 @@
1
- import { getPluginIdentifier } from "..";
1
+ import { getPluginIdentifier, memoizedGetPluginIdentifier } from "..";
2
2
 
3
3
  describe("getPluginIdentifier", () => {
4
4
  const prefix = "tv_buttons";
5
5
 
6
- it("get first plugin identifier", () => {
6
+ beforeAll(() => {
7
+ memoizedGetPluginIdentifier.clear();
8
+ });
9
+
10
+ it("returns the first valid plugin identifier", () => {
11
+ expect.assertions(2);
12
+
7
13
  const configuration = {
8
14
  tv_buttons_button_1_other: "value",
9
15
  tv_buttons_button_1_assign_action:
@@ -13,13 +19,21 @@ describe("getPluginIdentifier", () => {
13
19
  };
14
20
 
15
21
  const index = 0;
16
-
17
22
  const result = getPluginIdentifier(configuration, prefix, index);
18
23
 
24
+ const memoizedResult = memoizedGetPluginIdentifier(
25
+ configuration,
26
+ prefix,
27
+ index
28
+ );
29
+
19
30
  expect(result).toEqual(configuration.tv_buttons_button_1_assign_action);
31
+ expect(memoizedResult).toEqual(result);
20
32
  });
21
33
 
22
- it("get second plugin identifier", () => {
34
+ it("returns the second valid plugin identifier", () => {
35
+ expect.assertions(2);
36
+
23
37
  const configuration = {
24
38
  tv_buttons_button_1_other: "value_1",
25
39
  tv_buttons_button_1_assign_action:
@@ -30,24 +44,216 @@ describe("getPluginIdentifier", () => {
30
44
  };
31
45
 
32
46
  const index = 1;
33
-
34
47
  const result = getPluginIdentifier(configuration, prefix, index);
35
48
 
49
+ const memoizedResult = memoizedGetPluginIdentifier(
50
+ configuration,
51
+ prefix,
52
+ index
53
+ );
54
+
36
55
  expect(result).toEqual(configuration.tv_buttons_button_2_assign_action);
56
+ expect(memoizedResult).toEqual(result);
37
57
  });
38
58
 
39
- it("get undefined if no assign_actions at all", () => {
59
+ it("returns undefined when there are no assign_action keys", () => {
60
+ expect.assertions(2);
61
+
40
62
  const configuration = {};
63
+ const index = 0;
64
+
65
+ const result = getPluginIdentifier(configuration, prefix, index);
66
+
67
+ const memoizedResult = memoizedGetPluginIdentifier(
68
+ configuration,
69
+ prefix,
70
+ index
71
+ );
72
+
73
+ expect(result).toBeUndefined();
74
+ expect(memoizedResult).toBeUndefined();
75
+ });
76
+
77
+ it("skips undefined values and returns the correct plugin identifier", () => {
78
+ expect.assertions(2);
79
+
80
+ const configuration = {
81
+ tv_buttons_button_1_assign_action: "tv_buttons_button_1_assign_action",
82
+ tv_buttons_button_2_assign_action: undefined,
83
+ tv_buttons_button_3_assign_action: "tv_buttons_button_3_assign_action",
84
+ };
85
+
86
+ const index = 1;
87
+ const result = getPluginIdentifier(configuration, prefix, index);
88
+
89
+ const memoizedResult = memoizedGetPluginIdentifier(
90
+ configuration,
91
+ prefix,
92
+ index
93
+ );
94
+
95
+ expect(result).toEqual(configuration.tv_buttons_button_3_assign_action);
96
+ expect(memoizedResult).toEqual(result);
97
+ });
98
+
99
+ it("handles missing intermediate keys and returns the correct plugin identifier", () => {
100
+ expect.assertions(2);
101
+
102
+ const configuration = {
103
+ tv_buttons_button_1_assign_action: "tv_buttons_button_1_assign_action",
104
+ tv_buttons_button_3_assign_action: "tv_buttons_button_3_assign_action",
105
+ };
106
+
107
+ const index = 1;
108
+ const result = getPluginIdentifier(configuration, prefix, index);
109
+
110
+ const memoizedResult = memoizedGetPluginIdentifier(
111
+ configuration,
112
+ prefix,
113
+ index
114
+ );
115
+
116
+ expect(result).toEqual(configuration.tv_buttons_button_3_assign_action);
117
+ expect(memoizedResult).toEqual(result);
118
+ });
119
+
120
+ it("skips empty string values and returns the first non-empty assign_action", () => {
121
+ expect.assertions(2);
122
+
123
+ const configuration = {
124
+ tv_buttons_button_1_assign_action: "",
125
+ tv_buttons_button_2_assign_action: "tv_buttons_button_2_assign_action",
126
+ };
41
127
 
42
128
  const index = 0;
129
+ const result = getPluginIdentifier(configuration, prefix, index);
130
+
131
+ const memoizedResult = memoizedGetPluginIdentifier(
132
+ configuration,
133
+ prefix,
134
+ index
135
+ );
136
+
137
+ expect(result).toEqual(configuration.tv_buttons_button_2_assign_action);
138
+ expect(memoizedResult).toEqual(result);
139
+ });
140
+
141
+ it("returns undefined for negative index", () => {
142
+ expect.assertions(2);
143
+
144
+ const configuration = {
145
+ tv_buttons_button_1_assign_action: "a",
146
+ tv_buttons_button_2_assign_action: "b",
147
+ };
43
148
 
149
+ const index = -1;
44
150
  const result = getPluginIdentifier(configuration, prefix, index);
45
151
 
152
+ const memoizedResult = memoizedGetPluginIdentifier(
153
+ configuration,
154
+ prefix,
155
+ index
156
+ );
157
+
46
158
  expect(result).toBeUndefined();
159
+ expect(memoizedResult).toBeUndefined();
160
+ });
161
+
162
+ it("returns undefined for out-of-bounds index", () => {
163
+ expect.assertions(2);
164
+
165
+ const configuration = {
166
+ tv_buttons_button_1_assign_action: "a",
167
+ tv_buttons_button_2_assign_action: "b",
168
+ };
169
+
170
+ const index = 5;
171
+ const result = getPluginIdentifier(configuration, prefix, index);
172
+
173
+ const memoizedResult = memoizedGetPluginIdentifier(
174
+ configuration,
175
+ prefix,
176
+ index
177
+ );
178
+
179
+ expect(result).toBeUndefined();
180
+ expect(memoizedResult).toBeUndefined();
181
+ });
182
+
183
+ it("ignores keys with wrong prefix or format", () => {
184
+ expect.assertions(2);
185
+
186
+ const configuration = {
187
+ tv_buttons_button_1_assign_action: "a",
188
+ tv_buttons_wrongprefix_2_assign_action: "b",
189
+ tv_buttons_button_x_assign_action: "c",
190
+ };
191
+
192
+ const index = 1;
193
+ const result = getPluginIdentifier(configuration, prefix, index);
194
+
195
+ const memoizedResult = memoizedGetPluginIdentifier(
196
+ configuration,
197
+ prefix,
198
+ index
199
+ );
200
+
201
+ expect(result).toBeUndefined();
202
+ expect(memoizedResult).toBeUndefined();
203
+ });
204
+
205
+ it("returns undefined if all assign_actions are null or empty", () => {
206
+ expect.assertions(2);
207
+
208
+ const configuration = {
209
+ tv_buttons_button_1_assign_action: null,
210
+ tv_buttons_button_2_assign_action: undefined,
211
+ tv_buttons_button_3_assign_action: "",
212
+ };
213
+
214
+ const index = 0;
215
+ const result = getPluginIdentifier(configuration, prefix, index);
216
+
217
+ const memoizedResult = memoizedGetPluginIdentifier(
218
+ configuration,
219
+ prefix,
220
+ index
221
+ );
222
+
223
+ expect(result).toBeUndefined();
224
+ expect(memoizedResult).toBeUndefined();
225
+ });
226
+
227
+ it("handles non-string values correctly", () => {
228
+ expect.assertions(6);
229
+
230
+ const configuration = {
231
+ tv_buttons_button_1_assign_action: 123,
232
+ tv_buttons_button_2_assign_action: true,
233
+ tv_buttons_button_3_assign_action: { nested: "value" },
234
+ };
235
+
236
+ expect(getPluginIdentifier(configuration, prefix, 0)).toEqual(123);
237
+ expect(getPluginIdentifier(configuration, prefix, 1)).toEqual(true);
238
+
239
+ expect(getPluginIdentifier(configuration, prefix, 2)).toEqual({
240
+ nested: "value",
241
+ });
242
+
243
+ expect(memoizedGetPluginIdentifier(configuration, prefix, 0)).toEqual(123);
244
+ expect(memoizedGetPluginIdentifier(configuration, prefix, 1)).toEqual(true);
245
+
246
+ expect(memoizedGetPluginIdentifier(configuration, prefix, 2)).toEqual({
247
+ nested: "value",
248
+ });
47
249
  });
48
250
  });
49
251
 
50
- describe("getPluginIdentifier - when configuration has same values for different keys", () => {
252
+ describe("getPluginIdentifier - when configuration has duplicate values", () => {
253
+ beforeAll(() => {
254
+ memoizedGetPluginIdentifier.clear();
255
+ });
256
+
51
257
  const prefix = "tv_buttons";
52
258
 
53
259
  const configuration = {
@@ -55,19 +261,35 @@ describe("getPluginIdentifier - when configuration has same values for different
55
261
  tv_buttons_button_2_assign_action: "navigation_action",
56
262
  };
57
263
 
58
- it("get first plugin identifier", () => {
59
- const index = 0;
264
+ it("returns the first plugin identifier when values are identical", () => {
265
+ expect.assertions(2);
60
266
 
267
+ const index = 0;
61
268
  const result = getPluginIdentifier(configuration, prefix, index);
62
269
 
270
+ const memoizedResult = memoizedGetPluginIdentifier(
271
+ configuration,
272
+ prefix,
273
+ index
274
+ );
275
+
63
276
  expect(result).toEqual(configuration.tv_buttons_button_1_assign_action);
277
+ expect(memoizedResult).toEqual(result);
64
278
  });
65
279
 
66
- it("get second plugin identifier", () => {
67
- const index = 1;
280
+ it("returns the second plugin identifier when values are identical", () => {
281
+ expect.assertions(2);
68
282
 
283
+ const index = 1;
69
284
  const result = getPluginIdentifier(configuration, prefix, index);
70
285
 
286
+ const memoizedResult = memoizedGetPluginIdentifier(
287
+ configuration,
288
+ prefix,
289
+ index
290
+ );
291
+
71
292
  expect(result).toEqual(configuration.tv_buttons_button_2_assign_action);
293
+ expect(memoizedResult).toEqual(result);
72
294
  });
73
295
  });
@@ -1,7 +1,7 @@
1
1
  import * as R from "ramda";
2
+ import memoizee from "memoizee";
2
3
 
3
4
  import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
4
- import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
5
5
 
6
6
  export const getButtonsCount = (
7
7
  configuration: Record<string, unknown>,
@@ -21,23 +21,27 @@ export const getPluginIdentifier = (
21
21
  configuration: Record<string, unknown>,
22
22
  prefix: string,
23
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);
24
+ ): string | undefined => {
25
+ const re = new RegExp(`${prefix}_button_\\d_assign_action`);
26
+ let count = 0;
27
+
28
+ for (const [key, value] of Object.entries(configuration)) {
29
+ if (
30
+ key.startsWith(`${prefix}_button`) &&
31
+ re.test(key) &&
32
+ value != null &&
33
+ value !== ""
34
+ ) {
35
+ if (count === index) return value as string;
36
+ count++;
37
+ }
38
+ }
35
39
 
36
- return matched ? value : undefined;
37
- })
38
- );
40
+ return undefined;
39
41
  };
40
42
 
43
+ export const memoizedGetPluginIdentifier = memoizee(getPluginIdentifier);
44
+
41
45
  type Label = {
42
46
  name: string;
43
47
  };
@@ -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