@applicaster/zapp-react-native-ui-components 15.1.4 → 16.0.0-alpha.1042942411

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 (202) hide show
  1. package/Components/BackgroundImage/index.ts +1 -8
  2. package/Components/BaseFocusable/index.ios.ts +12 -2
  3. package/Components/Cell/FocusableWrapper.tsx +3 -0
  4. package/Components/Cell/TvOSCellComponent.tsx +6 -3
  5. package/Components/CellRendererResolver/index.ts +1 -1
  6. package/Components/Focusable/Focusable.tsx +4 -2
  7. package/Components/Focusable/FocusableTvOS.tsx +18 -1
  8. package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +1 -0
  9. package/Components/FocusableGroup/FocusableTvOS.tsx +30 -1
  10. package/Components/GeneralContentScreen/GeneralContentScreenHookAdapter.tsx +39 -0
  11. package/Components/GeneralContentScreen/__tests__/GeneralContentScreenHookAdapter.test.tsx +64 -0
  12. package/Components/GeneralContentScreen/__tests__/HookContentFocusGroup.web.test.tsx +91 -0
  13. package/Components/GeneralContentScreen/hookAdapter/__tests__/networkService.test.ts +74 -0
  14. package/Components/GeneralContentScreen/hookAdapter/__tests__/runInBackground.test.ts +139 -0
  15. package/Components/GeneralContentScreen/hookAdapter/__tests__/validationHelper.test.ts +124 -0
  16. package/Components/GeneralContentScreen/hookAdapter/logger.ts +6 -0
  17. package/Components/GeneralContentScreen/hookAdapter/networkService.ts +53 -0
  18. package/Components/GeneralContentScreen/hookAdapter/runInBackground.ts +48 -0
  19. package/Components/GeneralContentScreen/hookAdapter/validationHelper.ts +72 -0
  20. package/Components/GeneralContentScreen/hookFocus/index.tsx +13 -0
  21. package/Components/GeneralContentScreen/hookFocus/index.web.tsx +69 -0
  22. package/Components/GeneralContentScreen/index.ts +2 -0
  23. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
  24. package/Components/HandlePlayable/HandlePlayable.tsx +13 -8
  25. package/Components/Layout/TV/NavBarContainer.tsx +1 -10
  26. package/Components/Layout/TV/ScreenContainer.tsx +2 -6
  27. package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +7 -12
  28. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +7 -12
  29. package/Components/Layout/TV/index.tsx +3 -4
  30. package/Components/Layout/TV/index.web.tsx +3 -4
  31. package/Components/LinkHandler/LinkHandler.tsx +2 -2
  32. package/Components/MasterCell/CONFIG_BUILDER_TO_REACT_COMPONENT.md +144 -0
  33. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +27 -48
  34. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/model.test.ts +80 -0
  35. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +213 -0
  36. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/selectors.test.ts +45 -0
  37. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/style.test.ts +49 -0
  38. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +165 -0
  39. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +405 -0
  40. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/index.ts +1 -0
  41. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/model.ts +47 -0
  42. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +198 -0
  43. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/selectors.ts +26 -0
  44. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/style.ts +29 -0
  45. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/types.ts +37 -0
  46. package/Components/MasterCell/DefaultComponents/BorderContainerView/__tests__/index.test.tsx +217 -46
  47. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +50 -119
  48. package/Components/MasterCell/DefaultComponents/Button.tsx +50 -58
  49. package/Components/MasterCell/DefaultComponents/ButtonContainerView/components/HorizontalSeparator.tsx +8 -0
  50. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.android.tv.tsx +58 -0
  51. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tsx +15 -0
  52. package/Components/MasterCell/DefaultComponents/{tv/ButtonContainerView/index.tsx → ButtonContainerView/index.tv.tsx} +3 -11
  53. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.web.ts +1 -0
  54. package/Components/MasterCell/DefaultComponents/ButtonContainerView/types.ts +40 -0
  55. package/Components/MasterCell/DefaultComponents/CellBadge/__tests__/__snapshots__/index.test.tsx.snap +18 -0
  56. package/Components/MasterCell/DefaultComponents/CellBadge/__tests__/index.test.tsx +90 -0
  57. package/Components/MasterCell/DefaultComponents/CellBadge/index.tsx +13 -14
  58. package/Components/MasterCell/DefaultComponents/DataProvider/index.tsx +163 -0
  59. package/Components/MasterCell/DefaultComponents/FocusableView/index.android.tsx +2 -23
  60. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -22
  61. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +8 -2
  62. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +11 -3
  63. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +9 -1
  64. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +15 -14
  65. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -3
  66. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +1 -2
  67. package/Components/MasterCell/DefaultComponents/PressableView.tsx +54 -0
  68. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +46 -50
  69. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +47 -39
  70. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
  71. package/Components/MasterCell/DefaultComponents/SecondaryImage/utils.ts +19 -6
  72. package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +15 -0
  73. package/Components/MasterCell/DefaultComponents/Text/index.tsx +45 -15
  74. package/Components/MasterCell/DefaultComponents/UIKitAdapters/Badge.tsx +49 -0
  75. package/Components/MasterCell/DefaultComponents/__tests__/ActionButton.test.tsx +122 -0
  76. package/Components/MasterCell/DefaultComponents/__tests__/Button.test.tsx +186 -0
  77. package/Components/MasterCell/DefaultComponents/__tests__/DataProvider.test.tsx +141 -0
  78. package/Components/MasterCell/DefaultComponents/__tests__/DynamicBadge.test.tsx +156 -0
  79. package/Components/MasterCell/DefaultComponents/__tests__/__snapshots__/ActionButton.test.tsx.snap +60 -0
  80. package/Components/MasterCell/DefaultComponents/__tests__/text.test.tsx +196 -67
  81. package/Components/MasterCell/DefaultComponents/index.ts +13 -3
  82. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +189 -0
  83. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +33 -0
  84. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +33 -0
  85. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +159 -0
  86. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +18 -0
  87. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabel.ts +67 -0
  88. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +39 -0
  89. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +402 -0
  90. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +141 -0
  91. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +354 -0
  92. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/helpers.ts +105 -0
  93. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +129 -0
  94. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +123 -0
  95. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +238 -0
  96. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Asset.ts +4 -18
  97. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Button.ts +24 -73
  98. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TextLabelsContainer.ts +37 -18
  99. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TvActionButton.tsx +27 -0
  100. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +89 -0
  101. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/renderedTree.test.tsx +231 -0
  102. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +47 -48
  103. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +115 -29
  104. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/insertButtonsBetweenLabels.test.ts +45 -13
  105. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +101 -144
  106. package/Components/MasterCell/MappingFunctions/index.js +3 -2
  107. package/Components/MasterCell/README.md +4 -0
  108. package/Components/MasterCell/__tests__/__snapshots__/dataAdapter.test.js.snap +24 -0
  109. package/Components/MasterCell/__tests__/configInflater.test.js +1 -0
  110. package/Components/MasterCell/__tests__/elementMapper.test.js +46 -0
  111. package/Components/MasterCell/contexts/PressedStateContext.ts +3 -0
  112. package/Components/MasterCell/dataAdapter.ts +4 -1
  113. package/Components/MasterCell/elementMapper.tsx +54 -7
  114. package/Components/MasterCell/hooks/index.ts +2 -0
  115. package/Components/MasterCell/hooks/usePressedState.ts +4 -0
  116. package/Components/MasterCell/index.tsx +20 -4
  117. package/Components/MasterCell/utils/__tests__/cloneChildrenWithIds.test.tsx +43 -0
  118. package/Components/MasterCell/utils/__tests__/useFilterChildren.test.tsx +80 -0
  119. package/Components/MasterCell/utils/index.ts +82 -16
  120. package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +33 -69
  121. package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +25 -47
  122. package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +122 -39
  123. package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +671 -0
  124. package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +257 -88
  125. package/Components/ModalComponent/AudioPlayer/Components/index.ts +6 -4
  126. package/Components/ModalComponent/BottomSheetDragArea.tsx +33 -0
  127. package/Components/ModalComponent/BottomSheetModalContent.tsx +17 -10
  128. package/Components/ModalComponent/__tests__/BottomSheetDragArea.test.tsx +74 -0
  129. package/Components/OfflineHandler/__tests__/__snapshots__/index.test.tsx.snap +4 -85
  130. package/Components/OfflineHandler/__tests__/index.test.tsx +101 -32
  131. package/Components/OfflineHandler/hooks.ts +123 -0
  132. package/Components/OfflineHandler/index.tsx +51 -73
  133. package/Components/OfflineHandler/utils/index.ts +1 -13
  134. package/Components/PlayerContainer/PlayerContainer.tsx +6 -5
  135. package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +2 -4
  136. package/Components/PreloaderWrapper/index.tsx +1 -1
  137. package/Components/River/ComponentsMap/ComponentsMap.tsx +8 -21
  138. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  139. package/Components/River/RefreshControl.tsx +19 -105
  140. package/Components/River/River.tsx +9 -82
  141. package/Components/River/TV/River.tsx +31 -14
  142. package/Components/River/TV/index.tsx +8 -4
  143. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +30 -0
  144. package/Components/River/TV/utils/index.ts +4 -0
  145. package/Components/River/TV/withFocusableGroupForContent.tsx +71 -0
  146. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +4 -0
  147. package/Components/River/__tests__/componentsMap.test.js +38 -0
  148. package/Components/River/hooks/__tests__/usePullToRefresh.test.ts +132 -0
  149. package/Components/River/hooks/index.ts +1 -0
  150. package/Components/River/hooks/usePullToRefresh.ts +51 -0
  151. package/Components/Screen/TV/index.web.tsx +4 -2
  152. package/Components/Screen/__tests__/Screen.test.tsx +65 -42
  153. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +68 -44
  154. package/Components/Screen/hooks.ts +2 -3
  155. package/Components/Screen/index.tsx +2 -3
  156. package/Components/Screen/orientationHandler.ts +3 -3
  157. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +5 -3
  158. package/Components/ScreenResolver/index.tsx +9 -5
  159. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +1 -1
  160. package/Components/ScreenRevealManager/ScreenRevealManager.ts +40 -8
  161. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +86 -69
  162. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +1 -1
  163. package/Components/Tabs/TabContent.tsx +7 -4
  164. package/Components/TopCutoffOverlay/__tests__/TopCutoffOverlay.test.tsx +201 -0
  165. package/Components/TopCutoffOverlay/hooks/__tests__/useMarginTop.test.ts +130 -0
  166. package/Components/TopCutoffOverlay/hooks/index.ts +1 -0
  167. package/Components/TopCutoffOverlay/hooks/useMarginTop.ts +59 -0
  168. package/Components/TopCutoffOverlay/index.tsx +55 -0
  169. package/Components/Transitioner/index.js +3 -3
  170. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +5 -5
  171. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
  172. package/Components/VideoModal/utils.ts +12 -10
  173. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
  174. package/Components/Viewport/ViewportAware/index.tsx +16 -7
  175. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  176. package/Components/default-cell-renderer/viewTrees/mobile/index.ts +0 -3
  177. package/Contexts/CachedDimensionsContext/__tests__/index.test.ts +154 -0
  178. package/Contexts/ScreenContext/index.tsx +25 -18
  179. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  180. package/Decorators/Analytics/index.tsx +6 -5
  181. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +1 -0
  182. package/Decorators/ConfigurationWrapper/const.ts +1 -0
  183. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
  184. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  185. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  186. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
  187. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
  188. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +80 -0
  189. package/Helpers/DataSourceHelper/index.ts +19 -0
  190. package/package.json +5 -5
  191. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +0 -135
  192. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/types.ts +0 -25
  193. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +0 -112
  194. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +0 -107
  195. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +0 -153
  196. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +0 -67
  197. package/Components/OfflineHandler/NotificationView/utils.ts +0 -34
  198. package/Components/River/TV/withTVEventHandler.tsx +0 -36
  199. package/Helpers/DataSourceHelper/index.js +0 -19
  200. /package/Components/BackgroundImage/{BackgroundImage.tv.android.tsx → BackgroundImage.android.tv.tsx} +0 -0
  201. /package/Components/BackgroundImage/{BackgroundImage.tv.ios.tsx → BackgroundImage.ios.tv.tsx} +0 -0
  202. /package/Components/BackgroundImage/{BackgroundImage.tv.web.tsx → BackgroundImage.tsx} +0 -0
