@applicaster/zapp-react-native-utils 15.0.0-rc.99 → 15.1.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/actionsExecutor/ActionExecutorContext.tsx +51 -5
  2. package/actionsExecutor/feedDecorator.ts +6 -6
  3. package/adsUtils/index.ts +2 -2
  4. package/analyticsUtils/README.md +1 -1
  5. package/appUtils/HooksManager/__tests__/__snapshots__/hooksManager.test.js.snap +188 -0
  6. package/appUtils/HooksManager/__tests__/hooksManager.test.js +2 -16
  7. package/appUtils/HooksManager/index.ts +45 -10
  8. package/appUtils/RiverFocusManager/{index.ts → index.js} +18 -25
  9. package/appUtils/accessibilityManager/const.ts +0 -4
  10. package/appUtils/accessibilityManager/utils.ts +1 -0
  11. package/appUtils/contextKeysManager/__tests__/getKeys/failure.test.ts +2 -7
  12. package/appUtils/contextKeysManager/__tests__/getKeys/success.test.ts +0 -48
  13. package/appUtils/contextKeysManager/contextResolver.ts +22 -51
  14. package/appUtils/contextKeysManager/index.ts +10 -65
  15. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +0 -3
  16. package/appUtils/focusManager/index.ios.ts +4 -43
  17. package/appUtils/focusManagerAux/utils/index.ts +3 -5
  18. package/appUtils/focusManagerAux/utils/utils.ios.ts +3 -199
  19. package/appUtils/keyCodes/keys/keys.web.ts +4 -1
  20. package/appUtils/localizationsHelper.ts +4 -0
  21. package/appUtils/orientationHelper.ts +4 -2
  22. package/appUtils/platform/platformUtils.ts +1 -1
  23. package/appUtils/playerManager/index.ts +9 -0
  24. package/appUtils/playerManager/player.ts +1 -5
  25. package/appUtils/playerManager/playerNative.ts +22 -30
  26. package/appUtils/playerManager/usePlayer.tsx +5 -3
  27. package/appUtils/playerManager/usePlayerState.tsx +2 -14
  28. package/cellUtils/__tests__/cellUtils.test.ts +39 -0
  29. package/cellUtils/index.ts +11 -1
  30. package/cloudEventsUtils/index.ts +1 -65
  31. package/configurationUtils/__tests__/manifestKeyParser.test.ts +26 -26
  32. package/dateUtils/__tests__/dayjs.test.ts +0 -3
  33. package/dateUtils/index.ts +2 -0
  34. package/focusManager/aux/index.ts +1 -1
  35. package/headersUtils/__tests__/headersUtils.test.js +1 -11
  36. package/headersUtils/index.ts +1 -2
  37. package/manifestUtils/defaultManifestConfigurations/player.js +7 -75
  38. package/manifestUtils/tvAction/container/index.js +1 -1
  39. package/numberUtils/__tests__/toNumber.test.ts +0 -12
  40. package/numberUtils/index.ts +1 -19
  41. package/package.json +4 -4
  42. package/pluginUtils/index.ts +4 -9
  43. package/reactHooks/advertising/index.ts +2 -2
  44. package/reactHooks/app/__tests__/useAppState.test.ts +1 -1
  45. package/reactHooks/autoscrolling/__tests__/useTrackCurrentAutoScrollingElement.test.ts +1 -1
  46. package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +2 -1
  47. package/reactHooks/casting/index.ts +1 -0
  48. package/reactHooks/casting/useIsCasting.tsx +57 -0
  49. package/reactHooks/cell-click/__tests__/index.test.js +3 -1
  50. package/reactHooks/componentsMap/index.ts +55 -0
  51. package/reactHooks/configuration/__tests__/index.test.tsx +1 -1
  52. package/reactHooks/connection/__tests__/index.test.js +1 -1
  53. package/reactHooks/debugging/__tests__/index.test.js +4 -4
  54. package/reactHooks/device/useIsTablet.tsx +19 -14
  55. package/reactHooks/device/useMemoizedIsTablet.ts +3 -3
  56. package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +23 -32
  57. package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +19 -19
  58. package/reactHooks/feed/__tests__/useEntryScreenId.test.tsx +1 -4
  59. package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +30 -42
  60. package/reactHooks/feed/__tests__/useFeedRefresh.test.tsx +1 -1
  61. package/reactHooks/feed/__tests__/useInflatedUrl.test.tsx +1 -1
  62. package/reactHooks/feed/useBatchLoading.ts +1 -7
  63. package/reactHooks/feed/useEntryScreenId.ts +2 -2
  64. package/reactHooks/feed/useInflatedUrl.ts +1 -1
  65. package/reactHooks/feed/usePipesCacheReset.ts +1 -3
  66. package/reactHooks/flatList/useLoadNextPageIfNeeded.ts +16 -13
  67. package/reactHooks/layout/__tests__/index.test.tsx +1 -1
  68. package/reactHooks/layout/__tests__/useLayoutVersion.test.tsx +1 -1
  69. package/reactHooks/layout/useDimensions/__tests__/{useDimensions.test.tsx → useDimensions.test.ts} +25 -105
  70. package/reactHooks/layout/useDimensions/useDimensions.ts +2 -2
  71. package/reactHooks/navigation/__tests__/index.test.tsx +4 -2
  72. package/reactHooks/navigation/index.ts +6 -7
  73. package/reactHooks/navigation/useRoute.ts +6 -8
  74. package/reactHooks/player/TVSeekControlller/TVSeekController.ts +10 -27
  75. package/reactHooks/player/__tests__/useAutoSeek._test.tsx +1 -1
  76. package/reactHooks/player/__tests__/useTapSeek._test.ts +1 -1
  77. package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +1 -1
  78. package/reactHooks/resolvers/__tests__/useComponentResolver.test.tsx +1 -1
  79. package/reactHooks/resolvers/useCellResolver.ts +2 -6
  80. package/reactHooks/resolvers/useComponentResolver.ts +13 -9
  81. package/reactHooks/screen/__tests__/useCurrentScreenData.test.tsx +2 -2
  82. package/reactHooks/screen/__tests__/useCurrentScreenIsHook.test.ts +103 -0
  83. package/reactHooks/screen/__tests__/useCurrentScreenIsStartupHook.test.ts +94 -0
  84. package/reactHooks/screen/__tests__/useScreenBackgroundColor.test.tsx +1 -1
  85. package/reactHooks/screen/__tests__/useScreenData.test.tsx +1 -1
  86. package/reactHooks/screen/__tests__/useTargetScreenData.test.tsx +4 -12
  87. package/reactHooks/screen/index.ts +6 -0
  88. package/reactHooks/screen/useCurrentScreenIsHook.ts +9 -0
  89. package/reactHooks/screen/useCurrentScreenIsStartupHook.ts +8 -0
  90. package/reactHooks/screen/useIsStandaloneFullscreen.ts +12 -0
  91. package/reactHooks/screen/useTargetScreenData.ts +2 -4
  92. package/reactHooks/state/__tests__/useComponentScreenState.test.ts +246 -0
  93. package/reactHooks/state/index.ts +2 -0
  94. package/reactHooks/state/useComponentScreenState.ts +45 -0
  95. package/reactHooks/state/useRivers.ts +1 -1
  96. package/reactHooks/usePluginConfiguration.ts +2 -2
  97. package/reactHooks/utils/__tests__/index.test.js +1 -1
  98. package/rectUtils/index.ts +2 -2
  99. package/screenPickerUtils/index.ts +5 -0
  100. package/screenState/__tests__/index.test.ts +1 -1
  101. package/stringUtils/index.ts +1 -1
  102. package/testUtils/index.tsx +21 -30
  103. package/typeGuards/index.ts +0 -4
  104. package/utils/__tests__/clone.test.ts +158 -0
  105. package/utils/clone.ts +7 -0
  106. package/utils/index.ts +1 -13
  107. package/utils/path.ts +3 -6
  108. package/utils/pathOr.ts +1 -5
  109. package/zappFrameworkUtils/HookCallback/callbackNavigationAction.ts +1 -1
  110. package/zappFrameworkUtils/HookCallback/hookCallbackManifestExtensions.config.js +1 -1
  111. package/zappFrameworkUtils/loginPluginUtils.ts +1 -1
  112. package/adsUtils/__tests__/createVMAP.test.ts +0 -419
  113. package/appDataUtils/__tests__/urlScheme.test.ts +0 -678
  114. package/appUtils/focusManagerAux/utils/index.ios.ts +0 -122
  115. package/cloudEventsUtils/__tests__/index.test.ts +0 -529
  116. package/enumUtils/__tests__/getEnumKeyByEnumValue.test.ts +0 -207
  117. package/errorUtils/__tests__/GeneralError.test.ts +0 -97
  118. package/errorUtils/__tests__/HttpStatusCode.test.ts +0 -344
  119. package/errorUtils/__tests__/MissingPluginError.test.ts +0 -113
  120. package/errorUtils/__tests__/NetworkError.test.ts +0 -202
  121. package/errorUtils/__tests__/getParsedResponse.test.ts +0 -188
  122. package/errorUtils/__tests__/invariant.test.ts +0 -112
  123. package/numberUtils/__tests__/toPositiveNumber.test.ts +0 -165
  124. package/reactHooks/analytics/__tests__/useSendAnalyticsOnPress.test.ts +0 -537
  125. package/reactHooks/dev/__tests__/useReRenderLog.test.ts +0 -188
  126. package/reactHooks/ui/__tests__/useFadeOutWhenBlurred.test.ts +0 -580
  127. package/rectUtils/__tests__/index.test.ts +0 -549
  128. package/screenPickerUtils/__tests__/index.test.ts +0 -333
  129. package/searchUtils/const.ts +0 -7
  130. package/searchUtils/index.ts +0 -3
  131. package/time/__tests__/BackgroundTimer.test.ts +0 -156
  132. package/time/__tests__/Timer.test.ts +0 -236
  133. package/typeGuards/__tests__/isString.test.ts +0 -21
  134. package/utils/__tests__/mergeRight.test.ts +0 -48
  135. package/utils/__tests__/selectors.test.ts +0 -124
  136. package/utils/mergeRight.ts +0 -5
  137. package/utils/selectors.ts +0 -46
