@applicaster/zapp-react-native-ui-components 15.1.0-rc.1 → 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 (144) hide show
  1. package/Components/BaseFocusable/index.ios.ts +12 -2
  2. package/Components/Cell/FocusableWrapper.tsx +3 -0
  3. package/Components/Cell/TvOSCellComponent.tsx +6 -3
  4. package/Components/Focusable/Focusable.tsx +4 -2
  5. package/Components/Focusable/FocusableTvOS.tsx +18 -1
  6. package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +1 -0
  7. package/Components/FocusableGroup/FocusableTvOS.tsx +30 -1
  8. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
  9. package/Components/HandlePlayable/HandlePlayable.tsx +13 -8
  10. package/Components/Layout/TV/LayoutBackground.tsx +5 -2
  11. package/Components/Layout/TV/NavBarContainer.tsx +1 -10
  12. package/Components/Layout/TV/ScreenContainer.tsx +2 -6
  13. package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +7 -12
  14. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +7 -12
  15. package/Components/Layout/TV/index.tsx +3 -4
  16. package/Components/Layout/TV/index.web.tsx +3 -4
  17. package/Components/LinkHandler/LinkHandler.tsx +2 -2
  18. package/Components/MasterCell/CONFIG_BUILDER_TO_REACT_COMPONENT.md +144 -0
  19. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/model.test.ts +80 -0
  20. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +187 -0
  21. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/selectors.test.ts +45 -0
  22. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/style.test.ts +49 -0
  23. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +165 -0
  24. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +405 -0
  25. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/index.ts +1 -0
  26. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/model.ts +47 -0
  27. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +170 -0
  28. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/selectors.ts +26 -0
  29. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/style.ts +29 -0
  30. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/types.ts +37 -0
  31. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +4 -10
  32. package/Components/MasterCell/DefaultComponents/Button.tsx +0 -15
  33. package/Components/MasterCell/DefaultComponents/ButtonContainerView/components/HorizontalSeparator.tsx +8 -0
  34. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tsx +15 -0
  35. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tv.android.tsx +58 -0
  36. package/Components/MasterCell/DefaultComponents/{tv/ButtonContainerView/index.tsx → ButtonContainerView/index.tv.tsx} +3 -11
  37. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.web.ts +1 -0
  38. package/Components/MasterCell/DefaultComponents/ButtonContainerView/types.ts +40 -0
  39. package/Components/MasterCell/DefaultComponents/DataProvider/index.tsx +163 -0
  40. package/Components/MasterCell/DefaultComponents/FocusableView/index.android.tsx +2 -23
  41. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -22
  42. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +8 -2
  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/index.tsx +1 -2
  47. package/Components/MasterCell/DefaultComponents/PressableView.tsx +34 -0
  48. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
  49. package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +11 -0
  50. package/Components/MasterCell/DefaultComponents/__tests__/DataProvider.test.tsx +141 -0
  51. package/Components/MasterCell/DefaultComponents/index.ts +9 -3
  52. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +135 -0
  53. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +33 -0
  54. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +22 -0
  55. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +125 -0
  56. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +16 -0
  57. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabel.ts +67 -0
  58. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +37 -0
  59. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +393 -0
  60. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +141 -0
  61. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +343 -0
  62. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/helpers.ts +105 -0
  63. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +122 -0
  64. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +118 -0
  65. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +238 -0
  66. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Asset.ts +4 -18
  67. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Button.ts +24 -73
  68. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TextLabelsContainer.ts +37 -18
  69. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TvActionButton.tsx +27 -0
  70. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +89 -0
  71. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/renderedTree.test.tsx +231 -0
  72. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +47 -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 +101 -144
  75. package/Components/MasterCell/MappingFunctions/index.js +3 -2
  76. package/Components/MasterCell/README.md +4 -0
  77. package/Components/MasterCell/__tests__/__snapshots__/dataAdapter.test.js.snap +24 -0
  78. package/Components/MasterCell/__tests__/configInflater.test.js +1 -0
  79. package/Components/MasterCell/__tests__/elementMapper.test.js +46 -0
  80. package/Components/MasterCell/dataAdapter.ts +4 -1
  81. package/Components/MasterCell/elementMapper.tsx +52 -7
  82. package/Components/MasterCell/utils/__tests__/cloneChildrenWithIds.test.tsx +43 -0
  83. package/Components/MasterCell/utils/__tests__/useFilterChildren.test.tsx +80 -0
  84. package/Components/MasterCell/utils/index.ts +85 -15
  85. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +2 -2
  86. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +17 -18
  87. package/Components/OfflineHandler/__tests__/index.test.tsx +27 -18
  88. package/Components/PlayerContainer/PlayerContainer.tsx +14 -13
  89. package/Components/River/ComponentsMap/ComponentsMap.tsx +6 -19
  90. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  91. package/Components/River/RefreshControl.tsx +19 -88
  92. package/Components/River/River.tsx +9 -82
  93. package/Components/River/TV/River.tsx +31 -14
  94. package/Components/River/TV/index.tsx +8 -4
  95. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +30 -0
  96. package/Components/River/TV/utils/index.ts +4 -0
  97. package/Components/River/TV/withFocusableGroupForContent.tsx +71 -0
  98. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +1 -0
  99. package/Components/River/__tests__/componentsMap.test.js +38 -0
  100. package/Components/River/hooks/__tests__/usePullToRefresh.test.ts +132 -0
  101. package/Components/River/hooks/index.ts +1 -0
  102. package/Components/River/hooks/usePullToRefresh.ts +51 -0
  103. package/Components/Screen/TV/index.web.tsx +4 -2
  104. package/Components/Screen/__tests__/Screen.test.tsx +65 -42
  105. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +68 -44
  106. package/Components/Screen/hooks.ts +2 -3
  107. package/Components/Screen/index.tsx +2 -3
  108. package/Components/Screen/orientationHandler.ts +3 -3
  109. package/Components/ScreenResolver/index.tsx +9 -5
  110. package/Components/ScreenRevealManager/ScreenRevealManager.ts +40 -8
  111. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +86 -69
  112. package/Components/Tabs/TabContent.tsx +7 -4
  113. package/Components/TopCutoffOverlay/__tests__/TopCutoffOverlay.test.tsx +201 -0
  114. package/Components/TopCutoffOverlay/hooks/__tests__/useMarginTop.test.ts +130 -0
  115. package/Components/TopCutoffOverlay/hooks/index.ts +1 -0
  116. package/Components/TopCutoffOverlay/hooks/useMarginTop.ts +59 -0
  117. package/Components/TopCutoffOverlay/index.tsx +55 -0
  118. package/Components/Transitioner/index.js +3 -3
  119. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +5 -5
  120. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
  121. package/Components/VideoModal/utils.ts +12 -9
  122. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
  123. package/Components/Viewport/ViewportAware/index.tsx +16 -7
  124. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  125. package/Components/ZappFrameworkComponents/BarView/BarView.tsx +4 -6
  126. package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +2 -2
  127. package/Components/default-cell-renderer/viewTrees/mobile/index.ts +0 -3
  128. package/Contexts/ScreenContext/index.tsx +25 -18
  129. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  130. package/Decorators/Analytics/index.tsx +6 -5
  131. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +1 -0
  132. package/Decorators/ConfigurationWrapper/const.ts +1 -0
  133. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
  134. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  135. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  136. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
  137. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
  138. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +80 -0
  139. package/Helpers/DataSourceHelper/index.ts +19 -0
  140. package/package.json +5 -5
  141. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +0 -135
  142. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/types.ts +0 -25
  143. package/Components/River/TV/withTVEventHandler.tsx +0 -36
  144. package/Helpers/DataSourceHelper/index.js +0 -19
