@applicaster/zapp-react-native-utils 15.0.0-alpha.5870313360 → 15.0.0-alpha.5904402522

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/README.md +0 -6
  2. package/adsUtils/__tests__/createVMAP.test.ts +419 -0
  3. package/analyticsUtils/analyticsMapper.ts +10 -2
  4. package/appDataUtils/__tests__/urlScheme.test.ts +678 -0
  5. package/appUtils/HooksManager/__tests__/__snapshots__/hooksManager.test.js.snap +0 -188
  6. package/appUtils/HooksManager/__tests__/hooksManager.test.js +16 -2
  7. package/appUtils/RiverFocusManager/{index.js → index.ts} +25 -18
  8. package/appUtils/contextKeysManager/__tests__/getKeys/failure.test.ts +7 -2
  9. package/appUtils/contextKeysManager/__tests__/getKeys/success.test.ts +48 -0
  10. package/appUtils/contextKeysManager/contextResolver.ts +51 -22
  11. package/appUtils/contextKeysManager/index.ts +65 -10
  12. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +3 -0
  13. package/appUtils/focusManager/index.ios.ts +35 -2
  14. package/appUtils/focusManager/treeDataStructure/Tree/__tests__/Tree.test.js +46 -0
  15. package/appUtils/focusManager/treeDataStructure/Tree/index.js +18 -18
  16. package/appUtils/focusManagerAux/utils/index.ios.ts +122 -0
  17. package/appUtils/focusManagerAux/utils/index.ts +2 -4
  18. package/appUtils/focusManagerAux/utils/utils.ios.ts +61 -4
  19. package/appUtils/playerManager/playerNative.ts +2 -1
  20. package/cloudEventsUtils/__tests__/index.test.ts +529 -0
  21. package/cloudEventsUtils/index.ts +65 -1
  22. package/configurationUtils/__tests__/imageSrcFromMediaItem.test.ts +38 -0
  23. package/configurationUtils/index.ts +17 -11
  24. package/dateUtils/__tests__/dayjs.test.ts +330 -0
  25. package/enumUtils/__tests__/getEnumKeyByEnumValue.test.ts +207 -0
  26. package/errorUtils/__tests__/GeneralError.test.ts +97 -0
  27. package/errorUtils/__tests__/HttpStatusCode.test.ts +344 -0
  28. package/errorUtils/__tests__/MissingPluginError.test.ts +113 -0
  29. package/errorUtils/__tests__/NetworkError.test.ts +202 -0
  30. package/errorUtils/__tests__/getParsedResponse.test.ts +188 -0
  31. package/errorUtils/__tests__/invariant.test.ts +112 -0
  32. package/headersUtils/__tests__/headersUtils.test.js +11 -1
  33. package/headersUtils/index.ts +2 -1
  34. package/manifestUtils/_internals/__tests__/index.test.js +41 -0
  35. package/manifestUtils/_internals/index.js +33 -0
  36. package/manifestUtils/defaultManifestConfigurations/player.js +34 -10
  37. package/manifestUtils/fieldUtils/__tests__/fieldUtils.test.js +49 -0
  38. package/manifestUtils/fieldUtils/index.js +54 -0
  39. package/manifestUtils/index.js +6 -0
  40. package/manifestUtils/keys.js +240 -0
  41. package/manifestUtils/mobileAction/button/__tests__/mobileActionButton.test.js +168 -0
  42. package/manifestUtils/mobileAction/button/index.js +140 -0
  43. package/manifestUtils/mobileAction/container/__tests__/mobileActionButtonsContainer.test.js +104 -0
  44. package/manifestUtils/mobileAction/container/index.js +79 -0
  45. package/manifestUtils/mobileAction/groups/__tests__/buildMobileActionButtonGroups.test.js +127 -0
  46. package/manifestUtils/mobileAction/groups/defaults.js +77 -0
  47. package/manifestUtils/mobileAction/groups/index.js +80 -0
  48. package/manifestUtils/platformIsTV.js +13 -0
  49. package/navigationUtils/index.ts +15 -5
  50. package/numberUtils/__tests__/toNumber.test.ts +27 -0
  51. package/numberUtils/__tests__/toPositiveNumber.test.ts +193 -0
  52. package/numberUtils/index.ts +23 -1
  53. package/package.json +4 -4
  54. package/reactHooks/analytics/__tests__/useSendAnalyticsOnPress.test.ts +537 -0
  55. package/reactHooks/app/__tests__/useAppState.test.ts +1 -1
  56. package/reactHooks/autoscrolling/__tests__/useTrackCurrentAutoScrollingElement.test.ts +1 -1
  57. package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +1 -2
  58. package/reactHooks/cell-click/__tests__/index.test.js +1 -3
  59. package/reactHooks/configuration/__tests__/index.test.tsx +1 -1
  60. package/reactHooks/connection/__tests__/index.test.js +1 -1
  61. package/reactHooks/dev/__tests__/useReRenderLog.test.ts +188 -0
  62. package/reactHooks/device/useIsTablet.tsx +14 -19
  63. package/reactHooks/events/index.ts +20 -0
  64. package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +32 -23
  65. package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +19 -19
  66. package/reactHooks/feed/__tests__/useEntryScreenId.test.tsx +1 -1
  67. package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +42 -30
  68. package/reactHooks/feed/__tests__/useInflatedUrl.test.tsx +1 -1
  69. package/reactHooks/feed/index.ts +0 -2
  70. package/reactHooks/feed/useInflatedUrl.ts +1 -1
  71. package/reactHooks/hookModal/hooks/useHookModalScreenData.ts +12 -8
  72. package/reactHooks/index.ts +2 -0
  73. package/reactHooks/layout/__tests__/index.test.tsx +1 -1
  74. package/reactHooks/layout/__tests__/useLayoutVersion.test.tsx +1 -1
  75. package/reactHooks/navigation/__tests__/index.test.tsx +40 -9
  76. package/reactHooks/navigation/index.ts +20 -5
  77. package/reactHooks/navigation/useRoute.ts +3 -1
  78. package/reactHooks/player/__tests__/useAutoSeek._test.tsx +1 -1
  79. package/reactHooks/player/__tests__/useTapSeek._test.ts +1 -1
  80. package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +1 -1
  81. package/reactHooks/resolvers/__tests__/useComponentResolver.test.tsx +1 -1
  82. package/reactHooks/screen/__tests__/useCurrentScreenData.test.tsx +2 -2
  83. package/reactHooks/screen/__tests__/useScreenBackgroundColor.test.tsx +1 -1
  84. package/reactHooks/screen/__tests__/useScreenData.test.tsx +1 -1
  85. package/reactHooks/screen/__tests__/useTargetScreenData.test.tsx +2 -2
  86. package/reactHooks/state/__tests__/useComponentScreenState.test.ts +246 -0
  87. package/reactHooks/state/index.ts +2 -0
  88. package/reactHooks/state/useComponentScreenState.ts +45 -0
  89. package/reactHooks/state/useRefWithInitialValue.ts +10 -0
  90. package/reactHooks/ui/__tests__/useFadeOutWhenBlurred.test.ts +580 -0
  91. package/reactHooks/utils/__tests__/index.test.js +1 -1
  92. package/rectUtils/__tests__/index.test.ts +549 -0
  93. package/rectUtils/index.ts +2 -2
  94. package/refreshUtils/RefreshCoordinator/__tests__/refreshCoordinator.test.ts +161 -0
  95. package/refreshUtils/RefreshCoordinator/index.ts +216 -0
  96. package/refreshUtils/RefreshCoordinator/utils/__tests__/getDataRefreshConfig.test.ts +104 -0
  97. package/refreshUtils/RefreshCoordinator/utils/index.ts +29 -0
  98. package/screenPickerUtils/__tests__/index.test.ts +333 -0
  99. package/screenPickerUtils/index.ts +5 -0
  100. package/screenState/__tests__/index.test.ts +1 -1
  101. package/screenUtils/index.ts +3 -0
  102. package/searchUtils/const.ts +7 -0
  103. package/searchUtils/index.ts +3 -0
  104. package/services/storageServiceSync.web.ts +1 -1
  105. package/stringUtils/index.ts +1 -1
  106. package/testUtils/index.tsx +1 -1
  107. package/time/__tests__/BackgroundTimer.test.ts +156 -0
  108. package/time/__tests__/Timer.test.ts +236 -0
  109. package/typeGuards/__tests__/isString.test.ts +21 -0
  110. package/typeGuards/index.ts +4 -0
  111. package/utils/__tests__/clone.test.ts +158 -0
  112. package/utils/__tests__/mergeRight.test.ts +48 -0
  113. package/utils/__tests__/path.test.ts +7 -0
  114. package/utils/clone.ts +7 -0
  115. package/utils/index.ts +5 -1
  116. package/utils/mergeRight.ts +5 -0
  117. package/reactHooks/componentsMap/index.ts +0 -55
  118. package/reactHooks/feed/__tests__/useFeedRefresh.test.tsx +0 -75
  119. package/reactHooks/feed/useFeedRefresh.tsx +0 -65
