@applicaster/zapp-react-native-utils 14.0.0-alpha.1661204539 → 14.0.0-alpha.1740013076
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 +11 -6
- package/actionsExecutor/ScreenActions.ts +91 -17
- package/actionsExecutor/feedDecorator.ts +171 -0
- package/actionsExecutor/screenResolver.ts +6 -3
- 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 +30 -3
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +5 -0
- package/appUtils/focusManager/__tests__/focusManager.test.js +1 -1
- package/appUtils/focusManager/index.ios.ts +10 -0
- package/appUtils/focusManager/index.ts +82 -11
- package/appUtils/focusManager/treeDataStructure/Tree/index.js +1 -1
- package/appUtils/focusManagerAux/utils/index.ts +106 -3
- package/appUtils/platform/platformUtils.ts +31 -1
- package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +0 -15
- package/appUtils/playerManager/useChapterMarker.tsx +0 -1
- package/appUtils/playerManager/usePlayerControllerSetup.tsx +16 -0
- package/arrayUtils/__tests__/isEmptyArray.test.ts +63 -0
- package/arrayUtils/__tests__/isFilledArray.test.ts +1 -1
- package/arrayUtils/index.ts +8 -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 +26 -16
- package/focusManager/Tree.ts +25 -21
- package/focusManager/__tests__/FocusManager.test.ts +50 -8
- 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 +1253 -200
- package/manifestUtils/progressBar/__tests__/mobileProgressBar.test.js +0 -30
- package/manifestUtils/sharedConfiguration/screenPicker/stylesFields.js +1 -2
- package/navigationUtils/__tests__/mapContentTypesToRivers.test.ts +130 -0
- package/navigationUtils/index.ts +7 -5
- 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 +59 -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 +3 -0
- 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 +17 -10
- package/reactHooks/feed/useFeedLoader.tsx +39 -44
- package/reactHooks/feed/useLoadPipesDataDispatch.ts +63 -0
- package/reactHooks/feed/usePipesCacheReset.ts +3 -3
- package/reactHooks/flatList/useSequentialRenderItem.tsx +3 -3
- package/reactHooks/layout/__tests__/index.test.tsx +3 -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/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 +7 -0
- package/services/js2native.ts +1 -0
- package/storage/ScreenSingleValueProvider.ts +138 -26
- package/storage/ScreenStateMultiSelectProvider.ts +210 -36
- package/testUtils/index.tsx +7 -8
- package/time/BackgroundTimer.ts +6 -4
- package/utils/__tests__/find.test.ts +36 -0
- package/utils/__tests__/pathOr.test.ts +37 -0
- package/utils/__tests__/startsWith.test.ts +30 -0
- package/utils/find.ts +3 -0
- package/utils/index.ts +24 -1
- package/utils/pathOr.ts +5 -0
- package/utils/startsWith.ts +9 -0
- package/playerUtils/configurationGenerator.ts +0 -2572
|
@@ -1,14 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
isNil,
|
|
3
|
+
last,
|
|
4
|
+
startsWith,
|
|
5
|
+
find,
|
|
6
|
+
pathOr,
|
|
7
|
+
} from "@applicaster/zapp-react-native-utils/utils";
|
|
8
|
+
|
|
3
9
|
import {
|
|
4
10
|
QUICK_BRICK_CONTENT,
|
|
5
11
|
QUICK_BRICK_NAVBAR,
|
|
6
12
|
} from "@applicaster/quick-brick-core/const";
|
|
7
13
|
|
|
14
|
+
import {
|
|
15
|
+
getFocusableId,
|
|
16
|
+
SCREEN_PICKER_CONTAINER,
|
|
17
|
+
} from "@applicaster/zapp-react-native-utils/screenPickerUtils";
|
|
18
|
+
|
|
8
19
|
// run check each 300 ms
|
|
9
20
|
// run this check too often could lead to performance penalty on low-end devices
|
|
10
21
|
const HOW_OFTEN_TO_CHECK_CONDITION = 300; // ms
|
|
11
22
|
|
|
23
|
+
const isTopMenu = (node) => startsWith(QUICK_BRICK_NAVBAR, node?.id);
|
|
24
|
+
const isContent = (node) => startsWith(QUICK_BRICK_CONTENT, node?.id);
|
|
25
|
+
const isRoot = (node) => node?.id === "root";
|
|
26
|
+
|
|
27
|
+
const isScrenPicker = (node) => startsWith(SCREEN_PICKER_CONTAINER, node?.id);
|
|
28
|
+
|
|
12
29
|
type Props = {
|
|
13
30
|
maxTimeout: number;
|
|
14
31
|
conditionFn: () => boolean;
|
|
@@ -49,7 +66,7 @@ export const waitForActiveScreen = (currentRoute: string, focusableTree) => {
|
|
|
49
66
|
|
|
50
67
|
const route = find((route) => route.id === currentRoute, routes);
|
|
51
68
|
|
|
52
|
-
return
|
|
69
|
+
return !isNil(route);
|
|
53
70
|
};
|
|
54
71
|
|
|
55
72
|
return waitUntil({
|
|
@@ -99,3 +116,89 @@ export const waitForContent = (focusableTree) => {
|
|
|
99
116
|
conditionFn: contentHasAnyChildren,
|
|
100
117
|
});
|
|
101
118
|
};
|
|
119
|
+
|
|
120
|
+
export const findSelectedTabId = (item: ZappEntry): string => {
|
|
121
|
+
const selectedTabId = getFocusableId(item.id);
|
|
122
|
+
|
|
123
|
+
return selectedTabId;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const findSelectedMenuId = (focusableTree) => {
|
|
127
|
+
// Set focus with back button context
|
|
128
|
+
const navbar = getNavbarNode(focusableTree);
|
|
129
|
+
|
|
130
|
+
const selectedMenuItemId = find(
|
|
131
|
+
(child) => child.component.props.selected,
|
|
132
|
+
navbar.children
|
|
133
|
+
)?.id;
|
|
134
|
+
|
|
135
|
+
return selectedMenuItemId;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export const isTabsScreenContentFocused = (node) => {
|
|
139
|
+
if (isRoot(node)) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (isTopMenu(node)) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (isContent(node)) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (isScrenPicker(node)) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return isTabsScreenContentFocused(node.parent);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export const isCurrentFocusOnMenu = (node) => {
|
|
159
|
+
if (isRoot(node)) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (isTopMenu(node)) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (isContent(node)) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return isCurrentFocusOnMenu(node.parent);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export const isCurrentFocusOnContent = (node) => {
|
|
175
|
+
if (isRoot(node)) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (isTopMenu(node)) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (isContent(node)) {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return isCurrentFocusOnContent(node.parent);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export const isCurrentFocusOn = (id, node) => {
|
|
191
|
+
if (!node) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (isRoot(node)) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (node?.id === id) {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return isCurrentFocusOn(id, node.parent);
|
|
204
|
+
};
|
|
@@ -10,6 +10,7 @@ import { PLATFORM_KEYS, PLATFORMS, ZappPlatform } from "./const";
|
|
|
10
10
|
import { createLogger, utilsLogger } from "../../logger";
|
|
11
11
|
import { getPlatform } from "../../reactUtils";
|
|
12
12
|
import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
|
|
13
|
+
import { calculateReadingTime } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager/utils";
|
|
13
14
|
|
|
14
15
|
const { log_debug } = createLogger({
|
|
15
16
|
category: "General",
|
|
@@ -212,9 +213,16 @@ export class TTSManager {
|
|
|
212
213
|
}
|
|
213
214
|
|
|
214
215
|
readText(text: string) {
|
|
216
|
+
this.ttsState$.next(true);
|
|
217
|
+
|
|
215
218
|
if (isSamsungPlatform() && window.speechSynthesis) {
|
|
216
219
|
const utterance = new SpeechSynthesisUtterance(text);
|
|
220
|
+
|
|
221
|
+
window.speechSynthesis.cancel(); // Cancel previous speech before speaking new text
|
|
217
222
|
window.speechSynthesis.speak(utterance);
|
|
223
|
+
|
|
224
|
+
// Estimate reading time and set inactive when done
|
|
225
|
+
this.scheduleTTSComplete(text);
|
|
218
226
|
}
|
|
219
227
|
|
|
220
228
|
if (isLgPlatform() && window.webOS?.service) {
|
|
@@ -225,23 +233,45 @@ export class TTSManager {
|
|
|
225
233
|
log_debug("There was a failure setting up webOS TTS service", {
|
|
226
234
|
error,
|
|
227
235
|
});
|
|
236
|
+
|
|
237
|
+
this.ttsState$.next(false);
|
|
228
238
|
},
|
|
229
239
|
onSuccess(response: any) {
|
|
230
240
|
log_debug("webOS TTS service is configured successfully", {
|
|
231
241
|
response,
|
|
232
242
|
});
|
|
243
|
+
|
|
244
|
+
// Estimate reading time and set inactive when done
|
|
245
|
+
this.scheduleTTSComplete(text);
|
|
233
246
|
},
|
|
234
247
|
parameters: {
|
|
235
248
|
text,
|
|
249
|
+
clear: true, // Clear any previous speech before speaking new text
|
|
236
250
|
},
|
|
237
251
|
});
|
|
238
252
|
} catch (error) {
|
|
239
253
|
log_debug("webOS TTS service error", { error });
|
|
254
|
+
this.ttsState$.next(false);
|
|
240
255
|
}
|
|
241
256
|
}
|
|
242
257
|
|
|
243
|
-
if (!window.VIZIO?.Chromevox)
|
|
258
|
+
if (!window.VIZIO?.Chromevox) {
|
|
259
|
+
// For platforms without TTS, estimate reading time
|
|
260
|
+
this.scheduleTTSComplete(text);
|
|
261
|
+
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
244
264
|
|
|
245
265
|
window.VIZIO.Chromevox.play(text);
|
|
266
|
+
// Estimate reading time and set inactive when done
|
|
267
|
+
this.scheduleTTSComplete(text);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private scheduleTTSComplete(text: string) {
|
|
271
|
+
const readingTime = calculateReadingTime(text);
|
|
272
|
+
|
|
273
|
+
setTimeout(() => {
|
|
274
|
+
this.ttsState$.next(false);
|
|
275
|
+
}, readingTime);
|
|
246
276
|
}
|
|
247
277
|
}
|
|
@@ -18,13 +18,6 @@ export const { log_verbose, log_debug, log_info, log_error } = createLogger({
|
|
|
18
18
|
parent: utilsLogger,
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
type ActionChapter = {
|
|
22
|
-
type: string;
|
|
23
|
-
options?: {
|
|
24
|
-
title: string;
|
|
25
|
-
};
|
|
26
|
-
};
|
|
27
|
-
|
|
28
21
|
type ChapterMarkerOriginal = {
|
|
29
22
|
id: string;
|
|
30
23
|
title: string;
|
|
@@ -33,14 +26,6 @@ type ChapterMarkerOriginal = {
|
|
|
33
26
|
actions: ActionChapter[];
|
|
34
27
|
};
|
|
35
28
|
|
|
36
|
-
export type ChapterMarkerEvent = {
|
|
37
|
-
id: string;
|
|
38
|
-
title: string;
|
|
39
|
-
start_time: number;
|
|
40
|
-
end_time: number;
|
|
41
|
-
actions: ActionChapter[];
|
|
42
|
-
};
|
|
43
|
-
|
|
44
29
|
export type TitleSummaryEvent = {
|
|
45
30
|
title: string | number;
|
|
46
31
|
summary: string | number;
|
|
@@ -5,6 +5,9 @@ import { playerManager } from "./index";
|
|
|
5
5
|
import { useValidatePlayerConfig } from "../../playerUtils/useValidatePlayerConfig";
|
|
6
6
|
import { PlayerRole } from "./conts";
|
|
7
7
|
import { isAppleTV } from "@applicaster/zapp-react-native-ui-components/Helpers/Platform";
|
|
8
|
+
import { TVSeekController } from "../../reactHooks/player/TVSeekControlller/TVSeekController";
|
|
9
|
+
import { KeyInputHandler } from "../keyInputHandler/KeyInputHandler";
|
|
10
|
+
import { isTV } from "../../reactUtils";
|
|
8
11
|
|
|
9
12
|
// TODO: Rename to ControllerType
|
|
10
13
|
export const usePlayerControllerSetup = ({
|
|
@@ -76,5 +79,18 @@ export const usePlayerControllerSetup = ({
|
|
|
76
79
|
};
|
|
77
80
|
}, [playerId, playerController]);
|
|
78
81
|
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (!isTV()) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (playerController) {
|
|
88
|
+
const seekController = new TVSeekController(playerController);
|
|
89
|
+
playerController.seekController = seekController;
|
|
90
|
+
|
|
91
|
+
return KeyInputHandler.getInstance().addListener(seekController);
|
|
92
|
+
}
|
|
93
|
+
}, [playerController]);
|
|
94
|
+
|
|
79
95
|
return playerController;
|
|
80
96
|
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { isEmptyArray } from "..";
|
|
2
|
+
|
|
3
|
+
describe("isEmptyArray", () => {
|
|
4
|
+
it("non-empty array is not empty", () => {
|
|
5
|
+
const value = [1, 2, 3];
|
|
6
|
+
|
|
7
|
+
expect(isEmptyArray(value)).toBe(false);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("empty array is empty", () => {
|
|
11
|
+
const value = [];
|
|
12
|
+
|
|
13
|
+
expect(isEmptyArray(value)).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("number is not array", () => {
|
|
17
|
+
const value = 123;
|
|
18
|
+
|
|
19
|
+
expect(isEmptyArray(value)).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("string is not array", () => {
|
|
23
|
+
const value = "vfnjdk";
|
|
24
|
+
|
|
25
|
+
expect(isEmptyArray(value)).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("empty string is not array", () => {
|
|
29
|
+
const value = "";
|
|
30
|
+
|
|
31
|
+
expect(isEmptyArray(value)).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("NaN is not array", () => {
|
|
35
|
+
const value = NaN;
|
|
36
|
+
|
|
37
|
+
expect(isEmptyArray(value)).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("object is not array", () => {
|
|
41
|
+
const value = { test: 1 };
|
|
42
|
+
|
|
43
|
+
expect(isEmptyArray(value)).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("empty object is not array", () => {
|
|
47
|
+
const value = {};
|
|
48
|
+
|
|
49
|
+
expect(isEmptyArray(value)).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("undefined is not array", () => {
|
|
53
|
+
const value = undefined;
|
|
54
|
+
|
|
55
|
+
expect(isEmptyArray(value)).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("null is not array", () => {
|
|
59
|
+
const value = null;
|
|
60
|
+
|
|
61
|
+
expect(isEmptyArray(value)).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
});
|
package/arrayUtils/index.ts
CHANGED
|
@@ -93,18 +93,23 @@ export const isIndexInRange = (index: number, length: number): boolean => {
|
|
|
93
93
|
export const makeListOfIndexes = (size: number): number[] =>
|
|
94
94
|
Array.from({ length: size }, (_, index) => index);
|
|
95
95
|
|
|
96
|
-
export const makeListOf = (value:
|
|
96
|
+
export const makeListOf = <T>(value: T, size: number): T[] => {
|
|
97
97
|
return Array(size).fill(value);
|
|
98
98
|
};
|
|
99
99
|
|
|
100
100
|
/** Checks if a value is a non-empty array */
|
|
101
101
|
export function isFilledArray(value: unknown): boolean {
|
|
102
|
-
return
|
|
102
|
+
return Array.isArray(value) && value.length > 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Checks if a value is a empty array */
|
|
106
|
+
export function isEmptyArray(value: unknown): boolean {
|
|
107
|
+
return Array.isArray(value) && value.length === 0;
|
|
103
108
|
}
|
|
104
109
|
|
|
105
110
|
// get random item from the list
|
|
106
111
|
export const sample = (xs: unknown[]): unknown => {
|
|
107
|
-
invariant(
|
|
112
|
+
invariant(Array.isArray(xs), `input value is not an array: ${xs}`);
|
|
108
113
|
invariant(isFilledArray(xs), `input array is empty: ${xs}`);
|
|
109
114
|
|
|
110
115
|
const index = Math.floor(Math.random() * xs.length);
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { getArtworkImage } from "..";
|
|
2
|
+
import { DEFAULT_IMAGE } from "../assets";
|
|
3
|
+
|
|
4
|
+
describe("getArtworkImage", () => {
|
|
5
|
+
const entryWithImage = {
|
|
6
|
+
media_group: [
|
|
7
|
+
{
|
|
8
|
+
type: "image",
|
|
9
|
+
media_item: [
|
|
10
|
+
{ key: "artwork_key", src: "image_from_entry" },
|
|
11
|
+
{ key: "other_key", src: "other_image" },
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
extensions: {
|
|
16
|
+
artwork_key: "artwork_key",
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const entryWithoutImage = {
|
|
21
|
+
media_group: [
|
|
22
|
+
{
|
|
23
|
+
type: "image",
|
|
24
|
+
media_item: [{ key: "other_key", src: "other_image" }],
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
extensions: {
|
|
28
|
+
artwork_key: "artwork_key",
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const pluginConfigWithImage = {
|
|
33
|
+
artwork_key: "plugin_artwork_key",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
it("returns image from entry extensions key", () => {
|
|
37
|
+
const result = getArtworkImage({
|
|
38
|
+
key: "artwork_key",
|
|
39
|
+
entry: entryWithImage,
|
|
40
|
+
plugin_configuration: {},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(result).toBe("image_from_entry");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("returns image from plugin configuration key if not in entry", () => {
|
|
47
|
+
const entryNoMatch = {
|
|
48
|
+
...entryWithoutImage,
|
|
49
|
+
extensions: { artwork_key: "not_found_key" },
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const pluginConfig = {
|
|
53
|
+
artwork_key: "plugin_artwork_key",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const entryWithPluginImage = {
|
|
57
|
+
...entryNoMatch,
|
|
58
|
+
media_group: [
|
|
59
|
+
{
|
|
60
|
+
type: "image",
|
|
61
|
+
media_item: [{ key: "plugin_artwork_key", src: "image_from_plugin" }],
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const result = getArtworkImage({
|
|
67
|
+
key: "artwork_key",
|
|
68
|
+
entry: entryWithPluginImage,
|
|
69
|
+
plugin_configuration: pluginConfig,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(result).toBe("image_from_plugin");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("returns DEFAULT_IMAGE if neither entry nor plugin configuration has image", () => {
|
|
76
|
+
const entryNoImage = {
|
|
77
|
+
media_group: [
|
|
78
|
+
{
|
|
79
|
+
type: "image",
|
|
80
|
+
media_item: [{ key: "other_key", src: "other_image" }],
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
extensions: { artwork_key: "not_found_key" },
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const pluginConfig = { artwork_key: "not_found_key" };
|
|
87
|
+
|
|
88
|
+
const result = getArtworkImage({
|
|
89
|
+
key: "artwork_key",
|
|
90
|
+
entry: entryNoImage,
|
|
91
|
+
plugin_configuration: pluginConfig,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(result).toBe(DEFAULT_IMAGE);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("handles undefined key gracefully", () => {
|
|
98
|
+
const result = getArtworkImage({
|
|
99
|
+
key: undefined,
|
|
100
|
+
entry: entryWithImage,
|
|
101
|
+
plugin_configuration: pluginConfigWithImage,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(result).toBe(DEFAULT_IMAGE);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("handles missing entry or plugin_configuration gracefully", () => {
|
|
108
|
+
const result = getArtworkImage({
|
|
109
|
+
key: "artwork_key",
|
|
110
|
+
entry: undefined,
|
|
111
|
+
plugin_configuration: undefined,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(result).toBe(DEFAULT_IMAGE);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("handles empty media_group", () => {
|
|
118
|
+
const entryEmptyMedia = {
|
|
119
|
+
media_group: [],
|
|
120
|
+
extensions: { artwork_key: "artwork_key" },
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const result = getArtworkImage({
|
|
124
|
+
key: "artwork_key",
|
|
125
|
+
entry: entryEmptyMedia,
|
|
126
|
+
plugin_configuration: pluginConfigWithImage,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(result).toBe(DEFAULT_IMAGE);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("handles missing extensions/config keys", () => {
|
|
133
|
+
const entryNoExt = { media_group: [], extensions: {} };
|
|
134
|
+
const pluginConfig = {};
|
|
135
|
+
|
|
136
|
+
const result = getArtworkImage({
|
|
137
|
+
key: "artwork_key",
|
|
138
|
+
entry: entryNoExt,
|
|
139
|
+
plugin_configuration: pluginConfig,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(result).toBe(DEFAULT_IMAGE);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { getBackgroundImage } from "..";
|
|
2
|
+
import { DEFAULT_IMAGE } from "../assets";
|
|
3
|
+
|
|
4
|
+
describe("getBackgroundImage", () => {
|
|
5
|
+
const entryBase = {
|
|
6
|
+
media_group: [
|
|
7
|
+
{
|
|
8
|
+
media_item: [
|
|
9
|
+
{ key: "image_key_1", src: "image_src_1" },
|
|
10
|
+
{ key: "image_key_2", src: "image_src_2" },
|
|
11
|
+
],
|
|
12
|
+
type: "image",
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
extensions: {},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const pluginConfigBase = {};
|
|
19
|
+
|
|
20
|
+
it("returns image from entry.extensions.image_key if present", () => {
|
|
21
|
+
const entry = {
|
|
22
|
+
...entryBase,
|
|
23
|
+
extensions: { image_key: "image_key_2" },
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const result = getBackgroundImage({
|
|
27
|
+
entry,
|
|
28
|
+
plugin_configuration: pluginConfigBase,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
expect(result).toBe("image_src_2");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("returns audio_player_background_image from entry.extensions if present and no image_key image", () => {
|
|
35
|
+
const entry = {
|
|
36
|
+
...entryBase,
|
|
37
|
+
extensions: { audio_player_background_image: "audio_img_ext" },
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const result = getBackgroundImage({
|
|
41
|
+
entry,
|
|
42
|
+
plugin_configuration: pluginConfigBase,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(result).toBe("audio_img_ext");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("returns image from plugin_configuration.audio_player_image_key if present and not found in entry.extensions", () => {
|
|
49
|
+
const entry = { ...entryBase };
|
|
50
|
+
const plugin_configuration = { audio_player_image_key: "image_key_1" };
|
|
51
|
+
const result = getBackgroundImage({ entry, plugin_configuration });
|
|
52
|
+
expect(result).toBe("image_src_1");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("returns audio_player_background_image from plugin_configuration if present and not found in entry/extensions", () => {
|
|
56
|
+
const entry = { ...entryBase };
|
|
57
|
+
|
|
58
|
+
const plugin_configuration = {
|
|
59
|
+
audio_player_background_image: "audio_img_conf",
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const result = getBackgroundImage({ entry, plugin_configuration });
|
|
63
|
+
expect(result).toBe("audio_img_conf");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("returns DEFAULT_IMAGE if nothing is found", () => {
|
|
67
|
+
const entry = { media_group: [], extensions: {} };
|
|
68
|
+
const plugin_configuration = {};
|
|
69
|
+
const result = getBackgroundImage({ entry, plugin_configuration });
|
|
70
|
+
expect(result).toBe(DEFAULT_IMAGE);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { getImageFromEntry } from "..";
|
|
2
|
+
|
|
3
|
+
const entry = {
|
|
4
|
+
media_group: [
|
|
5
|
+
{
|
|
6
|
+
media_item: [
|
|
7
|
+
{
|
|
8
|
+
key: "image_base_key",
|
|
9
|
+
src: "image_base_src",
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
key: "thumb_1",
|
|
13
|
+
src: null,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
key: "thumb_2",
|
|
17
|
+
src: null,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
key: "thumb_3",
|
|
21
|
+
src: null,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
type: "image",
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
describe("getImageFromEntry", () => {
|
|
30
|
+
it("returns the src value for existing key", () => {
|
|
31
|
+
const result = getImageFromEntry({
|
|
32
|
+
entry,
|
|
33
|
+
imageKey: "image_base_key",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
expect(result).toEqual("image_base_src");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("returns undefined for non-existing key", () => {
|
|
40
|
+
const result = getImageFromEntry({
|
|
41
|
+
entry,
|
|
42
|
+
imageKey: "non_existing_key",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(result).toBeUndefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("returns undefined for undefined key", () => {
|
|
49
|
+
const result = getImageFromEntry({
|
|
50
|
+
entry,
|
|
51
|
+
imageKey: undefined,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(result).toBeUndefined();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("returns undefined for non string src", () => {
|
|
58
|
+
const entryWithNonStringSrc = {
|
|
59
|
+
media_group: [
|
|
60
|
+
{
|
|
61
|
+
media_item: [
|
|
62
|
+
{
|
|
63
|
+
key: "image_base_key",
|
|
64
|
+
src: 123,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
key: "thumb_1",
|
|
68
|
+
src: null,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
type: "image",
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const result = getImageFromEntry({
|
|
77
|
+
entry: entryWithNonStringSrc,
|
|
78
|
+
imageKey: "image_base_key",
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(result).toBeUndefined();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("returns undefined for empty src", () => {
|
|
85
|
+
const entryWithEmptySrc = {
|
|
86
|
+
media_group: [
|
|
87
|
+
{
|
|
88
|
+
media_item: [
|
|
89
|
+
{
|
|
90
|
+
key: "image_base_key",
|
|
91
|
+
src: "",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
key: "thumb_1",
|
|
95
|
+
src: null,
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
type: "image",
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const result = getImageFromEntry({
|
|
104
|
+
entry: entryWithEmptySrc,
|
|
105
|
+
imageKey: "image_base_key",
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
expect(result).toBeUndefined();
|
|
109
|
+
});
|
|
110
|
+
});
|