@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,231 @@
1
+ import React from "react";
2
+ import { Text as RNText, View } from "react-native";
3
+ import { render } 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 { TvActionButtons } from "..";
10
+
11
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/actions", () => ({
12
+ useActions: jest.fn(),
13
+ }));
14
+
15
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks", () => ({
16
+ useIsScreenActive: jest.fn(() => false),
17
+ }));
18
+
19
+ jest.mock("@applicaster/zapp-react-native-utils/theme", () => ({
20
+ useTheme: () => ({}),
21
+ }));
22
+
23
+ jest.mock("@applicaster/zapp-react-native-utils/localizationUtils", () => ({
24
+ useIsRTL: jest.fn(() => false),
25
+ applyRTLStylesIfNeeded: jest.fn((style) => style),
26
+ }));
27
+
28
+ jest.mock(
29
+ "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager/hooks",
30
+ () => ({
31
+ useAccessibilityManager: jest.fn(() => ({
32
+ addHeading: jest.fn(),
33
+ })),
34
+ })
35
+ );
36
+
37
+ jest.mock(
38
+ "@applicaster/zapp-react-native-ui-components/Components/Focusable",
39
+ () => ({
40
+ Focusable: ({ children, ...props }) => {
41
+ const { View: MockView } = require("react-native");
42
+
43
+ return (
44
+ <MockView testID="tv-focusable" {...props}>
45
+ {typeof children === "function" ? children(false) : children}
46
+ </MockView>
47
+ );
48
+ },
49
+ })
50
+ );
51
+
52
+ const mockUseActions = useActions as jest.Mock;
53
+
54
+ const item = {
55
+ id: "entry-1",
56
+ } as ZappEntry;
57
+
58
+ const buildActionContext = (entryState = {}) => ({
59
+ initialEntryState: jest.fn(() => ({
60
+ label: {
61
+ label_1: "Play",
62
+ },
63
+ ...entryState,
64
+ })),
65
+ invokeAction: jest.fn(),
66
+ addListener: jest.fn(() => jest.fn()),
67
+ addListeners: jest.fn(() => jest.fn()),
68
+ });
69
+
70
+ const tvConfiguration = {
71
+ tv_buttons_container_buttons_enabled: true,
72
+ tv_buttons_container_align: "middle",
73
+ tv_buttons_container_margin_top: 1,
74
+ tv_buttons_container_margin_right: 2,
75
+ tv_buttons_container_margin_bottom: 3,
76
+ tv_buttons_container_margin_left: 4,
77
+ tv_buttons_container_horizontal_gutter: 10,
78
+ tv_buttons_container_independent_styles: true,
79
+ tv_buttons_button_1_button_enabled: true,
80
+ tv_buttons_button_1_assign_action: "navigation_action",
81
+ tv_buttons_button_1_background_padding_top: 11,
82
+ tv_buttons_button_1_background_padding_right: 12,
83
+ tv_buttons_button_1_background_padding_bottom: 13,
84
+ tv_buttons_button_1_background_padding_left: 14,
85
+ tv_buttons_button_1_background_color: "rgba(1,1,1,1)",
86
+ tv_buttons_button_1_background_border_color: "rgba(2,2,2,1)",
87
+ tv_buttons_button_1_background_border_thickness: 1,
88
+ tv_buttons_button_1_background_corner_radius: 8,
89
+ tv_buttons_button_1_focused_background_color: "rgba(3,3,3,1)",
90
+ tv_buttons_button_1_focused_background_border_color: "rgba(4,4,4,1)",
91
+ tv_buttons_button_1_display_mode: "dynamic",
92
+ tv_buttons_button_1_asset_enabled: true,
93
+ tv_buttons_button_1_asset_width: 40,
94
+ tv_buttons_button_1_asset_height: 40,
95
+ tv_buttons_button_1_asset_margin_top: 0,
96
+ tv_buttons_button_1_asset_margin_right: 0,
97
+ tv_buttons_button_1_asset_margin_bottom: 0,
98
+ tv_buttons_button_1_asset_margin_left: 0,
99
+ tv_buttons_button_1_asset_alignment: "left",
100
+ tv_buttons_button_1_label_1_toggle: true,
101
+ tv_buttons_button_1_label_1_margin_top: 0,
102
+ tv_buttons_button_1_label_1_margin_right: 0,
103
+ tv_buttons_button_1_label_1_margin_bottom: 0,
104
+ tv_buttons_button_1_label_1_margin_left: 0,
105
+ tv_buttons_button_1_label_1_padding_top: 0,
106
+ tv_buttons_button_1_label_1_padding_right: 0,
107
+ tv_buttons_button_1_label_1_padding_bottom: 0,
108
+ tv_buttons_button_1_label_1_padding_left: 0,
109
+ tv_buttons_button_1_label_1_text_alignment: "left",
110
+ tv_buttons_button_1_label_1_text_transform: "default",
111
+ tv_buttons_button_1_label_1_font_color: "rgba(10,10,10,1)",
112
+ tv_buttons_button_1_label_1_focused_font_color: "rgba(20,20,20,1)",
113
+ tv_buttons_button_1_label_1_font_size: 18,
114
+ tv_buttons_button_1_label_1_line_height: 24,
115
+ tv_buttons_button_1_label_1_font_family: "Ubuntu-Bold",
116
+ tv_buttons_button_1_label_1_letter_spacing: -0.2,
117
+ };
118
+
119
+ const mergeStyle = (style) =>
120
+ (Array.isArray(style) ? style : [style]).reduce(
121
+ (acc, part) => ({ ...acc, ...(part || {}) }),
122
+ {}
123
+ );
124
+
125
+ describe("TvActionButtons rendered tree", () => {
126
+ beforeEach(() => {
127
+ jest.clearAllMocks();
128
+ });
129
+
130
+ it("preserves the rendered primitive tree when the tv builder is wrapped with DataProvider", () => {
131
+ mockUseActions.mockImplementation((identifier) => {
132
+ if (identifier === "") {
133
+ return {
134
+ actions: {
135
+ navigation_action: {
136
+ module: {
137
+ context: {
138
+ _currentValue: {
139
+ isActionAvailable: jest.fn(() => true),
140
+ },
141
+ },
142
+ },
143
+ },
144
+ },
145
+ };
146
+ }
147
+
148
+ return buildActionContext();
149
+ });
150
+
151
+ const node = configInflater(
152
+ item,
153
+ TvActionButtons({
154
+ value: (key) => tvConfiguration[key],
155
+ platformValue: (key) => tvConfiguration[key],
156
+ configuration: tvConfiguration,
157
+ state: "focused",
158
+ skipButtons: false,
159
+ })
160
+ );
161
+
162
+ const { UNSAFE_getAllByType, getByText } = render(
163
+ <React.Fragment>
164
+ {elementMapper(defaultComponents)(node as never)}
165
+ </React.Fragment>
166
+ );
167
+
168
+ const views = UNSAFE_getAllByType(View);
169
+ const text = getByText("Play");
170
+
171
+ const labelContainer = views.find(
172
+ (view) => mergeStyle(view.props.style).flexDirection === "column"
173
+ );
174
+
175
+ const buttonView = views.find((view) => {
176
+ const style = mergeStyle(view.props.style);
177
+
178
+ return style.display === "flex" && style.alignItems === "center";
179
+ });
180
+
181
+ const outerContainer = views.find((view) => {
182
+ const style = mergeStyle(view.props.style);
183
+
184
+ return style.flexDirection === "row" && style.justifyContent === "center";
185
+ });
186
+
187
+ expect(mergeStyle(outerContainer?.props.style)).toEqual(
188
+ expect.objectContaining({
189
+ flexDirection: "row",
190
+ justifyContent: "center",
191
+ marginTop: 1,
192
+ marginRight: 2,
193
+ marginBottom: 3,
194
+ marginLeft: 4,
195
+ })
196
+ );
197
+
198
+ expect(mergeStyle(buttonView?.props.style)).toEqual(
199
+ expect.objectContaining({
200
+ display: "flex",
201
+ alignItems: "center",
202
+ paddingTop: 11,
203
+ paddingRight: 12,
204
+ paddingBottom: 13,
205
+ paddingLeft: 14,
206
+ backgroundColor: "rgba(1,1,1,1)",
207
+ borderColor: "rgba(2,2,2,1)",
208
+ borderWidth: 1,
209
+ borderRadius: 8,
210
+ })
211
+ );
212
+
213
+ expect(mergeStyle(labelContainer?.props.style)).toEqual(
214
+ expect.objectContaining({
215
+ flexDirection: "column",
216
+ })
217
+ );
218
+
219
+ expect(UNSAFE_getAllByType(RNText)).toHaveLength(1);
220
+
221
+ expect(mergeStyle(text.props.style)).toEqual(
222
+ expect.objectContaining({
223
+ fontSize: 18,
224
+ lineHeight: 24,
225
+ letterSpacing: -0.2,
226
+ fontFamily: "Ubuntu-Bold",
227
+ color: "rgba(10,10,10,1)",
228
+ })
229
+ );
230
+ });
231
+ });
@@ -1,12 +1,6 @@
1
- import { times } from "@applicaster/zapp-react-native-utils/utils";
2
- import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
3
-
4
1
  import { Button } from "./Button";