@@ -0,0 +1,188 @@
1
+ import { getParsedResponse } from "../index";
2
+ import { RESPONSES } from "../errorCodes";
3
+
4
+ describe("getParsedResponse", () => {
5
+ it("should parse error with response status code from response.data", () => {
6
+ const err = {
7
+ response: {
8
+ data: {
9
+ statusCode: 404,
10
+ message: "Resource not found",
11
+ },
12
+ config: {
13
+ url: "https://api.example.com/data",
14
+ params: { id: 123 },
15
+ },
16
+ },
17
+ name: "AxiosError",
18
+ context: { userId: "user123" },
19
+ };
20
+
21
+ const result = getParsedResponse(err);
22
+
23
+ expect(result.message).toBe("Error: Resource not found");
24
+ expect(result.data.statusCode).toBe("404");
25
+ expect(result.data.url).toBe("https://api.example.com/data");
26
+ expect(result.data.params).toEqual({ id: 123 });
27
+ expect(result.data.context).toEqual({ userId: "user123" });
28
+ expect(result.data.name).toBe("AxiosError");
29
+ expect(result.jsOnly).toBe(false);
30
+ expect(result.exception).toBe(result.message);
31
+ });
32
+
33
+ it("should parse error with response status code from response.status", () => {
34
+ const err = {
35
+ response: {
36
+ status: 500,
37
+ config: {
38
+ url: "https://api.example.com/data",
39
+ },
40
+ },
41
+ };
42
+
43
+ const result = getParsedResponse(err);
44
+
45
+ expect(result.message).toBe(`Error: ${RESPONSES[500]}`);
46
+ expect(result.data.statusCode).toBe("500");
47
+ });
48
+
49
+ it("should handle error with request but no response", () => {
50
+ const err = {
51
+ request: {},
52
+ message: "Network timeout",
53
+ config: {
54
+ url: "https://api.example.com/data",
55
+ params: { test: "value" },
56
+ },
57
+ name: "NetworkError",
58
+ context: { app: "test" },
59
+ };
60
+
61
+ const result = getParsedResponse(err);
62
+
63
+ expect(result.message).toBe("Error: Network timeout");
64
+ expect(result.data.url).toBe("https://api.example.com/data");
65
+ expect(result.data.params).toEqual({ test: "value" });
66
+ expect(result.data.name).toBe("NetworkError");
67
+ expect(result.data.context).toEqual({ app: "test" });
68
+ expect(result.jsOnly).toBe(false);
69
+ });
70
+
71
+ it("should handle error with request but no response and no message", () => {
72
+ const err = {
73
+ request: {},
74
+ config: {
75
+ url: "https://api.example.com/data",
76
+ },
77
+ };
78
+
79
+ const result = getParsedResponse(err);
80
+
81
+ expect(result.message).toBe(`Error: ${RESPONSES.noResponse}`);
82
+ });
83
+
84
+ it("should handle error with neither response nor request", () => {
85
+ const err = {
86
+ message: "Failed to set up request",
87
+ config: {
88
+ url: "https://api.example.com/data",
89
+ },
90
+ };
91
+
92
+ const result = getParsedResponse(err);
93
+
94
+ expect(result.message).toBe("Error: Failed to set up request");
95
+ });
96
+
97
+ it("should handle error with neither response nor request and no message", () => {
98
+ const err = {
99
+ config: {
100
+ url: "https://api.example.com/data",
101
+ },
102
+ };
103
+
104
+ const result = getParsedResponse(err);
105
+
106
+ expect(result.message).toBe(`Error: ${RESPONSES.unknown}`);
107
+ });
108
+
109
+ it("should handle null error", () => {
110
+ const err = null;
111
+
112
+ const result = getParsedResponse(err);
113
+
114
+ expect(result.message).toBe(`Error: ${RESPONSES.unknown}`);
115
+
116
+ expect(result.data).toEqual({
117
+ context: undefined,
118
+ name: undefined,
119
+ params: undefined,
120
+ url: undefined,
121
+ });
122
+ });
123
+
124
+ it("should handle undefined error", () => {
125
+ const err = undefined;
126
+
127
+ const result = getParsedResponse(err);
128
+
129
+ expect(result.message).toBe(`Error: ${RESPONSES.unknown}`);
130
+ });
131
+
132
+ it("should extract params from err.config when response is not present", () => {
133
+ const err = {
134
+ request: {},
135
+ message: "Request failed",
136
+ config: {
137
+ url: "https://api.example.com/data",
138
+ params: { key: "value" },
139
+ },
140
+ };
141
+
142
+ const result = getParsedResponse(err);
143
+
144
+ expect(result.data.params).toEqual({ key: "value" });
145
+ expect(result.data.url).toBe("https://api.example.com/data");
146
+ });
147
+
148
+ it("should prioritize response config over error config", () => {
149
+ const err = {
150
+ response: {
151
+ status: 404,
152
+ config: {
153
+ url: "https://api.example.com/response-url",
154
+ params: { from: "response" },
155
+ },
156
+ },
157
+ config: {
158
+ url: "https://api.example.com/error-url",
159
+ params: { from: "error" },
160
+ },
161
+ };
162
+
163
+ const result = getParsedResponse(err);
164
+
165
+ expect(result.data.url).toBe("https://api.example.com/response-url");
166
+ expect(result.data.params).toEqual({ from: "response" });
167
+ });
168
+
169
+ it("should handle response with custom status codes", () => {
170
+ const err = {
171
+ response: {
172
+ data: {
173
+ statusCode: 418,
174
+ message: "I'm a teapot",
175
+ },
176
+ status: 418,
177
+ config: {
178
+ url: "https://api.example.com/coffee",
179
+ },
180
+ },
181
+ };
182
+
183
+ const result = getParsedResponse(err);
184
+
185
+ expect(result.message).toBe("Error: I'm a teapot");
186
+ expect(result.data.statusCode).toBe("418");
187
+ });
188
+ });
@@ -0,0 +1,112 @@
1
+ import { invariant } from "../index";
2
+
3
+ // Mock __DEV__ global
4
+ declare global {
5
+ var __DEV__: boolean;
6
+ }
7
+
8
+ describe("invariant", () => {
9
+ const originalDev = global.__DEV__;
10
+
11
+ afterEach(() => {
12
+ global.__DEV__ = originalDev;
13
+ });
14
+
15
+ describe("in development mode", () => {
16
+ beforeEach(() => {
17
+ global.__DEV__ = true;
18
+ });
19
+
20
+ it("should not throw when condition is true", () => {
21
+ expect(() => {
22
+ invariant(true, "This should not throw");
23
+ }).not.toThrow();
24
+ });
25
+
26
+ it("should throw when condition is false", () => {
27
+ expect(() => {
28
+ invariant(false, "This should throw");
29
+ }).toThrow("This should throw");
30
+ });
31
+
32
+ it("should throw with correct error message", () => {
33
+ const errorMessage = "Custom error message";
34
+
35
+ expect(() => {
36
+ invariant(false, errorMessage);
37
+ }).toThrow(errorMessage);
38
+ });
39
+
40
+ it("should not throw when condition is truthy value", () => {
41
+ expect(() => {
42
+ invariant(1 as any, "Should not throw");
43
+ }).not.toThrow();
44
+
45
+ expect(() => {
46
+ invariant("string" as any, "Should not throw");
47
+ }).not.toThrow();
48
+
49
+ expect(() => {
50
+ invariant({} as any, "Should not throw");
51
+ }).not.toThrow();
52
+
53
+ expect(() => {
54
+ invariant([] as any, "Should not throw");
55
+ }).not.toThrow();
56
+ });
57
+
58
+ it("should throw when condition is falsy value", () => {
59
+ expect(() => {
60
+ invariant(0 as any, "Should throw for 0");
61
+ }).toThrow("Should throw for 0");
62
+
63
+ expect(() => {
64
+ invariant("" as any, "Should throw for empty string");
65
+ }).toThrow("Should throw for empty string");
66
+
67
+ expect(() => {
68
+ invariant(null as any, "Should throw for null");
69
+ }).toThrow("Should throw for null");
70
+
71
+ expect(() => {
72
+ invariant(undefined as any, "Should throw for undefined");
73
+ }).toThrow("Should throw for undefined");
74
+
75
+ expect(() => {
76
+ invariant(NaN as any, "Should throw for NaN");
77
+ }).toThrow("Should throw for NaN");
78
+ });
79
+ });
80
+
81
+ describe("in production mode", () => {
82
+ beforeEach(() => {
83
+ global.__DEV__ = false;
84
+ });
85
+
86
+ it("should not throw when condition is true", () => {
87
+ expect(() => {
88
+ invariant(true, "This should not throw");
89
+ }).not.toThrow();
90
+ });
91
+
92
+ it("should not throw when condition is false", () => {
93
+ expect(() => {
94
+ invariant(false, "This should not throw in production");
95
+ }).not.toThrow();
96
+ });
97
+
98
+ it("should not throw for any condition", () => {
99
+ expect(() => {
100
+ invariant(0 as any, "Should not throw in production");
101
+ }).not.toThrow();
102
+
103
+ expect(() => {
104
+ invariant(null as any, "Should not throw in production");
105
+ }).not.toThrow();
106
+
107
+ expect(() => {
108
+ invariant(undefined as any, "Should not throw in production");
109
+ }).not.toThrow();
110
+ });
111
+ });
112
+ });
@@ -1,6 +1,15 @@
1
1
  import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
