@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
@@ -1,92 +1,221 @@
1
- import React from "react";
2
- import RN from "react-native";
3
- import { renderWithProviders } from "@applicaster/zapp-react-native-utils/testUtils";
1
+ import * as React from "react";
2
+ import { render, waitFor } from "@testing-library/react-native";
3
+ import { StyleSheet } from "react-native";
4
4
 
5
- const mockUseIsRTL = jest.fn(() => true);
6
- const mockGetIsRTL = jest.fn(() => true);
7
- const mockUseLocalizedStrings = jest.fn(() => ({}));
8
- const mockGetUILanguage = jest.fn(() => "he-HE");
5
+ import Text from "../Text";
6
+ import { MeasurementPortalContext } from "../../../MeasurmentsPortal";
7
+ import { CellFocusedStateContext } from "@applicaster/zapp-react-native-ui-components/Contexts/CellFocusedStateContext";
8
+
9
+ const mockAddHeading = jest.fn();
10
+
11
+ jest.mock(
12
+ "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager/hooks",
13
+ () => ({
14
+ useAccessibilityManager: jest.fn(() => ({
15
+ addHeading: mockAddHeading,
16
+ })),
17
+ })
18
+ );
9
19
 
10
20
  jest.mock("@applicaster/zapp-react-native-utils/localizationUtils", () => ({
11
- useIsRTL: mockUseIsRTL,
12
- getIsRTL: mockGetIsRTL,
13
- useLocalizedStrings: mockUseLocalizedStrings,
14
- getUILanguage: mockGetUILanguage,
21
+ useIsRTL: jest.fn(() => false),
15
22
  }));
16
23
 
17
- const CustomText = require("../Text").default;
18
-
19
- const defaultProps = {
20
- entry: {},
21
- style: {},
22
- transformText: "default",
23
- };
24
+ jest.mock("@applicaster/uikit/lib/components/uikit/text", () => ({
25
+ Text: ({
26
+ style,
27
+ focusedStyle,
28
+ normalStyles,
29
+ focusedState,
30
+ children,
31
+ ...props
32
+ }) => {
33
+ const React = require("react");
34
+ const { Text } = require("react-native");
35
+
36
+ const resolvedStyle = focusedState
37
+ ? [focusedState === "focused" ? focusedStyle : normalStyles, style]
38
+ : style;
39
+
40
+ return (
41
+ <Text {...props} allowFontScaling={false} style={resolvedStyle}>
42
+ {children}
43
+ </Text>
44
+ );
45
+ },
46
+ }));
24
47
 