@@ -0,0 +1,67 @@
1
+ import { Platform } from "react-native";
2
+
3
+ import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
4
+
5
+ type Props = {
6
+ prefix: string;
7
+ value: Function;
8
+ actionIdentifier: string;
9
+ testID?: string;
10
+ };
11
+
12
+ export const TextLabel = ({
13
+ prefix,
14
+ value,
15
+ actionIdentifier,
16
+ testID,
17
+ }: Props) => {
18
+ if (!value(`${prefix}_label_enabled`)) {
19
+ return null;
20
+ }
21
+
22
+ return {
23
+ type: "Text",
24
+ style: {
25
+ color: value(`${prefix}_font_color`),
26
+ fontSize: toNumberWithDefaultZero(value(`${prefix}_font_size`)),
27
+ lineHeight: toNumberWithDefaultZero(value(`${prefix}_line_height`)),
28
+ marginTop: toNumberWithDefaultZero(value(`${prefix}_margin_top`)),
29
+ marginRight: toNumberWithDefaultZero(value(`${prefix}_margin_right`)),
30
+ marginBottom: toNumberWithDefaultZero(value(`${prefix}_margin_bottom`)),
31
+ marginLeft: toNumberWithDefaultZero(value(`${prefix}_margin_left`)),
32
+ fontFamily:
33
+ Platform.OS === "ios"
34
+ ? value(`${prefix}_ios_font_family`)
35
+ : value(`${prefix}_android_font_family`),
36
+ letterSpacing: toNumberWithDefaultZero(
37
+ Platform.OS === "ios"
38
+ ? value(`${prefix}_ios_letter_spacing`)
39
+ : value(`${prefix}_android_letter_spacing`)
40
+ ),
41
+ },
42
+ data: [
43
+ {
44
+ func: (entry) => entry,
45
+ args: [],
46
+ propName: "entry",
47
+ },
48
+ ],
49
+ additionalProps: {
50
+ label: {
51
+ context: actionIdentifier,
52
+ name: "label_1",
53
+ },
54
+ normalStyles: {
55
+ color: value(`${prefix}_font_color`),
56
+ },
57
+ focusedStyles: {
58
+ color: value(`${prefix}_focused_font_color`),
59
+ },
60
+ state: "default",
61
+ mobileActionRole: "label",
62
+ transformText: value(`${prefix}_text_transform`) || "default",
63
+ numberOfLines: value(`${prefix}_number_of_lines`),
64
+ testID: testID ? `${testID}-label` : undefined,
65
+ },
66
+ };
67
+ };
@@ -0,0 +1,37 @@
1
+ type Props = {
2
+ actionIdentifier: string;
3
+ testID?: string;
4
+ style?: object;
5
+ labelEnabled?: boolean;
6
+ extraProps?: object;
7
+ };
8
+
9
+ export const TextLabelsContainer = ({
10
+ actionIdentifier,
11
+ testID,
12
+ style,
13
+ extraProps,
14
+ }: Props) => {
15
+ return {
16
+ type: "View",
17
+ additionalProps: {
18
+ mobileActionRole: "label_container",
19
+ },
20
+ elements: [
21
+ {
22
+ type: "Text",
23
+ style: style,
24
+ additionalProps: {
25
+ label: {
26
+ context: actionIdentifier,
27
+ name: "label_1",
28
+ },
29
+ state: "default",
30
+ mobileActionRole: "label",
31
+ testID: testID ? `${testID}-label` : undefined,
32
+ ...extraProps,
33
+ },
34
+ },
35
+ ],
36
+ };
37
+ };
@@ -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
+ });