@@ -0,0 +1,158 @@
1
+ import { clone } from "../clone";
2
+
3
+ describe("clone", () => {
4
+ const originalStructuredClone = global.structuredClone;
5
+
6
+ afterEach(() => {
7
+ // Restore the original structuredClone after each test
8
+ global.structuredClone = originalStructuredClone;
9
+ });
10
+
11
+ describe("when structuredClone is available", () => {
12
+ beforeEach(() => {
13
+ // Mock structuredClone if it's not available
14
+ if (typeof global.structuredClone === "undefined") {
15
+ global.structuredClone = jest.fn((value) =>
16
+ JSON.parse(JSON.stringify(value))
17
+ );
18
+ }
19
+ });
20
+
21
+ test("clones primitive values", () => {
22
+ expect(clone(42)).toBe(42);
23
+ expect(clone("test")).toBe("test");
24
+ expect(clone(true)).toBe(true);
25
+ expect(clone(null)).toBe(null);
26
+ });
27
+
28
+ test("clones simple objects", () => {
29
+ const original = { a: 1, b: 2 };
30
+ const cloned = clone(original);
31
+
32
+ expect(cloned).toEqual(original);
33
+ expect(cloned).not.toBe(original);
34
+ });
35
+
36
+ test("clones nested objects", () => {
37
+ const original = { a: { b: { c: 1 } } };
38
+ const cloned = clone(original);
39
+
40
+ expect(cloned).toEqual(original);
41
+ expect(cloned).not.toBe(original);
42
+ expect(cloned.a).not.toBe(original.a);
43
+ expect(cloned.a.b).not.toBe(original.a.b);
44
+ });
45
+
46
+ test("clones arrays", () => {
47
+ const original = [1, 2, 3];
48
+ const cloned = clone(original);
49
+
50
+ expect(cloned).toEqual(original);
51
+ expect(cloned).not.toBe(original);
52
+ });
53
+
54
+ test("clones arrays with nested objects", () => {
55
+ const original = [{ a: 1 }, { b: 2 }];
56
+ const cloned = clone(original);
57
+
58
+ expect(cloned).toEqual(original);
59
+ expect(cloned).not.toBe(original);
60
+ expect(cloned[0]).not.toBe(original[0]);
61
+ expect(cloned[1]).not.toBe(original[1]);
62
+ });
63
+
64
+ test("does not mutate the original value", () => {
65
+ const original = { a: 1, b: { c: 2 } };
66
+ const cloned = clone(original);
67
+
68
+ cloned.a = 99;
69
+ cloned.b.c = 99;
70
+
71
+ expect(original.a).toBe(1);
72
+ expect(original.b.c).toBe(2);
73
+ });
74
+ });
75
+
76
+ describe("when structuredClone is not available (fallback to cloneDeep)", () => {
77
+ beforeEach(() => {
78
+ // Delete structuredClone to force fallback to cloneDeep
79
+ delete (global as any).structuredClone;
80
+ });
81
+
82
+ test("clones primitive values using cloneDeep", () => {
83
+ expect(clone(42)).toBe(42);
84
+ expect(clone("test")).toBe("test");
85
+ expect(clone(true)).toBe(true);
86
+ expect(clone(null)).toBe(null);
87
+ });
88
+
89
+ test("clones simple objects using cloneDeep", () => {
90
+ const original = { a: 1, b: 2 };
91
+ const cloned = clone(original);
92
+
93
+ expect(cloned).toEqual(original);
94
+ expect(cloned).not.toBe(original);
95
+ });
96
+
97
+ test("clones nested objects using cloneDeep", () => {
98
+ const original = { a: { b: { c: 1 } } };
99
+ const cloned = clone(original);
100
+
101
+ expect(cloned).toEqual(original);
102
+ expect(cloned).not.toBe(original);
103
+ expect(cloned.a).not.toBe(original.a);
104
+ expect(cloned.a.b).not.toBe(original.a.b);
105
+ });
106
+
107
+ test("clones arrays using cloneDeep", () => {
108
+ const original = [1, 2, 3];
109
+ const cloned = clone(original);
110
+
111
+ expect(cloned).toEqual(original);
112
+ expect(cloned).not.toBe(original);
113
+ });
114
+
115
+ test("clones arrays with nested objects using cloneDeep", () => {
116
+ const original = [{ a: 1 }, { b: 2 }];
117
+ const cloned = clone(original);
118
+
119
+ expect(cloned).toEqual(original);
120
+ expect(cloned).not.toBe(original);
121
+ expect(cloned[0]).not.toBe(original[0]);
122
+ expect(cloned[1]).not.toBe(original[1]);
123
+ });
124
+
125
+ test("does not mutate the original value when using cloneDeep", () => {
126
+ const original = { a: 1, b: { c: 2 } };
127
+ const cloned = clone(original);
128
+
129
+ cloned.a = 99;
130
+ cloned.b.c = 99;
131
+
132
+ expect(original.a).toBe(1);
133
+ expect(original.b.c).toBe(2);
134
+ });
135
+
136
+ test("handles complex objects with multiple levels", () => {
137
+ const original = {
138
+ name: "test",
139
+ data: {
140
+ items: [
141
+ { id: 1, value: "a" },
142
+ { id: 2, value: "b" },
143
+ ],
144
+ meta: { count: 2 },
145
+ },
146
+ };
147
+
148
+ const cloned = clone(original);
149
+
150
+ expect(cloned).toEqual(original);
151
+ expect(cloned).not.toBe(original);
152
+ expect(cloned.data).not.toBe(original.data);
153
+ expect(cloned.data.items).not.toBe(original.data.items);
154
+ expect(cloned.data.items[0]).not.toBe(original.data.items[0]);
155
+ expect(cloned.data.meta).not.toBe(original.data.meta);
156
+ });
157
+ });
158
+ });
package/utils/clone.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { cloneDeep } from "lodash";
2
+
3
+ export const clone = (value) => {
4
+ return typeof structuredClone !== "undefined"
5
+ ? structuredClone(value) // no support for hermes, fallback to cloneDeep
6
+ : cloneDeep(value);
7
+ };
package/utils/index.ts CHANGED
@@ -18,10 +18,9 @@ export { take } from "./take";
18
18
 