25
- const getRenderedText = (label: string, storeConfig: any) => {
26
- const props = { ...{ ...defaultProps, label } };
48
+ const renderText = ({
49
+ props = {},
50
+ measurementValue = null,
51
+ cellFocused = false,
52
+ }: {
53
+ props?: Partial<React.ComponentProps<typeof Text>>;
54
+ measurementValue?: React.ContextType<typeof MeasurementPortalContext>;
55
+ cellFocused?: boolean;
56
+ } = {}) => {
57
+ const defaultProps = {
58
+ label: "Hero title",
59
+ style: {
60
+ color: "navy",
61
+ fontSize: 18,
62
+ lineHeight: 24,
63
+ },
64
+ };
27
65
 
28
- const { UNSAFE_getByType } = renderWithProviders(
29
- <CustomText {...props} />,
30
- storeConfig
66
+ return render(
67
+ <MeasurementPortalContext.Provider value={measurementValue}>
68
+ <CellFocusedStateContext.Provider value={cellFocused}>
69
+ <Text {...defaultProps} {...props} />
70
+ </CellFocusedStateContext.Provider>
71
+ </MeasurementPortalContext.Provider>
31
72
  );
32
-
33
- return UNSAFE_getByType(RN.Text).props.children;
34
73
  };
35
74
 
36
- describe("RTL app: Hebrew text contains english word", () => {
37
- const textWithFirstEnglishWord = "Word השיר של נועה נועה קירל באירוויזיון";
38
- const textWithNotFirstEnglishWord = "השיר של נועה נועה קירל באירוויזיון Word";
39
- const textWithoutEnglishWord = "השיר של נועה נועה קירל באירוויזיון";
40
-
41
- const storeConfig = {
42
- remoteConfigurations: { localizations: { he: {} } },
43
- appData: {
44
- languageCode: "he",
45
- countryCode: "IL",
46
- },
47
- };
48
-
49
- it("Hebrew text contains first english word", () => {
50
- const renderedText = getRenderedText(textWithFirstEnglishWord, storeConfig);
51
- const desiredText = "\u200fWord\u202c השיר של נועה נועה קירל באירוויזיון";
75
+ describe("MasterCell Text", () => {
76
+ beforeEach(() => {
77
+ jest.clearAllMocks();
78
+ });
52
79
 
53
- expect(renderedText).toEqual(desiredText);
80
+ it("renders the resolved text content with the explicit UI props preserved", () => {
81
+ const { getByText } = renderText({
82
+ props: {
83
+ transformText: "uppercase",
84
+ numberOfLines: 2,
85
+ ellipsizeMode: "tail",
86
+ testID: "mastercell-text",
87
+ accessibilityLabel: "MasterCell title",
88
+ accessibilityHint: "Opens details",
89
+ nativeID: "text-native-id",
90
+ selectable: true,
91
+ },
92
+ });
93
+
94
+ const text = getByText("HERO TITLE");
95
+
96
+ expect(text.props.numberOfLines).toBe(2);
97
+ expect(text.props.ellipsizeMode).toBe("tail");
98
+ expect(text.props.testID).toBe("mastercell-text");
99
+ expect(text.props.accessibilityLabel).toBe("MasterCell title");
100
+ expect(text.props.accessibilityHint).toBe("Opens details");
101
+ expect(text.props.nativeID).toBe("text-native-id");
102
+ expect(text.props.selectable).toBe(true);
103
+ expect(text.props.allowFontScaling).toBe(false);
54
104
  });
55
105
 
56
- it("Hebrew text contains not first english word", () => {
57
- const renderedText = getRenderedText(
58
- textWithNotFirstEnglishWord,
59
- storeConfig
106
+ it("applies the focused visual styles to the rendered text node", () => {
107
+ const { getByText } = renderText({
108
+ props: {
109
+ state: "focused",
110
+ focusedStyles: {
111
+ color: "gold",
112
+ fontWeight: "700",
113
+ },
114
+ },
115
+ });
116
+
117
+ const text = getByText("Hero title");
118
+ const flattenedStyle = StyleSheet.flatten(text.props.style);
119
+
120
+ expect(flattenedStyle).toEqual(
121
+ expect.objectContaining({
122
+ color: "gold",
123
+ fontWeight: "700",
124
+ fontSize: 18,
125
+ })
60
126
  );
127
+ });
61
128
 
62
- expect(renderedText).toEqual(textWithNotFirstEnglishWord);
129
+ it("applies the normal visual styles when the text is not focused", () => {
130
+ const { getByText } = renderText({
131
+ props: {
132
+ state: "default",
133
+ normalStyles: {
134
+ color: "gray",
135
+ opacity: 0.7,
136
+ },
137
+ },
138
+ });
139
+
140
+ const text = getByText("Hero title");
141
+ const flattenedStyle = StyleSheet.flatten(text.props.style);
142
+
143
+ expect(flattenedStyle).toEqual(
144
+ expect.objectContaining({
145
+ color: "gray",
146
+ opacity: 0.7,
147
+ fontSize: 18,
148
+ })
149
+ );
63
150
  });
64
151
 
65
- it("Hebrew text doesn't contain english word", () => {
66
- const renderedText = getRenderedText(textWithoutEnglishWord, storeConfig);
67
- expect(renderedText).toEqual(textWithoutEnglishWord);
152
+ it("adds a capped height during measurement when lineHeight and numberOfLines are available", () => {
153
+ const { getByText } = renderText({
154
+ measurementValue: { measuringInProgress: true } as any,
155
+ props: {
156
+ numberOfLines: 3,
157
+ },
158
+ });
159
+
160
+ const text = getByText("Hero title");
161
+ const flattenedStyle = StyleSheet.flatten(text.props.style);
162
+
163
+ expect(flattenedStyle.height).toBe(72);
68
164
  });
69
- });
70
165
 
71
- describe("LTR app: English text", () => {
72
- beforeAll(() => {
73
- mockUseIsRTL.mockReturnValue(false);
74
- mockGetIsRTL.mockReturnValue(false);
75
- mockGetUILanguage.mockReturnValue("en-US");
166
+ it("returns null when the resolved label is empty", () => {
167
+ const wrapper = renderText({
168
+ props: {
169
+ label: "",
170
+ },
171
+ });
172
+
173
+ expect(wrapper.toJSON()).toBeNull();
76
174
  });
77
175
 
78
- const englishText = "Test sentence";
176
+ it("announces the heading for a focused text label", async () => {
177
+ renderText({
178
+ cellFocused: true,
179
+ props: {
180
+ state: "focused",
181
+ },
182
+ });
183
+
184
+ await waitFor(() => {
185
+ expect(mockAddHeading).toHaveBeenCalledWith("Hero title");
186
+ });
187
+ });
79
188
 
80
- const storeConfig = {
81
- remoteConfigurations: { localizations: { en: {} } },
82
- appData: {
83
- languageCode: "en",
84
- countryCode: "US",
85
- },
86
- };
189
+ it("announces the selected heading for a focused selected text label", async () => {
190
+ renderText({
191
+ cellFocused: true,
192
+ props: {
193
+ state: "focused_selected",
194
+ },
195
+ });
196
+
197
+ await waitFor(() => {
198
+ expect(mockAddHeading).toHaveBeenCalledWith("Hero title, Selected");
199
+ });
200
+ });
87
201
 
88
- it("English text", () => {
89
- const renderedText = getRenderedText(englishText, storeConfig);
90
- expect(renderedText).toEqual(englishText);
202
+ it("does not leak internal control props onto the rendered text node", () => {
203
+ const { getByText } = renderText({
204
+ props: {
205
+ state: "focused",
206
+ focusedStyles: {
207
+ color: "gold",
208
+ },
209
+ normalStyles: {
210
+ color: "gray",
211
+ },
212
+ },
213
+ });
214
+
215
+ const text = getByText("Hero title");
216
+
217
+ expect(text.props.state).toBeUndefined();
218
+ expect(text.props.focusedStyles).toBeUndefined();
219
+ expect(text.props.normalStyles).toBeUndefined();
91
220
  });
92
221
  });
@@ -6,26 +6,33 @@ import PureImage from "./Image";
6
6
  import { CollapsibleTextContainer } from "../SharedUI/CollapsibleTextContainer/CollapsibleTextContainer";
7
7
  import { Toggle } from "./Toggle";
8
8
  import { Button } from "./Button";
9
- import { FocusableView } from "./FocusableView";
10
9
  import ProgressBar from "../SharedUI/ProgressBar/ProgressBar";
11
10
  import { LiveImage } from "./LiveImage";
12
11
  import { DynamicBadge } from "./DynamicBadge";
13
12
  import { SecondaryImage } from "./SecondaryImage/Image";
14
13
  import { BorderContainerView } from "./BorderContainerView";
15
14
  import { CellBadge } from "./CellBadge";
15
+ import { DataProvider } from "./DataProvider";
16
16
  import { TvActionButtons } from "./tv/TvActionButtons";
17
- import { ButtonContainerView } from "./tv/ButtonContainerView";
17
+ import { TvActionButton } from "./tv/TvActionButtons/TvActionButton";
18
+ import { ButtonContainerView } from "./ButtonContainerView";
18
19
  import { ImageBorderContainer } from "./ImageBorderContainer";
20
+ import { PressableView } from "./PressableView";
21
+ import { ActionButton as MobileActionButton } from "./mobile/MobileActionButtons/ActionButton";
22
+
23
+ // UIKIT adapters
24
+ import { UIKitBadge } from "./UIKitAdapters/Badge";
19
25
 
20
26
  export const defaultComponents = {
27
+ UIKitBadge,
21
28
  View,
22
29
  CollapsibleTextContainer,
23
30
  Text,
24
31
  Button,
32
+ PressableView,
25
33
  Image,
26
34
  PureImage,
27
35
  ButtonContainerView,
28
- FocusableView,
29
36
  ProgressBar,
30
37
  Toggle,
31
38
  LiveImage,
@@ -34,5 +41,8 @@ export const defaultComponents = {
34
41
  DynamicBadge,
35
42
  SecondaryImage,
36
43
  TvActionButtons,
44
+ TvActionButton,
45
+ MobileActionButton,
37
46
  CellBadge,
47
+ DataProvider,
38
48
  };
@@ -0,0 +1,189 @@
1
+ import React, { useCallback } from "react";
2
+ import { PressableView } from "../../PressableView";
3
+ import { usePressedState } from "../../../hooks";
4
+
5
+ import { ActionButtonController } from "../../ActionButtonsCore/components";
6
+
7
+ import { masterCellLogger } from "../../../logger";
8
+ import { resolveLabelText, selectByAssetFlavour } from "./utils";
9
+
10
+ type ChildElementProps = {
11
+ children?: React.ReactNode;
12
+ mobileActionRole?: "asset" | "label" | "label_container";
13
+ uri?: string;
14
+ asset?: React.ReactNode;
15
+ state?: "default" | "focused";
16
+ entry?: any;
17
+ };
18
+
19
+ const isValidElement = (
20
+ child: React.ReactNode
21
+ ): child is React.ReactElement<ChildElementProps> =>
22
+ React.isValidElement(child);
23
+
24
+ type ActionButtonContentProps = {
25
+ style: any;
26
+ children: React.ReactNode;
27
+ action: any;
28
+ focusedStyles: any;
29
+ entry: any;
30
+ props: Record<string, any>;
31
+ actionContext: any;
32
+ actionState: any;
33
+ isActive: boolean;
34
+ onPress: () => any;
35
+ };
36
+
37
+ const ActionButtonContent = ({
38
+ children,
39
+ action,
40
+ entry,
41
+ actionContext,
42
+ actionState,
43
+ isActive,
44
+ }: ActionButtonContentProps) => {
45
+ const isPressed = usePressedState();
46
+
47
+ const supportsEntryState =
48
+ typeof actionContext?.initialEntryState === "function";
49
+
50
+ const shouldRenderAsset = Boolean(
51
+ supportsEntryState ? actionState?.mobileButtonAssets : false
52
+ );
53
+
54
+ const shouldRenderLabel = Boolean(
55
+ supportsEntryState && resolveLabelText(actionState?.label)
56
+ );
57
+
58
+ const _cloneChildrenWithState = useCallback(
59
+ (nodes?: React.ReactNode): React.ReactNode => {
60
+ return React.Children.map(nodes, (child) => {
61
+ if (!isValidElement(child)) {
62
+ return child;
63
+ }
64
+
65
+ const role = child.props.mobileActionRole;
66
+
67
+ const nextChildren = _cloneChildrenWithState(child.props.children);
68
+
69
+ // Prevents of the asset rendering when the action doesn't provide asset for current entry state
70
+ if (role === "asset" && !shouldRenderAsset) {
71
+ return null;
72
+ }
73
+
74
+ // Prevents of the label rendering when the action doesn't provide label for current entry state.
75
+ // Also prevents of the label container when it doesn't have children to avoid unnecessary empty space.
76
+ if (role === "label" && !shouldRenderLabel) {
77
+ return null;
78
+ }
79
+
80
+ // Prevents of the label container rendering when it doesn't have children to avoid unnecessary empty space.
81
+ if (
82
+ role === "label_container" &&
83
+ React.Children.count(nextChildren) === 0
84
+ ) {
85
+ return null;
86
+ }
87
+
88
+ const nextProps: Partial<ChildElementProps> = {};
89
+
90
+ // Inject asset or uri to Asset component with role asset
91
+ if (role === "asset") {
92
+ const resolvedAsset = selectByAssetFlavour(
93
+ actionState,
94
+ action.flavour,
95
+ isActive
96
+ );
97
+
98
+ if (typeof resolvedAsset === "function") {
99
+ const AssetComponent = resolvedAsset as CellActionAssetComponent;
100
+
101
+ nextProps.asset = (
102
+ <AssetComponent
103
+ flavour={action.flavour || "flavour_1"}
104
+ width={action.width}
105
+ height={action.height}
106
+ />
107
+ );
108
+ } else {
109
+ nextProps.uri = resolvedAsset;
110
+ }
111
+ }
112
+
113
+ // Inject state and entry to Text component with role label
114
+ // Use isPressed state for mobile buttons to set focused state on mobile
115
+ if (role === "label") {
116
+ nextProps.state = isActive || isPressed ? "focused" : "default";
117
+ nextProps.entry = entry;
118
+ }
119
+
120
+ if (nextChildren !== child.props.children) {
121
+ nextProps.children = nextChildren;
122
+ }
123
+
124
+ return React.cloneElement(child, nextProps);
125
+ });
126
+ },
127
+ [
128
+ shouldRenderAsset,
129
+ shouldRenderLabel,
130
+ actionState,
131
+ action.flavour,
132
+ action.width,
133
+ action.height,
134
+ isActive,
135
+ isPressed,
136
+ entry,
137
+ ]
138
+ );
139
+
140
+ if (!shouldRenderAsset && !shouldRenderLabel) {
141
+ return null;
142
+ }
143
+
144
+ return _cloneChildrenWithState(children);
145
+ };
146
+
147
+ export const ActionButton = ({
148
+ style,
149
+ children,
150
+ action,
151
+ focusedStyles,
152
+ entry,
153
+ ...props
154
+ }) => {
155
+ return (
156
+ <ActionButtonController
157
+ action={action}
158
+ entry={entry}
159
+ onMissingActionContext={(identifier) => {
160
+ masterCellLogger.warning(
161
+ `You're missing an action plugin(${identifier || action?.identifier}) required by your mobile action button.`
162
+ );
163
+ }}
164
+ >
165
+ {(renderProps) => (
166
+ <PressableView
167
+ testOnly_pressed={props.testOnly_pressed}
168
+ testID={props.testID || `${entry?.id}`}
169
+ style={style}
170
+ pressedStyle={focusedStyles}
171
+ onPress={renderProps.onPress}
172
+ accessibilityLabel={props.accessibilityLabel || `${entry?.id}`}
173
+ accessibilityHint={props.accessibilityHint}
174
+ >
175
+ <ActionButtonContent
176
+ style={style}
177
+ action={action}
178
+ focusedStyles={focusedStyles}
179
+ entry={entry}
180
+ props={props}
181
+ {...renderProps}
182
+ >
183
+ {children}
184
+ </ActionButtonContent>
185
+ </PressableView>
186
+ )}
187
+ </ActionButtonController>
188
+ );
189
+ };
@@ -0,0 +1,33 @@
1
+ import { AssetComponent } from "./AssetComponent";
2
+
3
+ type Props = {
4
+ testID?: string;
5
+ style?: Record<string, unknown>;
6
+ };
7
+
8
+ type AssetNode = {
9
+ type: "ReactComponent";
10
+ style?: Record<string, unknown>;
11
+ additionalProps: {
12
+ component: typeof AssetComponent;
13
+ requiresCellUUID: true;
14
+ renderChildren: false;
15
+ mobileActionRole: "asset";
16
+ testID?: string;
17
+ };
18
+ };
19
+
20
+ /** Asset used in conjunction with ActionButton inside mobile action buttons, uri is provided by ActionButton */
21
+ export const Asset = ({ testID, style }: Props): AssetNode => {
22
+ return {
23
+ type: "ReactComponent",
24
+ style,
25
+ additionalProps: {
26
+ component: AssetComponent,
27
+ requiresCellUUID: true,
28
+ renderChildren: false,
29
+ mobileActionRole: "asset",
30
+ testID: testID ? `${testID}-asset` : undefined,
31
+ },
32
+ };
33
+ };
@@ -0,0 +1,33 @@
1
+ import React from "react";
2
+ import { StyleSheet, View } from "react-native";
3
+ import { ImageContainer } from "../../ImageContainer";
4
+
5
+ type Props = {
6
+ style?: Record<string, unknown>;
7
+ uri?: string;
8
+ asset?: React.ReactNode;
9
+ testID?: string;
10
+ cellUUID?: string;
11
+ };
12
+
13
+ const styles = StyleSheet.create({
14
+ assetContainer: {
15
+ flexShrink: 0,
16
+ justifyContent: "center",
17
+ alignItems: "center",
18
+ },
19
+ });
20
+
21
+ export const AssetComponent = (props: Props & Record<string, unknown>) => {
22
+ return props.asset && React.isValidElement(props.asset) ? (
23
+ <View style={[styles.assetContainer, props.style]} testID={props.testID}>
24
+ {React.cloneElement(props.asset, { cellUUID: props.cellUUID })}
25
+ </View>
26
+ ) : (
27
+ <ImageContainer
28
+ style={[styles.assetContainer, props.style]}
29
+ {...(props as any)}
30
+ uri={props.uri}
31
+ />
32
+ );
33
+ };