@@ -0,0 +1,159 @@
1
+ import {
2
+ toNumberWithDefault,
3
+ toNumberWithDefaultZero,
4
+ } from "@applicaster/zapp-react-native-utils/numberUtils";
5
+ import { compact } from "@applicaster/zapp-react-native-utils/cellUtils";
6
+
7
+ import { Asset } from "./Asset";
8
+ import { TextLabelsContainer } from "./TextLabelsContainer";
9
+ import {
10
+ getAssetStyles,
11
+ getContentDirection,
12
+ getContentsAlignment,
13
+ getPressableStyles,
14
+ getTextLabelStyles,
15
+ } from "./utils";
16
+ import { ViewStyle } from "react-native";
17
+ import { Spacer } from "./Spacer";
18
+
19
+ const displayModeStyle = (value, isVertical: boolean) => {
20
+ const mode = value("display_mode") || "dynamic";
21
+
22
+ const defaultStyle = {
23
+ flexShrink: 1,
24
+ };
25
+
26
+ const width = toNumberWithDefault(140, value("width"));
27
+
28
+ switch (mode) {
29
+ case "fixed":
30
+ return {
31
+ ...defaultStyle,
32
+ width,
33
+ maxWidth: width,
34
+ };
35
+ case "fill":
36
+ return {
37
+ ...defaultStyle,
38
+ width: isVertical ? "100%" : undefined,
39
+ flex: !isVertical ? 1 : undefined,
40
+ };
41
+ case "dynamic":
42
+ default:
43
+ return {
44
+ ...defaultStyle,
45
+ };
46
+ }
47
+ };
48
+
49
+ type Props = {
50
+ index: number;
51
+ value: Function;
52
+ stylePrefix: string;
53
+ specificPrefix: string;
54
+ spacingStyle: ViewStyle;
55
+ isVertical: boolean;
56
+ };
57
+
58
+ export const Button = ({
59
+ index,
60
+ value,
61
+ stylePrefix,
62
+ specificPrefix,
63
+ spacingStyle,
64
+ isVertical,
65
+ }: Props) => {
66
+ // Retrieves values depending to the slot
67
+ const getSlotBasedValue = (property: string) =>
68
+ value(`${specificPrefix}_${property}`);
69
+
70
+ /**
71
+ * Retrieves values depending to the independ_style toggle:
72
+ * (e.g for button value button_2_label_enabled returns button_1_label_enabled when independent_style: false)
73
+ */
74
+ const getValue = (property: string) => value(`${stylePrefix}_${property}`);
75
+
76
+ const assetEnabled = getValue("asset_enabled");
77
+ const labelEnabled = getValue("label_enabled");
78
+
79
+ if (!getSlotBasedValue("button_enabled")) {
80
+ return null;
81
+ }
82
+
83
+ if (!assetEnabled && !labelEnabled) {
84
+ return null;
85
+ }
86
+
87
+ const testID = `mobile_action_button_${index + 1}`;
88
+ const actionIdentifier = getSlotBasedValue("assign_action");
89
+ const assetAlignment = getValue("asset_alignment") || "left";
90
+
91
+ const hasHorizontalAlignment = ["left", "right"].includes(assetAlignment);
92
+ const vGutter = toNumberWithDefaultZero(getValue("vertical_gutter"));
93
+ const hGutter = toNumberWithDefaultZero(getValue("horizontal_gutter"));
94
+ const actionAssetFlavour = getValue("action_asset_flavour");
95
+ const contentsAlignment = getValue("contents_alignment") || "center";
96
+ const displayMode = getValue("display_mode") || "dynamic";
97
+
98
+ return {
99
+ type: "MobileActionButton",
100
+ style: {
101
+ // alignContent: "center", Why do we need this?
102
+ ...getPressableStyles(getValue),
103
+ ...displayModeStyle(getValue, isVertical),
104
+ ...spacingStyle,
105
+ },
106
+ additionalProps: {
107
+ displayMode,
108
+ alignment: {
109
+ alignContent: "center",
110
+ flexDirection: getContentDirection(assetAlignment),
111
+ alignItems: getContentsAlignment(contentsAlignment, assetAlignment),
112
+ justifyContent: getContentsAlignment(contentsAlignment, assetAlignment),
113
+ },
114
+ action: {
115
+ identifier: actionIdentifier,
116
+ flavour: actionAssetFlavour,
117
+ width: toNumberWithDefault(24, getValue("asset_width")),
118
+ height: toNumberWithDefault(24, getValue("asset_height")),
119
+ },
120
+ focusedStyles: {
121
+ backgroundColor: getValue("focused_background_color"),
122
+ borderColor: getValue("focused_border_color"),
123
+ },
124
+ testID,
125
+ },
126
+ elements: compact([
127
+ assetEnabled
128
+ ? Asset({
129
+ style: getAssetStyles(getValue),
130
+ testID,
131
+ })
132
+ : null,
133
+ Spacer({
134
+ enabled: assetEnabled && labelEnabled,
135
+ style: hasHorizontalAlignment
136
+ ? { width: hGutter }
137
+ : { height: vGutter },
138
+ }),
139
+ labelEnabled
140
+ ? TextLabelsContainer({
141
+ style: getTextLabelStyles(getValue),
142
+ extraProps: {
143
+ normalStyles: {
144
+ color: getValue("font_color"),
145
+ },
146
+ focusedStyles: {
147
+ color: getValue("focused_font_color"),
148
+ },
149
+ transformText: getValue("text_transform") || "default",
150
+ numberOfLines: getValue("number_of_lines") || 1,
151
+ ellipsizeMode: "tail",
152
+ },
153
+ actionIdentifier,
154
+ testID,
155
+ })
156
+ : null,
157
+ ]),
158
+ };
159
+ };
@@ -0,0 +1,18 @@
1
+ type Props = {
2
+ enabled?: boolean;
3
+ style?: object;
4
+ };
5
+
6
+ export const Spacer = ({
7
+ enabled = false,
8
+ style = { flex: 1 },
9
+ }: Props = {}) => {
10
+ if (!enabled) {
11
+ return null;
12
+ }
13
+
14
+ return {
15
+ type: "View",
16
+ style,
17
+ };
18
+ };
@@ -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,39 @@
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
+ style: {
18
+ flexShrink: 1,
19
+ },
20
+ additionalProps: {
21
+ mobileActionRole: "label_container",
22
+ },
23
+ elements: [
24
+ {
25
+ type: "Text",
26
+ style: style,
27
+ additionalProps: {
28
+ label: {
29
+ context: actionIdentifier,
30
+ name: "label_1",
31
+ },
32
+ mobileActionRole: "label",
33
+ testID: testID ? `${testID}-label` : undefined,
34
+ ...extraProps,
35
+ },
36
+ },
37
+ ],
38
+ };
39
+ };
@@ -0,0 +1,402 @@
1
+ import React from "react";
2
+ import { Pressable, Text as RNText, 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
+ import { usePressedState } from "../../../../hooks/usePressedState";
12
+
13
+ jest.mock("../../../../hooks/usePressedState", () => ({
14
+ usePressedState: jest.fn(),
15
+ }));
16
+
17
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/actions", () => ({
18
+ useActions: jest.fn(),
19
+ }));
20
+
21
+ jest.mock("@applicaster/zapp-react-native-utils/theme", () => ({
22
+ useTheme: () => ({}),
23
+ }));
24
+
25
+ jest.mock("@applicaster/zapp-react-native-utils/localizationUtils", () => ({
26
+ useIsRTL: jest.fn(() => false),
27
+ }));
28
+
29
+ jest.mock(
30
+ "@applicaster/zapp-react-native-utils/reactHooks/navigation/useNavigation",
31
+ () => ({
32
+ useNavigation: jest.fn(() => ({
33
+ currentRoute: "home",
34
+ videoModalState: {
35
+ visible: false,
36
+ },
37
+ })),
38
+ })
39
+ );
40
+
41
+ jest.mock(
42
+ "@applicaster/zapp-react-native-utils/reactHooks/navigation/usePathname",
43
+ () => ({
44
+ usePathname: jest.fn(() => "home"),
45
+ })
46
+ );
47
+
48
+ jest.mock(
49
+ "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager/hooks",
50
+ () => ({
51
+ useAccessibilityManager: jest.fn(() => ({
52
+ addHeading: jest.fn(),
53
+ })),
54
+ })
55
+ );
56
+
57
+ const mockUseActions = useActions as jest.Mock;
58
+
59
+ const entry = {
60
+ id: "entry-1",
61
+ } as ZappEntry;
62
+
63
+ const buildActionContext = (entryState = {}) => ({
64
+ initialEntryState: jest.fn(() => ({
65
+ asset: {
66
+ inactive: "https://example.com/image-inactive.png",
67
+ active: "https://example.com/image-active.png",
68
+ },
69
+ mobileButtonAssets: [
70
+ "https://example.com/image-inactive.png",
71
+ "https://example.com/image-active.png",
72
+ ],
73
+ label: {
74
+ label_1: "Play",
75
+ },
76
+ ...entryState,
77
+ })),
78
+ invokeAction: jest.fn(),
79
+ addListener: jest.fn(() => jest.fn()),
80
+ isActionAvailable: jest.fn(() => true),
81
+ });
82
+
83
+ const mobileConfiguration = {
84
+ mobile_buttons_container_buttons_enabled: true,
85
+ mobile_buttons_container_position: "over_image",
86
+ mobile_buttons_container_align: "left",
87
+ mobile_buttons_container_margin_top: 0,
88
+ mobile_buttons_container_margin_right: 0,
89
+ mobile_buttons_container_margin_bottom: 0,
90
+ mobile_buttons_container_margin_left: 0,
91
+ mobile_buttons_container_stacking: "horizontal",
92
+ mobile_buttons_container_horizontal_gutter: 8,
93
+ mobile_buttons_container_vertical_gutter: 8,
94
+ mobile_buttons_container_independent_styles: true,
95
+ mobile_buttons_container_over_image_position: "top_right",
96
+ mobile_button_1_button_enabled: true,
97
+ mobile_button_1_assign_action: "navigation_action",
98
+ mobile_button_1_display_mode: "dynamic",
99
+ mobile_button_1_contents_alignment: "center",
100
+ mobile_button_1_background_color: "rgba(1,1,1,1)",
101
+ mobile_button_1_border_color: "rgba(2,2,2,1)",
102
+ mobile_button_1_border_size: 1,
103
+ mobile_button_1_corner_radius: 8,
104
+ mobile_button_1_padding_top: 10,
105
+ mobile_button_1_padding_right: 10,
106
+ mobile_button_1_padding_bottom: 10,
107
+ mobile_button_1_padding_left: 10,
108
+ mobile_button_1_asset_width: 24,
109
+ mobile_button_1_asset_height: 24,
110
+ mobile_button_1_asset_margin_top: 0,
111
+ mobile_button_1_asset_margin_right: 0,
112
+ mobile_button_1_asset_margin_bottom: 0,
113
+ mobile_button_1_asset_margin_left: 0,
114
+ mobile_button_1_asset_enabled: true,
115
+ mobile_button_1_label_enabled: true,
116
+ mobile_button_1_font_color: "rgba(10,10,10,1)",
117
+ mobile_button_1_focused_font_color: "rgba(20,20,20,1)",
118
+ mobile_button_1_font_size: 15,
119
+ mobile_button_1_line_height: 24,
120
+ mobile_button_1_ios_font_family: "Ubuntu-Bold",
121
+ mobile_button_1_android_font_family: "Ubuntu-Bold",
122
+ mobile_button_1_ios_letter_spacing: -0.2,
123
+ mobile_button_1_android_letter_spacing: -0.2,
124
+ mobile_button_1_text_transform: "default",
125
+ };
126
+
127
+ const baseNode = {
128
+ type: "MobileActionButton",
129
+ style: {
130
+ backgroundColor: "rgba(1,1,1,1)",
131
+ borderColor: "rgba(2,2,2,1)",
132
+ borderWidth: 1,
133
+ },
134
+ props: {
135
+ entry,
136
+ action: {
137
+ identifier: "navigation_action",
138
+ },
139
+ focusedStyles: {
140
+ backgroundColor: "rgba(3,3,3,1)",
141
+ borderColor: "rgba(4,4,4,1)",
142
+ },
143
+ testID: "mobile-action-button",
144
+ testOnly_pressed: false,
145
+ },
146
+ elements: [
147
+ {
148
+ type: "ReactComponent",
149
+ style: {
150
+ width: 24,
151
+ height: 24,
152
+ },
153
+ props: {
154
+ component: AssetComponent,
155
+ renderChildren: false,
156
+ requiresCellUUID: true,
157
+ testID: "mobile-action-button-asset",
158
+ mobileActionRole: "asset",
159
+ },
160
+ },
161
+ {
162
+ type: "View",
163
+ style: {
164
+ flexDirection: "column",
165
+ },
166
+ props: {
167
+ mobileActionRole: "label_container",
168
+ },
169
+ elements: [
170
+ {
171
+ type: "Text",
172
+ style: {
173
+ fontSize: 15,
174
+ },
175
+ props: {
176
+ testID: "mobile-action-button-label",
177
+ label: {
178
+ context: "navigation_action",
179
+ name: "label_1",
180
+ },
181
+ focusedStyles: {
182
+ color: "rgba(20,20,20,1)",
183
+ },
184
+ normalStyles: {
185
+ color: "rgba(10,10,10,1)",
186
+ },
187
+ mobileActionRole: "label",
188
+ transformText: "default",
189
+ },
190
+ },
191
+ ],
192
+ },
193
+ ],
194
+ };
195
+
196
+ describe("MobileActionButton", () => {
197
+ beforeEach(() => {
198
+ jest.clearAllMocks();
199
+ (usePressedState as jest.Mock).mockReturnValue(false);
200
+ });
201
+
202
+ const renderNode = ({ node = baseNode, pressed = false } = {}) => {
203
+ if (pressed) {
204
+ node.props.testOnly_pressed = true;
205
+ }
206
+
207
+ return render(
208
+ <React.Fragment>
209
+ {elementMapper(defaultComponents)(node as never)}
210
+ </React.Fragment>
211
+ );
212
+ };
213
+
214
+ it("renders nested image and text children through elementMapper", () => {
215
+ mockUseActions.mockReturnValue(buildActionContext());
216
+
217
+ const { getByText, getByTestId } = renderNode();
218
+
219
+ expect(getByTestId("mobile-action-button")).toBeTruthy();
220
+ expect(getByTestId("mobile-action-button-asset")).toBeTruthy();
221
+ expect(getByText("Play")).toBeTruthy();
222
+ });
223
+
224
+ it("applies focused styles when action entry state is active", () => {
225
+ (usePressedState as jest.Mock).mockReturnValue(true);
226
+ mockUseActions.mockReturnValue(buildActionContext({ active: true }));
227
+
228
+ const { getByTestId, getByText } = renderNode({ pressed: true });
229
+
230
+ expect(
231
+ getByTestId("mobile-action-button").props.style[2].backgroundColor
232
+ ).toBe("rgba(3,3,3,1)");
233
+
234
+ expect(getByText("Play").props.style).toMatchObject({
235
+ color: "rgba(20,20,20,1)",
236
+ });
237
+ });
238
+
239
+ it("invokes action on press", () => {
240
+ const actionContext = buildActionContext();
241
+ mockUseActions.mockReturnValue(actionContext);
242
+
243
+ const { getByTestId } = renderNode();
244
+
245
+ fireEvent.press(getByTestId("mobile-action-button"));
246
+
247
+ expect(actionContext.invokeAction).toHaveBeenCalledWith(
248
+ entry,
249
+ expect.objectContaining({
250
+ updateState: expect.any(Function),
251
+ })
252
+ );
253
+ });
254
+
255
+ it("preserves the rendered primitive tree when the mobile builder is wrapped with DataProvider", () => {
256
+ mockUseActions.mockReturnValue(buildActionContext());
257
+
258
+ const node = configInflater(
259
+ entry,
260
+ MobileActionButtons({
261
+ value: (key) => mobileConfiguration[key],
262
+ configuration: mobileConfiguration,
263
+ placement: "over_image",
264
+ })
265
+ );
266
+
267
+ node.props.testOnly_pressed = false;
268
+
269
+ const { UNSAFE_getAllByType, UNSAFE_getByType, getByTestId, getByText } =
270
+ render(
271
+ <React.Fragment>
272
+ {elementMapper(defaultComponents)(node as never)}
273
+ </React.Fragment>
274
+ );
275
+
276
+ const pressable = UNSAFE_getByType(Pressable);
277
+ const label = getByText("Play");
278
+ const asset = getByTestId("mobile_action_button_1-asset");
279
+
280
+ expect(pressable.props.testID).toBe("mobile_action_button_1");
281
+ expect(pressable.props.accessibilityLabel).toBe("entry-1");
282
+
283
+ expect(pressable.props.style({ pressed: false })[0]).toMatchObject({
284
+ backgroundColor: "rgba(1,1,1,1)",
285
+ borderColor: "rgba(2,2,2,1)",
286
+ borderWidth: 1,
287
+ });
288
+
289
+ expect(asset.props.style).toMatchObject({
290
+ width: 24,
291
+ height: 24,
292
+ });
293
+
294
+ expect(label.props.style).toMatchObject({
295
+ color: "rgba(10,10,10,1)",
296
+ fontSize: 15,
297
+ lineHeight: 24,
298
+ });
299
+
300
+ expect(UNSAFE_getAllByType(View).length).toBeGreaterThan(0);
301
+ expect(UNSAFE_getAllByType(RNText).length).toBeGreaterThan(0);
302
+ });
303
+
304
+ describe("component asset via mobileButtonAssets", () => {
305
+ const nodeWithFlavour1 = {
306
+ ...baseNode,
307
+ props: {
308
+ ...baseNode.props,
309
+ action: {
310
+ identifier: "navigation_action",
311
+ flavour: "flavour_1" as const,
312
+ },
313
+ },
314
+ };
315
+
316
+ const nodeWithFlavour2 = {
317
+ ...baseNode,
318
+ props: {
319
+ ...baseNode.props,
320
+ action: {
321
+ identifier: "navigation_action",
322
+ flavour: "flavour_2" as const,
323
+ },
324
+ },
325
+ };
326
+
327
+ it("renders mobileButtonAssets component instead of Image when it is a function", () => {
328
+ const MockAssetComponent = jest.fn(({ testID }) => (
329
+ <View testID={testID} accessibilityLabel="mock-asset" />
330
+ ));
331
+
332
+ mockUseActions.mockReturnValue(
333
+ buildActionContext({ mobileButtonAssets: MockAssetComponent })
334
+ );
335
+
336
+ const { getByTestId } = renderNode({ node: nodeWithFlavour1 });
337
+
338
+ expect(MockAssetComponent).toHaveBeenCalled();
339
+ expect(getByTestId("mobile-action-button-asset")).toBeTruthy();
340
+ });
341
+
342
+ it("passes flavour_1 to the mobileButtonAssets component", () => {
343
+ const MockAssetComponent = jest.fn(({ testID }) => (
344
+ <View testID={testID} />
345
+ ));
346
+
347
+ mockUseActions.mockReturnValue(
348
+ buildActionContext({ mobileButtonAssets: MockAssetComponent })
349
+ );
350
+
351
+ renderNode({ node: nodeWithFlavour1 });
352
+
353
+ expect(MockAssetComponent).toHaveBeenCalledWith(
354
+ expect.objectContaining({ flavour: "flavour_1" }),
355
+ expect.anything()
356
+ );
357
+ });
358
+
359
+ it("passes flavour_2 to the mobileButtonAssets component", () => {
360
+ const MockAssetComponent = jest.fn(({ testID }) => (
361
+ <View testID={testID} />
362
+ ));
363
+
364
+ mockUseActions.mockReturnValue(
365
+ buildActionContext({ mobileButtonAssets: MockAssetComponent })
366
+ );
367
+
368
+ renderNode({ node: nodeWithFlavour2 });
369
+
370
+ expect(MockAssetComponent).toHaveBeenCalledWith(
371
+ expect.objectContaining({ flavour: "flavour_2" }),
372
+ expect.anything()
373
+ );
374
+ });
375
+
376
+ it("does not render asset when mobileButtonAssets is absent", () => {
377
+ mockUseActions.mockReturnValue(
378
+ buildActionContext({ mobileButtonAssets: undefined })
379
+ );
380
+
381
+ const { queryByTestId } = renderNode({ node: nodeWithFlavour1 });
382
+
383
+ expect(queryByTestId("mobile-action-button-asset")).toBeNull();
384
+ });
385
+
386
+ it("renders asset when mobileButtonAssets exists even if asset is absent", () => {
387
+ mockUseActions.mockReturnValue(
388
+ buildActionContext({
389
+ asset: undefined,
390
+ mobileButtonAssets: [
391
+ "https://example.com/image-inactive.png",
392
+ "https://example.com/image-active.png",
393
+ ],
394
+ })
395
+ );
396
+
397
+ const { getByTestId } = renderNode({ node: nodeWithFlavour1 });
398
+
399
+ expect(getByTestId("mobile-action-button-asset")).toBeTruthy();
400
+ });
401
+ });
402
+ });