@applicaster/zapp-react-native-ui-components 15.0.0-alpha.5170277721 → 15.0.0-alpha.5197372201

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 (119) hide show
  1. package/Components/BaseFocusable/index.ios.ts +12 -2
  2. package/Components/Cell/Cell.tsx +14 -3
  3. package/Components/Cell/CellWithFocusable.tsx +9 -0
  4. package/Components/Cell/FocusableWrapper.tsx +3 -0
  5. package/Components/Cell/TvOSCellComponent.tsx +26 -5
  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 +32 -1
  10. package/Components/GeneralContentScreen/GeneralContentScreen.tsx +3 -8
  11. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
  12. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +22 -6
  13. package/Components/HandlePlayable/HandlePlayable.tsx +26 -36
  14. package/Components/HandlePlayable/utils.ts +31 -0
  15. package/Components/Layout/TV/LayoutBackground.tsx +5 -2
  16. package/Components/Layout/TV/ScreenContainer.tsx +2 -6
  17. package/Components/Layout/TV/index.tsx +3 -4
  18. package/Components/Layout/TV/index.web.tsx +3 -4
  19. package/Components/LinkHandler/LinkHandler.tsx +2 -2
  20. package/Components/MasterCell/DefaultComponents/BorderContainerView/__tests__/index.test.tsx +16 -1
  21. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +30 -2
  22. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +5 -1
  23. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +11 -3
  24. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +9 -1
  25. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +15 -14
  26. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +10 -6
  27. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +40 -39
  28. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/Image.test.tsx +95 -0
  29. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +86 -0
  30. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/index.test.ts +141 -0
  31. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
  32. package/Components/MasterCell/DefaultComponents/SecondaryImage/index.ts +1 -1
  33. package/Components/MasterCell/DefaultComponents/Text/index.tsx +8 -8
  34. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +6 -2
  35. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +233 -11
  36. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +19 -15
  37. package/Components/MasterCell/hoc/__tests__/withAsyncRender.test.tsx +219 -0
  38. package/Components/MasterCell/hoc/withAsyncRender.tsx +9 -7
  39. package/Components/MasterCell/index.tsx +2 -0
  40. package/Components/MasterCell/utils/__tests__/resolveColor.test.js +82 -3
  41. package/Components/MasterCell/utils/index.ts +61 -31
  42. package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +102 -87
  43. package/Components/MeasurmentsPortal/__tests__/MeasurementsPortal.test.tsx +355 -0
  44. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +17 -9
  45. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +16 -8
  46. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +2 -2
  47. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +17 -18
  48. package/Components/OfflineHandler/NotificationView/utils.ts +34 -0
  49. package/Components/OfflineHandler/__tests__/index.test.tsx +27 -18
  50. package/Components/PlayerContainer/PlayerContainer.tsx +43 -55
  51. package/Components/PlayerImageBackground/index.tsx +3 -22
  52. package/Components/River/ComponentsMap/ComponentsMap.tsx +16 -0
  53. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  54. package/Components/River/RefreshControl.tsx +9 -3
  55. package/Components/River/TV/River.tsx +31 -14
  56. package/Components/River/TV/index.tsx +8 -4
  57. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +30 -0
  58. package/Components/River/TV/utils/index.ts +4 -0
  59. package/Components/River/TV/withFocusableGroupForContent.tsx +71 -0
  60. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +2 -0
  61. package/Components/River/__tests__/componentsMap.test.js +38 -0
  62. package/Components/Screen/TV/index.web.tsx +4 -2
  63. package/Components/Screen/__tests__/Screen.test.tsx +66 -42
  64. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +68 -44
  65. package/Components/Screen/hooks.ts +75 -6
  66. package/Components/Screen/index.tsx +9 -4
  67. package/Components/Screen/navigationHandler.ts +49 -24
  68. package/Components/Screen/orientationHandler.ts +10 -13
  69. package/Components/ScreenResolver/index.tsx +26 -16
  70. package/Components/ScreenRevealManager/ScreenRevealManager.ts +40 -8
  71. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +86 -69
  72. package/Components/Tabs/TV/Tabs.tsx +20 -3
  73. package/Components/Tabs/TabContent.tsx +7 -4
  74. package/Components/Transitioner/Scene.tsx +10 -3
  75. package/Components/Transitioner/index.js +3 -3
  76. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +118 -171
  77. package/Components/VideoModal/ModalAnimation/index.ts +2 -13
  78. package/Components/VideoModal/ModalAnimation/utils.ts +1 -327
  79. package/Components/VideoModal/PlayerWrapper.tsx +14 -52
  80. package/Components/VideoModal/VideoModal.tsx +1 -5
  81. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -0
  82. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
  83. package/Components/VideoModal/hooks/useModalSize.ts +5 -1
  84. package/Components/VideoModal/playerWrapperStyle.ts +70 -0
  85. package/Components/VideoModal/playerWrapperUtils.ts +16 -12
  86. package/Components/VideoModal/utils.ts +19 -9
  87. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
  88. package/Components/Viewport/ViewportAware/index.tsx +16 -7
  89. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  90. package/Contexts/ScreenContext/index.tsx +54 -19
  91. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  92. package/Contexts/ZappHookModalContext/index.tsx +37 -61
  93. package/Contexts/ZappPipesContext/ZappPipesContextFactory.tsx +18 -7
  94. package/Contexts/index.ts +0 -2
  95. package/Decorators/Analytics/index.tsx +6 -5
  96. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +1 -0
  97. package/Decorators/ConfigurationWrapper/const.ts +1 -0
  98. package/Decorators/ZappPipesDataConnector/ResolverSelector.tsx +25 -7
  99. package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +212 -5
  100. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
  101. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  102. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  103. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
  104. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
  105. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +80 -0
  106. package/Helpers/DataSourceHelper/index.ts +19 -0
  107. package/events/index.ts +3 -0
  108. package/events/scrollEndReached.ts +15 -0
  109. package/index.d.ts +7 -0
  110. package/package.json +6 -5
  111. package/Components/River/TV/withTVEventHandler.tsx +0 -27
  112. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +0 -60
  113. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +0 -417
  114. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.web.tsx +0 -294
  115. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +0 -176
  116. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.web.tsx +0 -93
  117. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +0 -500
  118. package/Components/VideoModal/ModalAnimation/__tests__/getMoveUpValue.test.ts +0 -108
  119. package/Helpers/DataSourceHelper/index.js +0 -19
