@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,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
+ });
@@ -0,0 +1,354 @@
1
+ import { MobileActionButtons } from "..";
2
+
3
+ describe("MobileActionButtons", () => {
4
+ const configuration = {
5
+ mobile_buttons_container_buttons_enabled: true,
6
+ mobile_buttons_container_position: "over_image",
7
+ mobile_buttons_container_align: "left",
8
+ mobile_buttons_container_margin_top: 0,
9
+ mobile_buttons_container_margin_right: 0,
10
+ mobile_buttons_container_margin_bottom: 0,
11
+ mobile_buttons_container_margin_left: 0,
12
+ mobile_buttons_container_stacking: "horizontal",
13
+ mobile_buttons_container_horizontal_gutter: 8,
14
+ mobile_buttons_container_vertical_gutter: 8,
15
+ mobile_buttons_container_independent_styles: true,
16
+ mobile_buttons_container_over_image_position: "top_right",
17
+ mobile_button_1_button_enabled: true,
18
+ mobile_button_1_assign_action: "navigation_action",
19
+ mobile_button_1_display_mode: "dynamic",
20
+ mobile_button_1_contents_alignment: "center",
21
+ mobile_button_1_background_color: "rgba(1,1,1,1)",
22
+ mobile_button_1_border_color: "rgba(0,0,0,0)",
23
+ mobile_button_1_border_size: 0,
24
+ mobile_button_1_corner_radius: 8,
25
+ mobile_button_1_padding_top: 10,
26
+ mobile_button_1_padding_right: 10,
27
+ mobile_button_1_padding_bottom: 10,
28
+ mobile_button_1_padding_left: 10,
29
+ mobile_button_1_asset_width: 24,
30
+ mobile_button_1_asset_height: 24,
31
+ mobile_button_1_asset_margin_top: 0,
32
+ mobile_button_1_asset_margin_right: 0,
33
+ mobile_button_1_asset_margin_bottom: 0,
34
+ mobile_button_1_asset_margin_left: 0,
35
+ mobile_button_1_asset_enabled: true,
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
+ };
40
+
41
+ const value = (key) => configuration[key];
42
+
43
+ it("renders over-image buttons only for over_image placement", () => {
44
+ const result = MobileActionButtons({
45
+ value,
46
+ configuration,
47
+ placement: "over_image",
48
+ });
49
+
50
+ expect(result).toBeTruthy();
51
+ expect(result.type).toBe("ButtonContainerView");
52
+ expect(result.style.position).toBe("absolute");
53
+ expect(result.style.top).toBe(0);
54
+ expect(result.style.right).toBe(0);
55
+
56
+ // Content view
57
+ expect(result.additionalProps.contentStyle.flexDirection).toBe("row");
58
+
59
+ // First button wrapped in DataProvider
60
+ const dataProvider = result.elements[0];
61
+ expect(dataProvider.type).toBe("DataProvider");
62
+
63
+ expect(dataProvider.data).toEqual([
64
+ {
65
+ func: "identity",
66
+ args: [],
67
+ propName: "entry",
68
+ },
69
+ ]);
70
+
71
+ const button = dataProvider.elements[0];
72
+ expect(button.type).toBe("MobileActionButton");
73
+
74
+ expect(button.elements).toEqual(
75
+ expect.arrayContaining([
76
+ expect.objectContaining({ type: "ReactComponent" }),
77
+ expect.objectContaining({ type: "View" }),
78
+ ])
79
+ );
80
+ });
81
+
82
+ it("does not render label placement when position is over_image", () => {
83
+ const result = MobileActionButtons({
84
+ value,
85
+ configuration,
86
+ placement: "labels",
87
+ });
88
+
89
+ expect(result).toBeNull();
90
+ });
91
+
92
+ it("renders button 1 when only button 1 is enabled", () => {
93
+ const configurationWithSingleButton = {
94
+ ...configuration,
95
+ mobile_button_1_button_enabled: true,
96
+ mobile_button_2_button_enabled: false,
97
+ mobile_button_3_button_enabled: false,
98
+ };
99
+
100
+ const singleButtonValue = (key) => configurationWithSingleButton[key];
101
+
102
+ const result = MobileActionButtons({
103
+ value: singleButtonValue,
104
+ configuration: configurationWithSingleButton,
105
+ placement: "over_image",
106
+ });
107
+
108
+ expect(result?.elements?.[0]?.elements).toHaveLength(1);
109
+
110
+ expect(result?.elements?.[0]?.type).toBe("DataProvider");
111
+
112
+ expect(result?.elements?.[0]?.elements?.[0]?.type).toBe(
113
+ "MobileActionButton"
114
+ );
115
+ });
116
+
117
+ it("renders button 2 when only button 2 is enabled", () => {
118
+ const configurationWithSingleButton = {
119
+ ...configuration,
120
+ mobile_button_1_button_enabled: false,
121
+ mobile_button_2_button_enabled: true,
122
+ mobile_button_2_asset_enabled: true,
123
+ mobile_button_3_button_enabled: false,
124
+ };
125
+
126
+ const singleButtonValue = (key) => configurationWithSingleButton[key];
127
+
128
+ const result = MobileActionButtons({
129
+ value: singleButtonValue,
130
+ configuration: configurationWithSingleButton,
131
+ placement: "over_image",
132
+ });
133
+
134
+ expect(result?.elements?.[0]?.elements).toHaveLength(1);
135
+
136
+ expect(result?.elements?.[0]?.elements?.[0]?.type).toBe(
137
+ "MobileActionButton"
138
+ );
139
+
140
+ expect(
141
+ result?.elements?.[0]?.elements?.[0]?.elements?.[0]?.additionalProps
142
+ ?.action?.identifier
143
+ ).toBe(configurationWithSingleButton.mobile_button_2_assign_action);
144
+ });
145
+
146
+ it("renders button 3 when only button 3 is enabled", () => {
147
+ const configurationWithSingleButton = {
148
+ ...configuration,
149
+ mobile_button_1_button_enabled: false,
150
+ mobile_button_2_button_enabled: false,
151
+ mobile_button_3_button_enabled: true,
152
+ mobile_button_3_assign_action: "local_storage_favourites_action",
153
+ mobile_button_3_asset_enabled: true,
154
+ };
155
+
156
+ const singleButtonValue = (key) => configurationWithSingleButton[key];
157
+
158
+ const result = MobileActionButtons({
159
+ value: singleButtonValue,
160
+ configuration: configurationWithSingleButton,
161
+ placement: "over_image",
162
+ });
163
+
164
+ expect(result?.elements?.[0]?.elements).toHaveLength(1);
165
+
166
+ expect(result?.elements?.[0]?.elements?.[0]?.type).toBe(
167
+ "MobileActionButton"
168
+ );
169
+
170
+ expect(
171
+ result?.elements?.[0]?.elements?.[0]?.additionalProps?.action?.identifier
172
+ ).toBe(configurationWithSingleButton.mobile_button_3_assign_action);
173
+ });
174
+
175
+ it("renders sparse slots contiguously while preserving slot-specific actions", () => {
176
+ const configurationWithSparseButtons = {
177
+ ...configuration,
178
+ mobile_button_1_button_enabled: true,
179
+ mobile_button_1_assign_action: "action_1",
180
+ mobile_button_2_button_enabled: false,
181
+ mobile_button_3_button_enabled: true,
182
+ mobile_button_3_assign_action: "action_3",
183
+ mobile_button_3_asset_enabled: true,
184
+ };
185
+
186
+ const sparseValue = (key) => configurationWithSparseButtons[key];
187
+
188
+ const result = MobileActionButtons({
189
+ value: sparseValue,
190
+ configuration: configurationWithSparseButtons,
191
+ placement: "over_image",
192
+ });
193
+
194
+ // Content view has 2 DataProvider children (one per enabled button)
195
+ expect(result?.elements).toHaveLength(2);
196
+
197
+ expect(
198
+ result?.elements?.[0]?.elements?.[0]?.additionalProps?.action?.identifier
199
+ ).toBe("action_1");
200
+
201
+ expect(
202
+ result?.elements?.[1]?.elements?.[0]?.additionalProps?.action?.identifier
203
+ ).toBe("action_3");
204
+
205
+ expect(result?.elements?.[0]?.elements?.[0]?.additionalProps?.testID).toBe(
206
+ "mobile_action_button_1"
207
+ );
208
+
209
+ expect(result?.elements?.[1]?.elements?.[0]?.additionalProps?.testID).toBe(
210
+ "mobile_action_button_2"
211
+ );
212
+ });
213
+
214
+ it("maps shared semantic layout data into content styles", () => {
215
+ const configurationWithLayout = {
216
+ ...configuration,
217
+ mobile_buttons_container_align: "middle",
218
+ mobile_buttons_container_margin_top: 5,
219
+ mobile_buttons_container_margin_right: 6,
220
+ mobile_buttons_container_margin_bottom: 7,
221
+ mobile_buttons_container_margin_left: 8,
222
+ mobile_buttons_container_stacking: "vertical",
223
+ };
224
+
225
+ const layoutValue = (key) => configurationWithLayout[key];
226
+
227
+ const result = MobileActionButtons({
228
+ value: layoutValue,
229
+ configuration: configurationWithLayout,
230
+ placement: "labels",
231
+ });
232
+
233
+ expect(result).toBeNull();
234
+
235
+ const overImageResult = MobileActionButtons({
236
+ value: layoutValue,
237
+ configuration: configurationWithLayout,
238
+ placement: "over_image",
239
+ });
240
+
241
+ expect(overImageResult?.additionalProps?.contentStyle).toMatchObject({
242
+ flexDirection: "column",
243
+ alignItems: "center",
244
+ paddingTop: 5,
245
+ paddingRight: 6,
246
+ paddingBottom: 7,
247
+ paddingLeft: 8,
248
+ });
249
+
250
+ expect(overImageResult?.style).toMatchObject({
251
+ alignItems: "flex-end",
252
+ bottom: 0,
253
+ justifyContent: "flex-start",
254
+ left: 0,
255
+ position: "absolute",
256
+ right: 0,
257
+ top: 0,
258
+ zIndex: 10,
259
+ });
260
+
261
+ expect(
262
+ overImageResult?.additionalProps?.contentStyle?.alignSelf
263
+ ).toBeUndefined();
264
+ });
265
+
266
+ describe("button content visibility", () => {
267
+ const makeConfig = (overrides) => ({
268
+ ...configuration,
269
+ ...overrides,
270
+ });
271
+
272
+ it("renders only an asset when only asset is enabled", () => {
273
+ const config = makeConfig({
274
+ mobile_button_1_asset_enabled: true,
275
+ mobile_button_1_label_enabled: false,
276
+ });
277
+
278
+ const result = MobileActionButtons({
279
+ value: (key) => config[key],
280
+ configuration: config,
281
+ placement: "over_image",
282
+ });
283
+
284
+ const buttonElements =
285
+ result?.elements?.[0]?.elements?.[0]?.elements ?? [];
286
+
287
+ expect(buttonElements.some((el) => el.type === "ReactComponent")).toBe(
288
+ true
289
+ );
290
+
291
+ expect(buttonElements.some((el) => el.type === "View")).toBe(false);
292
+ });
293
+
294
+ it("renders only a label container when only label is enabled", () => {
295
+ const config = makeConfig({
296
+ mobile_button_1_asset_enabled: false,
297
+ mobile_button_1_label_enabled: true,
298
+ });
299
+
300
+ const result = MobileActionButtons({
301
+ value: (key) => config[key],
302
+ configuration: config,
303
+ placement: "over_image",
304
+ });
305
+
306
+ const buttonElements =
307
+ result?.elements?.[0]?.elements?.[0]?.elements ?? [];
308
+
309
+ expect(buttonElements.some((el) => el.type === "ReactComponent")).toBe(
310
+ false
311
+ );
312
+
313
+ expect(buttonElements.some((el) => el.type === "View")).toBe(true);
314
+ });
315
+
316
+ it("renders both asset and label container when both are enabled", () => {
317
+ const config = makeConfig({
318
+ mobile_button_1_asset_enabled: true,
319
+ mobile_button_1_label_enabled: true,
320
+ });
321
+
322
+ const result = MobileActionButtons({
323
+ value: (key) => config[key],
324
+ configuration: config,
325
+ placement: "over_image",
326
+ });
327
+
328
+ const buttonElements =
329
+ result?.elements?.[0]?.elements?.[0]?.elements ?? [];
330
+
331
+ expect(buttonElements.some((el) => el.type === "ReactComponent")).toBe(
332
+ true
333
+ );
334
+
335
+ expect(buttonElements.some((el) => el.type === "View")).toBe(true);
336
+ });
337
+
338
+ it("omits the button entirely when neither asset nor label is enabled", () => {
339
+ const config = makeConfig({
340
+ mobile_button_1_asset_enabled: false,
341
+ mobile_button_1_label_enabled: false,
342
+ });
343
+
344
+ const result = MobileActionButtons({
345
+ value: (key) => config[key],
346
+ configuration: config,
347
+ placement: "over_image",
348
+ });
349
+
350
+ // Button() returns null so compact filters the DataProvider wrapper
351
+ expect(result?.elements).toHaveLength(0);
352
+ });
353
+ });
354
+ });
@@ -0,0 +1,105 @@
1
+ import { Platform } from "react-native";
2
+
3
+ export function isStringAsset(asset) {
4
+ return typeof asset === "string" || Array.isArray(asset);
5
+ }
6
+
7
+ export function getAssetValue(asset, flavour, fallbackAsset = null) {
8
+ if (!asset) {
9
+ return fallbackAsset;
10
+ }
11
+
12
+ if (typeof asset === "string") {
13
+ return asset;
14
+ }
15
+
16
+ if (Array.isArray(asset)) {
17
+ const flavourIndex = Number(String(flavour || "").replace("flavour_", ""));
18
+
19
+ if (!Number.isNaN(flavourIndex) && flavourIndex > 0) {
20
+ return asset[flavourIndex - 1];
21
+ }
22
+
23
+ return asset[0];
24
+ }
25
+
26
+ return asset.src || fallbackAsset;
27
+ }
28
+
29
+ export function getContentDirection(alignment = "left") {
30
+ switch (alignment) {
31
+ case "right":
32
+ return "row-reverse";
33
+ case "above":
34
+ return "column";
35
+ case "below":
36
+ return "column-reverse";
37
+ case "left":
38
+ default:
39
+ return "row";
40
+ }
41
+ }
42
+
43
+ export function getContentsAlignment(alignment = "center") {
44
+ switch (alignment) {
45
+ case "left":
46
+ return "flex-start";
47
+ case "right":
48
+ return "flex-end";
49
+ case "center":
50
+ default:
51
+ return "center";
52
+ }
53
+ }
54
+
55
+ export function resolveLabelText(label) {
56
+ if (typeof label === "string") {
57
+ return label;
58
+ }
59
+
60
+ return label?.label_1 || "";
61
+ }
62
+
63
+ export function resolveIsActive(actionState, fallbackSelected = false) {
64
+ if (actionState == null) {
65
+ return fallbackSelected;
66
+ }
67
+
68
+ return Boolean(
69
+ actionState?.active ??
70
+ actionState?.isActive ??
71
+ actionState?.selected ??
72
+ actionState?.isSelected ??
73
+ fallbackSelected
74
+ );
75
+ }
76
+
77
+ export function buildLegacySelection(item, actionContext) {
78
+ const defaultIsSelected = (actionContext?.state || []).includes(item);
79
+
80
+ return actionContext?.masterCell?.isSelected
81
+ ? actionContext?.masterCell?.isSelected(item)
82
+ : defaultIsSelected;
83
+ }
84
+
85
+ export function buildLabelStyle(label) {
86
+ if (!label?.enabled) {
87
+ return {};
88
+ }
89
+
90
+ const platformFontFamily =
91
+ Platform.OS === "ios"
92
+ ? label?.iosFontFamily || label?.normalStyle?.fontFamily
93
+ : label?.androidFontFamily || label?.normalStyle?.fontFamily;
94
+
95
+ const platformLetterSpacing =
96
+ Platform.OS === "ios"
97
+ ? label?.iosLetterSpacing
98
+ : label?.androidLetterSpacing;
99
+
100
+ return {
101
+ ...label?.normalStyle,
102
+ fontFamily: platformFontFamily,
103
+ letterSpacing: platformLetterSpacing,
104
+ };
105
+ }