2
2
  import { getAppDataHeaders } from "../index";
3
3
 
4
+ const mockUILanguage = "en-US";
5
+
6
+ jest.mock(
7
+ "@applicaster/zapp-react-native-utils/appUtils/localizationsHelper",
8
+ () => ({
9
+ getUILanguage: jest.fn(() => mockUILanguage),
10
+ })
11
+ );
12
+
4
13
  describe("getAppDataHeaders", () => {
5
14
  const mockAppData = {
6
15
  riversConfigurationId: "test-layout",
@@ -25,7 +34,7 @@ describe("getAppDataHeaders", () => {
25
34
  const expectedHeaders = {
26
35
  "x-applicaster-layout-id": mockAppData.riversConfigurationId,
27
36
  "x-applicaster-screen-id": screenId,
28
- "x-applicaster-language-code": mockAppData.languageCode,
37
+ "x-applicaster-language-code": mockUILanguage,
29
38
  "x-applicaster-version-number": mockAppData.version_name,
30
39
  "x-applicaster-platform": mockAppData.platform,
31
40
  "x-applicaster-country-code": mockAppData.countryCode,
@@ -33,6 +42,7 @@ describe("getAppDataHeaders", () => {
33
42
  mockAppData.signedDeviceInfoToken,
34
43
  "x-applicaster-device-make": mockAppData.deviceMake,
35
44
  "x-applicaster-device-model": mockAppData.deviceModel,
45
+ "x-applicaster-uuid": undefined,
36
46
  };
37
47
 
38
48
  const headers = getAppDataHeaders(screenId);
@@ -1,6 +1,7 @@
1
1
  import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
2
2
  import { getPlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
3
3
  import { utilsLogger } from "../logger";
4
+ import { getUILanguage } from "../localizationUtils";
4
5
 
5
6
  const logger = utilsLogger.addSubsystem("headersUtils");
6
7
 
@@ -21,7 +22,7 @@ export const getAppDataHeaders = (screenId: string) => {
21
22
  "x-applicaster-layout-id":
22
23
  appData.riversConfigurationId || appData.rivers_configuration_id,
23
24
  "x-applicaster-screen-id": screenId,
24
- "x-applicaster-language-code": appData.languageCode,
25
+ "x-applicaster-language-code": getUILanguage(),
25
26
  "x-applicaster-version-number": appData.version_name || appData.versionName,
26
27
  "x-applicaster-platform": (platform || appData.platform).toLowerCase(),
27
28
  "x-applicaster-country-code": appData.countryCode,
@@ -0,0 +1,41 @@
1
+ const {
2
+ isKeyHasSuffix,
3
+ isKeyHasAnyOfSuffixes,
4
+ getKeyWithPrefixGenerator,
5
+ } = require("..");
6
+
7
+ describe("manifestUtils/_internals helpers", () => {
8
+ it("checks a key suffix", () => {
9
+ expect(
10
+ isKeyHasSuffix("button_enabled", "mobile_button_1_button_enabled")
11
+ ).toBe(true);
12
+
13
+ expect(
14
+ isKeyHasSuffix("button_enabled", "mobile_button_1_assign_action")
15
+ ).toBe(false);
16
+ });
17
+
18
+ it("checks key against multiple suffixes", () => {
19
+ const suffixes = ["font_color", "focused_font_color", "text_transform"];
20
+
21
+ expect(
22
+ isKeyHasAnyOfSuffixes(suffixes, "mobile_button_1_text_transform")
23
+ ).toBe(true);
24
+
25
+ expect(
26
+ isKeyHasAnyOfSuffixes(suffixes, "mobile_button_1_asset_alignment")
27
+ ).toBe(false);
28
+ });
29
+
30
+ it("generates stable key names using a prefix", () => {
31
+ const withMobileButtonPrefix = getKeyWithPrefixGenerator("mobile_button_2");
32
+
33
+ expect(withMobileButtonPrefix("display_mode")).toBe(
34
+ "mobile_button_2_display_mode"
35
+ );
36
+
37
+ expect(withMobileButtonPrefix("asset_enabled")).toBe(
38
+ "mobile_button_2_asset_enabled"
39
+ );
40
+ });
41
+ });
@@ -174,6 +174,36 @@ function generateFieldsFromDefaultsWithoutPrefixedLabel(
174
174
  )(fields);
175
175
  }
176
176
 
177
+ /**
178
+ * Checks whether a generated manifest field key ends with the given suffix.
179
+ *
180
+ * @param {string} suffix
181
+ * @param {string} key
182
+ * @returns {boolean}
183
+ */
184
+ const isKeyHasSuffix = (suffix, key) => key.endsWith(`_${suffix}`);
185
+
186
+ /**
187
+ * Checks whether a generated manifest field key ends with any supported suffix.
188
+ *
189
+ * @param {string[]} suffixes
190
+ * @param {string} key
191
+ * @returns {boolean}
192
+ */
193
+ const isKeyHasAnyOfSuffixes = (suffixes, key) =>
194
+ suffixes.some((suffix) => key.endsWith(`_${suffix}`));
195
+
196
+ /**
197
+ * Creates a helper that prefixes a manifest field suffix with a shared key stem.
198
+ *
199
+ * @param {string} prefix
200
+ * @returns {(suffix: string) => string}
201
+ */
202
+ function getKeyWithPrefixGenerator(prefix) {
203
+ // expect prefix as lower snake case, e.g. "mobile_buttons_container"
204
+ return (suffix) => `${prefix}_${suffix}`;
205
+ }
206
+
177
207
  module.exports = {
178
208
  toSnakeCase,
179
209
  toCamelCase,
@@ -185,4 +215,7 @@ module.exports = {
185
215
  getDefaultConfiguration,
186
216
  compact,
187
217
  replaceUnderscoreToSpace,
218
+ isKeyHasSuffix,
219
+ isKeyHasAnyOfSuffixes,
220
+ getKeyWithPrefixGenerator,
188
221
  };
@@ -331,6 +331,35 @@ function getPlayerConfiguration({ platform, version }) {
331
331
  label_tooltip: "Hint for skip intro button accessibility",
332
332
  type: "text_input",
333
333
  },
334
+ {
335
+ type: "text_input",
336
+ label: "Text Tracks Label",
337
+ key: "text_tracks_label",
338
+ initial_value: "Subtitles",
339
+ label_tooltip: "Label for the text tracks list",
340
+ },
341
+ {
342
+ type: "text_input",
343
+ label: "Audio Tracks Label",
344
+ key: "audio_tracks_label",
345
+ initial_value: "Audio",
346
+ label_tooltip: "Label for the audio tracks list",
347
+ },
348
+ {
349
+ type: "text_input",
350
+ label: "Off Track Label",
351
+ key: "off_track_label",
352
+ initial_value: "Off",
353
+ label_tooltip: "Label for the 'Off' option in text tracks list",
354
+ },
355
+ {
356
+ type: "text_input",
357
+ label: "Default Track Label",
358
+ key: "default_track_label",
359
+ initial_value: "Default",
360
+ label_tooltip:
361
+ "Label for the 'Default' audio track in audio tracks list",
362
+ },
334
363
  ],
335
364
  };
336
365
 
@@ -1200,40 +1229,35 @@ function getPlayerConfiguration({ platform, version }) {
1200
1229
  label: "Playback Speed 0.8x",
1201
1230
  type: "uploader",
1202
1231
  label_tooltip: "Playback Speed 0.8x",
1203
- initial_value:
1204
- "data:image/png;base64,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",
1232
+ initial_value: null,
1205
1233
  },
1206
1234
  {
1207
1235
  key: "speed_1",
1208
1236
  label: "Playback Speed 1x",
1209
1237
  type: "uploader",
1210
1238
  label_tooltip: "Playback Speed 1x",
1211
- initial_value:
1212
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAI4SURBVHgB7dzhcRJBGIDhT8cCkg6SDmIHsQNLiBUYKwAqUCtIC5ZACVqBdIAdxN3JMSLecXcE8Tt5npkdJlxyf97ZcLcLRAAAAAAAAAAAAAAAAAAAAAAAAADAMb2IiViv17fl4aGMq45fub68vFwFv3kVyZWwF+VhVsZ9MFrqwANmLT1SBm5mbQ37NniWl5FMifu+PHwPcY8izQwuYa/iadbeBkeTaQbfxf64P8r4GoyS7l90h89lXJfxLRgl+23Sqox35f52WX8o/8aDcTLP4EUZrzdxOUzGGbws40MJ6/X2CDIFrhdRNeyn4GjSBBb275jKVTQHEvg/d/aB69Lo+sljGR/jADvnmEUiZx242a2qr/0XzVP35bmHkeeY7Zxj3pw3hXOfwTctz90NjdzEnbccuo0kzj1w1712b+Q9cfed9+TOOnCzSrboONwZuSfuopz3SyRx9hdZJcY8RkQeELfr2D/hNimGR55a3Cr9m+5OpcZpdqvabnNq5HpBdtPx5ynjVmbwlp6ZPLm4lcA7eiLvSh23ErjFwMjp41YCd3t85vEUBG7Rc7W8Mc+27txG4B0D426kjyzwlp64y47nU0cWuDFgEeNNdF94pY0scAxfoeq5uk4Z2Yb/yOXHqUW24X/A2vKAyGk+OGfDv13vIsaBy5onZ8P/T4NXqPZEtuGfwdaG/yp+vfF+HiNsRV5tnSPNhv9kvoSlaj75f9F2zBewAAAAAAAAAAAAAAAAAAAAAAAAwHT8BNhH51wNSx95AAAAAElFTkSuQmCC",
1239
+ initial_value: null,
1213
1240
  },
1214
1241
  {
1215
1242
  key: "speed_1_2",
1216
1243
  label: "Playback Speed 1.2x",
1217
1244
  type: "uploader",
1218
1245
  label_tooltip: "Playback Speed 1.2x",
1219
- initial_value:
1220
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAUjSURBVHgB7d3bUeNIFAbgA7Xv2EUCmgiWjWBNBAwRYCKYdQQrIliIYEUEw0awymDIYDQBUFC8g+eccWsulPomteTu1v9VdZlSGyP0W1KrWxciAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgPgc0Mw8Pj4u+KUt4mm5XDaUqVED5oX5gV/ea6o3vGDvaQI8Hyt+ueAir4XmbTIvNZebnAIfJWBeoCf88g/tFqjOKS/ImkbE8/FezUdBfu5o9wVsKHGHFJBs/rjIAv1E5nBHpebjX/7xI/mHK+SL8Zk/429K3G8UiNoMykItaI/UPvZ/Lic0XMmfR7wmX1GiBq/BP621slAL2j9Z60KE25KQ/6JEDdoHD9jHieD7YJ6fNe22IjpPXG5p16Bq1DT5MpyReZciv/eO5/eJEtN7E63C/Wh5myyQBU1Hu888ODi42263lx0h1Vyu1ZdDvqxd8yvTZC0uKTFDNtGm4GQhbrjc0ERUy73QVNeLxeLctAZyXcUv56R3QQkK2or+9oGHhxXtNmfXNK2Voe6SHKhdxq2muuAvUUGJCRlww+X06Ojock/7qj810+89j2fvDHUFJSZUwLIp/mPsjgsL3S7jC/mZpHdtKkOPg2VhbPYcbEuOVbs2rw35mbJROLohAdeqYRKFgF+ywlCX3GFS7010xiMwZ5rpzVSDIyEFb0WnTLWS1111fHRQU4IQ8K+0vWCvr69J9kcjYEWNHK001c5jxNIvz2XLpfdolIyEtZ/x/Pw8qIMFAdP3ExNKTXVDjl2UqruzHZgoaDdQ4RWyGuZct5/x8vJSqRGyXmYfMC88WUNMvW6nHh03q45pziG/Cdf2uU5mHbAKtzK8xfesjloz3RqyIVzRu/U+24Adwr3y7U+XfgFujOn6srUhW8K9GnJIOsuAHcMtqYfj4+O1T8gO4ZY0wOwCdgj3duhCdQ157HDFrAJ2DHdNATiELCcmrjX1QcIVswl4ynBblpB1540FC1fMImB1nFsZ3hI83JYl5LeChiuyD1jt70yt4dHCbTmGHDxckXXAKtzS8JbRw23xYMXW8hZbfb+/S5mKKVxLa7nl3a3pItiVDaGpoTs5NVf6YeW8qjuP300t3FbwKymiDLgrIJ4m3XXWfuGEwpX/p6slHTTk6AJWIzJlR5UsDDnR/tTwu7ZwGy6Vuo6qF9dTg1w6MR4eHireN3cNBwYLOcY1+MxQt5Khs661WF0/VJJZQbtrqPpquLyzvcm1h0pa1xwyjRlyjI0s29jnief0Sfl2P/r2XfuKMWDbeczRnvimdi9rTbX2ONch5N5f3hgDrgx1N8u4r/BbaaZbOzEsIRfUU3QBq0aMXEvUvKmS1m/s1+nWHdOce6gMIffeao3dyJJj10ZTp5v+beCcN0s17farsk9uHFqvFenPqAjFuPVQ813wjx/UJO+TBiRk/gz5Oxfq7w0a8AcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANif7B8Q/dOlJHI7iEJNvlcl+8tCsg5Y3R/Ldm1QuUz46aI2ud9lx+XCr1HubhOLLNdgh6eQdjn3uZNPKnIN+DP5XzQtl6ha77+Rmuw20eoOOgX5K4bcfSdWOe6Dh9yMJYobuYSUY8BDnj2Y1XMLRY4BD7lJS3LPJrTJMeAht1lK7tmENmhF/4BWdEI25C/L3qwsA1YdFj6BXcX0LOSQcu+LLvnF1g258b2XVUrmMppUcvmdfhznNlz+43Kd+2jSV0z9PFgMpokeAAAAAElFTkSuQmCC",
1246
+ initial_value: null,
1221
1247
  },
1222
1248
  {
1223
1249
  key: "speed_1_5",
1224
1250
  label: "Playback Speed 1.5x",
1225
1251
  type: "uploader",
1226
1252
  label_tooltip: "Playback Speed 1.5x",
1227
- initial_value:
1228
- "data:image/png;base64,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",
1253
+ initial_value: null,
1229
1254
  },
1230
1255
  {
1231
1256
  key: "speed_2",
1232
1257
  label: "Playback Speed 2x",
1233
1258
  type: "uploader",
1234
1259
  label_tooltip: "Playback Speed 2x",
1235
- initial_value:
1236
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAQnSURBVHgB7d2PUdRAFMfxh2MBUIGxArECjwqECjwqUCowV4FYgdABVCAdSAekg7ME35pFgnOb3ZDAvmy+n5lMGJLL3dzvkuyfZCMCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2LMnM7Hdbvd11p2ag4ODRtDLdMAa6kpnH3U61qkKrHbrp0sN/EbwiMmAfbBfdVrJMI1OZxr0leAvcwFruC7YWsY515DPBLYC1nC/6eyLTONCQz6VhXslRvg9d6pwnbX/wSyaiT1Yg6h0dhdZzZ1Xr6U9zzqHOn2QtgDW52jJhS8rAf/Q2Tqw2JWQT0JVIv/j+CnhUvaNvvZIFip7wL5+uw0sbqTdA5vINiqd/ZK2fryLbuLgtyyQhXPwYc+yTUpjhl/ne88qK1koCwGvQgs0uAtJ17duJQtlIeDQYfVWBojs6fuyUK8lP1c63hXmlOfMRZ5/newBT1WF0YJW37m8kYUy09Axgb6ABx3uSzKb7sI+vqrlqknVjsXJ9WC/HRlTpZpiG1MqZQ92zZxVYNmlJPBNpdv2z6c1cfrXjdrG1Ga/B+sX+Vln54HFV7onnUh8GytpW8O6BnVWBFrjTnJ3Xc56D/YFq1C47hCZ2mW46/y99qGlfI5QU+uhZDbbgDtt0CFJrWBeqBAWDTnSjn4jmc0y4E64oQYMF+65JPJVtU1gcTDkSLgbC71YszsHJ/QeuWuz1vK0bdfSFth2eXROTgi3FgNmFXBCuLf6xb6Xce9RSyTkuYTrzOmyWVdg6Tssu/Po0RT1z0jIjYR/YKbCdWYR8EuG23nPWsIh72IuXMd8wDnC7bx3LWkhmwzXsX7he7ZwO5+hlv6QzYbrmK0mWQjXezNyeVZW72wwEW6ktNxl9hpsc3uwfqmfZF7hOsnNmi/NVMA+3AsJh9tI24CfM9ybwP9Nhmzpzob7cEMaSbiEdoLPEWvEcH3Lg5s1c7Fy4Xss3PueoUaeKKVdeEgL1ZBmzZwsXPi+kv5eoUnoF74X+Rx9N77trArNIWQLh+hKMtOgjmVguI7/f9/h+lgyK+miuzGqwP+jjRiRkOnwN2JXh39yC1VPyFeSGQHLvwKYu3bLBd1IOwxELQP49U//28atZGahkFXJC9wcNvA+JwAAAAAAAAAAAAAAAAAAAAAAAIxWxIjvffytMW5M6e4ziO+fOTxkRNpZKjrgyGDh92oNeSOFKvbuQj9Ef8qQwrVft0hF7sEa2FpnQwdDyT78/nMoNeA7GT40RKMBv5XCFHeI9oO6VDJc5V9blBLPwWPGxcg+psbUSgx4X55uzGtNKjHgMcMcPvfItS+uxIDHDHySfdCUqVGKfkApekZSn3jWVWRrVpEB+waLIYFtSh1mqfS26FriD9U4G/KUtLlZSm9SrdM7eajnNjpd63Reem/SH2FDq35f8ateAAAAAElFTkSuQmCC",
1260
+ initial_value: null,
1237
1261
  },
1238
1262
  {
1239
1263
  key: "sleep_timer",
@@ -0,0 +1,49 @@
1
+ const {
2
+ withConditional,
3
+ getConditionalKey,
4
+ createConditionalField,
5
+ } = require("..");
6
+
7
+ describe("manifestUtils/fieldUtils", () => {
8
+ it("appends conditions and adds all_conditions when there is more than one condition", () => {
9
+ const config = {
10
+ key: "mobile_button_1_width",
11
+ conditional_fields: [
12
+ { key: "styles/mobile_button_1_button_enabled", condition_value: true },
13
+ ],
14
+ };
15
+
16
+ const result = withConditional([
17
+ { key: "styles/mobile_button_1_display_mode", condition_value: "fixed" },
18
+ ])(config);
19
+
20
+ expect(result.conditional_fields).toEqual([
21
+ { key: "styles/mobile_button_1_button_enabled", condition_value: true },
22
+ { key: "styles/mobile_button_1_display_mode", condition_value: "fixed" },
23
+ ]);
24
+
25
+ expect(result.rules).toBe("all_conditions");
26
+ });
27
+
28
+ it("creates category-prefixed conditional key", () => {
29
+ expect(
30
+ getConditionalKey("mobile_buttons_container_position", "styles")
31
+ ).toBe("styles/mobile_buttons_container_position");
32
+ });
33
+
34
+ it("creates conditional fields with default category and with raw key", () => {
35
+ expect(
36
+ createConditionalField("mobile_button_1_asset_enabled", true)
37
+ ).toEqual({
38
+ key: "styles/mobile_button_1_asset_enabled",
39
+ condition_value: true,
40
+ });
41
+
42
+ expect(
43
+ createConditionalField("mobile_button_1_asset_enabled", true, null)
44
+ ).toEqual({
45
+ key: "mobile_button_1_asset_enabled",
46
+ condition_value: true,
47
+ });
48
+ });
49
+ });
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Appends new conditional_fields from conditions array
3
+ * to config.conditional_fields
4
+ * if there are more than 1 condition, sets rules to "all_conditions"
5
+ *
6
+ * @param {Array<{key: string, condition_value: unknown}>} conditions
7
+ * @returns {(config: object) => object}
8
+ */
9
+ const withConditional = (conditions) => (config) => {
10
+ const conditional_fields = [
11
+ ...(config.conditional_fields || []),
12
+ ...conditions,
13
+ ];
14
+
15
+ const next = { ...config, conditional_fields };
16
+
17
+ if (conditional_fields.length > 1) {
18
+ return { ...next, rules: "all_conditions" };
19
+ }
20
+
21
+ return next;
22
+ };
23
+
24
+ /**
25
+ * Builds key for conditional fields prepending category, e.g. "styles/mobile_buttons_container_position"
26
+ *
27
+ * @param {string} key
28
+ * @param {string} category
29
+ * @returns {string}
30
+ */
31
+ function getConditionalKey(key, category) {
32
+ return `${category}/${key}`;
33
+ }
34
+
35
+ /**
36
+ * Returns a conditional field object for manifest visibility rules.
37
+ *
38
+ * @param {string} key
39
+ * @param {unknown} condition_value
40
+ * @param {string|null} [category="styles"] Pass `null` to use the key as-is.
41
+ * @returns {{key: string, condition_value: unknown}}
42
+ */
43
+ function createConditionalField(key, condition_value, category = "styles") {
44
+ return {
45
+ key: category ? getConditionalKey(key, category) : key,
46
+ condition_value,
47
+ };
48
+ }
49
+
50
+ module.exports = {
51
+ withConditional,
52
+ getConditionalKey,
53
+ createConditionalField,
54
+ };
@@ -8,6 +8,9 @@ const {
8
8
 
9
9
  const { tvActionButtonsContainer } = require("./tvAction/container");
10
10
  const { tvActionButton } = require("./tvAction/button");
11
+ const { mobileActionButtonsContainer } = require("./mobileAction/container");
12
+ const { mobileActionButton } = require("./mobileAction/button");
13
+ const { buildMobileActionButtonGroups } = require("./mobileAction/groups");
11
14
  const { compact } = require("./_internals");
12
15
 
13
16
  const { spacingKey, absolutePositionElement } = require("./containers");
@@ -43,6 +46,9 @@ module.exports = {
43
46
  tvMenuLabel,
44
47
  tvActionButtonsContainer,
45
48
  tvActionButton,
49
+ mobileActionButtonsContainer,
50
+ mobileActionButton,
51
+ buildMobileActionButtonGroups,
46
52
  mobileCellLabel,
47
53
  tvCellLabel,
48
54
  tvBadges,