@applicaster/zapp-react-native-utils 13.0.0-alpha.5771418585 → 13.0.0-alpha.5814285061

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 (83) hide show
  1. package/actionsExecutor/ActionExecutorContext.tsx +126 -6
  2. package/actionsExecutor/consts.ts +4 -0
  3. package/analyticsUtils/currentScreenProperities.ts +6 -4
  4. package/appUtils/HooksManager/Hook.ts +4 -0
  5. package/appUtils/HooksManager/index.ts +3 -1
  6. package/appUtils/__tests__/__snapshots__/localizationsHelper.test.ts.snap +151 -0
  7. package/appUtils/__tests__/allZappLocales.ts +79 -0
  8. package/appUtils/__tests__/{localizationsHelper.test.js → localizationsHelper.test.ts} +11 -0
  9. package/appUtils/contextKeysManager/contextResolver.ts +1 -12
  10. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +2 -0
  11. package/appUtils/focusManager/index.ios.ts +14 -4
  12. package/appUtils/focusManager/index.ts +14 -10
  13. package/appUtils/focusManager/utils/__tests__/findChild.test.ts +35 -0
  14. package/appUtils/focusManager/utils/index.ts +5 -0
  15. package/appUtils/keyCodes/index.ts +11 -7
  16. package/appUtils/localizationsHelper.ts +31 -0
  17. package/appUtils/orientationHelper.ts +11 -13
  18. package/appUtils/playerManager/index.ts +1 -1
  19. package/appUtils/playerManager/player.ts +31 -20
  20. package/appUtils/playerManager/playerHooks/usePlayerCurrentTime.tsx +11 -7
  21. package/appUtils/playerManager/playerNative.ts +23 -8
  22. package/appUtils/playerManager/usePlayerTitleSummaryOverlay.tsx +7 -1
  23. package/cellUtils/index.ts +9 -5
  24. package/componentsUtils/__tests__/isFirstComponentGallery.test.ts +33 -0
  25. package/componentsUtils/__tests__/isFirstComponentScreenPicker.test.ts +33 -0
  26. package/componentsUtils/__tests__/isGallery.test.ts +33 -0
  27. package/componentsUtils/__tests__/isScreenPicker.test.ts +33 -0
  28. package/componentsUtils/index.ts +39 -0
  29. package/dateUtils/index.ts +19 -0
  30. package/equalUtils/__tests__/isEqual/combination.test.ts +263 -0
  31. package/equalUtils/index.ts +41 -29
  32. package/focusManager/FocusManager.ts +3 -1
  33. package/focusManager/index.ts +23 -4
  34. package/functionUtils/index.ts +46 -8
  35. package/index.d.ts +0 -12
  36. package/localizationUtils/index.ts +8 -13
  37. package/manifestUtils/defaultManifestConfigurations/generalContent.js +13 -0
  38. package/manifestUtils/defaultManifestConfigurations/player.js +14 -0
  39. package/manifestUtils/fonts/index.js +5 -6
  40. package/manifestUtils/keys.js +27 -2
  41. package/manifestUtils/sharedConfiguration/screenPicker/__tests__/utils.test.js +304 -0
  42. package/manifestUtils/sharedConfiguration/screenPicker/stylesFields.js +875 -0
  43. package/manifestUtils/sharedConfiguration/screenPicker/utils.js +130 -0
  44. package/modalState/__tests__/index.test.ts +51 -0
  45. package/modalState/index.ts +32 -2
  46. package/navigationUtils/__tests__/__snapshots__/navigationUtils.test.js.snap +1 -0
  47. package/navigationUtils/index.ts +3 -1
  48. package/numberUtils/__tests__/toPositiveNumberWithDefault.test.ts +23 -3
  49. package/numberUtils/index.ts +11 -5
  50. package/package.json +4 -10
  51. package/playerUtils/configurationGenerator.ts +14 -8
  52. package/reactHooks/advertising/index.ts +5 -5
  53. package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +5 -7
  54. package/reactHooks/autoscrolling/useTrackedView.ts +12 -1
  55. package/reactHooks/feed/useBatchLoading.ts +10 -12
  56. package/reactHooks/feed/useFeedLoader.tsx +17 -8
  57. package/reactHooks/hookModal/hooks/useHookModalScreenData.ts +39 -0
  58. package/reactHooks/navigation/__mocks__/useNavigation.ts +3 -0
  59. package/reactHooks/navigation/__tests__/index.test.tsx +6 -17
  60. package/reactHooks/navigation/index.ts +6 -4
  61. package/reactHooks/navigation/useRoute.ts +4 -6
  62. package/reactHooks/player/TVSeekControlller/TVSeekController.ts +13 -40
  63. package/reactHooks/player/TVSeekControlller/TapSeekController.ts +3 -10
  64. package/reactHooks/screen/__tests__/useScreenBackgroundColor.test.tsx +69 -0
  65. package/reactHooks/screen/useScreenBackgroundColor.ts +3 -15
  66. package/reactHooks/useListenEventBusEvent.ts +1 -1
  67. package/reactUtils/__tests__/reactUtils.test.js +1 -19
  68. package/reactUtils/createContext/__tests__/{index.test.js → index.test.ts} +2 -2
  69. package/reactUtils/createContext/helpers.ts +6 -1
  70. package/reactUtils/createContext/index.ts +11 -25
  71. package/reactUtils/index.ts +0 -11
  72. package/services/js2native.ts +673 -0
  73. package/time/BackgroundTimer.ts +55 -0
  74. package/time/Timer.ts +6 -6
  75. package/utils/README.md +30 -0
  76. package/utils/chunk.ts +5 -0
  77. package/utils/index.ts +5 -0
  78. package/utils/times.ts +5 -0
  79. package/zappFrameworkUtils/localStorageHelper.ts +6 -0
  80. package/zappFrameworkUtils/loginPluginUtils.ts +6 -2
  81. package/zappFrameworkUtils/types.ts +11 -0
  82. package/reactUtils/createContext/__tests__/index-no-react.test.js +0 -27
  83. /package/reactUtils/createContext/__tests__/__snapshots__/{index.test.js.snap → index.test.ts.snap} +0 -0
