@applicaster/zapp-react-native-utils 14.0.0-rc.9 → 15.0.0-alpha.1089439460
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.
- package/actionsExecutor/ActionExecutorContext.tsx +60 -84
- package/actionsExecutor/ScreenActions.ts +164 -0
- package/actionsExecutor/StorageActions.ts +110 -0
- package/actionsExecutor/feedDecorator.ts +171 -0
- package/actionsExecutor/screenResolver.ts +11 -0
- package/analyticsUtils/AnalyticPlayerListener.ts +5 -2
- package/analyticsUtils/AnalyticsEvents/helper.ts +81 -0
- package/analyticsUtils/AnalyticsEvents/sendHeaderClickEvent.ts +1 -1
- package/analyticsUtils/AnalyticsEvents/sendMenuClickEvent.ts +2 -1
- package/analyticsUtils/AnalyticsEvents/sendOnClickEvent.ts +14 -4
- package/analyticsUtils/__tests__/analyticsUtils.test.js +3 -0
- package/analyticsUtils/events.ts +8 -0
- package/analyticsUtils/index.tsx +3 -4
- package/analyticsUtils/manager.ts +1 -1
- package/analyticsUtils/playerAnalyticsTracker.ts +2 -1
- package/appUtils/HooksManager/Hook.ts +4 -4
- package/appUtils/HooksManager/index.ts +11 -1
- package/appUtils/accessibilityManager/const.ts +13 -0
- package/appUtils/accessibilityManager/hooks.ts +35 -1
- package/appUtils/accessibilityManager/index.ts +154 -30
- package/appUtils/accessibilityManager/utils.ts +24 -0
- package/appUtils/contextKeysManager/contextResolver.ts +42 -1
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +8 -0
- package/appUtils/focusManager/__tests__/focusManager.test.js +1 -1
- package/appUtils/focusManager/events.ts +2 -0
- package/appUtils/focusManager/index.ios.ts +27 -0
- package/appUtils/focusManager/index.ts +86 -11
- package/appUtils/focusManager/treeDataStructure/Tree/index.js +1 -1
- package/appUtils/focusManagerAux/utils/index.ts +112 -3
- package/appUtils/focusManagerAux/utils/utils.ios.ts +35 -0
- package/appUtils/platform/platformUtils.ts +33 -3
- package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +0 -15
- package/appUtils/playerManager/conts.ts +21 -0
- package/appUtils/playerManager/player.ts +4 -0
- package/appUtils/playerManager/useChapterMarker.tsx +0 -1
- package/appUtils/playerManager/usePlayerControllerSetup.tsx +16 -0
- package/appUtils/playerManager/usePlayerState.tsx +14 -2
- package/arrayUtils/__tests__/allTruthy.test.ts +24 -0
- package/arrayUtils/__tests__/anyThruthy.test.ts +24 -0
- package/arrayUtils/__tests__/isEmptyArray.test.ts +63 -0
- package/arrayUtils/__tests__/isFilledArray.test.ts +1 -1
- package/arrayUtils/index.ts +13 -3
- package/audioPlayerUtils/__tests__/getArtworkImage.test.ts +144 -0
- package/audioPlayerUtils/__tests__/getBackgroundImage.test.ts +72 -0
- package/audioPlayerUtils/__tests__/getImageFromEntry.test.ts +110 -0
- package/audioPlayerUtils/assets/index.ts +2 -0
- package/audioPlayerUtils/index.ts +242 -0
- package/componentsUtils/__tests__/isTabsScreen.test.ts +38 -0
- package/componentsUtils/index.ts +4 -1
- package/conf/player/__tests__/selectors.test.ts +34 -0
- package/conf/player/selectors.ts +10 -0
- package/configurationUtils/__tests__/configurationUtils.test.js +0 -31
- package/configurationUtils/__tests__/getMediaItems.test.ts +65 -0
- package/configurationUtils/__tests__/imageSrcFromMediaItem.test.ts +34 -0
- package/configurationUtils/__tests__/manifestKeyParser.test.ts +546 -0
- package/configurationUtils/index.ts +64 -35
- package/configurationUtils/manifestKeyParser.ts +57 -32
- package/focusManager/FocusManager.ts +104 -20
- package/focusManager/Tree.ts +25 -21
- package/focusManager/__tests__/FocusManager.test.ts +50 -8
- package/focusManager/aux/index.ts +98 -0
- package/focusManager/utils.ts +12 -6
- package/index.d.ts +1 -10
- package/manifestUtils/_internals/getDefaultConfiguration.js +28 -0
- package/manifestUtils/{_internals.js → _internals/index.js} +2 -25
- package/manifestUtils/createConfig.js +4 -1
- package/manifestUtils/defaultManifestConfigurations/player.js +2348 -1103
- package/manifestUtils/index.js +4 -0
- package/manifestUtils/keys.js +12 -0
- package/manifestUtils/progressBar/__tests__/mobileProgressBar.test.js +0 -30
- package/manifestUtils/sharedConfiguration/screenPicker/stylesFields.js +6 -0
- package/navigationUtils/__tests__/mapContentTypesToRivers.test.ts +130 -0
- package/navigationUtils/index.ts +26 -21
- package/package.json +2 -3
- package/playerUtils/PlayerTTS/PlayerTTS.ts +359 -0
- package/playerUtils/PlayerTTS/index.ts +1 -0
- package/playerUtils/__tests__/configurationUtils.test.ts +1 -65
- package/playerUtils/__tests__/getPlayerActionButtons.test.ts +54 -0
- package/playerUtils/_internals/__tests__/utils.test.ts +71 -0
- package/playerUtils/_internals/index.ts +1 -0
- package/playerUtils/_internals/utils.ts +31 -0
- package/playerUtils/configurationUtils.ts +0 -44
- package/playerUtils/getPlayerActionButtons.ts +17 -0
- package/playerUtils/index.ts +53 -0
- package/playerUtils/usePlayerTTS.ts +21 -0
- package/playerUtils/useValidatePlayerConfig.tsx +22 -19
- package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +15 -14
- package/reactHooks/cell-click/__tests__/index.test.js +3 -0
- package/reactHooks/cell-click/index.ts +8 -1
- package/reactHooks/debugging/__tests__/index.test.js +0 -1
- package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +47 -90
- package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +71 -31
- package/reactHooks/feed/index.ts +2 -0
- package/reactHooks/feed/useBatchLoading.ts +23 -10
- package/reactHooks/feed/useFeedLoader.tsx +36 -43
- package/reactHooks/feed/useInflatedUrl.ts +23 -29
- package/reactHooks/feed/useLoadPipesDataDispatch.ts +63 -0
- package/reactHooks/feed/usePipesCacheReset.ts +6 -4
- package/reactHooks/flatList/useSequentialRenderItem.tsx +3 -3
- package/reactHooks/layout/__tests__/index.test.tsx +3 -1
- package/reactHooks/layout/index.ts +1 -1
- package/reactHooks/layout/isTablet/index.ts +12 -5
- package/reactHooks/layout/useDimensions/__tests__/useDimensions.test.ts +34 -36
- package/reactHooks/layout/useDimensions/useDimensions.ts +2 -3
- package/reactHooks/layout/useLayoutVersion.ts +5 -5
- package/reactHooks/navigation/index.ts +7 -5
- package/reactHooks/navigation/useIsScreenActive.ts +9 -5
- package/reactHooks/navigation/useRoute.ts +7 -2
- package/reactHooks/navigation/useScreenStateStore.ts +8 -0
- package/reactHooks/player/TVSeekControlller/TVSeekController.ts +27 -10
- package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +4 -0
- package/reactHooks/screen/useScreenContext.ts +1 -1
- package/reactHooks/state/__tests__/ZStoreProvider.test.tsx +2 -1
- package/reactHooks/state/index.ts +1 -1
- package/reactHooks/state/useHomeRiver.ts +4 -2
- package/reactHooks/state/useRivers.ts +7 -8
- package/riverComponetsMeasurementProvider/index.tsx +1 -1
- package/screenPickerUtils/index.ts +13 -0
- package/services/js2native.ts +1 -0
- package/storage/ScreenSingleValueProvider.ts +204 -0
- package/storage/ScreenStateMultiSelectProvider.ts +293 -0
- package/storage/StorageMultiSelectProvider.ts +192 -0
- package/storage/StorageSingleSelectProvider.ts +108 -0
- package/testUtils/index.tsx +7 -8
- package/time/BackgroundTimer.ts +6 -4
- package/utils/__tests__/endsWith.test.ts +30 -0
- package/utils/__tests__/find.test.ts +36 -0
- package/utils/__tests__/mapAccum.test.ts +73 -0
- package/utils/__tests__/omit.test.ts +19 -0
- package/utils/__tests__/path.test.ts +33 -0
- package/utils/__tests__/pathOr.test.ts +37 -0
- package/utils/__tests__/startsWith.test.ts +30 -0
- package/utils/__tests__/take.test.ts +40 -0
- package/utils/endsWith.ts +9 -0
- package/utils/find.ts +3 -0
- package/utils/index.ts +40 -1
- package/utils/mapAccum.ts +23 -0
- package/utils/omit.ts +5 -0
- package/utils/path.ts +5 -0
- package/utils/pathOr.ts +5 -0
- package/utils/startsWith.ts +9 -0
- package/utils/take.ts +5 -0
- package/zappFrameworkUtils/HookCallback/callbackNavigationAction.ts +231 -0
- package/zappFrameworkUtils/HookCallback/hookCallbackManifestExtensions.config.js +76 -0
- package/zappFrameworkUtils/HookCallback/useCallbackActions.ts +19 -0
- package/zappFrameworkUtils/loginPluginUtils.ts +1 -1
- package/playerUtils/configurationGenerator.ts +0 -2572
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
import { getAllSpecificStyles } from "../manifestKeyParser";
|
|
2
|
+
|
|
3
|
+
jest.mock("@applicaster/zapp-react-native-utils/reactUtils", () => ({
|
|
4
|
+
platformSelect: jest.fn((platforms) => platforms.samsung_tv), // Default to samsung for tests
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
jest.mock("@applicaster/zapp-react-native-utils/stringUtils", () => ({
|
|
8
|
+
toCamelCase: jest.fn((str: string) => {
|
|
9
|
+
// Simple camelCase implementation for testing
|
|
10
|
+
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
11
|
+
}),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
describe("getAllSpecificStyles", () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
jest.clearAllMocks();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("Basic functionality", () => {
|
|
20
|
+
it("should throw error if outStyles is not provided", () => {
|
|
21
|
+
expect(() => {
|
|
22
|
+
getAllSpecificStyles({
|
|
23
|
+
componentName: "button",
|
|
24
|
+
subComponentName: "",
|
|
25
|
+
configuration: {},
|
|
26
|
+
outStyles: null as any,
|
|
27
|
+
});
|
|
28
|
+
}).toThrow("outStyles is required");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should throw error if componentName is not provided", () => {
|
|
32
|
+
expect(() => {
|
|
33
|
+
getAllSpecificStyles({
|
|
34
|
+
componentName: "",
|
|
35
|
+
subComponentName: "",
|
|
36
|
+
configuration: {},
|
|
37
|
+
outStyles: {},
|
|
38
|
+
});
|
|
39
|
+
}).toThrow("componentName is required");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should initialize default key if not present", () => {
|
|
43
|
+
const outStyles = {};
|
|
44
|
+
|
|
45
|
+
getAllSpecificStyles({
|
|
46
|
+
componentName: "button",
|
|
47
|
+
subComponentName: "",
|
|
48
|
+
configuration: {},
|
|
49
|
+
outStyles,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(outStyles).toHaveProperty("default");
|
|
53
|
+
expect(outStyles["default"]).toEqual({});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should handle empty configuration", () => {
|
|
57
|
+
const outStyles = { default: {} };
|
|
58
|
+
|
|
59
|
+
getAllSpecificStyles({
|
|
60
|
+
componentName: "button",
|
|
61
|
+
subComponentName: "",
|
|
62
|
+
configuration: {},
|
|
63
|
+
outStyles,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(outStyles).toEqual({ default: {} });
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("Style name parsing", () => {
|
|
71
|
+
it("should parse basic style without state or platform", () => {
|
|
72
|
+
const outStyles = {};
|
|
73
|
+
|
|
74
|
+
const configuration = {
|
|
75
|
+
button_style_background_color: "#FF0000",
|
|
76
|
+
button_style_border_width: 2,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
getAllSpecificStyles({
|
|
80
|
+
componentName: "button",
|
|
81
|
+
subComponentName: "",
|
|
82
|
+
configuration,
|
|
83
|
+
outStyles,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(outStyles["default"]).toEqual({
|
|
87
|
+
backgroundColor: "#FF0000",
|
|
88
|
+
borderWidth: 2,
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should handle subComponentName correctly", () => {
|
|
93
|
+
const outStyles = {};
|
|
94
|
+
|
|
95
|
+
const configuration = {
|
|
96
|
+
player_controls_style_button_background_color: "#FF0000",
|
|
97
|
+
player_controls_style_button_text_size: 16,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
getAllSpecificStyles({
|
|
101
|
+
componentName: "player_controls",
|
|
102
|
+
subComponentName: "button",
|
|
103
|
+
configuration,
|
|
104
|
+
outStyles,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(outStyles["default"]).toEqual({
|
|
108
|
+
backgroundColor: "#FF0000",
|
|
109
|
+
textSize: 16,
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("should ignore keys that do not match the prefix", () => {
|
|
114
|
+
const outStyles = {};
|
|
115
|
+
|
|
116
|
+
const configuration = {
|
|
117
|
+
button_style_background_color: "#FF0000",
|
|
118
|
+
other_component_style_text_color: "#00FF00", // Should be ignored
|
|
119
|
+
random_key: "value", // Should be ignored
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
getAllSpecificStyles({
|
|
123
|
+
componentName: "button",
|
|
124
|
+
subComponentName: "",
|
|
125
|
+
configuration,
|
|
126
|
+
outStyles,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(outStyles["default"]).toEqual({
|
|
130
|
+
backgroundColor: "#FF0000",
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe("State handling", () => {
|
|
136
|
+
it("should handle pressed state", () => {
|
|
137
|
+
const outStyles = {};
|
|
138
|
+
|
|
139
|
+
const configuration = {
|
|
140
|
+
button_style_background_color: "#FF0000",
|
|
141
|
+
button_style_pressed_background_color: "#00FF00",
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
getAllSpecificStyles({
|
|
145
|
+
componentName: "button",
|
|
146
|
+
subComponentName: "",
|
|
147
|
+
configuration,
|
|
148
|
+
outStyles,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
expect(outStyles["default"]).toEqual({
|
|
152
|
+
backgroundColor: "#FF0000",
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(outStyles["pressed"]).toEqual({
|
|
156
|
+
backgroundColor: "#00FF00",
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("should handle focused state", () => {
|
|
161
|
+
const outStyles = {};
|
|
162
|
+
|
|
163
|
+
const configuration = {
|
|
164
|
+
button_style_focused_border_width: 3,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
getAllSpecificStyles({
|
|
168
|
+
componentName: "button",
|
|
169
|
+
subComponentName: "",
|
|
170
|
+
configuration,
|
|
171
|
+
outStyles,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
expect(outStyles["focused"]).toEqual({
|
|
175
|
+
borderWidth: 3,
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("should handle selected state", () => {
|
|
180
|
+
const outStyles = {};
|
|
181
|
+
|
|
182
|
+
const configuration = {
|
|
183
|
+
button_style_selected_opacity: 0.5,
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
getAllSpecificStyles({
|
|
187
|
+
componentName: "button",
|
|
188
|
+
subComponentName: "",
|
|
189
|
+
configuration,
|
|
190
|
+
outStyles,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
expect(outStyles["selected"]).toEqual({
|
|
194
|
+
opacity: 0.5,
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should handle focused_selected state", () => {
|
|
199
|
+
const outStyles = {};
|
|
200
|
+
|
|
201
|
+
const configuration = {
|
|
202
|
+
button_style_focused_selected_scale: 1.2,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
getAllSpecificStyles({
|
|
206
|
+
componentName: "button",
|
|
207
|
+
subComponentName: "",
|
|
208
|
+
configuration,
|
|
209
|
+
outStyles,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
expect(outStyles["focused_selected"]).toEqual({
|
|
213
|
+
scale: 1.2,
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("should merge default styles into other states", () => {
|
|
218
|
+
const outStyles = {};
|
|
219
|
+
|
|
220
|
+
const configuration = {
|
|
221
|
+
button_style_background_color: "#FF0000",
|
|
222
|
+
button_style_border_width: 1,
|
|
223
|
+
button_style_opacity: 1,
|
|
224
|
+
button_style_pressed_background_color: "#00FF00",
|
|
225
|
+
button_style_focused_opacity: 0.8,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
getAllSpecificStyles({
|
|
229
|
+
componentName: "button",
|
|
230
|
+
subComponentName: "",
|
|
231
|
+
configuration,
|
|
232
|
+
outStyles,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
expect(outStyles["default"]).toEqual({
|
|
236
|
+
backgroundColor: "#FF0000",
|
|
237
|
+
borderWidth: 1,
|
|
238
|
+
opacity: 1,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Pressed should have default styles merged with its specific override
|
|
242
|
+
expect(outStyles["pressed"]).toEqual({
|
|
243
|
+
backgroundColor: "#00FF00", // Override
|
|
244
|
+
borderWidth: 1, // From default
|
|
245
|
+
opacity: 1, // From default
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Focused should have default styles merged with its specific override
|
|
249
|
+
expect(outStyles["focused"]).toEqual({
|
|
250
|
+
backgroundColor: "#FF0000", // From default
|
|
251
|
+
borderWidth: 1, // From default
|
|
252
|
+
opacity: 0.8, // Override
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("should preserve existing state values when merging", () => {
|
|
257
|
+
const outStyles = {
|
|
258
|
+
pressed: { existingKey: "existingValue" },
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const configuration = {
|
|
262
|
+
button_style_background_color: "#FF0000",
|
|
263
|
+
button_style_pressed_text_color: "#FFFFFF",
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
getAllSpecificStyles({
|
|
267
|
+
componentName: "button",
|
|
268
|
+
subComponentName: "",
|
|
269
|
+
configuration,
|
|
270
|
+
outStyles,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
expect(outStyles["pressed"]).toEqual({
|
|
274
|
+
existingKey: "existingValue", // Should be preserved
|
|
275
|
+
backgroundColor: "#FF0000", // From default
|
|
276
|
+
textColor: "#FFFFFF", // New pressed style
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe("Platform handling", () => {
|
|
282
|
+
it("should include styles for current platform (samsung)", () => {
|
|
283
|
+
const outStyles = {};
|
|
284
|
+
|
|
285
|
+
const configuration = {
|
|
286
|
+
button_style_samsung_background_color: "#FF0000",
|
|
287
|
+
button_style_background_color: "#00FF00",
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
getAllSpecificStyles({
|
|
291
|
+
componentName: "button",
|
|
292
|
+
subComponentName: "",
|
|
293
|
+
configuration,
|
|
294
|
+
outStyles,
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
expect(outStyles["default"]).toEqual({
|
|
298
|
+
backgroundColor: "#FF0000", // Samsung-specific should be included
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("should skip styles for other platforms", () => {
|
|
303
|
+
const outStyles = {};
|
|
304
|
+
|
|
305
|
+
const configuration = {
|
|
306
|
+
button_style_ios_background_color: "#FF0000",
|
|
307
|
+
button_style_android_text_color: "#00FF00",
|
|
308
|
+
button_style_tvos_border_width: 2,
|
|
309
|
+
button_style_lg_opacity: 0.5,
|
|
310
|
+
button_style_background_color: "#FFFFFF",
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
getAllSpecificStyles({
|
|
314
|
+
componentName: "button",
|
|
315
|
+
subComponentName: "",
|
|
316
|
+
configuration,
|
|
317
|
+
outStyles,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
expect(outStyles["default"]).toEqual({
|
|
321
|
+
backgroundColor: "#FFFFFF", // Only non-platform specific
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("should handle platform in middle of style name", () => {
|
|
326
|
+
const outStyles = {};
|
|
327
|
+
|
|
328
|
+
const configuration = {
|
|
329
|
+
button_style_text_samsung_color: "#FF0000",
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
getAllSpecificStyles({
|
|
333
|
+
componentName: "button",
|
|
334
|
+
subComponentName: "text",
|
|
335
|
+
configuration,
|
|
336
|
+
outStyles,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
expect(outStyles["default"]).toEqual({
|
|
340
|
+
color: "#FF0000",
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it("should handle platform-specific styles with states", () => {
|
|
345
|
+
const outStyles = {};
|
|
346
|
+
|
|
347
|
+
const configuration = {
|
|
348
|
+
button_style_samsung_pressed_background_color: "#FF0000",
|
|
349
|
+
button_style_ios_pressed_background_color: "#00FF00", // Should be ignored
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
getAllSpecificStyles({
|
|
353
|
+
componentName: "button",
|
|
354
|
+
subComponentName: "",
|
|
355
|
+
configuration,
|
|
356
|
+
outStyles,
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
expect(outStyles["pressed"]).toEqual({
|
|
360
|
+
backgroundColor: "#FF0000", // Only samsung style
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it("should work correctly when platform is Samsung", () => {
|
|
365
|
+
const outStyles = {};
|
|
366
|
+
|
|
367
|
+
const configuration = {
|
|
368
|
+
button_style_ios_background_color: "#FF0000",
|
|
369
|
+
button_style_samsung_background_color: "#00FF00",
|
|
370
|
+
button_style_background_color: "#FFFFFF",
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
getAllSpecificStyles({
|
|
374
|
+
componentName: "button",
|
|
375
|
+
subComponentName: "",
|
|
376
|
+
configuration,
|
|
377
|
+
outStyles,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
expect(outStyles["default"]).toEqual({
|
|
381
|
+
backgroundColor: "#00FF00", // Samsung-specific should win
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
describe("Complex scenarios", () => {
|
|
387
|
+
it("should handle multiple states and platforms correctly", () => {
|
|
388
|
+
const outStyles = {};
|
|
389
|
+
|
|
390
|
+
const configuration = {
|
|
391
|
+
// Default styles
|
|
392
|
+
button_style_background_color: "#FFFFFF",
|
|
393
|
+
button_style_text_color: "#000000",
|
|
394
|
+
button_style_border_width: 1,
|
|
395
|
+
|
|
396
|
+
// Platform-specific default
|
|
397
|
+
button_style_samsung_padding: 10,
|
|
398
|
+
|
|
399
|
+
// State-specific
|
|
400
|
+
button_style_pressed_background_color: "#EEEEEE",
|
|
401
|
+
button_style_focused_scale: 1.1,
|
|
402
|
+
|
|
403
|
+
// Platform + state specific
|
|
404
|
+
button_style_samsung_pressed_opacity: 0.8,
|
|
405
|
+
button_style_ios_pressed_opacity: 0.6, // Should be ignored
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
getAllSpecificStyles({
|
|
409
|
+
componentName: "button",
|
|
410
|
+
subComponentName: "",
|
|
411
|
+
configuration,
|
|
412
|
+
outStyles,
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
expect(outStyles["default"]).toEqual({
|
|
416
|
+
backgroundColor: "#FFFFFF",
|
|
417
|
+
textColor: "#000000",
|
|
418
|
+
borderWidth: 1,
|
|
419
|
+
padding: 10,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
expect(outStyles["pressed"]).toEqual({
|
|
423
|
+
backgroundColor: "#EEEEEE",
|
|
424
|
+
textColor: "#000000",
|
|
425
|
+
borderWidth: 1,
|
|
426
|
+
padding: 10,
|
|
427
|
+
opacity: 0.8,
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
expect(outStyles["focused"]).toEqual({
|
|
431
|
+
backgroundColor: "#FFFFFF",
|
|
432
|
+
textColor: "#000000",
|
|
433
|
+
borderWidth: 1,
|
|
434
|
+
padding: 10,
|
|
435
|
+
scale: 1.1,
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it("should handle edge case keys correctly", () => {
|
|
440
|
+
const outStyles = {};
|
|
441
|
+
|
|
442
|
+
const configuration = {
|
|
443
|
+
button_style_: "shouldBeIgnored", // Empty style name
|
|
444
|
+
button_style_samsung_: "shouldBeIgnored", // Platform but no style
|
|
445
|
+
button_style__pressed: "shouldBeIgnored", // No style name before state
|
|
446
|
+
button_style_valid_key: "included",
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
getAllSpecificStyles({
|
|
450
|
+
componentName: "button",
|
|
451
|
+
subComponentName: "",
|
|
452
|
+
configuration,
|
|
453
|
+
outStyles,
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
expect(outStyles["default"]).toEqual({
|
|
457
|
+
validKey: "included",
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it("should handle all state suffixes in priority order", () => {
|
|
462
|
+
const outStyles = {};
|
|
463
|
+
|
|
464
|
+
const configuration = {
|
|
465
|
+
// Test that focused_selected is recognized before focused
|
|
466
|
+
button_style_focused_selected_test: "focused_selected_value",
|
|
467
|
+
button_style_focused_another: "focused_value",
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
getAllSpecificStyles({
|
|
471
|
+
componentName: "button",
|
|
472
|
+
subComponentName: "",
|
|
473
|
+
configuration,
|
|
474
|
+
outStyles,
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
expect(outStyles["focused_selected"]).toEqual({
|
|
478
|
+
test: "focused_selected_value",
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
expect(outStyles["focused"]).toEqual({
|
|
482
|
+
another: "focused_value",
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
describe("Immutability", () => {
|
|
488
|
+
it("should not mutate configuration object", () => {
|
|
489
|
+
const configuration = Object.freeze({
|
|
490
|
+
button_style_background_color: "#FF0000",
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
const outStyles = {};
|
|
494
|
+
|
|
495
|
+
expect(() => {
|
|
496
|
+
getAllSpecificStyles({
|
|
497
|
+
componentName: "button",
|
|
498
|
+
subComponentName: "",
|
|
499
|
+
configuration,
|
|
500
|
+
outStyles,
|
|
501
|
+
});
|
|
502
|
+
}).not.toThrow();
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it("platform pressed overrides generic pressed for same key", () => {
|
|
506
|
+
const outStyles = {};
|
|
507
|
+
|
|
508
|
+
const configuration = {
|
|
509
|
+
button_style_pressed_background_color: "#111111", // generic
|
|
510
|
+
button_style_samsung_pressed_background_color: "#222222", // platform
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
getAllSpecificStyles({
|
|
514
|
+
componentName: "button",
|
|
515
|
+
subComponentName: "",
|
|
516
|
+
configuration,
|
|
517
|
+
outStyles,
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
expect(outStyles["pressed"]["backgroundColor"]).toBe("#222222");
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
it("should handle frozen outStyles properties", () => {
|
|
524
|
+
const outStyles = {
|
|
525
|
+
default: Object.freeze({ existingProp: "value" }),
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
const configuration = {
|
|
529
|
+
button_style_background_color: "#FF0000",
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
getAllSpecificStyles({
|
|
533
|
+
componentName: "button",
|
|
534
|
+
subComponentName: "",
|
|
535
|
+
configuration,
|
|
536
|
+
outStyles,
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
// Should create new object instead of mutating frozen one
|
|
540
|
+
expect(outStyles["default"]).toEqual({
|
|
541
|
+
existingProp: "value",
|
|
542
|
+
backgroundColor: "#FF0000",
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
});
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/// <reference types="@applicaster/applicaster-types" />
|
|
2
2
|
import * as R from "ramda";
|
|
3
3
|
import { Platform } from "react-native";
|
|
4
|
+
import {
|
|
5
|
+
isFilledArray,
|
|
6
|
+
isEmptyArray,
|
|
7
|
+
} from "@applicaster/zapp-react-native-utils/arrayUtils";
|
|
8
|
+
import { isEmpty } from "@applicaster/zapp-react-native-utils/utils";
|
|
9
|
+
|
|
4
10
|
import { applyTransform } from "../transform";
|
|
5
11
|
import { ManifestField } from "../types";
|
|
6
12
|
import { isNilOrEmpty } from "../reactUtils/helpers";
|
|
@@ -122,54 +128,77 @@ export function flattenFields(
|
|
|
122
128
|
}
|
|
123
129
|
|
|
124
130
|
/**
|
|
125
|
-
* Retrieves the
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* @
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
* Retrieves all media items from the entry's media_group that are of type "image" or "thumbnail".
|
|
132
|
+
*
|
|
133
|
+
* @param {ZappEntry} entry - The entry object containing the media_group array.
|
|
134
|
+
* @returns {Option<ZappMediaItem[]>} An array of media items of type "image" or "thumbnail", or undefined if no media_group is present.
|
|
135
|
+
*/
|
|
136
|
+
export function getMediaItems(entry: ZappEntry): Option<ZappMediaItem[]> {
|
|
137
|
+
const mediaGroup = entry?.media_group || [];
|
|
138
|
+
|
|
139
|
+
if (isEmptyArray(mediaGroup)) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const mediaItems = mediaGroup
|
|
144
|
+
.filter((group) => ["image", "thumbnail"].includes(group.type))
|
|
145
|
+
.flatMap((group) =>
|
|
146
|
+
Array.isArray(group.media_item) ? group.media_item : [group.media_item]
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (isFilledArray(mediaItems)) {
|
|
150
|
+
return mediaItems;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Retrieves the "src" value from a media item in the entry's media group,
|
|
158
|
+
* based on a provided key, with fallback logic.
|
|
159
|
+
*
|
|
160
|
+
* Fallback order:
|
|
161
|
+
* 1. Attempts to find a media item with the specified key (or "image_base" if none provided).
|
|
162
|
+
* 2. If not found, attempts to find a media item with the key "image_base".
|
|
163
|
+
* 3. If still not found, falls back to the first available media item.
|
|
164
|
+
*
|
|
165
|
+
* Special handling: If the resolved item's "src" is an empty string, returns undefined,
|
|
166
|
+
* since empty URIs are invalid in some platforms (e.g., React Native).
|
|
167
|
+
*
|
|
168
|
+
* @param {ZappEntry} entry - The entry object containing a media group.
|
|
169
|
+
* @param {string[] | unknown} arg - A single-element array containing the key to look up, or any unknown value.
|
|
170
|
+
* @returns {?string} The "src" URI from the matched media item, or undefined if not found or empty.
|
|
132
171
|
*/
|
|
133
172
|
export function imageSrcFromMediaItem(
|
|
134
173
|
entry: ZappEntry,
|
|
135
174
|
arg: string[] | unknown
|
|
136
|
-
) {
|
|
175
|
+
): Option<string> {
|
|
137
176
|
const args: unknown = R.unless(Array.isArray, Array)(arg || []);
|
|
138
177
|
const imageKey: string = args?.[0] || "image_base"; // always a single key in this function
|
|
139
|
-
const mediaGroup = R.path<ZappMediaGroup[]>(["media_group"], entry);
|
|
140
178
|
|
|
141
|
-
|
|
179
|
+
const mediaItems = getMediaItems(entry);
|
|
180
|
+
|
|
181
|
+
if (!mediaItems) {
|
|
142
182
|
return undefined;
|
|
143
183
|
}
|
|
144
184
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
R.propEq("type", "thumbnail")
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
const pickMediaItemProp = R.prop<ZappMediaItem>("media_item");
|
|
185
|
+
// Try to find the item with the given key
|
|
186
|
+
let foundItem = mediaItems.find((item) => item.key === imageKey);
|
|
151
187
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
)(mediaGroup);
|
|
188
|
+
// If not found and key was not "image_base", try to find "image_base"
|
|
189
|
+
if (!foundItem && imageKey !== "image_base") {
|
|
190
|
+
foundItem = mediaItems.find((item) => item.key === "image_base");
|
|
191
|
+
}
|
|
157
192
|
|
|
158
|
-
|
|
159
|
-
|
|
193
|
+
// If still not found, default to first item
|
|
194
|
+
if (!foundItem) {
|
|
195
|
+
foundItem = mediaItems[0];
|
|
160
196
|
}
|
|
161
197
|
|
|
162
|
-
const src =
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
R.find(R.propEq("key", imageKey))
|
|
167
|
-
)(mediaItems);
|
|
168
|
-
|
|
169
|
-
// Special case for react native - uri cannot be an empty string (yellow warning).
|
|
170
|
-
// R.isEmpty is tailored specifically for checks like these,
|
|
171
|
-
// it returns false for undefined values.
|
|
172
|
-
return R.isEmpty(src) ? undefined : src;
|
|
198
|
+
const src = foundItem?.src;
|
|
199
|
+
|
|
200
|
+
// React Native quirk: empty string is invalid for URIs
|
|
201
|
+
return isEmpty(src) ? undefined : src;
|
|
173
202
|
}
|
|
174
203
|
|
|
175
204
|
/**
|
|
@@ -370,7 +399,7 @@ export const populateConfigurationValues =
|
|
|
370
399
|
flattenAndPopulateFields(fields, configuration, skipDefaults)
|
|
371
400
|
);
|
|
372
401
|
|
|
373
|
-
export const
|
|
402
|
+
export const getAccessibilityProps = (item: ZappEntry) => ({
|
|
374
403
|
accessible: item?.extensions?.accessibility,
|
|
375
404
|
accessibilityLabel: item?.extensions?.accessibility?.label || item?.title,
|
|
376
405
|
accessibilityHint: item?.extensions?.accessibility?.hint,
|