19
19
  export { mapAccum } from "./mapAccum";
20
20
 
21
- export { mergeRight } from "./mergeRight";
21
+ export { clone } from "./clone";
22
22
 
23
23
  export {
24
- cloneDeep as clone,
25
24
  flatten,
26
25
  drop,
27
26
  size,
@@ -38,20 +37,9 @@ export {
38
37
  last,
39
38
  toLower,
40
39
  isEqual as equals,
41
- uniq,
42
40
  uniqWith,
43
41
  flowRight as compose,
44
42
  partial,
45
- clamp,
46
43
  reverse,
47
- takeRight,
48
- fromPairs,
49
- sortBy,
50
- merge,
51
- values,
52
- head,
53
- findIndex,
54
- set,
55
44
  compact,
56
- identity,
57
45
  } from "lodash";
package/utils/path.ts CHANGED
@@ -1,8 +1,5 @@
1
- import { pathOr } from "./pathOr";
1
+ import { get } from "lodash";
2
2
 
3
- export const path = <T = any>(
4
- route: (string | number) | (string | number)[],
5
- record: any
6
- ) => {
7
- return pathOr<T>(undefined, route, record);
3
+ export const path = (route, record) => {
4
+ return get(record, route, undefined);
8
5
  };
package/utils/pathOr.ts CHANGED
@@ -1,9 +1,5 @@
1
1
  import { get } from "lodash";
2
2
 
3
- export const pathOr = <T = any>(
4
- defaultValue: T,
5
- path: (number | string) | (number | string)[],
6
- record: any
7
- ): T => {
3
+ export const pathOr = (defaultValue, path, record) => {
8
4
  return get(record, path, defaultValue);
9
5
  };
@@ -85,7 +85,7 @@ export const getNavigationKeys = (
85
85
  const general = (item?.general ?? {}) as General;
86
86
 
87
87
  const pluginIdentifier =
88
- (item as any).identifier ?? item?.type ?? item?.component_type ?? "";
88
+ (item as any)?.identifier ?? item?.type ?? item?.component_type ?? "";
89
89
 
90
90
  const legacy =
91
91
  legacyMappingKeys[`${pluginIdentifier}.${resultType}`] ??
@@ -20,7 +20,7 @@ const callbackKeyPrefix = (key, prefix, keySeparator = "_") =>
20
20
 
21
21
  const extendManifestWithHookCallback = (prefix = null) => ({
22
22
  group: true,
23
- label: `CallBack Navigation: ${prefix}`,
23
+ label: prefix ? `Callback Navigation: ${prefix}` : "Callback Navigation",
24
24
  folded: true,
25
25
  fields: [
26
26
  {
@@ -20,7 +20,7 @@ const isAuthenticationRequired = ({ payload, logs }) => {
20
20
 
21
21
  if (!payload) {
22
22
  log_info(
23
- "isAuthenticationRequired: No payload exist, authentication required"
23
+ "isAuthenticationRequired: No payload exists, authentication required"
24
24
  );
25
25
 
26
26
  return true;
@@ -1,419 +0,0 @@
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&param2=value2",
253
- },
254
- ];
255
-
256
- const result = createVMAP(ads);
257
-
258
- expect(result).toContain(
259
- "https://example.com/ad.xml?param1=value1&param2=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
- });