@applicaster/zapp-react-native-utils 15.1.6-alpha.3988508055 → 16.0.0-alpha.1020865559
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/README.md +0 -6
- package/actionUtils/index.ts +7 -0
- package/actionsExecutor/ActionExecutorContext.tsx +0 -4
- package/actionsExecutor/actions/__tests__/showToast.test.ts +29 -4
- package/actionsExecutor/actions/index.ts +0 -4
- package/actionsExecutor/actions/refreshComponent.ts +4 -35
- package/actionsExecutor/actions/showToast.ts +59 -6
- package/adsUtils/__tests__/createVMAP.test.ts +419 -0
- package/adsUtils/index.ts +2 -2
- package/analyticsUtils/README.md +1 -1
- package/appDataUtils/__tests__/urlScheme.test.ts +678 -0
- package/appUtils/HooksManager/__tests__/__snapshots__/hooksManager.test.js.snap +0 -188
- package/appUtils/HooksManager/__tests__/hooksManager.test.js +16 -2
- package/appUtils/HooksManager/index.ts +12 -18
- package/appUtils/RiverFocusManager/{index.js → index.ts} +25 -18
- package/appUtils/accessibilityManager/const.ts +4 -0
- package/appUtils/accessibilityManager/utils.ts +0 -1
- package/appUtils/contextKeysManager/__tests__/getKeys/failure.test.ts +7 -2
- package/appUtils/contextKeysManager/__tests__/getKeys/success.test.ts +48 -0
- package/appUtils/contextKeysManager/contextResolver.ts +51 -22
- package/appUtils/contextKeysManager/index.ts +65 -10
- package/appUtils/contextKeysManager/utils/index.ts +38 -25
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +3 -0
- package/appUtils/focusManager/index.ios.ts +43 -4
- package/appUtils/focusManager/treeDataStructure/Tree/__tests__/Tree.test.js +46 -0
- package/appUtils/focusManager/treeDataStructure/Tree/index.js +18 -18
- package/appUtils/focusManagerAux/utils/index.ios.ts +122 -0
- package/appUtils/focusManagerAux/utils/index.ts +11 -3
- package/appUtils/focusManagerAux/utils/utils.ios.ts +202 -3
- package/appUtils/keyCodes/keys/keys.web.ts +1 -4
- package/appUtils/orientationHelper.ts +2 -4
- package/appUtils/platform/platformUtils.ts +12 -1
- package/appUtils/playerManager/player.ts +4 -0
- package/appUtils/playerManager/playerNative.ts +30 -21
- package/appUtils/playerManager/usePlayerState.tsx +14 -2
- package/arrayUtils/__tests__/{anyThruthy.test.ts → anyTruthy.test.ts} +8 -0
- package/arrayUtils/__tests__/arrayUtils.test.ts +165 -108
- package/arrayUtils/__tests__/isEmptyArray.test.ts +11 -0
- package/arrayUtils/__tests__/isFilledArray.test.ts +12 -0
- package/arrayUtils/__tests__/isFirst.test.ts +17 -0
- package/arrayUtils/__tests__/isIndexInRange.test.ts +14 -0
- package/arrayUtils/__tests__/isLast.test.ts +31 -0
- package/arrayUtils/__tests__/makeListOf.test.ts +31 -0
- package/arrayUtils/__tests__/makeListOfIndexes.test.ts +20 -0
- package/arrayUtils/__tests__/sample.test.ts +61 -0
- package/arrayUtils/index.ts +57 -20
- package/cloudEventsUtils/__tests__/index.test.ts +529 -0
- package/cloudEventsUtils/index.ts +65 -1
- package/componentsUtils/index.ts +8 -0
- package/configurationUtils/__tests__/manifestKeyParser.test.ts +26 -26
- package/enumUtils/__tests__/getEnumKeyByEnumValue.test.ts +207 -0
- package/errorUtils/__tests__/GeneralError.test.ts +97 -0
- package/errorUtils/__tests__/HttpStatusCode.test.ts +344 -0
- package/errorUtils/__tests__/MissingPluginError.test.ts +113 -0
- package/errorUtils/__tests__/NetworkError.test.ts +202 -0
- package/errorUtils/__tests__/getParsedResponse.test.ts +188 -0
- package/errorUtils/__tests__/invariant.test.ts +112 -0
- package/focusManager/aux/index.ts +1 -1
- package/headersUtils/__tests__/headersUtils.test.js +11 -1
- package/headersUtils/index.ts +2 -1
- package/manifestUtils/_internals/__tests__/index.test.js +41 -0
- package/manifestUtils/_internals/index.js +33 -0
- package/manifestUtils/defaultManifestConfigurations/player.js +59 -1
- package/manifestUtils/index.js +3 -1
- package/manifestUtils/keys.js +236 -0
- package/manifestUtils/mobileAction/button/__tests__/mobileActionButton.test.js +168 -0
- package/manifestUtils/mobileAction/button/index.js +156 -0
- package/manifestUtils/mobileAction/container/__tests__/mobileActionButtonsContainer.test.js +102 -0
- package/manifestUtils/mobileAction/container/index.js +75 -0
- package/manifestUtils/mobileAction/groups/__tests__/buildMobileActionButtonGroups.test.js +127 -0
- package/manifestUtils/mobileAction/groups/defaults.js +78 -0
- package/manifestUtils/mobileAction/groups/index.js +80 -0
- package/manifestUtils/tvAction/container/index.js +1 -1
- package/modalState/index.ts +83 -30
- package/numberUtils/__tests__/isMinusZero.test.ts +20 -0
- package/numberUtils/__tests__/isZero.test.ts +27 -0
- package/numberUtils/__tests__/requireNumber.test.ts +50 -0
- package/numberUtils/__tests__/toNumber.test.ts +54 -0
- package/numberUtils/__tests__/toNumberWithDefault.test.ts +64 -0
- package/numberUtils/__tests__/toNumberWithDefaultZero.test.ts +48 -0
- package/numberUtils/__tests__/toPositiveNumber.test.ts +193 -0
- package/numberUtils/index.ts +46 -5
- package/package.json +4 -4
- package/pluginUtils/index.ts +4 -0
- package/reactHooks/advertising/index.ts +2 -2
- package/reactHooks/analytics/__tests__/useSendAnalyticsOnPress.test.ts +537 -0
- package/reactHooks/app/__tests__/useAppState.test.ts +1 -1
- package/reactHooks/autoscrolling/__tests__/useTrackCurrentAutoScrollingElement.test.ts +1 -1
- package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +1 -2
- package/reactHooks/cell-click/__tests__/index.test.js +1 -3
- package/reactHooks/cell-click/index.ts +15 -7
- package/reactHooks/configuration/__tests__/index.test.tsx +1 -1
- package/reactHooks/connection/__tests__/index.test.js +1 -1
- package/reactHooks/debugging/__tests__/index.test.js +4 -4
- package/reactHooks/dev/__tests__/useReRenderLog.test.ts +188 -0
- package/reactHooks/device/useIsTablet.tsx +14 -19
- package/reactHooks/device/useMemoizedIsTablet.ts +3 -3
- package/reactHooks/feed/__mocks__/useMarkPipesDataStale.ts +4 -0
- package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +32 -23
- package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +19 -19
- package/reactHooks/feed/__tests__/useEntryScreenId.test.tsx +4 -1
- package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +42 -30
- package/reactHooks/feed/__tests__/useInflatedUrl.test.tsx +1 -1
- package/reactHooks/feed/index.ts +5 -3
- package/reactHooks/feed/useBatchLoading.ts +17 -3
- package/reactHooks/feed/useEntryScreenId.ts +2 -2
- package/reactHooks/feed/{usePipesCacheReset.ts → useMarkPipesDataStale.ts} +14 -4
- package/reactHooks/flatList/useLoadNextPageIfNeeded.ts +13 -16
- package/reactHooks/layout/__tests__/index.test.tsx +1 -1
- package/reactHooks/layout/__tests__/useLayoutVersion.test.tsx +1 -1
- package/reactHooks/layout/useDimensions/__tests__/{useDimensions.test.ts → useDimensions.test.tsx} +105 -25
- package/reactHooks/layout/useDimensions/useDimensions.ts +2 -2
- package/reactHooks/navigation/__tests__/index.test.tsx +2 -4
- package/reactHooks/navigation/index.ts +7 -6
- package/reactHooks/navigation/useRoute.ts +8 -6
- package/reactHooks/player/TVSeekControlller/TVSeekController.ts +27 -10
- package/reactHooks/player/__tests__/useAutoSeek._test.tsx +1 -1
- package/reactHooks/player/__tests__/useTapSeek._test.ts +1 -1
- package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +1 -1
- package/reactHooks/resolvers/__tests__/useComponentResolver.test.tsx +1 -1
- package/reactHooks/resolvers/useCellResolver.ts +6 -2
- package/reactHooks/resolvers/useComponentResolver.ts +8 -2
- package/reactHooks/screen/__tests__/useCurrentScreenData.test.tsx +2 -2
- package/reactHooks/screen/__tests__/useIsStandaloneFullscreen.test.ts +114 -0
- package/reactHooks/screen/__tests__/useScreenBackgroundColor.test.tsx +1 -1
- package/reactHooks/screen/__tests__/useScreenData.test.tsx +1 -1
- package/reactHooks/screen/__tests__/useTargetScreenData.test.tsx +12 -4
- package/reactHooks/screen/useTargetScreenData.ts +4 -2
- package/reactHooks/state/useComponentScreenState.ts +1 -1
- package/reactHooks/state/useRivers.ts +1 -1
- package/reactHooks/ui/__tests__/useFadeOutWhenBlurred.test.ts +580 -0
- package/reactHooks/usePluginConfiguration.ts +2 -2
- package/reactHooks/utils/__tests__/index.test.js +1 -1
- package/reactHooks/utils/index.ts +3 -2
- package/rectUtils/__tests__/index.test.ts +549 -0
- package/rectUtils/index.ts +2 -2
- package/refreshUtils/RefreshCoordinator/__tests__/refreshCoordinator.test.ts +206 -0
- package/refreshUtils/RefreshCoordinator/index.ts +245 -0
- package/refreshUtils/RefreshCoordinator/utils/__tests__/getDataRefreshConfig.test.ts +104 -0
- package/refreshUtils/RefreshCoordinator/utils/index.ts +29 -0
- package/riverComponetsMeasurementProvider/index.tsx +1 -1
- package/screenPickerUtils/__tests__/index.test.ts +333 -0
- package/screenState/__tests__/index.test.ts +1 -1
- package/screenUtils/index.ts +3 -0
- package/searchUtils/const.ts +7 -0
- package/searchUtils/index.ts +3 -0
- package/stringUtils/index.ts +1 -1
- package/testUtils/index.tsx +30 -21
- package/time/__tests__/BackgroundTimer.test.ts +156 -0
- package/time/__tests__/Timer.test.ts +236 -0
- package/typeGuards/__tests__/isString.test.ts +21 -0
- package/typeGuards/index.ts +4 -0
- package/utils/__tests__/mergeRight.test.ts +48 -0
- package/utils/__tests__/path.test.ts +7 -0
- package/utils/__tests__/selectors.test.ts +124 -0
- package/utils/index.ts +13 -0
- package/utils/mergeRight.ts +5 -0
- package/utils/path.ts +6 -3
- package/utils/pathOr.ts +5 -1
- package/utils/selectors.ts +46 -0
- package/zappFrameworkUtils/HookCallback/callbackNavigationAction.ts +1 -1
- package/zappFrameworkUtils/HookCallback/hookCallbackManifestExtensions.config.js +1 -1
- package/zappFrameworkUtils/__tests__/localStorageHelper.test.ts +146 -0
- package/zappFrameworkUtils/localStorageHelper.ts +19 -15
- package/zappFrameworkUtils/loginPluginUtils.ts +1 -1
- package/actionsExecutor/actions/__tests__/dismissBottomSheet.test.ts +0 -22
- package/actionsExecutor/actions/__tests__/loadItemsFromSource.test.ts +0 -88
- package/actionsExecutor/actions/__tests__/openBottomSheet.customContent.test.ts +0 -76
- package/actionsExecutor/actions/dismissBottomSheet.ts +0 -22
- package/actionsExecutor/actions/openBottomSheet.ts +0 -318
- package/modalState/ContentViewModel.ts +0 -111
- package/modalState/EditableCollection.ts +0 -17
- package/modalState/EditableCollectionRegistry.ts +0 -51
- package/modalState/ModalOrchestrator.ts +0 -198
- package/modalState/RemoteEditableCollection.ts +0 -189
- package/modalState/__tests__/ContentViewModel.editable.test.ts +0 -69
- package/modalState/__tests__/ContentViewModel.test.ts +0 -165
- package/modalState/__tests__/EditableCollectionRegistry.test.ts +0 -112
- package/modalState/__tests__/ModalOrchestrator.phase3.test.ts +0 -28
- package/modalState/__tests__/RemoteEditableCollection.test.ts +0 -259
- package/modalState/__tests__/useBottomSheetContent.test.ts +0 -372
- package/modalState/store.ts +0 -102
- package/modalState/types.ts +0 -110
- package/modalState/useBottomSheetContent.ts +0 -98
- package/reactHooks/componentsMap/index.ts +0 -55
- package/reactHooks/feed/__mocks__/usePipesCacheReset.ts +0 -1
- package/reactHooks/feed/__tests__/useFeedRefresh.test.tsx +0 -75
- package/reactHooks/feed/useFeedRefresh.tsx +0 -65
package/README.md
CHANGED
|
@@ -245,12 +245,6 @@ const connectionType = useConnectionInfo(true);
|
|
|
245
245
|
|
|
246
246
|
`@applicaster/zapp-react-native/reactHooks`
|
|
247
247
|
|
|
248
|
-
- `useFeedRefresh: ({ reloadData: function, component: { id: boolean | string, rules: {enable_data_refreshing: boolean, refreshing_interval: number} } }) => void` - Hook will call `reloadData` function, in the specified intervals if `enable_data_refreshing` is set to true;
|
|
249
|
-
|
|
250
|
-
```javascript
|
|
251
|
-
useFeedRefresh({ reloadData, component });
|
|
252
|
-
```
|
|
253
|
-
|
|
254
248
|
- `useFeedLoader: ({ feedUrl: string, pipesOptions?: { clearCache?: boolean, loadLocalFavourites?: boolean, silentRefresh?: boolean} }) => ({data: ?ApplicasterFeed, loading: boolean, url: string, error: Error,reloadData: (silentRefresh?: boolean) => void, loadNext: () => void})` - Hook will load data to the redux store and return a feed for the provided DSP URL. If the data for the provided url was already loaded, it will return that value
|
|
255
249
|
|
|
256
250
|
```javascript
|
|
@@ -19,11 +19,9 @@ import {
|
|
|
19
19
|
appRestartAction,
|
|
20
20
|
confirmDialogAction,
|
|
21
21
|
createNavigateToScreenAction,
|
|
22
|
-
dismissBottomSheetAction,
|
|
23
22
|
localStorageRemoveAction,
|
|
24
23
|
localStorageSetAction,
|
|
25
24
|
localStorageToggleFlagAction,
|
|
26
|
-
openBottomSheetAction,
|
|
27
25
|
refreshComponentAction,
|
|
28
26
|
screenSetVariableAction,
|
|
29
27
|
screenToggleFlagAction,
|
|
@@ -92,8 +90,6 @@ const prepareDefaultActions = (actionExecutor) => {
|
|
|
92
90
|
|
|
93
91
|
actionExecutor.registerAction("screenSetVariable", screenSetVariableAction);
|
|
94
92
|
actionExecutor.registerAction("screenToggleFlag", screenToggleFlagAction);
|
|
95
|
-
actionExecutor.registerAction("openBottomSheet", openBottomSheetAction);
|
|
96
|
-
actionExecutor.registerAction("dismissBottomSheet", dismissBottomSheetAction);
|
|
97
93
|
actionExecutor.registerAction("showToast", showToastAction);
|
|
98
94
|
};
|
|
99
95
|
|
|
@@ -11,14 +11,13 @@ describe("showToastAction", () => {
|
|
|
11
11
|
jest.clearAllMocks();
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
it("calls postEvent with
|
|
14
|
+
it("calls postEvent with confirmation source, default timeout, and returns Success", async () => {
|
|
15
15
|
const action = {
|
|
16
16
|
type: "showToast",
|
|
17
17
|
options: {
|
|
18
18
|
id: "toast-1",
|
|
19
19
|
message: "Added to queue",
|
|
20
20
|
extraMessage: "Extra info",
|
|
21
|
-
timeout: 3000,
|
|
22
21
|
},
|
|
23
22
|
};
|
|
24
23
|
|
|
@@ -32,13 +31,38 @@ describe("showToastAction", () => {
|
|
|
32
31
|
message: "Added to queue",
|
|
33
32
|
extraMessage: "Extra info",
|
|
34
33
|
style: undefined,
|
|
35
|
-
timeout:
|
|
34
|
+
timeout: 1000,
|
|
35
|
+
source: "confirmation",
|
|
36
36
|
},
|
|
37
37
|
]);
|
|
38
38
|
|
|
39
39
|
expect(result).toBe(ActionResult.Success);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
+
it("preserves an explicit timeout and style override", async () => {
|
|
43
|
+
const action = {
|
|
44
|
+
type: "showToast",
|
|
45
|
+
options: {
|
|
46
|
+
message: "Playlist created",
|
|
47
|
+
timeout: 3000,
|
|
48
|
+
style: { backgroundColor: "#00FF00" },
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
await showToastAction(action);
|
|
53
|
+
|
|
54
|
+
expect(postEvent).toHaveBeenCalledWith("showToast", [
|
|
55
|
+
{
|
|
56
|
+
id: undefined,
|
|
57
|
+
message: "Playlist created",
|
|
58
|
+
extraMessage: undefined,
|
|
59
|
+
style: { backgroundColor: "#00FF00" },
|
|
60
|
+
timeout: 3000,
|
|
61
|
+
source: "confirmation",
|
|
62
|
+
},
|
|
63
|
+
]);
|
|
64
|
+
});
|
|
65
|
+
|
|
42
66
|
it("handles missing or empty options without throwing", async () => {
|
|
43
67
|
const action = {
|
|
44
68
|
type: "showToast",
|
|
@@ -54,7 +78,8 @@ describe("showToastAction", () => {
|
|
|
54
78
|
message: undefined,
|
|
55
79
|
extraMessage: undefined,
|
|
56
80
|
style: undefined,
|
|
57
|
-
timeout:
|
|
81
|
+
timeout: 1000,
|
|
82
|
+
source: "confirmation",
|
|
58
83
|
},
|
|
59
84
|
]);
|
|
60
85
|
|
|
@@ -28,7 +28,3 @@ export { screenToggleFlagAction } from "./screenToggleFlag";
|
|
|
28
28
|
export { createNavigateToScreenAction } from "./navigateToScreen";
|
|
29
29
|
|
|
30
30
|
export { showToastAction } from "./showToast";
|
|
31
|
-
|
|
32
|
-
export { openBottomSheetAction } from "./openBottomSheet";
|
|
33
|
-
|
|
34
|
-
export { dismissBottomSheetAction } from "./dismissBottomSheet";
|
|
@@ -14,10 +14,8 @@ const { log_info } = createLogger({
|
|
|
14
14
|
category: "General",
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
*/
|
|
20
|
-
function getComponentFeedData(component: any) {
|
|
17
|
+
/** Prefer component data.source, otherwise direct UIComponentContext parent. */
|
|
18
|
+
function getComponentFeedData(component) {
|
|
21
19
|
if (component?.data?.source) {
|
|
22
20
|
return component.data;
|
|
23
21
|
}
|
|
@@ -29,25 +27,6 @@ function getComponentFeedData(component: any) {
|
|
|
29
27
|
return null;
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
/**
|
|
33
|
-
* Helper function to find the parent component in a component tree.
|
|
34
|
-
*/
|
|
35
|
-
function findParentComponent(
|
|
36
|
-
childId: string,
|
|
37
|
-
parent: ZappRiver | ZappUIComponent
|
|
38
|
-
): ZappRiver | ZappUIComponent | null {
|
|
39
|
-
for (const child of parent?.ui_components || []) {
|
|
40
|
-
if (child.id === childId) return parent;
|
|
41
|
-
|
|
42
|
-
if (child.ui_components) {
|
|
43
|
-
const found = findParentComponent(childId, child);
|
|
44
|
-
if (found) return found;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
30
|
/**
|
|
52
31
|
* Refreshes a component's data by reloading its data source.
|
|
53
32
|
* Supports URL inflation with context variables.
|
|
@@ -58,11 +37,7 @@ export const refreshComponentAction: ActionHandler = async (
|
|
|
58
37
|
): Promise<ActionResult> => {
|
|
59
38
|
const dispatch = appStore.getDispatch();
|
|
60
39
|
|
|
61
|
-
const componentData =
|
|
62
|
-
getComponentFeedData(context?.component) ||
|
|
63
|
-
(context?.component?.id && context?.screenData
|
|
64
|
-
? findParentComponent(context.component.id, context.screenData)?.data
|
|
65
|
-
: null);
|
|
40
|
+
const componentData = getComponentFeedData(context?.component);
|
|
66
41
|
|
|
67
42
|
const source = componentData?.source;
|
|
68
43
|
const mapping = componentData?.mapping;
|
|
@@ -74,7 +49,7 @@ export const refreshComponentAction: ActionHandler = async (
|
|
|
74
49
|
getInflatedDataSourceUrl({
|
|
75
50
|
source,
|
|
76
51
|
contexts: {
|
|
77
|
-
entry: context?.
|
|
52
|
+
entry: context?.screenEntry,
|
|
78
53
|
screen: context?.screenData,
|
|
79
54
|
search: getSearchContext(null, mapping),
|
|
80
55
|
},
|
|
@@ -82,12 +57,6 @@ export const refreshComponentAction: ActionHandler = async (
|
|
|
82
57
|
}) || source;
|
|
83
58
|
}
|
|
84
59
|
|
|
85
|
-
if (!dataSource) {
|
|
86
|
-
log_info("handleAction: refreshComponent skipped: no dataSource found");
|
|
87
|
-
|
|
88
|
-
return ActionResult.Success;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
60
|
log_info(`handleAction: refreshComponent for dataSource:${dataSource}`, {
|
|
92
61
|
source,
|
|
93
62
|
inflatedUrl: dataSource,
|
|
@@ -3,8 +3,58 @@ import { ActionResult } from "../ActionExecutor";
|
|
|
3
3
|
import { ActionHandler } from "../types";
|
|
4
4
|
import { postEvent } from "../../reactHooks/useSubscriberFor";
|
|
5
5
|
|
|
6
|
+
export const CONFIRMATION_TOAST_SOURCE = "confirmation";
|
|
7
|
+
|
|
8
|
+
export const CONFIRMATION_TOAST_DEFAULT_TIMEOUT = 1000;
|
|
9
|
+
|
|
6
10
|
/**
|
|
7
|
-
*
|
|
11
|
+
* Shows a mobile confirmation toast with Theme defaults from
|
|
12
|
+
* `Confirmation Toast / Message` in the base theme plugin.
|
|
13
|
+
*
|
|
14
|
+
* The action stamps `source: "confirmation"` and defaults `timeout` to 1000 ms.
|
|
15
|
+
* Callers may override individual style fields; Theme supplies the rest.
|
|
16
|
+
*
|
|
17
|
+
* @example From React via ActionExecutorContext
|
|
18
|
+
* ```tsx
|
|
19
|
+
* import React, { useContext } from "react";
|
|
20
|
+
* import { ActionExecutorContext } from "@applicaster/zapp-react-native-utils/actionsExecutor/ActionExecutorContext";
|
|
21
|
+
*
|
|
22
|
+
* function QueueButton() {
|
|
23
|
+
* const { handleAction } = useContext(ActionExecutorContext);
|
|
24
|
+
*
|
|
25
|
+
* const onPress = () => {
|
|
26
|
+
* void handleAction({
|
|
27
|
+
* type: "showToast",
|
|
28
|
+
* options: { message: "Added to queue" },
|
|
29
|
+
* });
|
|
30
|
+
* };
|
|
31
|
+
*
|
|
32
|
+
* return <Button onPress={onPress} title="Add to queue" />;
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @example From code via the actionExecutor singleton
|
|
37
|
+
* ```ts
|
|
38
|
+
* import { actionExecutor } from "@applicaster/zapp-react-native-utils/actionsExecutor/ActionExecutor";
|
|
39
|
+
*
|
|
40
|
+
* await actionExecutor.handleAction({
|
|
41
|
+
* type: "showToast",
|
|
42
|
+
* options: {
|
|
43
|
+
* message: "Playlist created",
|
|
44
|
+
* style: { backgroundColor: "#00AA55" }, // optional partial override
|
|
45
|
+
* },
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @example From entry / pipes actions JSON
|
|
50
|
+
* ```json
|
|
51
|
+
* {
|
|
52
|
+
* "type": "showToast",
|
|
53
|
+
* "options": {
|
|
54
|
+
* "message": "Item removed from playlist"
|
|
55
|
+
* }
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
8
58
|
*/
|
|
9
59
|
export interface ShowToastActionOptions {
|
|
10
60
|
id?: string;
|
|
@@ -17,13 +67,16 @@ export interface ShowToastActionOptions {
|
|
|
17
67
|
export const showToastAction: ActionHandler<ShowToastActionOptions> = async (
|
|
18
68
|
action
|
|
19
69
|
): Promise<ActionResult> => {
|
|
70
|
+
const options = action?.options;
|
|
71
|
+
|
|
20
72
|
postEvent("showToast", [
|
|
21
73
|
{
|
|
22
|
-
id:
|
|
23
|
-
message:
|
|
24
|
-
extraMessage:
|
|
25
|
-
style:
|
|
26
|
-
timeout:
|
|
74
|
+
id: options?.id,
|
|
75
|
+
message: options?.message,
|
|
76
|
+
extraMessage: options?.extraMessage,
|
|
77
|
+
style: options?.style,
|
|
78
|
+
timeout: options?.timeout ?? CONFIRMATION_TOAST_DEFAULT_TIMEOUT,
|
|
79
|
+
source: CONFIRMATION_TOAST_SOURCE,
|
|
27
80
|
},
|
|
28
81
|
]);
|
|
29
82
|
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { createVMAP } from "../index";
|
|
2
|
+
|
|
3
|
+
describe("createVMAP", () => {
|
|
4
|
+
describe("basic functionality", () => {
|
|
5
|
+
it("should create VMAP XML with single ad", () => {
|
|
6
|
+
const ads = [
|
|
7
|
+
{
|
|
8
|
+
offset: 0,
|
|
9
|
+
ad_url: "https://example.com/ad1.xml",
|
|
10
|
+
},
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const result = createVMAP(ads);
|
|
14
|
+
|
|
15
|
+
expect(result).toContain(
|
|
16
|
+
'<vmap:VMAP xmlns:vmap="http://www.iab.net/videosuite/vmap" version="1.0">'
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
expect(result).toContain("</vmap:VMAP>");
|
|
20
|
+
expect(result).toContain('timeOffset="00:00:00.000"');
|
|
21
|
+
expect(result).toContain("https://example.com/ad1.xml");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should create VMAP XML with multiple ads", () => {
|
|
25
|
+
const ads = [
|
|
26
|
+
{
|
|
27
|
+
offset: 0,
|
|
28
|
+
ad_url: "https://example.com/preroll.xml",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
offset: 300,
|
|
32
|
+
ad_url: "https://example.com/midroll.xml",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
offset: "postroll",
|
|
36
|
+
ad_url: "https://example.com/postroll.xml",
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const result = createVMAP(ads);
|
|
41
|
+
|
|
42
|
+
expect(result).toContain('timeOffset="00:00:00.000"');
|
|
43
|
+
expect(result).toContain('timeOffset="00:05:00.000"');
|
|
44
|
+
expect(result).toContain('timeOffset="end"');
|
|
45
|
+
expect(result).toContain("https://example.com/preroll.xml");
|
|
46
|
+
expect(result).toContain("https://example.com/midroll.xml");
|
|
47
|
+
expect(result).toContain("https://example.com/postroll.xml");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should create empty VMAP with no ads", () => {
|
|
51
|
+
const ads: Array<{ [key: string]: any }> = [];
|
|
52
|
+
|
|
53
|
+
const result = createVMAP(ads);
|
|
54
|
+
|
|
55
|
+
expect(result).toBe(
|
|
56
|
+
'<vmap:VMAP xmlns:vmap="http://www.iab.net/videosuite/vmap" version="1.0">\n</vmap:VMAP>'
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("time offset conversion", () => {
|
|
62
|
+
it("should convert 0 seconds to 00:00:00.000", () => {
|
|
63
|
+
const ads = [{ offset: 0, ad_url: "https://example.com/ad.xml" }];
|
|
64
|
+
const result = createVMAP(ads);
|
|
65
|
+
|
|
66
|
+
expect(result).toContain('timeOffset="00:00:00.000"');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should convert seconds to HH:MM:SS.000 format", () => {
|
|
70
|
+
const ads = [
|
|
71
|
+
{ offset: 60, ad_url: "https://example.com/ad.xml" }, // 1 minute
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
const result = createVMAP(ads);
|
|
75
|
+
|
|
76
|
+
expect(result).toContain('timeOffset="00:01:00.000"');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should convert minutes to HH:MM:SS.000 format", () => {
|
|
80
|
+
const ads = [
|
|
81
|
+
{ offset: 300, ad_url: "https://example.com/ad.xml" }, // 5 minutes
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
const result = createVMAP(ads);
|
|
85
|
+
|
|
86
|
+
expect(result).toContain('timeOffset="00:05:00.000"');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should convert hours to HH:MM:SS.000 format", () => {
|
|
90
|
+
const ads = [
|
|
91
|
+
{ offset: 3661, ad_url: "https://example.com/ad.xml" }, // 1:01:01
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
const result = createVMAP(ads);
|
|
95
|
+
|
|
96
|
+
expect(result).toContain('timeOffset="01:01:01.000"');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should handle large time offsets", () => {
|
|
100
|
+
const ads = [
|
|
101
|
+
{ offset: 36000, ad_url: "https://example.com/ad.xml" }, // 10 hours
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
const result = createVMAP(ads);
|
|
105
|
+
|
|
106
|
+
expect(result).toContain('timeOffset="10:00:00.000"');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should convert postroll string to end", () => {
|
|
110
|
+
const ads = [
|
|
111
|
+
{ offset: "postroll", ad_url: "https://example.com/ad.xml" },
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
const result = createVMAP(ads);
|
|
115
|
+
|
|
116
|
+
expect(result).toContain('timeOffset="end"');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should convert numeric string to HH:MM:SS.000", () => {
|
|
120
|
+
const ads = [
|
|
121
|
+
{ offset: "120", ad_url: "https://example.com/ad.xml" }, // 2 minutes
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
const result = createVMAP(ads);
|
|
125
|
+
|
|
126
|
+
expect(result).toContain('timeOffset="00:02:00.000"');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("should keep non-numeric string as-is", () => {
|
|
130
|
+
const ads = [{ offset: "start", ad_url: "https://example.com/ad.xml" }];
|
|
131
|
+
|
|
132
|
+
const result = createVMAP(ads);
|
|
133
|
+
|
|
134
|
+
expect(result).toContain('timeOffset="start"');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("should handle fractional seconds", () => {
|
|
138
|
+
const ads = [{ offset: "90.5", ad_url: "https://example.com/ad.xml" }];
|
|
139
|
+
|
|
140
|
+
const result = createVMAP(ads);
|
|
141
|
+
|
|
142
|
+
// parseFloat("90.5") = 90.5, seconds = 90.5 % 60 = 30.5
|
|
143
|
+
// Note: The toHHMMSS function doesn't floor seconds, so fractional seconds appear as-is
|
|
144
|
+
expect(result).toContain('timeOffset="00:01:30.5.000"');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should pad single digit hours, minutes, and seconds", () => {
|
|
148
|
+
const ads = [
|
|
149
|
+
{ offset: 3661, ad_url: "https://example.com/ad.xml" }, // 1:01:01
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
const result = createVMAP(ads);
|
|
153
|
+
|
|
154
|
+
expect(result).toContain('timeOffset="01:01:01.000"');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("should handle offset 0 from various types", () => {
|
|
158
|
+
const testCases = [
|
|
159
|
+
{ offset: 0, expected: "00:00:00.000" },
|
|
160
|
+
{ offset: "0", expected: "00:00:00.000" },
|
|
161
|
+
// Note: null and undefined will cause errors when calling offset.toString()
|
|
162
|
+
// so they are not valid inputs for createVMAP
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
testCases.forEach(({ offset, expected }) => {
|
|
166
|
+
const ads = [{ offset, ad_url: "https://example.com/ad.xml" }];
|
|
167
|
+
const result = createVMAP(ads);
|
|
168
|
+
expect(result).toContain(`timeOffset="${expected}"`);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe("XML structure", () => {
|
|
174
|
+
it("should include proper VMAP namespace", () => {
|
|
175
|
+
const ads = [{ offset: 0, ad_url: "https://example.com/ad.xml" }];
|
|
176
|
+
const result = createVMAP(ads);
|
|
177
|
+
|
|
178
|
+
expect(result).toContain(
|
|
179
|
+
'xmlns:vmap="http://www.iab.net/videosuite/vmap"'
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
expect(result).toContain('version="1.0"');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should create AdBreak with breakType linear", () => {
|
|
186
|
+
const ads = [{ offset: 0, ad_url: "https://example.com/ad.xml" }];
|
|
187
|
+
const result = createVMAP(ads);
|
|
188
|
+
|
|
189
|
+
expect(result).toContain('breakType="linear"');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should create AdBreak with unique breakId based on offset", () => {
|
|
193
|
+
const ads = [
|
|
194
|
+
{ offset: 0, ad_url: "https://example.com/ad1.xml" },
|
|
195
|
+
{ offset: 300, ad_url: "https://example.com/ad2.xml" },
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
const result = createVMAP(ads);
|
|
199
|
+
|
|
200
|
+
expect(result).toContain('breakId="break-0"');
|
|
201
|
+
expect(result).toContain('breakId="break-300"');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("should create AdSource with proper attributes", () => {
|
|
205
|
+
const ads = [{ offset: 0, ad_url: "https://example.com/ad.xml" }];
|
|
206
|
+
const result = createVMAP(ads);
|
|
207
|
+
|
|
208
|
+
expect(result).toContain('allowMultipleAds="true"');
|
|
209
|
+
expect(result).toContain('followRedirects="true"');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("should use VAST3 template type", () => {
|
|
213
|
+
const ads = [{ offset: 0, ad_url: "https://example.com/ad.xml" }];
|
|
214
|
+
const result = createVMAP(ads);
|
|
215
|
+
|
|
216
|
+
expect(result).toContain('templateType="vast3"');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("should wrap URL in CDATA", () => {
|
|
220
|
+
const ads = [{ offset: 0, ad_url: "https://example.com/ad.xml" }];
|
|
221
|
+
const result = createVMAP(ads);
|
|
222
|
+
|
|
223
|
+
expect(result).toContain("<![CDATA[ https://example.com/ad.xml ]]>");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("should create unique ad IDs based on offset", () => {
|
|
227
|
+
const ads = [
|
|
228
|
+
{ offset: 0, ad_url: "https://example.com/ad1.xml" },
|
|
229
|
+
{ offset: 100, ad_url: "https://example.com/ad2.xml" },
|
|
230
|
+
];
|
|
231
|
+
|
|
232
|
+
const result = createVMAP(ads);
|
|
233
|
+
|
|
234
|
+
expect(result).toContain('id="ad-0-ad-1"');
|
|
235
|
+
expect(result).toContain('id="ad-100-ad-1"');
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
describe("URL handling", () => {
|
|
240
|
+
it("should trim whitespace from URLs", () => {
|
|
241
|
+
const ads = [{ offset: 0, ad_url: " https://example.com/ad.xml " }];
|
|
242
|
+
|
|
243
|
+
const result = createVMAP(ads);
|
|
244
|
+
|
|
245
|
+
expect(result).toContain("<![CDATA[ https://example.com/ad.xml ]]>");
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("should handle URLs with query parameters", () => {
|
|
249
|
+
const ads = [
|
|
250
|
+
{
|
|
251
|
+
offset: 0,
|
|
252
|
+
ad_url: "https://example.com/ad.xml?param1=value1¶m2=value2",
|
|
253
|
+
},
|
|
254
|
+
];
|
|
255
|
+
|
|
256
|
+
const result = createVMAP(ads);
|
|
257
|
+
|
|
258
|
+
expect(result).toContain(
|
|
259
|
+
"https://example.com/ad.xml?param1=value1¶m2=value2"
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("should handle URLs with special characters", () => {
|
|
264
|
+
const ads = [
|
|
265
|
+
{
|
|
266
|
+
offset: 0,
|
|
267
|
+
ad_url: "https://example.com/ad.xml?url=https%3A%2F%2Ftest.com",
|
|
268
|
+
},
|
|
269
|
+
];
|
|
270
|
+
|
|
271
|
+
const result = createVMAP(ads);
|
|
272
|
+
|
|
273
|
+
expect(result).toContain(
|
|
274
|
+
"https://example.com/ad.xml?url=https%3A%2F%2Ftest.com"
|
|
275
|
+
);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("should handle URLs with ampersands", () => {
|
|
279
|
+
const ads = [
|
|
280
|
+
{ offset: 0, ad_url: "https://example.com/ad.xml?a=1&b=2&c=3" },
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
const result = createVMAP(ads);
|
|
284
|
+
|
|
285
|
+
expect(result).toContain(
|
|
286
|
+
"<![CDATA[ https://example.com/ad.xml?a=1&b=2&c=3 ]]>"
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
describe("complex scenarios", () => {
|
|
292
|
+
it("should create VMAP with preroll, midroll, and postroll", () => {
|
|
293
|
+
const ads = [
|
|
294
|
+
{ offset: 0, ad_url: "https://example.com/preroll.xml" },
|
|
295
|
+
{ offset: 300, ad_url: "https://example.com/midroll1.xml" },
|
|
296
|
+
{ offset: 600, ad_url: "https://example.com/midroll2.xml" },
|
|
297
|
+
{ offset: "postroll", ad_url: "https://example.com/postroll.xml" },
|
|
298
|
+
];
|
|
299
|
+
|
|
300
|
+
const result = createVMAP(ads);
|
|
301
|
+
|
|
302
|
+
expect(result).toContain('timeOffset="00:00:00.000"');
|
|
303
|
+
expect(result).toContain('timeOffset="00:05:00.000"');
|
|
304
|
+
expect(result).toContain('timeOffset="00:10:00.000"');
|
|
305
|
+
expect(result).toContain('timeOffset="end"');
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("should handle ads in any order", () => {
|
|
309
|
+
const ads = [
|
|
310
|
+
{ offset: "postroll", ad_url: "https://example.com/postroll.xml" },
|
|
311
|
+
{ offset: 0, ad_url: "https://example.com/preroll.xml" },
|
|
312
|
+
{ offset: 300, ad_url: "https://example.com/midroll.xml" },
|
|
313
|
+
];
|
|
314
|
+
|
|
315
|
+
const result = createVMAP(ads);
|
|
316
|
+
|
|
317
|
+
// Should preserve order from input array
|
|
318
|
+
const postrollIndex = result.indexOf('timeOffset="end"');
|
|
319
|
+
const prerollIndex = result.indexOf('timeOffset="00:00:00.000"');
|
|
320
|
+
const midrollIndex = result.indexOf('timeOffset="00:05:00.000"');
|
|
321
|
+
|
|
322
|
+
expect(postrollIndex).toBeLessThan(prerollIndex);
|
|
323
|
+
expect(prerollIndex).toBeLessThan(midrollIndex);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it("should handle very long video with multiple ads", () => {
|
|
327
|
+
const ads = Array.from({ length: 10 }, (_, i) => ({
|
|
328
|
+
offset: i * 600, // Every 10 minutes
|
|
329
|
+
ad_url: `https://example.com/ad${i}.xml`,
|
|
330
|
+
}));
|
|
331
|
+
|
|
332
|
+
const result = createVMAP(ads);
|
|
333
|
+
|
|
334
|
+
expect(result).toContain('timeOffset="00:00:00.000"'); // 0 min
|
|
335
|
+
expect(result).toContain('timeOffset="00:10:00.000"'); // 10 min
|
|
336
|
+
expect(result).toContain('timeOffset="01:30:00.000"'); // 90 min
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it("should create valid XML structure", () => {
|
|
340
|
+
const ads = [{ offset: 0, ad_url: "https://example.com/ad.xml" }];
|
|
341
|
+
|
|
342
|
+
const result = createVMAP(ads);
|
|
343
|
+
|
|
344
|
+
// Check for proper XML nesting
|
|
345
|
+
expect(result.split("<vmap:VMAP").length - 1).toBe(1);
|
|
346
|
+
expect(result.split("</vmap:VMAP>").length - 1).toBe(1);
|
|
347
|
+
expect(result.split("<vmap:AdBreak").length - 1).toBe(1);
|
|
348
|
+
expect(result.split("</vmap:AdBreak>").length - 1).toBe(1);
|
|
349
|
+
expect(result.split("<vmap:AdSource").length - 1).toBe(1);
|
|
350
|
+
expect(result.split("</vmap:AdSource>").length - 1).toBe(1);
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
describe("edge cases", () => {
|
|
355
|
+
it("should preserve negative offset in breakId and timeOffset", () => {
|
|
356
|
+
const ads = [{ offset: -100, ad_url: "https://example.com/ad.xml" }];
|
|
357
|
+
|
|
358
|
+
const result = createVMAP(ads);
|
|
359
|
+
|
|
360
|
+
expect(result).toContain('breakId="break--100"');
|
|
361
|
+
expect(result).toContain('timeOffset="-1:-2:-40.000"');
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it("should handle very large offset", () => {
|
|
365
|
+
const ads = [{ offset: 999999, ad_url: "https://example.com/ad.xml" }];
|
|
366
|
+
|
|
367
|
+
const result = createVMAP(ads);
|
|
368
|
+
|
|
369
|
+
expect(result).toContain('timeOffset="277:46:39.000"');
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("should handle empty string offset", () => {
|
|
373
|
+
const ads = [{ offset: "", ad_url: "https://example.com/ad.xml" }];
|
|
374
|
+
|
|
375
|
+
const result = createVMAP(ads);
|
|
376
|
+
|
|
377
|
+
// Empty string is not a valid number, so it should be kept as-is or converted to 0
|
|
378
|
+
expect(result).toContain("timeOffset=");
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it("should handle mixed offset types", () => {
|
|
382
|
+
const ads = [
|
|
383
|
+
{ offset: 0, ad_url: "https://example.com/ad1.xml" },
|
|
384
|
+
{ offset: "60", ad_url: "https://example.com/ad2.xml" },
|
|
385
|
+
{ offset: 120, ad_url: "https://example.com/ad3.xml" },
|
|
386
|
+
{ offset: "postroll", ad_url: "https://example.com/ad4.xml" },
|
|
387
|
+
];
|
|
388
|
+
|
|
389
|
+
const result = createVMAP(ads);
|
|
390
|
+
|
|
391
|
+
expect(result).toContain('timeOffset="00:00:00.000"');
|
|
392
|
+
expect(result).toContain('timeOffset="00:01:00.000"');
|
|
393
|
+
expect(result).toContain('timeOffset="00:02:00.000"');
|
|
394
|
+
expect(result).toContain('timeOffset="end"');
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it("should handle ads with same offset", () => {
|
|
398
|
+
const ads = [
|
|
399
|
+
{ offset: 0, ad_url: "https://example.com/ad1.xml" },
|
|
400
|
+
{ offset: 0, ad_url: "https://example.com/ad2.xml" },
|
|
401
|
+
];
|
|
402
|
+
|
|
403
|
+
const result = createVMAP(ads);
|
|
404
|
+
|
|
405
|
+
// Both should have the same timeOffset but different ad IDs (though ID is also based on offset)
|
|
406
|
+
const matches = result.match(/timeOffset="00:00:00.000"/g);
|
|
407
|
+
expect(matches?.length).toBe(2);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it("should handle URL with newlines and tabs", () => {
|
|
411
|
+
const ads = [{ offset: 0, ad_url: "https://example.com/ad.xml\n\t" }];
|
|
412
|
+
|
|
413
|
+
const result = createVMAP(ads);
|
|
414
|
+
|
|
415
|
+
// trim() should remove the whitespace
|
|
416
|
+
expect(result).toContain("<![CDATA[ https://example.com/ad.xml ]]>");
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
});
|