@@ -1,9 +1,45 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
+ exports[`SecondaryImage - Image SecondaryImage fixed mode should not use async render props 1`] = `
4
+ <View
5
+ style={
6
+ {
7
+ "borderRadius": 10,
8
+ "height": 100,
9
+ "width": 100,
10
+ }
11
+ }
12
+ >
13
+ <Image
14
+ displayMode="fixed"
15
+ fitPosition="center"
16
+ fixedHeight={100}
17
+ fixedWidth={100}
18
+ imageSizing="fill"
19
+ source={
20
+ {
21
+ "uri": "someUrl",
22
+ }
23
+ }
24
+ style={
25
+ {
26
+ "aspectRatio": 1,
27
+ "borderRadius": 10,
28
+ "height": 100,
29
+ "width": 100,
30
+ }
31
+ }
32
+ uri="someUrl"
33
+ />
34
+ </View>
35
+ `;
36
+
3
37
  exports[`SecondaryImage - Image SecondaryImage should not render if no aspect ratio (dynamic) 1`] = `null`;
4
38
 
5
39
  exports[`SecondaryImage - Image SecondaryImage should not render if no uri 1`] = `null`;
6
40
 
41
+ exports[`SecondaryImage - Image SecondaryImage should not render in dynamic mode without image 1`] = `null`;
42
+
7
43
  exports[`SecondaryImage - Image SecondaryImage should render if known dimensions 1`] = `
8
44
  <View
9
45
  onLayout={[Function]}
@@ -17,6 +53,10 @@ exports[`SecondaryImage - Image SecondaryImage should render if known dimensions
17
53
  >
18
54
  <Image
19
55
  displayMode="dynamic"
56
+ fitPosition="center"
57
+ fixedHeight={0}
58
+ fixedWidth={0}
59
+ imageSizing="fill"
20
60
  onAsyncRender={[Function]}
