@applicaster/zapp-react-native-ui-components 15.0.0-rc.99 → 16.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/Components/Cell/TvOSCellComponent.tsx +1 -3
  2. package/Components/GeneralContentScreen/GeneralContentScreen.tsx +39 -28
  3. package/Components/GeneralContentScreen/__tests__/GeneralContentScreen.test.tsx +104 -0
  4. package/Components/GeneralContentScreen/utils/__tests__/getScreenDataSource.test.ts +19 -0
  5. package/Components/GeneralContentScreen/utils/getScreenDataSource.ts +9 -0
  6. package/Components/HandlePlayable/HandlePlayable.tsx +16 -29
  7. package/Components/HandlePlayable/utils.ts +31 -0
  8. package/Components/HookRenderer/HookRenderer.tsx +40 -10
  9. package/Components/HookRenderer/__tests__/HookRenderer.test.tsx +60 -0
  10. package/Components/Layout/TV/NavBarContainer.tsx +1 -10
  11. package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +7 -12
  12. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +7 -12
  13. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +5 -0
  14. package/Components/MasterCell/CONFIG_BUILDER_TO_REACT_COMPONENT.md +144 -0
  15. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/model.test.ts +80 -0
  16. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +187 -0
  17. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/selectors.test.ts +45 -0
  18. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/style.test.ts +49 -0
  19. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +165 -0
  20. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +405 -0
  21. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/index.ts +1 -0
  22. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/model.ts +47 -0
  23. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +170 -0
  24. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/selectors.ts +26 -0
  25. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/style.ts +29 -0
  26. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/types.ts +37 -0
  27. package/Components/MasterCell/DefaultComponents/Button.tsx +0 -15
  28. package/Components/MasterCell/DefaultComponents/ButtonContainerView/components/HorizontalSeparator.tsx +8 -0
  29. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tsx +15 -0
  30. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tv.android.tsx +58 -0
  31. package/Components/MasterCell/DefaultComponents/{tv/ButtonContainerView/index.tsx → ButtonContainerView/index.tv.tsx} +3 -11
  32. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.web.ts +1 -0
  33. package/Components/MasterCell/DefaultComponents/ButtonContainerView/types.ts +40 -0
  34. package/Components/MasterCell/DefaultComponents/DataProvider/index.tsx +163 -0
  35. package/Components/MasterCell/DefaultComponents/FocusableView/index.android.tsx +2 -23
  36. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -22
  37. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +3 -1
  38. package/Components/MasterCell/DefaultComponents/LiveImage/__tests__/prepareEntry.test.ts +352 -0
  39. package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +136 -0
  40. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +33 -16
  41. package/Components/MasterCell/DefaultComponents/PressableView.tsx +34 -0
  42. package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +11 -0
  43. package/Components/MasterCell/DefaultComponents/Text/index.tsx +2 -6
  44. package/Components/MasterCell/DefaultComponents/__tests__/DataProvider.test.tsx +141 -0
  45. package/Components/MasterCell/DefaultComponents/index.ts +9 -3
  46. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +135 -0
  47. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +33 -0
  48. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +22 -0
  49. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +125 -0
  50. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +16 -0
  51. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabel.ts +67 -0
  52. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +37 -0
  53. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +393 -0
  54. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +141 -0
  55. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +343 -0
  56. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/helpers.ts +105 -0
  57. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +122 -0
  58. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +118 -0
  59. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +238 -0
  60. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Asset.ts +4 -18
  61. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Button.ts +24 -73
  62. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TextLabelsContainer.ts +37 -18
  63. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TvActionButton.tsx +27 -0
  64. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +89 -0
  65. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/renderedTree.test.tsx +231 -0
  66. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +47 -52
  67. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +35 -171
  68. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +98 -145
  69. package/Components/MasterCell/MappingFunctions/index.js +3 -2
  70. package/Components/MasterCell/README.md +4 -0
  71. package/Components/MasterCell/__tests__/__snapshots__/dataAdapter.test.js.snap +24 -0
  72. package/Components/MasterCell/__tests__/configInflater.test.js +1 -0
  73. package/Components/MasterCell/__tests__/elementMapper.test.js +46 -0
  74. package/Components/MasterCell/dataAdapter.ts +4 -1
  75. package/Components/MasterCell/elementMapper.tsx +52 -7
  76. package/Components/MasterCell/utils/__tests__/cloneChildrenWithIds.test.tsx +43 -0
  77. package/Components/MasterCell/utils/__tests__/useFilterChildren.test.tsx +80 -0
  78. package/Components/MasterCell/utils/index.ts +85 -15
  79. package/Components/Navigator/StackNavigator.tsx +6 -0
  80. package/Components/PlayerContainer/PlayerContainer.tsx +2 -19
  81. package/Components/PreloaderWrapper/__tests__/index.test.tsx +26 -0
  82. package/Components/PreloaderWrapper/index.tsx +15 -0
  83. package/Components/River/ComponentsMap/ComponentsMap.tsx +2 -16
  84. package/Components/River/RefreshControl.tsx +19 -82
  85. package/Components/River/River.tsx +9 -82
  86. package/Components/River/RiverItem.tsx +26 -20
  87. package/Components/River/hooks/__tests__/usePullToRefresh.test.ts +132 -0
  88. package/Components/River/hooks/index.ts +1 -0
  89. package/Components/River/hooks/usePullToRefresh.ts +51 -0
  90. package/Components/Screen/__tests__/Screen.test.tsx +1 -0
  91. package/Components/Screen/hooks.ts +73 -3
  92. package/Components/Screen/index.tsx +7 -1
  93. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +46 -0
  94. package/Components/ScreenFeedLoader/__tests__/ScreenFeedLoader.test.tsx +94 -0
  95. package/Components/ScreenFeedLoader/index.ts +1 -0
  96. package/Components/ScreenResolver/__tests__/screenResolver.test.js +24 -0
  97. package/Components/ScreenResolver/hooks/index.ts +3 -0
  98. package/Components/ScreenResolver/hooks/useGetComponent.ts +15 -0
  99. package/Components/ScreenResolver/hooks/useScreenComponentResolver.tsx +90 -0
  100. package/Components/ScreenResolver/index.tsx +15 -117
  101. package/Components/ScreenResolver/utils/__tests__/getScreenTypeProps.test.ts +45 -0
  102. package/Components/ScreenResolver/utils/getScreenTypeProps.ts +43 -0
  103. package/Components/ScreenResolver/utils/index.ts +1 -0
  104. package/Components/ScreenResolver/withDefaultScreenContext.tsx +16 -0
  105. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +25 -0
  106. package/Components/ScreenResolverFeedProvider/__tests__/ScreenResolverFeedProvider.test.tsx +44 -0
  107. package/Components/ScreenResolverFeedProvider/index.ts +1 -0
  108. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +4 -1
  109. package/Components/TopCutoffOverlay/__tests__/TopCutoffOverlay.test.tsx +201 -0
  110. package/Components/TopCutoffOverlay/hooks/__tests__/useMarginTop.test.ts +130 -0
  111. package/Components/TopCutoffOverlay/hooks/index.ts +1 -0
  112. package/Components/TopCutoffOverlay/hooks/useMarginTop.ts +59 -0
  113. package/Components/TopCutoffOverlay/index.tsx +55 -0
  114. package/Components/Transitioner/Scene.tsx +9 -15
  115. package/Components/VideoLive/LiveImageManager.ts +199 -54
  116. package/Components/VideoLive/PlayerLiveImageComponent.tsx +31 -33
  117. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +2 -17
  118. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
  119. package/Components/Viewport/ViewportAware/index.tsx +16 -7
  120. package/Components/ZappUIComponent/index.tsx +12 -6
  121. package/Components/default-cell-renderer/viewTrees/mobile/index.ts +0 -3
  122. package/Components/index.js +1 -1
  123. package/Contexts/ScreenContext/__tests__/index.test.tsx +57 -0
  124. package/Contexts/ScreenContext/index.tsx +46 -1
  125. package/Contexts/ZappPipesContext/ZappPipesContextFactory.tsx +18 -7
  126. package/Decorators/ZappPipesDataConnector/ResolverSelector.tsx +25 -7
  127. package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +212 -5
  128. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
  129. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
  130. package/package.json +5 -5
  131. package/Components/MasterCell/DefaultComponents/Text/utils/__tests__/withAdjustedLineHeight.test.ts +0 -46
  132. package/Components/MasterCell/DefaultComponents/Text/utils/index.ts +0 -21
  133. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +0 -135
  134. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/types.ts +0 -25
  135. package/Components/PlayerContainer/ErrorDisplay/ErrorDisplay.tsx +0 -57
  136. package/Components/PlayerContainer/ErrorDisplay/index.ts +0 -9
  137. package/Components/PlayerContainer/useRestrictMobilePlayback.tsx +0 -101
  138. /package/Components/HookRenderer/{index.tsx → index.ts} +0 -0
