@applicaster/zapp-react-native-ui-components 15.0.0-rc.143 → 15.0.0-rc.145

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 (53) hide show
  1. package/Components/FocusableGroup/index.tsx +3 -2
  2. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +5 -0
  3. package/Components/MasterCell/CONFIG_BUILDER_TO_REACT_COMPONENT.md +144 -0
  4. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +165 -0
  5. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +405 -0
  6. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/index.ts +1 -0
  7. package/Components/MasterCell/DefaultComponents/ButtonContainerView/components/HorizontalSeparator.tsx +8 -0
  8. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tsx +15 -0
  9. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tv.android.tsx +58 -0
  10. package/Components/MasterCell/DefaultComponents/{tv/ButtonContainerView/index.tsx → ButtonContainerView/index.tv.tsx} +3 -11
  11. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.web.ts +1 -0
  12. package/Components/MasterCell/DefaultComponents/ButtonContainerView/types.ts +40 -0
  13. package/Components/MasterCell/DefaultComponents/DataProvider/index.tsx +163 -0
  14. package/Components/MasterCell/DefaultComponents/FocusableView/index.android.tsx +2 -23
  15. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -22
  16. package/Components/MasterCell/DefaultComponents/PressableView.tsx +7 -234
  17. package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +11 -0
  18. package/Components/MasterCell/DefaultComponents/__tests__/DataProvider.test.tsx +141 -0
  19. package/Components/MasterCell/DefaultComponents/index.ts +7 -3
  20. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +135 -0
  21. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +20 -29
  22. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +22 -0
  23. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +67 -69
  24. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +21 -16
  25. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +207 -9
  26. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +56 -55
  27. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +137 -16
  28. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +49 -31
  29. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +165 -0
  30. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Asset.ts +4 -18
  31. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Button.ts +24 -73
  32. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TextLabelsContainer.ts +37 -18
  33. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TvActionButton.tsx +27 -0
  34. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +24 -21
  35. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/renderedTree.test.tsx +231 -0
  36. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +24 -12
  37. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +62 -0
  38. package/Components/MasterCell/MappingFunctions/index.js +3 -2
  39. package/Components/MasterCell/README.md +4 -0
  40. package/Components/MasterCell/__tests__/__snapshots__/dataAdapter.test.js.snap +24 -0
  41. package/Components/MasterCell/__tests__/configInflater.test.js +1 -0
  42. package/Components/MasterCell/__tests__/elementMapper.test.js +46 -0
  43. package/Components/MasterCell/dataAdapter.ts +4 -1
  44. package/Components/MasterCell/elementMapper.tsx +51 -7
  45. package/Components/MasterCell/utils/__tests__/cloneChildrenWithIds.test.tsx +43 -0
  46. package/Components/MasterCell/utils/__tests__/useFilterChildren.test.tsx +80 -0
  47. package/Components/MasterCell/utils/index.ts +85 -15
  48. package/Components/Navigator/StackNavigator.tsx +6 -0
  49. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +4 -1
  50. package/package.json +5 -5
  51. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ButtonContainerView.ts +0 -23
  52. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +0 -135
  53. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/types.ts +0 -25