@@ -1,5 +1,3 @@
1
- import * as R from "ramda";
2
-
3
1
  // more keys can be added here. Simply make sure
4
2
  // the key matches the code value
5
3
 
@@ -34,12 +32,18 @@ export function keyCode(key) {
34
32
  keyCode(keyEvent).matchesAny("Enter", "Backspace", KEYS.ArrowLeft, ...)
35
33
  */
36
34
 
37
- const matches = R.compose(
38
- R.either(R.eqProps("code", key), R.eqProps("keyCode", key)),
39
- R.when(R.is(String), R.prop(R.__, KEYS))
40
- );
35
+ const matches = (compareKey) => {
36
+ // If compareKey is a string, look it up in KEYS object
37
+ const keyToCompare =
38
+ typeof compareKey === "string" ? KEYS[compareKey] : compareKey;
39
+
40
+ // Compare either code or keyCode properties
41
+ return (
42
+ keyToCompare?.code === key.code || keyToCompare?.keyCode === key.keyCode
43
+ );
44
+ };
41
45
 
42
- const matchesAny = (...args) => R.any(matches, args);
46
+ const matchesAny = (...args) => args.some(matches);
43
47
 
44
48
  const direction = () =>
45
49
  matchesAny(...ARROW_KEYS) ? directionHelper(key) : null;
@@ -65,6 +65,37 @@ export function getUILanguage() {
65
65
  return getLanguageCode();
66
66
  }
67
67
 
68
+ /**
69
+ * fallback to en for missing dayjslocalization
70
+ * converts the locale code to a dayjs compatible locale code
71
+ * lowercase input code is expected, e.g. "en-GB" => "en-gb"
72
+ * @param {string} code - locale code, e.g. "en", "fr", "es-LA"
73
+ * */
74
+ export const toDayJSLocaleMap = (code) => {
75
+ const map = {
76
+ hy: "hy-am",
77
+ fj: null,
78
+ la: null,
79
+ no: null,
80
+ pa: "pa-in",
81
+ qu: null,
82
+ sm: null,
83
+ to: null,
84
+ xh: null,
85
+ tt: null,
86
+ "es-LA": "es",
87
+ "en-UK": "en-gb",
88
+ };
89
+
90
+ if (map[code] === null) {
91
+ return "en";
92
+ } else if (map[code]) {
93
+ return map[code];
94
+ } else {
95
+ return code.toLowerCase();
96
+ }
97
+ };
98
+
68
99
  /**
69
100
  * returns the localization for the app based on the locale. If the locale is null
70
101
  * or undefined, or if the given locale doesn't exist in the localizations,
@@ -1,8 +1,8 @@
1
- /// <reference types="@types/react-native" />
2
1
  import * as ReactNative from "react-native";
3
- import { isTV, platformSelect } from "../reactUtils";
4
2
  import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
5
- import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks/device/useIsTablet";
3
+
4
+ import { isTV, platformSelect } from "../reactUtils";
5
+ import { useIsTablet } from "../reactHooks";
6
6
 
7
7
  import {
8
8
  QUICK_BRICK_EVENTS,
@@ -121,8 +121,15 @@ export const removeOrientationChangeListener = (listener: any): void => {
121
121
  */
122
122
  export const isOrientationCompatible = ({
123
123
  orientation,
124
- layoutData: { width, height },
124
+ layoutData: { width, height, isTablet, isTabletPortrait },
125
125
  }) => {
126
+ // We count portrait as compatible, because if default in zapp for mobile layout
127
+ // We count landscape as compatible, because if isTabletPortrait is false,
128
+ // it defaults for device
129
+ if (isTablet && !isTabletPortrait) {
130
+ return true;
131
+ }
132
+
126
133
  const isLandscape = width > height;
127
134
 
128
135
  if (
@@ -146,15 +153,6 @@ export const isOrientationCompatible = ({
146
153
  return true;
147
154
  };
148
155
 
149
- export const useIsOrientationCompatible = (orientation) => {
150
- const { height, width } = ReactNative.useWindowDimensions();
151
-
152
- return isOrientationCompatible({
153
- orientation,
154
- layoutData: { width, height },
155
- });
156
- };
157
-
158
156
  /**
159
157
  * Determine final screen orientation based on device type,
160
158
  * settings and orientation value provided by screen data settings
@@ -452,7 +452,7 @@ export class PlayerManager {
452
452
  if (this.castingReceiver) {
453
453
  this.castingReceiver.closeNativePlayer();
454
454
  } else {
455
- this.getActivePlayer().closeNativePlayer();
455
+ this.getActivePlayer()?.closeNativePlayer();
456
456
  this.invokeHandler("close");
457
457
  }
458
458
  }
@@ -97,8 +97,7 @@ export class Player {
97
97
  this.onVideoLoad = this.onVideoLoad.bind(this);
98
98
  this.isFullScreenSupported = this.isFullScreenSupported.bind(this);
99
99
  this.onPlayerSeekComplete = this.onPlayerSeekComplete.bind(this);
100
-
101
- this.isPaused = this.isPaused.bind(this);
100
+ this.onAdBreakEnd = this.onAdBreakEnd.bind(this);
102
101
  this.entryObservable = null;
103
102
  }
104
103
 
@@ -202,17 +201,25 @@ export class Player {
202
201
  adPosition: currentTime,
203
202
  };
204
203
  } else {
205
- if (!isEmptyOrNil(seekableDuration) && seekableDuration >= 0) {
206
- this.playerState.seekableDuration = seekableDuration;
207
- }
204
+ this.updateContentDuration({ seekableDuration, duration, currentTime });
205
+ }
206
+ };
208
207
 
209
- if (!isEmptyOrNil(duration) && duration >= 0) {
210
- this.playerState.contentDuration = duration;
211
- }
208
+ protected updateContentDuration = ({
209
+ seekableDuration = null,
210
+ duration = null,
211
+ currentTime = null,
212
+ }) => {
213
+ if (!isEmptyOrNil(seekableDuration) && seekableDuration >= 0) {
214
+ this.playerState.seekableDuration = seekableDuration;
215
+ }
212
216
 
213
- if (!isEmptyOrNil(currentTime) && currentTime >= 0) {
214
- this.playerState.contentPosition = currentTime;
215
- }
217
+ if (!isEmptyOrNil(duration) && duration >= 0) {
218
+ this.playerState.contentDuration = duration;
219
+ }
220
+
221
+ if (!isEmptyOrNil(currentTime) && currentTime >= 0) {
222
+ this.playerState.contentPosition = currentTime;
216
223
  }
217
224
  };
218
225
 
@@ -254,9 +261,9 @@ export class Player {
254
261
  return this.playerState.seekPosition !== null;
255
262
  };
256
263
 
257
- isPaused() {
264
+ isPaused = () => {
258
265
  return !!this.playerState.isPaused;
259
- }
266
+ };
260
267
 
261
268
  isPlaying = () => {
262
269
  return !!this.playerState.isPaused === false;
@@ -291,11 +298,11 @@ export class Player {
291
298
  };
292
299
 
293
300
  // Methods to call actions on the player
294
- seekTo = async (_position: number) => {};
301
+ seekTo = (_position: number) => {};
295
302
 
296
- forward = async (_deltaTime: number) => {};
303
+ forward = (_deltaTime: number) => {};
297
304
 
298
- rewind = async (_deltaTime: number) => {};
305
+ rewind = (_deltaTime: number) => {};
299
306
 
300
307
  /**
301
308
  * Determine whether content is live using three checks
@@ -560,7 +567,7 @@ export class Player {
560
567
  });
561
568
  };
562
569
 
563
- onAdBreakEnd = (event) => {
570
+ onAdBreakEnd(event) {
564
571
  this.playerState.adState = null;
565
572
  this.playerState.isInAdBreak = false;
566
573
 
@@ -568,7 +575,7 @@ export class Player {
568
575
  callbackName: SharedPlayerCallBacksKeys.OnAdBreakEnd,
569
576
  event,
570
577
  });
571
- };
578
+ }
572
579
 
573
580
  onAdEnd = (event) => {
574
581
  this.playerState.adState = null;
@@ -702,8 +709,8 @@ export class Player {
702
709
  onPlayerAttached: this.onPlayerAttached,
703
710
  });
704
711
 
705
- play = async () => {};
706
- pause = async () => {};
712
+ play = () => {};
713
+ pause = () => {};
707
714
  disableBufferAnimation = (): boolean => true;
708
715
 
709
716
  setPlaybackRate = (_rate) => {};
@@ -737,6 +744,10 @@ export class Player {
737
744
  return null;
738
745
  };
739
746
 
747
+ public hasResumePosition() {
748
+ return false;
749
+ }
750
+
740
751
  public getConfig = () => this.config;
741
752
 
742
753
  // TODO: replace with enum of supported orientations
@@ -12,7 +12,7 @@ import { useThrottle } from "../../../functionUtils";
12
12
  export const usePlayerCurrentTime = (
13
13
  listenerId: string,
14
14
  playerId?: string
15
- ): number | null => {
15
+ ): { currentTime: number | null; cancel: () => void } => {
16
16
  const [currentTime, setCurrentTime] = useState<number | null>(null);
17
17
  const player: Player = usePlayer(playerId);
18
18
 
@@ -20,15 +20,17 @@ export const usePlayerCurrentTime = (
20
20
  if (player) {
21
21
  const newCurrentTime = player.getContentPosition();
22
22
 
23
- setCurrentTime((prevTime) =>
24
- prevTime !== newCurrentTime ? newCurrentTime : prevTime
25
- );
23
+ setCurrentTime(newCurrentTime);
26
24
  }
27
25
  }, [player]);
28
26
 
27
+ // We update the current time every ~250ms since player can have rate set to 2x.
28
+ // We update more often than 2x a second to avoid noticiable 'pulsation' in update,
29
+ // since native level also updates the current time every ~250ms,
30
+ // and combined with assorted delays it creates interference pattern.
29
31
  const { throttledFunction: throttledUpdate, cancel } = useThrottle(
30
32
  updateCurrentTime,
31
- 1000
33
+ 1000 / 4
32
34
  );
33
35
 
34
36
  useEffect(() => {
@@ -36,7 +38,9 @@ export const usePlayerCurrentTime = (
36
38
  throttledUpdate();
37
39
 
38
40
  const listeners = {
39
- onVideoProgress: throttledUpdate,
41
+ onVideoProgress: () => {
42
+ setCurrentTime(player.getContentPosition());
43
+ },
40
44
  };
41
45
 
42
46
  const id = `${listenerId}_player_current_time`;
@@ -53,5 +57,5 @@ export const usePlayerCurrentTime = (
53
57
  }
54
58
  }, [player, listenerId, throttledUpdate, cancel]);
55
59
 
56
- return currentTime;
60
+ return { currentTime, cancel };
57
61
  };
@@ -54,6 +54,10 @@ export class PlayerNative extends Player {
54
54
  });
55
55
  }
56
56
 
57
+ public hasResumePosition() {
58
+ return true;
59
+ }
60
+
57
61
  getContentPosition = () => {
58
62
  return this.playerState.contentPosition;
59
63
  };
@@ -95,7 +99,7 @@ export class PlayerNative extends Player {
95
99
  };
96
100
 
97
101
  // Methods to call actions on the player
98
- seekTo = async (position: number) => {
102
+ seekTo = (position: number) => {
99
103
  if (!this.playerState.seekableDuration) {
100
104
  log_warning("seekTo: Can not seek, video is not seekable");
101
105
 
@@ -123,7 +127,7 @@ export class PlayerNative extends Player {
123
127
  this.logState(PlayerModuleFuncNames.seekTo, { position });
124
128
  };
125
129
 
126
- forward = async (deltaTime: number) => {
130
+ forward = (deltaTime: number) => {
127
131
  if (isEmptyOrNil(this.playerState.contentPosition)) {
128
132
  return;
129
133
  }
@@ -139,7 +143,7 @@ export class PlayerNative extends Player {
139
143
  this.logState(PlayerModuleFuncNames.forward, { deltaTime });
140
144
  };
141
145
 
142
- rewind = async (deltaTime: number) => {
146
+ rewind = (deltaTime: number) => {
143
147
  if (isEmptyOrNil(this.playerState.contentPosition)) {
144
148
  return;
145
149
  }
@@ -155,13 +159,13 @@ export class PlayerNative extends Player {
155
159
  this.logState(PlayerModuleFuncNames.rewind, { deltaTime });
156
160
  };
157
161
 
158
- play = async () => {
162
+ play = () => {
159
163
  if (!this.invokeNativeFunction(PlayerModuleFuncNames.play)) {
160
164
  this.currentPlayerComponent()?.[PlayerModuleFuncNames.play]?.();
161
165
  }
162
166
  };
163
167
 
164
- pause = async () => {
168
+ pause = () => {
165
169
  if (!this.invokeNativeFunction(PlayerModuleFuncNames.pause)) {
166
170
  this.currentPlayerComponent()?.[PlayerModuleFuncNames.pause]?.();
167
171
  }
@@ -221,11 +225,13 @@ export class PlayerNative extends Player {
221
225
  };
222
226
 
223
227
  onVideoLoad = (event) => {
224
- if (this.startPosition) {
225
- this.seekTo(this.startPosition);
226
- this.startPosition = null;
228
+ this.updateContentDuration(event);
227
229
 
230
+ if (this.startPosition && !this.playerState.isInAdBreak) {
228
231
  super.onVideoLoad({ ...event, ignoreContinueWatching: true });
232
+
233
+ this.seekTo(this.startPosition);
234
+ this.startPosition = null;
229
235
  } else {
230
236
  super.onVideoLoad(event);
231
237
  }
@@ -249,6 +255,15 @@ export class PlayerNative extends Player {
249
255
  }
250
256
  };
251
257
 
258
+ onAdBreakEnd = (event) => {
259
+ super.onAdBreakEnd(event);
260
+
261
+ if (this.startPosition) {
262
+ this.seekTo(this.startPosition);
263
+ this.startPosition = null;
264
+ }
265
+ };
266
+
252
267
  // This function is called when native player used directly without wrapped component,
253
268
  // Since event from native comes with wrapped in NativeEvent property
254
269
  getNativeViewListener = (): QuickBrickPlayer.SharedPlayerCallBacks => {
@@ -3,12 +3,18 @@ import { OverlaysObserver } from "./OverlayObserver/OverlaysObserver";
3
3
  import { useFeedLoader } from "../../reactHooks";
4
4
  import { usePlayer } from "./usePlayer";
5
5
 
6
+ const getLiveUrl = (url: string) => {
7
+ // Use the replace method with a regular expression to replace all instances of subClass with livePage
8
+ return url.replace(new RegExp("subClass", "g"), "livePage");
9
+ };
10
+
6
11
  export const useTitleSummaryOverlay = (
7
- feedUrl,
8
12
  entry,
9
13
  isLive = false,
10
14
  interval: number
11
15
  ) => {
16
+ // TODO: Have generic place for live url from configuration. Currently its hardcoded for client.
17
+ const feedUrl = getLiveUrl(entry?.extensions?.live?.channel_src ?? "");
12
18
  const player = usePlayer();
13
19
  const [title, setTitle] = useState<string | number>(entry?.title || "");
14
20
  const [summary, setSummary] = useState<string | number>(entry?.summary || "");
@@ -2,12 +2,16 @@ import * as R from "ramda";
2
2
  import dayjs from "dayjs";
3
3
  import validateColor from "validate-color";
4
4
 
5
+ import { transformColorCode as fixColorHexCode } from "@applicaster/zapp-react-native-utils/transform";
6
+ import {
7
+ capitalize,
8
+ isString,
9
+ } from "@applicaster/zapp-react-native-utils/stringUtils";
10
+ import { cellUtilsLogger } from "@applicaster/zapp-react-native-utils/cellUtils/logger";
11
+ import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
12
+ import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
13
+
5
14
  import { toNumberWithDefault, toNumberWithDefaultZero } from "../numberUtils";
6
- import { transformColorCode as fixColorHexCode } from "../transform";
7
- import { capitalize, isString } from "../stringUtils";
8
- import { cellUtilsLogger } from "./logger";
9
- import { isWeb } from "../reactUtils";
10
- import { isNotNil } from "../reactUtils/helpers";
11
15
 
12
16
  const CUSTOM_KEY = "other";
13
17
 
@@ -0,0 +1,33 @@
1
+ import { isFirstComponentGallery } from "../index";
2
+
3
+ describe("isFirstComponentGallery", () => {
4
+ it("should return true if the first component is a gallery", () => {
5
+ const components = [
6
+ { component_type: "gallery-qb" },
7
+ { component_type: "other-component" },
8
+ ];
9
+
10
+ expect(isFirstComponentGallery(components)).toBe(true);
11
+ });
12
+
13
+ it("should return false if the first component is not a gallery", () => {
14
+ const components = [
15
+ { component_type: "other-component" },
16
+ { component_type: "gallery-qb" },
17
+ ];
18
+
19
+ expect(isFirstComponentGallery(components)).toBe(false);
20
+ });
21
+
22
+ it("should return false if the components array is empty", () => {
23
+ const components = [];
24
+
25
+ expect(isFirstComponentGallery(components)).toBe(false);
26
+ });
27
+
28
+ it("should return false if the components array is undefined", () => {
29
+ const components = undefined;
30
+
31
+ expect(isFirstComponentGallery(components)).toBe(false);
32
+ });
33
+ });
@@ -0,0 +1,33 @@
1
+ import { isFirstComponentScreenPicker } from "..";
2
+
3
+ describe("isFirstComponentScreenPicker", () => {
4
+ it("should return true if the first component screen-picker", () => {
5
+ const items = [{ component_type: "screen-picker-qb-tv" }];
6
+ expect(isFirstComponentScreenPicker(items)).toBe(true);
7
+ });
8
+
9
+ it("should return false if the first component type is not screen-picker", () => {
10
+ const items = [{ component_type: "other-component" }];
11
+ expect(isFirstComponentScreenPicker(items)).toBe(false);
12
+ });
13
+
14
+ it("should return false if the component type is undefined", () => {
15
+ const items = [{ component_type: undefined }];
16
+ expect(isFirstComponentScreenPicker(items)).toBe(false);
17
+ });
18
+
19
+ it("should return false if the item is null", () => {
20
+ const items = null;
21
+ expect(isFirstComponentScreenPicker(items)).toBe(false);
22
+ });
23
+
24
+ it("should return false if the item is undefined", () => {
25
+ const items = undefined;
26
+ expect(isFirstComponentScreenPicker(items)).toBe(false);
27
+ });
28
+
29
+ it("should return false if the item is an empty object", () => {
30
+ const items = {};
31
+ expect(isFirstComponentScreenPicker(items)).toBe(false);
32
+ });
33
+ });
@@ -0,0 +1,33 @@
1
+ import { isGallery } from "..";
2
+
3
+ describe("isGallery", () => {
4
+ it("should return true if the component type is 'gallery-qb'", () => {
5
+ const item = { component_type: "gallery-qb" };
6
+ expect(isGallery(item)).toBe(true);
7
+ });
8
+
9
+ it("should return false if the component type is not 'gallery-qb'", () => {
10
+ const item = { component_type: "other-component" };
11
+ expect(isGallery(item)).toBe(false);
12
+ });
13
+
14
+ it("should return false if the component type is undefined", () => {
15
+ const item = { component_type: undefined };
16
+ expect(isGallery(item)).toBe(false);
17
+ });
18
+
19
+ it("should return false if the item is null", () => {
20
+ const item = null;
21
+ expect(isGallery(item)).toBe(false);
22
+ });
23
+
24
+ it("should return false if the item is undefined", () => {
25
+ const item = undefined;
26
+ expect(isGallery(item)).toBe(false);
27
+ });
28
+
29
+ it("should return false if the item is an empty object", () => {
30
+ const item = {};
31
+ expect(isGallery(item)).toBe(false);
32
+ });
33
+ });
@@ -0,0 +1,33 @@
1
+ import { isScreenPicker } from "..";
2
+
3
+ describe("isScreenPicker", () => {
4
+ it("should return true if the component type is 'screen-picker-qb-tv'", () => {
5
+ const item = { component_type: "screen-picker-qb-tv" };
6
+ expect(isScreenPicker(item)).toBe(true);
7
+ });
8
+
9
+ it("should return false if the component type is not 'screen-picker-qb-tv'", () => {
10
+ const item = { component_type: "other-component" };
11
+ expect(isScreenPicker(item)).toBe(false);
12
+ });
13
+
14
+ it("should return false if the component type is undefined", () => {
15
+ const item = { component_type: undefined };
16
+ expect(isScreenPicker(item)).toBe(false);
17
+ });
18
+
19
+ it("should return false if the item is null", () => {
20
+ const item = null;
21
+ expect(isScreenPicker(item)).toBe(false);
22
+ });
23
+
24
+ it("should return false if the item is undefined", () => {
25
+ const item = undefined;
26
+ expect(isScreenPicker(item)).toBe(false);
27
+ });
28
+
29
+ it("should return false if the item is an empty object", () => {
30
+ const item = {};
31
+ expect(isScreenPicker(item)).toBe(false);
32
+ });
33
+ });
@@ -0,0 +1,39 @@
1
+ const GROUP = "group-qb";
2
+ const GROUP_INFO = "group-info-qb";
3
+ const GROUP_INFO_OLD = "group-info";
4
+ const EMPTY_GROUP_COMPONENT = "empty_group_component";
5
+
6
+ const GALLERY = "gallery-qb";
7
+
8
+ const SCREEN_PICKER = "screen-picker-qb-tv";
9
+
10
+ const HORIZONTAL_LIST = "horizontal_list_qb";
11
+
12
+ export const isGallery = (item): boolean => item?.component_type === GALLERY;
13
+
14
+ export const isHorizontalList = (item): boolean =>
15
+ item?.component_type === HORIZONTAL_LIST;
16
+
17
+ export const isScreenPicker = (item): boolean =>
18
+ item?.component_type === SCREEN_PICKER;
19
+
20
+ export const isFirstComponentScreenPicker = (
21
+ components: ZappUIComponent[]
22
+ ): boolean => {
23
+ return isScreenPicker(components?.[0]);
24
+ };
25
+
26
+ export const isFirstComponentGallery = (
27
+ components: ZappUIComponent[]
28
+ ): boolean => {
29
+ return isGallery(components?.[0]);
30
+ };
31
+
32
+ export const isGroup = (item): boolean => item?.component_type === GROUP;
33
+
34
+ export const isEmptyGroup = (item): boolean =>
35
+ item?.component_type === EMPTY_GROUP_COMPONENT;
36
+
37
+ export const isGroupInfo = (item): boolean =>
38
+ item?.component_type === GROUP_INFO ||
39
+ item?.component_type === GROUP_INFO_OLD;
@@ -2,10 +2,29 @@ import dayjs from "dayjs";
2
2
  import utc from "dayjs/plugin/utc";
3
3
  import timezone from "dayjs/plugin/timezone";
4
4
  import isoWeek from "dayjs/plugin/isoWeek";
5
+ import duration from "dayjs/plugin/duration";
6
+ import relativeTime from "dayjs/plugin/relativeTime";
5
7
 
6
8
  // Extend dayjs with plugins
7
9
  dayjs.extend(utc);
8
10
  dayjs.extend(timezone);
9
11
  dayjs.extend(isoWeek);
12
+ dayjs.extend(duration);
13
+
14
+ dayjs.extend(relativeTime, {
15
+ thresholds: [
16
+ { l: "s", r: 1 },
17
+ { l: "m", r: 1 },
18
+ { l: "mm", r: 59, d: "minute" },
19
+ { l: "h", r: 1 },
20
+ { l: "hh", r: 23, d: "hour" },
21
+ { l: "d", r: 1 },
22
+ { l: "dd", r: 29, d: "day" },
23
+ { l: "M", r: 1 },
24
+ { l: "MM", r: 11, d: "month" },
25
+ { l: "y", r: 1 },
26
+ { l: "yy", d: "year" },
27
+ ],
28
+ });
10
29
 
11
30
  export { dayjs };