5
- import {
6
- getButtonsCount,
7
- getPluginIdentifier,
8
- mapSelfAlignment,
9
- } from "./utils";
2
+ import { memoizedGetPluginIdentifier } from "./utils";
3
+ import { buildActionButtonsModel } from "../../ActionButtonsCore/model";
10
4
 
11
5
  import { compact } from "@applicaster/zapp-react-native-utils/cellUtils";
12
6
  import { PREFIX, BUTTON_PREFIX } from "./const";
@@ -16,11 +10,9 @@ export {
16
10
  insertButtonsBetweenLabelContainers,
17
11
  } from "./utils";
18
12
 
19
- const buttonId = (index: number) => `${BUTTON_PREFIX}_${index}`;
20
-
21
13
  type Props = {
22
- value: Function;
23
- platformValue: Function;
14
+ value: (key: string) => unknown;
15
+ platformValue: (key: string) => unknown;
24
16
  configuration: Record<string, unknown>;
25
17
  state: string;
26
18
  skipButtons: boolean;
@@ -33,58 +25,65 @@ export const TvActionButtons = ({
33
25
  state,
34
26
  skipButtons,
35
27
  }: Props) => {
36
- if (skipButtons || !value(`${PREFIX}_container_buttons_enabled`)) {
28
+ if (skipButtons) {
37
29
  return null;
38
30
  }
39
31
 
40
- const buttonsCount = getButtonsCount(configuration, PREFIX);
32
+ const model = buildActionButtonsModel({
33
+ configuration,
34
+ value,
35
+ containerPrefix: `${PREFIX}_container`,
36
+ buttonPrefix: BUTTON_PREFIX,
37
+ });
41
38
 
42
- if (buttonsCount <= 0) {
39
+ if (!model) {
43
40
  return null;
44
41
  }
45
42
 
46
- const independentStyles = value(`${PREFIX}_container_independent_styles`);
47
-
48
43
  return {
49
44
  type: "ButtonContainerView",
50
45
  style: {
51
46
  flexDirection: "row",
52
-
53
- justifyContent: mapSelfAlignment(value(`${PREFIX}_container_align`)),
54
-
55
- marginTop: toNumberWithDefaultZero(
56
- value(`${PREFIX}_container_margin_top`)
57
- ),
58
- marginRight: toNumberWithDefaultZero(
59
- value(`${PREFIX}_container_margin_right`)
60
- ),
61
- marginBottom: toNumberWithDefaultZero(
62
- value(`${PREFIX}_container_margin_bottom`)
63
- ),
64
- marginLeft: toNumberWithDefaultZero(
65
- value(`${PREFIX}_container_margin_left`)
66
- ),
47
+ justifyContent: model.container.horizontalAlign,
48
+ marginTop: model.container.margins.top,
49
+ marginRight: model.container.margins.right,
50
+ marginBottom: model.container.margins.bottom,
51
+ marginLeft: model.container.margins.left,
67
52
  },
68
53
  additionalProps: {
69
- horizontalGutter: toNumberWithDefaultZero(
70
- value(`${PREFIX}_container_horizontal_gutter`)
71
- ),
54
+ horizontalGutter: model.container.horizontalGutter,
72
55
  state,
73
- buttonsCount,
56
+ buttonsCount: model.buttonsCount,
74
57
  },
75
58
  elements: compact(
76
- times((index) => {
77
- const prefixSpecificButton = buttonId(index + 1);
78
-
79
- return Button({
80
- prefix: independentStyles ? prefixSpecificButton : buttonId(1),
81
- value,
82
- platformValue,
83
- pluginIdentifier: getPluginIdentifier(configuration, PREFIX, index),
84
- suffixId: prefixSpecificButton,
85
- preferredFocus: index === 0,
86
- });
87
- }, buttonsCount)
59
+ model.buttons.map(
60
+ ({ slot, renderIndex, specificPrefix, stylePrefix }) => {
61
+ return {
62
+ type: "DataProvider",
63
+ data: [
64
+ {
65
+ func: "identity",
66
+ args: [],
67
+ propName: "entry",
68
+ },
69
+ ],
70
+ elements: [
71
+ Button({
72
+ prefix: stylePrefix,
73
+ value,
74
+ platformValue,
75
+ pluginIdentifier: memoizedGetPluginIdentifier(
76
+ configuration,
77
+ PREFIX,
78
+ slot
79
+ ) as string,
80
+ suffixId: specificPrefix,
81
+ preferredFocus: renderIndex === 0,
82
+ }),
83
+ ],
84
+ };
85
+ }
86
+ )
88
87
  ),
89
88
  };
90
89
  };
@@ -1,53 +1,123 @@
1
- import { getPluginIdentifier } from "..";
1
+ import { getPluginIdentifier, memoizedGetPluginIdentifier } from "..";
2
2
 
3
3
  describe("getPluginIdentifier", () => {
4
4
  const prefix = "tv_buttons";
5
5
 
6
- it("get first plugin identifier", () => {
6
+ beforeAll(() => {
7
+ memoizedGetPluginIdentifier.clear();
8
+ });
9
+
10
+ it("returns the requested slot action identifier", () => {
11
+ expect.assertions(2);
12
+
7
13
  const configuration = {
8
- tv_buttons_button_1_other: "value",
9
14
  tv_buttons_button_1_assign_action:
10
15
  "tv_buttons_button_1_assign_action_value",
11
- tv_buttons_button_2_assign_action:
12
- "tv_buttons_button_2_assign_action_value",
16
+ tv_buttons_button_3_assign_action:
17
+ "tv_buttons_button_3_assign_action_value",
13
18
  };
14
19
 
15
- const index = 0;
20
+ const slot = 3;
21
+ const result = getPluginIdentifier(configuration, prefix, slot);
16
22
 
17
- const result = getPluginIdentifier(configuration, prefix, index);
23
+ const memoizedResult = memoizedGetPluginIdentifier(
24
+ configuration,
25
+ prefix,
26
+ slot
27
+ );
18
28
 
19
- expect(result).toEqual(configuration.tv_buttons_button_1_assign_action);
29
+ expect(result).toEqual(configuration.tv_buttons_button_3_assign_action);
30
+ expect(memoizedResult).toEqual(result);
20
31
  });
21
32
 
22
- it("get second plugin identifier", () => {
33
+ it("returns undefined for a disabled or missing slot", () => {
34
+ expect.assertions(2);
35
+
23
36
  const configuration = {
24
- tv_buttons_button_1_other: "value_1",
25
- tv_buttons_button_1_assign_action:
26
- "tv_buttons_button_1_assign_action_value",
27
- tv_buttons_button_2_other: "value_2",
28
- tv_buttons_button_2_assign_action:
29
- "tv_buttons_button_2_assign_action_value",
37
+ tv_buttons_button_1_assign_action: "tv_buttons_button_1_assign_action",
30
38
  };
31
39
 
32
- const index = 1;
40
+ const slot = 2;
41
+ const result = getPluginIdentifier(configuration, prefix, slot);
33
42
 
34
- const result = getPluginIdentifier(configuration, prefix, index);
43
+ const memoizedResult = memoizedGetPluginIdentifier(
44
+ configuration,
45
+ prefix,
46
+ slot
47
+ );
35
48
 
36
- expect(result).toEqual(configuration.tv_buttons_button_2_assign_action);
49
+ expect(result).toBeUndefined();
50
+ expect(memoizedResult).toEqual(result);
51
+ });
52
+
53
+ it("returns undefined for empty string values", () => {
54
+ expect.assertions(2);
55
+
56
+ const configuration = {
57
+ tv_buttons_button_2_assign_action: "",
58
+ };
59
+
60
+ const slot = 2;
61
+
62
+ const result = getPluginIdentifier(configuration, prefix, slot);
63
+
64
+ const memoizedResult = memoizedGetPluginIdentifier(
65
+ configuration,
66
+ prefix,
67
+ slot
68
+ );
69
+
70
+ expect(result).toBeUndefined();
71
+ expect(memoizedResult).toBeUndefined();
37
72
  });
38
73
 
39
- it("get undefined if no assign_actions at all", () => {
40
- const configuration = {};
74
+ it("returns undefined for negative slots", () => {
75
+ expect.assertions(2);
76
+
77
+ const configuration = {
78
+ tv_buttons_button_1_assign_action: "a",
79
+ tv_buttons_button_2_assign_action: "b",
80
+ };
41
81
 
42
- const index = 0;
82
+ const slot = -1;
83
+ const result = getPluginIdentifier(configuration, prefix, slot);
43
84
 
44
- const result = getPluginIdentifier(configuration, prefix, index);
85
+ const memoizedResult = memoizedGetPluginIdentifier(
86
+ configuration,
87
+ prefix,
88
+ slot
89
+ );
45
90
 
46
91
  expect(result).toBeUndefined();
92
+ expect(memoizedResult).toBeUndefined();
93
+ });
94
+
95
+ it("handles non-string values by returning the configured slot value", () => {
96
+ expect.assertions(2);
97
+
98
+ const configuration = {
99
+ tv_buttons_button_2_assign_action: true,
100
+ };
101
+
102
+ const slot = 2;
103
+ const result = getPluginIdentifier(configuration, prefix, slot);
104
+
105
+ const memoizedResult = memoizedGetPluginIdentifier(
106
+ configuration,
107
+ prefix,
108
+ slot
109
+ );
110
+
111
+ expect(result).toBe(true);
112
+ expect(memoizedResult).toBe(true);
47
113
  });
48
114
  });
49
115
 
50
- describe("getPluginIdentifier - when configuration has same values for different keys", () => {
116
+ describe("getPluginIdentifier - when configuration has duplicate values", () => {
117
+ beforeAll(() => {
118
+ memoizedGetPluginIdentifier.clear();
119
+ });
120
+
51
121
  const prefix = "tv_buttons";
52
122
 
53
123
  const configuration = {
@@ -55,19 +125,35 @@ describe("getPluginIdentifier - when configuration has same values for different
55
125
  tv_buttons_button_2_assign_action: "navigation_action",
56
126
  };
57
127
 
58
- it("get first plugin identifier", () => {
59
- const index = 0;
128
+ it("returns the requested slot even when values are identical", () => {
129
+ expect.assertions(2);
60
130
 
61
- const result = getPluginIdentifier(configuration, prefix, index);
131
+ const slot = 1;
132
+ const result = getPluginIdentifier(configuration, prefix, slot);
133
+
134
+ const memoizedResult = memoizedGetPluginIdentifier(
135
+ configuration,
136
+ prefix,
137
+ slot
138
+ );
62
139
 
63
140
  expect(result).toEqual(configuration.tv_buttons_button_1_assign_action);
141
+ expect(memoizedResult).toEqual(result);
64
142
  });
65
143
 
66
- it("get second plugin identifier", () => {
67
- const index = 1;
144
+ it("returns the second slot when values are identical", () => {
145
+ expect.assertions(2);
146
+
147
+ const slot = 2;
148
+ const result = getPluginIdentifier(configuration, prefix, slot);
68
149
 
69
- const result = getPluginIdentifier(configuration, prefix, index);
150
+ const memoizedResult = memoizedGetPluginIdentifier(
151
+ configuration,
152
+ prefix,
153
+ slot
154
+ );
70
155
 
71
156
  expect(result).toEqual(configuration.tv_buttons_button_2_assign_action);
157
+ expect(memoizedResult).toEqual(result);
72
158
  });
73
159
  });