@@ -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
+ });
@@ -58,18 +58,30 @@ export const TvActionButtons = ({
58
58
  elements: compact(
59
59
  model.buttons.map(
60
60
  ({ slot, renderIndex, specificPrefix, stylePrefix }) => {
61
- return Button({
62
- prefix: stylePrefix,
63
- value,
64
- platformValue,
65
- pluginIdentifier: memoizedGetPluginIdentifier(
66
- configuration,
67
- PREFIX,
68
- slot
69
- ) as string,
70
- suffixId: specificPrefix,
71
- preferredFocus: renderIndex === 0,
72
- });
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
+ };
73
85
  }
74
86
  )
75
87
  ),
@@ -1,5 +1,9 @@
1
1
  import memoizee from "memoizee";
2
2
  import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
3
+ import {
4
+ toNumberWithDefault,
5
+ toNumberWithDefaultZero,
6
+ } from "@applicaster/zapp-react-native-utils/numberUtils";
3
7
  import { getEnabledButtonSlots } from "../../../ActionButtonsCore/selectors";
4
8
  import {
5
9
  insertBetweenLabelContainers,
@@ -83,3 +87,61 @@ export const withAntialiasing = () => {
83
87
 
84
88
  return {};
85
89
  };
90
+
91
+ export const getMarginStyles = (value) => ({
92
+ marginTop: toNumberWithDefaultZero(value("margin_top")),
93
+ marginRight: toNumberWithDefaultZero(value("margin_right")),
94
+ marginBottom: toNumberWithDefaultZero(value("margin_bottom")),
95
+ marginLeft: toNumberWithDefaultZero(value("margin_left")),
96
+ });
97
+
98
+ export const getPaddingStyles = (value) => ({
99
+ paddingTop: toNumberWithDefaultZero(value("padding_top")),
100
+ paddingRight: toNumberWithDefaultZero(value("padding_right")),
101
+ paddingBottom: toNumberWithDefaultZero(value("padding_bottom")),
102
+ paddingLeft: toNumberWithDefaultZero(value("padding_left")),
103
+ });
104
+
105
+ export const getBorderStyles = (value) => ({
106
+ borderRadius: toNumberWithDefaultZero(value("corner_radius")),
107
+ borderWidth: toNumberWithDefaultZero(value("border_thickness")),
108
+ borderStyle: "solid",
109
+ borderColor: value("border_color"),
110
+ });
111
+
112
+ export const getAssetStyles = (value) => ({
113
+ ...getMarginStyles(value),
114
+ width: toNumberWithDefault(40, value("width")),
115
+ height: toNumberWithDefault(40, value("height")),
116
+ });
117
+
118
+ export const getTextLabelStyles = (value, platformValue) => ({
119
+ ...getMarginStyles(value),
120
+ ...getPaddingStyles(value),
121
+ borderRadius: toNumberWithDefaultZero(value("corner_radius")),
122
+ textAlign: value("text_alignment"),
123
+ textTransform: getTextTransform(value("text_transform")),
124
+ lineHeight: platformValue("line_height"),
125
+ letterSpacing: platformValue("letter_spacing"),
126
+ fontSize: platformValue("font_size"),
127
+ fontFamily: platformValue("font_family"),
128
+ ...withAntialiasing(),
129
+ });
130
+
131
+ export const getDisplayModeStyles = (value) => {
132
+ const mode = value("display_mode") || "dynamic";
133
+ const width = toNumberWithDefault(240, value("fixed_and_fixed_center_width"));
134
+
135
+ if (mode === "fixed") {
136
+ return { width };
137
+ }
138
+
139
+ if (mode === "fixed_center") {
140
+ return {
141
+ width,
142
+ justifyContent: "center",
143
+ };
144
+ }
145
+
146
+ return {};
147
+ };
@@ -1,7 +1,7 @@
1
- import * as R from "ramda";
2
1
  import { isDateValid } from "./Utils";
3
2
  import { masterCellLogger } from "../logger";
4
3
  import { imageSrcFromMediaItem as imageSrcFromMediaItemConfigUtils } from "@applicaster/zapp-react-native-utils/configurationUtils";
4
+ import { pathOr, identity } from "@applicaster/zapp-react-native-utils/utils";
5
5
 
6
6
  export const imageSrcFromMediaItem = (...args) => {
7
7
  __DEV__ &&
@@ -34,11 +34,12 @@ export function stringifyDateFromPath(obj, path) {
34
34
  * @returns {any} Found object or empty string
35
35
  */
36
36
  export function pathWithFallback(obj, path) {
37
- return R.pathOr("", path)(obj);
37
+ return pathOr("", path, obj);
38
38
  }
39
39
 
40
40
  // prettier-ignore
41
41
  const functionsNames = {
42
+ "identity": identity,
42
43
  "path": pathWithFallback,
43
44
  "image_src_from_media_item": imageSrcFromMediaItemConfigUtils,
44
45
  "stringify_date_from_path": stringifyDateFromPath,
@@ -1,5 +1,9 @@
1
1
  # MasterCell
2
2
 
3
+ ## Guides
4
+
5
+ - [Config Builder -> React Component Migration](./CONFIG_BUILDER_TO_REACT_COMPONENT.md)
6
+
3
7
  ## How to use it
4
8
 
5
9
  ```javascript
@@ -7,6 +7,7 @@ exports[`dataAdapter creates a tree of nodes 1`] = `
7
7
  {
8
8
  "elements": undefined,
9
9
  "props": {
10
+ "_dataKey": "uri",
10
11
  "numberOfLines": 1,
11
12
  "uri": "http://urlOfTheImage.net/img.png",
12
13
  },
@@ -23,6 +24,7 @@ exports[`dataAdapter creates a tree of nodes 1`] = `
23
24
  {
24
25
  "elements": undefined,
25
26
  "props": {
27
+ "_dataKey": "label",
26
28
  "label": "Test Entry",
27
29
  "numberOfLines": 1,
28
30
  },
@@ -49,6 +51,7 @@ exports[`dataAdapter creates a tree of nodes 1`] = `
49
51
  {
50
52
  "elements": undefined,
51
53
  "props": {
54
+ "_dataKey": "label",
52
55
  "label": "Summary of the test entry",
53
56
  "numberOfLines": 2,
54
57
  },
@@ -76,6 +79,7 @@ exports[`dataAdapter creates a tree of nodes 1`] = `
76
79
  {
77
80
  "elements": undefined,
78
81
  "props": {
82
+ "_dataKey": "uri",
79
83
  "uri": "http://urlOfTheImage.net/channelLogo.png",
80
84
  },
81
85
  "style": {
@@ -119,6 +123,7 @@ exports[`dataAdapter returns a tree view for a specific content type fallback to
119
123
  {
120
124
  "elements": undefined,
121
125
  "props": {
126
+ "_dataKey": "uri",
122
127
  "numberOfLines": 1,
123
128
  "uri": "http://urlOfTheImage.net/img.png",
124
129
  },
@@ -135,6 +140,7 @@ exports[`dataAdapter returns a tree view for a specific content type fallback to
135
140
  {
136
141
  "elements": undefined,
137
142
  "props": {
143
+ "_dataKey": "label",
138
144
  "label": "Test Entry",
139
145
  "numberOfLines": 1,
140
146
  },
@@ -161,6 +167,7 @@ exports[`dataAdapter returns a tree view for a specific content type fallback to
161
167
  {
162
168
  "elements": undefined,
163
169
  "props": {
170
+ "_dataKey": "label",
164
171
  "label": "Summary of the test entry",
165
172
  "numberOfLines": 2,
166
173
  },
@@ -188,6 +195,7 @@ exports[`dataAdapter returns a tree view for a specific content type fallback to
188
195
  {
189
196
  "elements": undefined,
190
197
  "props": {
198
+ "_dataKey": "uri",
191
199
  "uri": "http://urlOfTheImage.net/channelLogo.png",
192
200
  },
193
201
  "style": {
@@ -231,6 +239,7 @@ exports[`dataAdapter returns a tree view for a specific content type if the cont
231
239
  {
232
240
  "elements": undefined,
233
241
  "props": {
242
+ "_dataKey": "uri",
234
243
  "numberOfLines": 1,
235
244
  "uri": "http://urlOfTheImage.net/img.png",
236
245
  },
@@ -247,6 +256,7 @@ exports[`dataAdapter returns a tree view for a specific content type if the cont
247
256
  {
248
257
  "elements": undefined,
249
258
  "props": {
259
+ "_dataKey": "label",
250
260
  "label": "Test Entry",
251
261
  "numberOfLines": 1,
252
262
  },
@@ -273,6 +283,7 @@ exports[`dataAdapter returns a tree view for a specific content type if the cont
273
283
  {
274
284
  "elements": undefined,
275
285
  "props": {
286
+ "_dataKey": "label",
276
287
  "label": "Summary of the test entry",
277
288
  "numberOfLines": 2,
278
289
  },
@@ -300,6 +311,7 @@ exports[`dataAdapter returns a tree view for a specific content type if the cont
300
311
  {
301
312
  "elements": undefined,
302
313
  "props": {
314
+ "_dataKey": "uri",
303
315
  "uri": "http://urlOfTheImage.net/channelLogo.png",
304
316
  },
305
317
  "style": {
@@ -343,6 +355,7 @@ exports[`dataAdapter returns a tree view for state fallback to the default if th
343
355
  {
344
356
  "elements": undefined,
345
357
  "props": {
358
+ "_dataKey": "uri",
346
359
  "numberOfLines": 1,
347
360
  "uri": "http://urlOfTheImage.net/img.png",
348
361
  },
@@ -359,6 +372,7 @@ exports[`dataAdapter returns a tree view for state fallback to the default if th
359
372
  {
360
373
  "elements": undefined,
361
374
  "props": {
375
+ "_dataKey": "label",
362
376
  "label": "Test Entry",
363
377
  "numberOfLines": 1,
364
378
  },
@@ -385,6 +399,7 @@ exports[`dataAdapter returns a tree view for state fallback to the default if th
385
399
  {
386
400
  "elements": undefined,
387
401
  "props": {
402
+ "_dataKey": "label",
388
403
  "label": "Summary of the test entry",
389
404
  "numberOfLines": 2,
390
405
  },
@@ -412,6 +427,7 @@ exports[`dataAdapter returns a tree view for state fallback to the default if th
412
427
  {
413
428
  "elements": undefined,
414
429
  "props": {
430
+ "_dataKey": "uri",
415
431
  "uri": "http://urlOfTheImage.net/channelLogo.png",
416
432
  },
417
433
  "style": {
@@ -455,6 +471,7 @@ exports[`dataAdapter returns a tree view for state fallback to the default if th
455
471
  {
456
472
  "elements": undefined,
457
473
  "props": {
474
+ "_dataKey": "uri",
458
475
  "numberOfLines": 1,
459
476
  "uri": "http://urlOfTheImage.net/img.png",
460
477
  },
@@ -471,6 +488,7 @@ exports[`dataAdapter returns a tree view for state fallback to the default if th
471
488
  {
472
489
  "elements": undefined,
473
490
  "props": {
491
+ "_dataKey": "label",
474
492
  "label": "Test Entry",
475
493
  "numberOfLines": 1,
476
494
  },
@@ -497,6 +515,7 @@ exports[`dataAdapter returns a tree view for state fallback to the default if th
497
515
  {
498
516
  "elements": undefined,
499
517
  "props": {
518
+ "_dataKey": "label",
500
519
  "label": "Summary of the test entry",
501
520
  "numberOfLines": 2,
502
521
  },
@@ -524,6 +543,7 @@ exports[`dataAdapter returns a tree view for state fallback to the default if th
524
543
  {
525
544
  "elements": undefined,
526
545
  "props": {
546
+ "_dataKey": "uri",
527
547
  "uri": "http://urlOfTheImage.net/channelLogo.png",
528
548
  },
529
549
  "style": {
@@ -567,6 +587,7 @@ exports[`dataAdapter returns a tree view for state matching the state if it exis
567
587
  {
568
588
  "elements": undefined,
569
589
  "props": {
590
+ "_dataKey": "uri",
570
591
  "numberOfLines": 1,
571
592
  "uri": "http://urlOfTheImage.net/img.png",
572
593
  },
@@ -583,6 +604,7 @@ exports[`dataAdapter returns a tree view for state matching the state if it exis
583
604
  {
584
605
  "elements": undefined,
585
606
  "props": {
607
+ "_dataKey": "label",
586
608
  "label": "Test Entry",
587
609
  "numberOfLines": 1,
588
610
  },
@@ -609,6 +631,7 @@ exports[`dataAdapter returns a tree view for state matching the state if it exis
609
631
  {
610
632
  "elements": undefined,
611
633
  "props": {
634
+ "_dataKey": "label",
612
635
  "label": "Summary of the test entry",
613
636
  "numberOfLines": 2,
614
637
  },
@@ -636,6 +659,7 @@ exports[`dataAdapter returns a tree view for state matching the state if it exis
636
659
  {
637
660
  "elements": undefined,
638
661
  "props": {
662
+ "_dataKey": "uri",
639
663
  "uri": "http://urlOfTheImage.net/channelLogo.png",
640
664
  },
641
665
  "style": {
@@ -32,6 +32,7 @@ describe("configInflater", () => {
32
32
  },
33
33
  type: "testType",
34
34
  props: {
35
+ _dataKey: "uri",
35
36
  uri: {
36
37
  extensions: {
37
38
  color: "red",
@@ -30,4 +30,50 @@ describe("elementMapper", () => {
30
30
 
31
31
  expect(wrapper.toJSON()).toMatchSnapshot();
32
32
  });
33
+
34
+ it("renders inline ReactComponent nodes", () => {
35
+ const InlineComponent = jest.fn(({ testID, children }) => (
36
+ <View testID={testID}>{children}</View>
37
+ ));
38
+
39
+ const node = {
40
+ type: "ReactComponent",
41
+ style: { marginTop: 8 },
42
+ props: {
43
+ component: InlineComponent,
44
+ testID: "inline-react-component",
45
+ requiresCellUUID: true,
46
+ },
47
+ elements: [
48
+ {
49
+ type: "View",
50
+ style: { width: 10 },
51
+ props: {
52
+ testID: "inline-react-child",
53
+ },
54
+ },
55
+ ],
56
+ };
57
+
58
+ const wrapper = render(
59
+ <View>
60
+ {elementMapper({ View }, { cellUUID: "cell-uuid-test" })(node)}
61
+ </View>
62
+ );
63
+
64
+ expect(wrapper.getByTestId("inline-react-component")).toBeTruthy();
65
+ expect(wrapper.getByTestId("inline-react-child")).toBeTruthy();
66
+
67
+ expect(InlineComponent).toHaveBeenCalledWith(
68
+ expect.objectContaining({
69
+ testID: "inline-react-component",
70
+ style: { marginTop: 8 },
71
+ cellUUID: "cell-uuid-test",
72
+ }),
73
+ expect.anything()
74
+ );
75
+
76
+ expect(InlineComponent.mock.calls[0][0].component).toBeUndefined();
77
+ expect(InlineComponent.mock.calls[0][0].requiresCellUUID).toBeUndefined();
78
+ });
33
79
  });
@@ -31,7 +31,9 @@ function retrieveData(entry, func, args) {
31
31
  * @param {String} masterCellConfig.type of the component
32
32
  * @param {Object} masterCellConfig.style default style object for the component
33
33
  * @param {?Object} masterCellConfig.data func/args/propName that indicates how to extract
34
- * the data from the entry and to which prop it should be injected after manipulation
34
+ * the data from the entry and to which prop it should be injected after manipulation.
35
+ * The last mapped propName is also stored as `_dataKey` so DataProvider can
36
+ * mirror the resolved value into both direct props and `dataProviderProps`.
35
37
  * @param {?[Object]} element.elements Optional array of nested elements to render within the current node
36
38
  * @returns {Object} inflated configuration, ready to be rendered by the master cell's element mapper
37
39
  */
@@ -54,6 +56,7 @@ export function configInflater(
54
56
  ) {
55
57
  const props = data.reduce(
56
58
  (acc, curr) => {
59
+ acc._dataKey = curr.propName;
57
60
  acc[curr.propName] = retrieveData(entry, curr.func, curr.args);
58
61
 
59
62
  return acc;