@@ -0,0 +1,393 @@
1
+ import React from "react";
2
+ import { Text as RNText, TouchableOpacity, View } from "react-native";
3
+ import { render, fireEvent } from "@testing-library/react-native";
4
+ import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
5
+
6
+ import { configInflater } from "../../../../dataAdapter";
7
+ import { elementMapper } from "../../../../elementMapper";
8
+ import { defaultComponents } from "../../../index";
9
+ import { MobileActionButtons } from "..";
10
+ import { AssetComponent } from "../AssetComponent";
11
+
12
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/actions", () => ({
13
+ useActions: jest.fn(),
14
+ }));
15
+
16
+ jest.mock("@applicaster/zapp-react-native-utils/theme", () => ({
17
+ useTheme: () => ({}),
18
+ }));
19
+
20
+ jest.mock("@applicaster/zapp-react-native-utils/localizationUtils", () => ({
21
+ useIsRTL: jest.fn(() => false),
22
+ }));
23
+
24
+ jest.mock(
25
+ "@applicaster/zapp-react-native-utils/reactHooks/navigation/useNavigation",
26
+ () => ({
27
+ useNavigation: jest.fn(() => ({
28
+ currentRoute: "home",
29
+ videoModalState: {
30
+ visible: false,
31
+ },
32
+ })),
33
+ })
34
+ );
35
+
36
+ jest.mock(
37
+ "@applicaster/zapp-react-native-utils/reactHooks/navigation/usePathname",
38
+ () => ({
39
+ usePathname: jest.fn(() => "home"),
40
+ })
41
+ );
42
+
43
+ jest.mock(
44
+ "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager/hooks",
45
+ () => ({
46
+ useAccessibilityManager: jest.fn(() => ({
47
+ addHeading: jest.fn(),
48
+ })),
49
+ })
50
+ );
51
+
52
+ const mockUseActions = useActions as jest.Mock;
53
+
54
+ const entry = {
55
+ id: "entry-1",
56
+ } as ZappEntry;
57
+
58
+ const buildActionContext = (entryState = {}) => ({
59
+ initialEntryState: jest.fn(() => ({
60
+ asset: {
61
+ inactive: "https://example.com/image-inactive.png",
62
+ active: "https://example.com/image-active.png",
63
+ },
64
+ mobileButtonAssets: [
65
+ "https://example.com/image-inactive.png",
66
+ "https://example.com/image-active.png",
67
+ ],
68
+ label: {
69
+ label_1: "Play",
70
+ },
71
+ ...entryState,
72
+ })),
73
+ invokeAction: jest.fn(),
74
+ addListener: jest.fn(() => jest.fn()),
75
+ isActionAvailable: jest.fn(() => true),
76
+ });
77
+
78
+ const mobileConfiguration = {
79
+ mobile_buttons_container_buttons_enabled: true,
80
+ mobile_buttons_container_position: "over_image",
81
+ mobile_buttons_container_align: "left",
82
+ mobile_buttons_container_margin_top: 0,
83
+ mobile_buttons_container_margin_right: 0,
84
+ mobile_buttons_container_margin_bottom: 0,
85
+ mobile_buttons_container_margin_left: 0,
86
+ mobile_buttons_container_stacking: "horizontal",
87
+ mobile_buttons_container_horizontal_gutter: 8,
88
+ mobile_buttons_container_vertical_gutter: 8,
89
+ mobile_buttons_container_independent_styles: true,
90
+ mobile_buttons_container_over_image_position: "top_right",
91
+ mobile_button_1_button_enabled: true,
92
+ mobile_button_1_assign_action: "navigation_action",
93
+ mobile_button_1_display_mode: "dynamic",
94
+ mobile_button_1_contents_alignment: "center",
95
+ mobile_button_1_background_color: "rgba(1,1,1,1)",
96
+ mobile_button_1_border_color: "rgba(2,2,2,1)",
97
+ mobile_button_1_border_size: 1,
98
+ mobile_button_1_corner_radius: 8,
99
+ mobile_button_1_padding_top: 10,
100
+ mobile_button_1_padding_right: 10,
101
+ mobile_button_1_padding_bottom: 10,
102
+ mobile_button_1_padding_left: 10,
103
+ mobile_button_1_asset_width: 24,
104
+ mobile_button_1_asset_height: 24,
105
+ mobile_button_1_asset_margin_top: 0,
106
+ mobile_button_1_asset_margin_right: 0,
107
+ mobile_button_1_asset_margin_bottom: 0,
108
+ mobile_button_1_asset_margin_left: 0,
109
+ mobile_button_1_asset_enabled: true,
110
+ mobile_button_1_label_enabled: true,
111
+ mobile_button_1_font_color: "rgba(10,10,10,1)",
112
+ mobile_button_1_focused_font_color: "rgba(20,20,20,1)",
113
+ mobile_button_1_font_size: 15,
114
+ mobile_button_1_line_height: 24,
115
+ mobile_button_1_ios_font_family: "Ubuntu-Bold",
116
+ mobile_button_1_android_font_family: "Ubuntu-Bold",
117
+ mobile_button_1_ios_letter_spacing: -0.2,
118
+ mobile_button_1_android_letter_spacing: -0.2,
119
+ mobile_button_1_text_transform: "default",
120
+ };
121
+
122
+ const baseNode = {
123
+ type: "MobileActionButton",
124
+ style: {
125
+ backgroundColor: "rgba(1,1,1,1)",
126
+ borderColor: "rgba(2,2,2,1)",
127
+ borderWidth: 1,
128
+ },
129
+ props: {
130
+ entry,
131
+ action: {
132
+ identifier: "navigation_action",
133
+ },
134
+ focusedStyles: {
135
+ backgroundColor: "rgba(3,3,3,1)",
136
+ borderColor: "rgba(4,4,4,1)",
137
+ },
138
+ testID: "mobile-action-button",
139
+ },
140
+ elements: [
141
+ {
142
+ type: "ReactComponent",
143
+ style: {
144
+ width: 24,
145
+ height: 24,
146
+ },
147
+ props: {
148
+ component: AssetComponent,
149
+ renderChildren: false,
150
+ requiresCellUUID: true,
151
+ testID: "mobile-action-button-asset",
152
+ mobileActionRole: "asset",
153
+ },
154
+ },
155
+ {
156
+ type: "View",
157
+ style: {
158
+ flexDirection: "column",
159
+ },
160
+ props: {
161
+ mobileActionRole: "label_container",
162
+ },
163
+ elements: [
164
+ {
165
+ type: "Text",
166
+ style: {
167
+ fontSize: 15,
168
+ },
169
+ props: {
170
+ testID: "mobile-action-button-label",
171
+ label: {
172
+ context: "navigation_action",
173
+ name: "label_1",
174
+ },
175
+ focusedStyles: {
176
+ color: "rgba(20,20,20,1)",
177
+ },
178
+ normalStyles: {
179
+ color: "rgba(10,10,10,1)",
180
+ },
181
+ mobileActionRole: "label",
182
+ transformText: "default",
183
+ },
184
+ },
185
+ ],
186
+ },
187
+ ],
188
+ };
189
+
190
+ describe("MobileActionButton", () => {
191
+ beforeEach(() => {
192
+ jest.clearAllMocks();
193
+ });
194
+
195
+ const renderNode = (node = baseNode) =>
196
+ render(
197
+ <React.Fragment>
198
+ {elementMapper(defaultComponents)(node as never)}
199
+ </React.Fragment>
200
+ );
201
+
202
+ it("renders nested image and text children through elementMapper", () => {
203
+ mockUseActions.mockReturnValue(buildActionContext());
204
+
205
+ const { getByText, getByTestId } = renderNode();
206
+
207
+ expect(getByTestId("mobile-action-button")).toBeTruthy();
208
+ expect(getByTestId("mobile-action-button-asset")).toBeTruthy();
209
+ expect(getByText("Play")).toBeTruthy();
210
+ });
211
+
212
+ it("applies focused styles when action entry state is active", () => {
213
+ mockUseActions.mockReturnValue(buildActionContext({ active: true }));
214
+
215
+ const { getByTestId, getByText } = renderNode();
216
+
217
+ expect(
218
+ getByTestId("mobile-action-button").props.style.backgroundColor
219
+ ).toBe("rgba(3,3,3,1)");
220
+
221
+ expect(getByText("Play").props.style).toEqual(
222
+ expect.arrayContaining([
223
+ expect.objectContaining({ color: "rgba(20,20,20,1)" }),
224
+ ])
225
+ );
226
+ });
227
+
228
+ it("invokes action on press", () => {
229
+ const actionContext = buildActionContext();
230
+ mockUseActions.mockReturnValue(actionContext);
231
+
232
+ const { getByTestId } = renderNode();
233
+
234
+ fireEvent.press(getByTestId("mobile-action-button"));
235
+
236
+ expect(actionContext.invokeAction).toHaveBeenCalledWith(
237
+ entry,
238
+ expect.objectContaining({
239
+ updateState: expect.any(Function),
240
+ })
241
+ );
242
+ });
243
+
244
+ it("preserves the rendered primitive tree when the mobile builder is wrapped with DataProvider", () => {
245
+ mockUseActions.mockReturnValue(buildActionContext());
246
+
247
+ const node = configInflater(
248
+ entry,
249
+ MobileActionButtons({
250
+ value: (key) => mobileConfiguration[key],
251
+ configuration: mobileConfiguration,
252
+ placement: "over_image",
253
+ })
254
+ );
255
+
256
+ const { UNSAFE_getAllByType, UNSAFE_getByType, getByTestId, getByText } =
257
+ render(
258
+ <React.Fragment>
259
+ {elementMapper(defaultComponents)(node as never)}
260
+ </React.Fragment>
261
+ );
262
+
263
+ const pressable = UNSAFE_getByType(TouchableOpacity);
264
+ const label = getByText("Play");
265
+ const asset = getByTestId("mobile_action_button_1-asset");
266
+
267
+ expect(pressable.props.testID).toBe("mobile_action_button_1");
268
+ expect(pressable.props.accessibilityLabel).toBe("entry-1");
269
+
270
+ expect(pressable.props.style).toMatchObject({
271
+ backgroundColor: "rgba(1,1,1,1)",
272
+ borderColor: "rgba(2,2,2,1)",
273
+ borderWidth: 1,
274
+ });
275
+
276
+ expect(asset.props.style).toMatchObject({
277
+ width: 24,
278
+ height: 24,
279
+ });
280
+
281
+ expect(label.props.style).toEqual(
282
+ expect.arrayContaining([
283
+ expect.objectContaining({
284
+ color: "rgba(10,10,10,1)",
285
+ fontSize: 15,
286
+ lineHeight: 24,
287
+ }),
288
+ ])
289
+ );
290
+
291
+ expect(UNSAFE_getAllByType(View).length).toBeGreaterThan(0);
292
+ expect(UNSAFE_getAllByType(RNText).length).toBeGreaterThan(0);
293
+ });
294
+
295
+ describe("component asset via mobileButtonAssets", () => {
296
+ const nodeWithFlavour1 = {
297
+ ...baseNode,
298
+ props: {
299
+ ...baseNode.props,
300
+ action: {
301
+ identifier: "navigation_action",
302
+ flavour: "flavour_1" as const,
303
+ },
304
+ },
305
+ };
306
+
307
+ const nodeWithFlavour2 = {
308
+ ...baseNode,
309
+ props: {
310
+ ...baseNode.props,
311
+ action: {
312
+ identifier: "navigation_action",
313
+ flavour: "flavour_2" as const,
314
+ },
315
+ },
316
+ };
317
+
318
+ it("renders mobileButtonAssets component instead of Image when it is a function", () => {
319
+ const MockAssetComponent = jest.fn(({ testID }) => (
320
+ <View testID={testID} accessibilityLabel="mock-asset" />
321
+ ));
322
+
323
+ mockUseActions.mockReturnValue(
324
+ buildActionContext({ mobileButtonAssets: MockAssetComponent })
325
+ );
326
+
327
+ const { getByTestId } = renderNode(nodeWithFlavour1);
328
+
329
+ expect(MockAssetComponent).toHaveBeenCalled();
330
+ expect(getByTestId("mobile-action-button-asset")).toBeTruthy();
331
+ });
332
+
333
+ it("passes flavour_1 to the mobileButtonAssets component", () => {
334
+ const MockAssetComponent = jest.fn(({ testID }) => (
335
+ <View testID={testID} />
336
+ ));
337
+
338
+ mockUseActions.mockReturnValue(
339
+ buildActionContext({ mobileButtonAssets: MockAssetComponent })
340
+ );
341
+
342
+ renderNode(nodeWithFlavour1);
343
+
344
+ expect(MockAssetComponent).toHaveBeenCalledWith(
345
+ expect.objectContaining({ flavour: "flavour_1" }),
346
+ expect.anything()
347
+ );
348
+ });
349
+
350
+ it("passes flavour_2 to the mobileButtonAssets component", () => {
351
+ const MockAssetComponent = jest.fn(({ testID }) => (
352
+ <View testID={testID} />
353
+ ));
354
+
355
+ mockUseActions.mockReturnValue(
356
+ buildActionContext({ mobileButtonAssets: MockAssetComponent })
357
+ );
358
+
359
+ renderNode(nodeWithFlavour2);
360
+
361
+ expect(MockAssetComponent).toHaveBeenCalledWith(
362
+ expect.objectContaining({ flavour: "flavour_2" }),
363
+ expect.anything()
364
+ );
365
+ });
366
+
367
+ it("does not render asset when mobileButtonAssets is absent", () => {
368
+ mockUseActions.mockReturnValue(
369
+ buildActionContext({ mobileButtonAssets: undefined })
370
+ );
371
+
372
+ const { queryByTestId } = renderNode(nodeWithFlavour1);
373
+
374
+ expect(queryByTestId("mobile-action-button-asset")).toBeNull();
375
+ });
376
+
377
+ it("renders asset when mobileButtonAssets exists even if asset is absent", () => {
378
+ mockUseActions.mockReturnValue(
379
+ buildActionContext({
380
+ asset: undefined,
381
+ mobileButtonAssets: [
382
+ "https://example.com/image-inactive.png",
383
+ "https://example.com/image-active.png",
384
+ ],
385
+ })
386
+ );
387
+
388
+ const { getByTestId } = renderNode(nodeWithFlavour1);
389
+
390
+ expect(getByTestId("mobile-action-button-asset")).toBeTruthy();
391
+ });
392
+ });
393
+ });
@@ -0,0 +1,141 @@
1
+ import { MobileActionButtons } from "..";
2
+ import { Asset } from "../Asset";
3
+ import { AssetComponent } from "../AssetComponent";
4
+ import { Button } from "../Button";
5
+ import { TextLabelsContainer } from "../TextLabelsContainer";
6
+
7
+ describe("mobile action button builders", () => {
8
+ const configuration = {
9
+ mobile_buttons_container_align: "left",
10
+ mobile_buttons_container_margin_top: 0,
11
+ mobile_buttons_container_margin_right: 12,
12
+ mobile_buttons_container_margin_bottom: 0,
13
+ mobile_buttons_container_margin_left: 6,
14
+ mobile_buttons_container_stacking: "horizontal",
15
+ mobile_button_1_button_enabled: true,
16
+ mobile_button_1_assign_action: "navigation_action",
17
+ mobile_button_1_display_mode: "dynamic",
18
+ mobile_button_1_contents_alignment: "center",
19
+ mobile_button_1_background_color: "rgba(1,1,1,1)",
20
+ mobile_button_1_border_color: "rgba(0,0,0,0)",
21
+ mobile_button_1_border_size: 0,
22
+ mobile_button_1_corner_radius: 8,
23
+ mobile_button_1_padding_top: 10,
24
+ mobile_button_1_padding_right: 10,
25
+ mobile_button_1_padding_bottom: 10,
26
+ mobile_button_1_padding_left: 10,
27
+ mobile_button_1_asset_enabled: true,
28
+ mobile_button_1_action_asset_flavour: "flavour_1",
29
+ mobile_button_1_asset_alignment: "left",
30
+ mobile_button_1_asset_width: 24,
31
+ mobile_button_1_asset_height: 24,
32
+ mobile_button_1_asset_margin_top: 1,
33
+ mobile_button_1_asset_margin_right: 2,
34
+ mobile_button_1_asset_margin_bottom: 3,
35
+ mobile_button_1_asset_margin_left: 4,
36
+ mobile_button_1_label_enabled: true,
37
+ mobile_button_1_font_color: "rgba(255,255,255,1)",
38
+ mobile_button_1_focused_font_color: "rgba(255,0,0,1)",
39
+ mobile_button_1_ios_font_family: "Ubuntu-Bold",
40
+ mobile_button_1_android_font_family: "Ubuntu-Bold",
41
+ mobile_button_1_font_size: 15,
42
+ mobile_button_1_line_height: 24,
43
+ mobile_button_1_ios_letter_spacing: -0.2,
44
+ mobile_button_1_android_letter_spacing: -0.2,
45
+ mobile_button_1_text_transform: "default",
46
+ };
47
+
48
+ const value = (key) => configuration[key];
49
+
50
+ it("builds the button node with asset and text label children", () => {
51
+ const result = Button({
52
+ index: 0,
53
+ value,
54
+ stylePrefix: "mobile_button_1",
55
+ specificPrefix: "mobile_button_1",
56
+ spacingStyle: { marginRight: 8 },
57
+ });
58
+
59
+ expect(result.type).toBe("MobileActionButton");
60
+ expect(result.additionalProps.action.identifier).toBe("navigation_action");
61
+
62
+ expect(result.elements).toEqual(
63
+ expect.arrayContaining([
64
+ expect.objectContaining({ type: "ReactComponent" }),
65
+ expect.objectContaining({ type: "View" }),
66
+ ])
67
+ );
68
+ });
69
+
70
+ it("wraps each mobile button with DataProvider", () => {
71
+ const dataProviderConfiguration = {
72
+ ...configuration,
73
+ mobile_buttons_container_buttons_enabled: true,
74
+ mobile_buttons_container_position: "over_image",
75
+ mobile_buttons_container_align: "left",
76
+ mobile_buttons_container_margin_top: 0,
77
+ mobile_buttons_container_margin_right: 12,
78
+ mobile_buttons_container_margin_bottom: 0,
79
+ mobile_buttons_container_margin_left: 6,
80
+ mobile_buttons_container_stacking: "horizontal",
81
+ mobile_buttons_container_horizontal_gutter: 8,
82
+ mobile_buttons_container_vertical_gutter: 8,
83
+ mobile_buttons_container_independent_styles: true,
84
+ mobile_buttons_container_over_image_position: "top_right",
85
+ };
86
+
87
+ const result = MobileActionButtons({
88
+ value: (key) => dataProviderConfiguration[key],
89
+ configuration: dataProviderConfiguration,
90
+ placement: "over_image",
91
+ });
92
+
93
+ // Top-level is ButtonContainerView (View)
94
+ expect(result.type).toBe("ButtonContainerView");
95
+
96
+ // Content view contains DataProvider-wrapped buttons
97
+ const dataProvider = result.elements[0];
98
+ expect(dataProvider.type).toBe("DataProvider");
99
+
100
+ expect(dataProvider).toMatchObject({
101
+ type: "DataProvider",
102
+ data: [
103
+ {
104
+ func: "identity",
105
+ args: [],
106
+ propName: "entry",
107
+ },
108
+ ],
109
+ elements: [expect.objectContaining({ type: "MobileActionButton" })],
110
+ });
111
+ });
112
+
113
+ it("builds the asset, label container, and label nodes", () => {
114
+ expect(
115
+ Asset({
116
+ style: { width: 24, height: 24 },
117
+ testID: "mobile_action_button_1",
118
+ })
119
+ ).toEqual(
120
+ expect.objectContaining({
121
+ type: "ReactComponent",
122
+ additionalProps: expect.objectContaining({
123
+ component: AssetComponent,
124
+ }),
125
+ })
126
+ );
127
+
128
+ expect(
129
+ TextLabelsContainer({
130
+ actionIdentifier: "navigation_action",
131
+ testID: "mobile_action_button_1",
132
+ style: { color: "rgba(255,255,255,1)" },
133
+ })
134
+ ).toEqual(
135
+ expect.objectContaining({
136
+ type: "View",
137
+ elements: [expect.objectContaining({ type: "Text" })],
138
+ })
139
+ );
140
+ });
141
+ });