21
61
  source={
22
62
  {
@@ -35,3 +75,49 @@ exports[`SecondaryImage - Image SecondaryImage should render if known dimensions
35
75
  />
36
76
  </View>
37
77
  `;
78
+
79
+ exports[`SecondaryImage - Image SecondaryImage should render in fixed mode with known dimensions 1`] = `
80
+ <View
81
+ style={
82
+ {
83
+ "borderRadius": 10,
84
+ "height": 100,
85
+ "width": 100,
86
+ }
87
+ }
88
+ >
89
+ <Image
90
+ displayMode="fixed"
91
+ fitPosition="center"
92
+ fixedHeight={100}
93
+ fixedWidth={100}
94
+ imageSizing="fill"
95
+ source={
96
+ {
97
+ "uri": "someUrl",
98
+ }
99
+ }
100
+ style={
101
+ {
102
+ "aspectRatio": 1,
103
+ "borderRadius": 10,
104
+ "height": 100,
105
+ "width": 100,
106
+ }
107
+ }
108
+ uri="someUrl"
109
+ />
110
+ </View>
111
+ `;
112
+
113
+ exports[`SecondaryImage - Image SecondaryImage should render in fixed mode without image until loaded 1`] = `
114
+ <View
115
+ style={
116
+ {
117
+ "borderRadius": 5,
118
+ "height": 100,
119
+ "width": 100,
120
+ }
121
+ }
122
+ />
123
+ `;
@@ -0,0 +1,141 @@
1
+ import { SecondaryImage } from "../index";
2
+
3
+ describe("SecondaryImage - Configuration Builder", () => {
4
+ const mockValue = (config: any) => (key: string, transformer?: Function) => {
5
+ const value = config[key];
6
+
7
+ return transformer ? transformer(value) : value;
8
+ };
9
+
10
+ it("should pass false as second argument to image_src_from_media_item", () => {
11
+ const config = {
12
+ secondary_image_switch: true,
13
+ secondary_image_position: "over_image",
14
+ secondary_image_visibility: "always",
15
+ secondary_image_image_key: "custom_image",
16
+ secondary_image_display_mode: "dynamic",
17
+ secondary_image_dynamic_width: 100,
18
+ };
19
+
20
+ const result = SecondaryImage({
21
+ value: mockValue(config),
22
+ currentPosition: "over_image",
23
+ state: "default",
24
+ });
25
+
26
+ expect(result).not.toBeNull();
27
+ expect(result?.elements).toBeDefined();
28
+ expect(result?.elements[0].data).toBeDefined();
29
+
30
+ const imageData = result?.elements[0].data[0];
31
+ expect(imageData.func).toBe("image_src_from_media_item");
32
+ expect(imageData.args).toEqual(["custom_image", false]);
33
+ expect(imageData.propName).toBe("uri");
34
+ });
35
+
36
+ it("should return null when secondary image is disabled", () => {
37
+ const config = {
38
+ secondary_image_switch: false,
39
+ };
40
+
41
+ const result = SecondaryImage({
42
+ value: mockValue(config),
43
+ currentPosition: "over_image",
44
+ state: "default",
45
+ });
46
+
47
+ expect(result).toBeNull();
48
+ });
49
+
50
+ it("should return null when position does not match currentPosition", () => {
51
+ const config = {
52
+ secondary_image_switch: true,
53
+ secondary_image_position: "above_text_label_1",
54
+ secondary_image_visibility: "always",
55
+ };
56
+
57
+ const result = SecondaryImage({
58
+ value: mockValue(config),
59
+ currentPosition: "over_image",
60
+ state: "default",
61
+ });
62
+
63
+ expect(result).toBeNull();
64
+ });
65
+
66
+ it("should return null when visibility does not match state", () => {
67
+ const config = {
68
+ secondary_image_switch: true,
69
+ secondary_image_position: "over_image",
70
+ secondary_image_visibility: "focused",
71
+ };
72
+
73
+ const result = SecondaryImage({
74
+ value: mockValue(config),
75
+ currentPosition: "over_image",
76
+ state: "default",
77
+ });
78
+
79
+ expect(result).toBeNull();
80
+ });
81
+
82
+ it("should include display mode and image sizing in additionalProps", () => {
83
+ const config = {
84
+ secondary_image_switch: true,
85
+ secondary_image_position: "over_image",
86
+ secondary_image_visibility: "always",
87
+ secondary_image_image_key: "logo",
88
+ secondary_image_display_mode: "fixed",
89
+ secondary_image_image_sizing: "fill",
90
+ secondary_image_fit_position: "center",
91
+ secondary_image_fixed_width: 200,
92
+ secondary_image_fixed_height: 150,
93
+ };
94
+
95
+ const result = SecondaryImage({
96
+ value: mockValue(config),
97
+ currentPosition: "over_image",
98
+ state: "default",
99
+ });
100
+
101
+ expect(result?.elements[0].additionalProps).toMatchObject({
102
+ displayMode: "fixed",
103
+ imageSizing: "fill",
104
+ fitPosition: "center",
105
+ fixedWidth: 200,
106
+ fixedHeight: 150,
107
+ });
108
+ });
109
+
110
+ it("should apply correct styles including margins and border radius", () => {
111
+ const config = {
112
+ secondary_image_switch: true,
113
+ secondary_image_position: "over_image",
114
+ secondary_image_visibility: "always",
115
+ secondary_image_display_mode: "fixed",
116
+ secondary_image_fixed_width: 100,
117
+ secondary_image_fixed_height: 100,
118
+ secondary_image_corner_radius: 12,
119
+ secondary_image_margin_top: 10,
120
+ secondary_image_margin_left: 5,
121
+ secondary_image_margin_right: 5,
122
+ secondary_image_margin_bottom: 10,
123
+ };
124
+
125
+ const result = SecondaryImage({
126
+ value: mockValue(config),
127
+ currentPosition: "over_image",
128
+ state: "default",
129
+ });
130
+
131
+ expect(result?.elements[0].style).toMatchObject({
132
+ width: 100,
133
+ height: 100,
134
+ borderRadius: 12,
135
+ marginTop: 10,
136
+ marginLeft: 5,
137
+ marginRight: 5,
138
+ marginBottom: 10,
139
+ });
140
+ });
141
+ });
@@ -1,4 +1,4 @@
1
- import { renderHook } from "@testing-library/react-hooks";
1
+ import { renderHook, waitFor } from "@testing-library/react-native";
2
2
  import { Image } from "react-native";
3
3
 
4
4
  import { useGetImageDimensions } from "../useGetImageDimensions";
@@ -13,15 +13,16 @@ jest.spyOn(Image, "getSize").mockImplementation((_uri, success) => {
13
13
 
14
14
  describe("useGetImageDimensions", () => {
15
15
  it("should return aspect ration initially when known dimensions", async () => {
16
- const { result, waitForNextUpdate } = renderHook(() =>
16
+ const { result } = renderHook(() =>
17
17
  useGetImageDimensions("https://some_url.com", WIDTH, undefined)
18
18
  );
19
19
 
20
20
  expect(result.current).toBeUndefined();
21
- await waitForNextUpdate();
22
21
 
23
- expect(result.current).toEqual(
24
- getDimension({ width: WIDTH, height: HEIGTH })
25
- );
22
+ await waitFor(() => {
23
+ expect(result.current).toEqual(
24
+ getDimension({ width: WIDTH, height: HEIGTH })
25
+ );
26
+ });
26
27
  });
27
28
  });
@@ -103,7 +103,7 @@ export const SecondaryImage = ({ value, currentPosition, state }: Props) => {
103
103
  data: [
104
104
  {
105
105
  func: "image_src_from_media_item",
106
- args: [imageKey],
106
+ args: [imageKey, false],
107
107
  propName: "uri",
108
108
  },
109
109
  ],
@@ -52,14 +52,14 @@ const _Text = ({
52
52
  : textTransform(transformText, _label);
53
53
 
54
54
  React.useLayoutEffect(() => {
55
- // For FocusableCells with action buttons
56
- if (otherProps.state) {
57
- if (otherProps.state === "focused" && cellFocused === true) {
58
- accessibilityManager.addHeading(textLabel);
59
- }
60
- } else {
61
- if (cellFocused === true) {
62
- accessibilityManager.addHeading(textLabel);
55
+ if (cellFocused) {
56
+ switch (otherProps.state) {
57
+ case "focused":
58
+ accessibilityManager.addHeading(textLabel);
59
+ break;
60
+ case "focused_selected":
61
+ accessibilityManager.addHeading(`${textLabel}, Selected`);
62
+ break;
63
63
  }
64
64
  }
65
65
  }, [cellFocused, otherProps.state, textLabel]);
@@ -4,7 +4,7 @@ import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/nu
4
4
  import { Button } from "./Button";
5
5
  import {
6
6
  getButtonsCount,
7
- getPluginIdentifier,
7
+ memoizedGetPluginIdentifier,
8
8
  mapSelfAlignment,
9
9
  } from "./utils";
10
10
 
@@ -80,7 +80,11 @@ export const TvActionButtons = ({
80
80
  prefix: independentStyles ? prefixSpecificButton : buttonId(1),
81
81
  value,
82
82
  platformValue,
83
- pluginIdentifier: getPluginIdentifier(configuration, PREFIX, index),
83
+ pluginIdentifier: memoizedGetPluginIdentifier(
84
+ configuration,
85
+ PREFIX,
86
+ index
87
+ ),
84
88
  suffixId: prefixSpecificButton,
85
89
  preferredFocus: index === 0,
86
90
  });
@@ -1,9 +1,15 @@
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 first valid plugin identifier", () => {
11
+ expect.assertions(2);
12
+
7
13
  const configuration = {
8
14
  tv_buttons_button_1_other: "value",
9
15
  tv_buttons_button_1_assign_action:
@@ -13,13 +19,21 @@ describe("getPluginIdentifier", () => {
13
19
  };
14
20
 
15
21
  const index = 0;
16
-
17
22
  const result = getPluginIdentifier(configuration, prefix, index);
18
23
 
24
+ const memoizedResult = memoizedGetPluginIdentifier(
25
+ configuration,
26
+ prefix,
27
+ index
28
+ );
29
+
19
30
  expect(result).toEqual(configuration.tv_buttons_button_1_assign_action);
31
+ expect(memoizedResult).toEqual(result);
20
32
  });
21
33
 
22
- it("get second plugin identifier", () => {
34
+ it("returns the second valid plugin identifier", () => {
35
+ expect.assertions(2);
36
+
23
37
  const configuration = {
24
38
  tv_buttons_button_1_other: "value_1",
25
39
  tv_buttons_button_1_assign_action:
@@ -30,24 +44,216 @@ describe("getPluginIdentifier", () => {
30
44
  };
31
45
 
32
46
  const index = 1;
33
-
34
47
  const result = getPluginIdentifier(configuration, prefix, index);
35
48
 
49
+ const memoizedResult = memoizedGetPluginIdentifier(
50
+ configuration,
51
+ prefix,
52
+ index
53
+ );
54
+
36
55
  expect(result).toEqual(configuration.tv_buttons_button_2_assign_action);
56
+ expect(memoizedResult).toEqual(result);
37
57
  });
38
58
 
39
- it("get undefined if no assign_actions at all", () => {
59
+ it("returns undefined when there are no assign_action keys", () => {
60
+ expect.assertions(2);
61
+
40
62
  const configuration = {};
63
+ const index = 0;
64
+
65
+ const result = getPluginIdentifier(configuration, prefix, index);
66
+
67
+ const memoizedResult = memoizedGetPluginIdentifier(
68
+ configuration,
69
+ prefix,
70
+ index
71
+ );
72
+
73
+ expect(result).toBeUndefined();
74
+ expect(memoizedResult).toBeUndefined();
75
+ });
76
+
77
+ it("skips undefined values and returns the correct plugin identifier", () => {
78
+ expect.assertions(2);
79
+
80
+ const configuration = {
81
+ tv_buttons_button_1_assign_action: "tv_buttons_button_1_assign_action",
82
+ tv_buttons_button_2_assign_action: undefined,
83
+ tv_buttons_button_3_assign_action: "tv_buttons_button_3_assign_action",
84
+ };
85
+
86
+ const index = 1;
87
+ const result = getPluginIdentifier(configuration, prefix, index);
88
+
89
+ const memoizedResult = memoizedGetPluginIdentifier(
90
+ configuration,
91
+ prefix,
92
+ index
93
+ );
94
+
95
+ expect(result).toEqual(configuration.tv_buttons_button_3_assign_action);
96
+ expect(memoizedResult).toEqual(result);
97
+ });
98
+
99
+ it("handles missing intermediate keys and returns the correct plugin identifier", () => {
100
+ expect.assertions(2);
101
+
102
+ const configuration = {
103
+ tv_buttons_button_1_assign_action: "tv_buttons_button_1_assign_action",
104
+ tv_buttons_button_3_assign_action: "tv_buttons_button_3_assign_action",
105
+ };
106
+
107
+ const index = 1;
108
+ const result = getPluginIdentifier(configuration, prefix, index);
109
+
110
+ const memoizedResult = memoizedGetPluginIdentifier(
111
+ configuration,
112
+ prefix,
113
+ index
114
+ );
115
+
116
+ expect(result).toEqual(configuration.tv_buttons_button_3_assign_action);
117
+ expect(memoizedResult).toEqual(result);
118
+ });
119
+
120
+ it("skips empty string values and returns the first non-empty assign_action", () => {
121
+ expect.assertions(2);
122
+
123
+ const configuration = {
124
+ tv_buttons_button_1_assign_action: "",
125
+ tv_buttons_button_2_assign_action: "tv_buttons_button_2_assign_action",
126
+ };
41
127
 
42
128
  const index = 0;
129
+ const result = getPluginIdentifier(configuration, prefix, index);
130
+
131
+ const memoizedResult = memoizedGetPluginIdentifier(
132
+ configuration,
133
+ prefix,
134
+ index
135
+ );
136
+
137
+ expect(result).toEqual(configuration.tv_buttons_button_2_assign_action);
138
+ expect(memoizedResult).toEqual(result);
139
+ });
140
+
141
+ it("returns undefined for negative index", () => {
142
+ expect.assertions(2);
143
+
144
+ const configuration = {
145
+ tv_buttons_button_1_assign_action: "a",
146
+ tv_buttons_button_2_assign_action: "b",
147
+ };
43
148
 
149
+ const index = -1;
44
150
  const result = getPluginIdentifier(configuration, prefix, index);
45
151
 
152
+ const memoizedResult = memoizedGetPluginIdentifier(
153
+ configuration,
154
+ prefix,
155
+ index
156
+ );
157
+
46
158
  expect(result).toBeUndefined();
159
+ expect(memoizedResult).toBeUndefined();
160
+ });
161
+
162
+ it("returns undefined for out-of-bounds index", () => {
163
+ expect.assertions(2);
164
+
165
+ const configuration = {
166
+ tv_buttons_button_1_assign_action: "a",
167
+ tv_buttons_button_2_assign_action: "b",
168
+ };
169
+
170
+ const index = 5;
171
+ const result = getPluginIdentifier(configuration, prefix, index);
172
+
173
+ const memoizedResult = memoizedGetPluginIdentifier(
174
+ configuration,
175
+ prefix,
176
+ index
177
+ );
178
+
179
+ expect(result).toBeUndefined();
180
+ expect(memoizedResult).toBeUndefined();
181
+ });
182
+
183
+ it("ignores keys with wrong prefix or format", () => {
184
+ expect.assertions(2);
185
+
186
+ const configuration = {
187
+ tv_buttons_button_1_assign_action: "a",
188
+ tv_buttons_wrongprefix_2_assign_action: "b",
189
+ tv_buttons_button_x_assign_action: "c",
190
+ };
191
+
192
+ const index = 1;
193
+ const result = getPluginIdentifier(configuration, prefix, index);
194
+
195
+ const memoizedResult = memoizedGetPluginIdentifier(
196
+ configuration,
197
+ prefix,
198
+ index
199
+ );
200
+
201
+ expect(result).toBeUndefined();
202
+ expect(memoizedResult).toBeUndefined();
203
+ });
204
+
205
+ it("returns undefined if all assign_actions are null or empty", () => {
206
+ expect.assertions(2);
207
+
208
+ const configuration = {
209
+ tv_buttons_button_1_assign_action: null,
210
+ tv_buttons_button_2_assign_action: undefined,
211
+ tv_buttons_button_3_assign_action: "",
212
+ };
213
+
214
+ const index = 0;
215
+ const result = getPluginIdentifier(configuration, prefix, index);
216
+
217
+ const memoizedResult = memoizedGetPluginIdentifier(
218
+ configuration,
219
+ prefix,
220
+ index
221
+ );
222
+
223
+ expect(result).toBeUndefined();
224
+ expect(memoizedResult).toBeUndefined();
225
+ });
226
+
227
+ it("handles non-string values correctly", () => {
228
+ expect.assertions(6);
229
+
230
+ const configuration = {
231
+ tv_buttons_button_1_assign_action: 123,
232
+ tv_buttons_button_2_assign_action: true,
233
+ tv_buttons_button_3_assign_action: { nested: "value" },
234
+ };
235
+
236
+ expect(getPluginIdentifier(configuration, prefix, 0)).toEqual(123);
237
+ expect(getPluginIdentifier(configuration, prefix, 1)).toEqual(true);
238
+
239
+ expect(getPluginIdentifier(configuration, prefix, 2)).toEqual({
240
+ nested: "value",
241
+ });
242
+
243
+ expect(memoizedGetPluginIdentifier(configuration, prefix, 0)).toEqual(123);
244
+ expect(memoizedGetPluginIdentifier(configuration, prefix, 1)).toEqual(true);
245
+
246
+ expect(memoizedGetPluginIdentifier(configuration, prefix, 2)).toEqual({
247
+ nested: "value",
248
+ });
47
249
  });
48
250
  });
49
251
 
50
- describe("getPluginIdentifier - when configuration has same values for different keys", () => {
252
+ describe("getPluginIdentifier - when configuration has duplicate values", () => {
253
+ beforeAll(() => {
254
+ memoizedGetPluginIdentifier.clear();
255
+ });
256
+
51
257
  const prefix = "tv_buttons";
52
258
 
53
259
  const configuration = {
@@ -55,19 +261,35 @@ describe("getPluginIdentifier - when configuration has same values for different
55
261
  tv_buttons_button_2_assign_action: "navigation_action",
56
262
  };
57
263
 
58
- it("get first plugin identifier", () => {
59
- const index = 0;
264
+ it("returns the first plugin identifier when values are identical", () => {
265
+ expect.assertions(2);
60
266
 
267
+ const index = 0;
61
268
  const result = getPluginIdentifier(configuration, prefix, index);
62
269
 
270
+ const memoizedResult = memoizedGetPluginIdentifier(
271
+ configuration,
272
+ prefix,
273
+ index
274
+ );
275
+
63
276
  expect(result).toEqual(configuration.tv_buttons_button_1_assign_action);
277
+ expect(memoizedResult).toEqual(result);
64
278
  });
65
279
 
66
- it("get second plugin identifier", () => {
67
- const index = 1;
280
+ it("returns the second plugin identifier when values are identical", () => {
281
+ expect.assertions(2);
68
282
 
283
+ const index = 1;
69
284
  const result = getPluginIdentifier(configuration, prefix, index);
70
285
 
286
+ const memoizedResult = memoizedGetPluginIdentifier(
287
+ configuration,
288
+ prefix,
289
+ index
290
+ );
291
+
71
292
  expect(result).toEqual(configuration.tv_buttons_button_2_assign_action);
293
+ expect(memoizedResult).toEqual(result);
72
294
  });
73
295
  });
@@ -1,7 +1,7 @@
1
1
  import * as R from "ramda";
2
+ import memoizee from "memoizee";
2
3
 
3
4
  import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
4
- import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
5
5
 
6
6
  export const getButtonsCount = (
7
7
  configuration: Record<string, unknown>,
@@ -21,23 +21,27 @@ export const getPluginIdentifier = (
21
21
  configuration: Record<string, unknown>,
22
22
  prefix: string,
23
23
  index: number
24
- ): string => {
25
- const match = `${prefix}_button_\\d_assign_action`;
26
- const re = new RegExp(match);
27
-
28
- const rejectNils = R.compose(R.path([index]), R.filter(isNotNil));
29
-
30
- return rejectNils(
31
- R.toPairs(configuration)
32
- .filter(([key]) => R.startsWith(`${prefix}_button`, key))
33
- .map(([key, value]) => {
34
- const matched = key.match(re);
24
+ ): string | undefined => {
25
+ const re = new RegExp(`${prefix}_button_\\d_assign_action`);
26
+ let count = 0;
27
+
28
+ for (const [key, value] of Object.entries(configuration)) {
29
+ if (
30
+ key.startsWith(`${prefix}_button`) &&
31
+ re.test(key) &&
32
+ value != null &&
33
+ value !== ""
34
+ ) {
35
+ if (count === index) return value as string;
36
+ count++;
37
+ }
38
+ }
35
39
 
36
- return matched ? value : undefined;
37
- })
38
- );
40
+ return undefined;
39
41
  };
40
42
 
43
+ export const memoizedGetPluginIdentifier = memoizee(getPluginIdentifier);
44
+
41
45
  type Label = {
42
46
  name: string;
43
47
  };