@applicaster/zapp-react-native-utils 14.0.0-alpha.3552323332 → 14.0.0-alpha.3652810444

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 (127) hide show
  1. package/actionsExecutor/ActionExecutorContext.tsx +60 -84
  2. package/actionsExecutor/ScreenActions.ts +164 -0
  3. package/actionsExecutor/StorageActions.ts +110 -0
  4. package/actionsExecutor/feedDecorator.ts +171 -0
  5. package/actionsExecutor/screenResolver.ts +11 -0
  6. package/analyticsUtils/AnalyticsEvents/helper.ts +81 -0
  7. package/analyticsUtils/AnalyticsEvents/sendHeaderClickEvent.ts +1 -1
  8. package/analyticsUtils/AnalyticsEvents/sendMenuClickEvent.ts +2 -1
  9. package/analyticsUtils/AnalyticsEvents/sendOnClickEvent.ts +14 -4
  10. package/analyticsUtils/__tests__/analyticsUtils.test.js +3 -0
  11. package/analyticsUtils/events.ts +8 -0
  12. package/analyticsUtils/index.tsx +3 -4
  13. package/analyticsUtils/manager.ts +1 -1
  14. package/analyticsUtils/playerAnalyticsTracker.ts +2 -1
  15. package/appUtils/HooksManager/Hook.ts +4 -4
  16. package/appUtils/HooksManager/index.ts +11 -1
  17. package/appUtils/accessibilityManager/index.ts +5 -5
  18. package/appUtils/contextKeysManager/contextResolver.ts +42 -1
  19. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +5 -0
  20. package/appUtils/focusManager/__tests__/focusManager.test.js +1 -1
  21. package/appUtils/focusManager/events.ts +2 -0
  22. package/appUtils/focusManager/index.ios.ts +10 -0
  23. package/appUtils/focusManager/index.ts +82 -11
  24. package/appUtils/focusManager/treeDataStructure/Tree/index.js +1 -1
  25. package/appUtils/focusManagerAux/utils/index.ts +106 -3
  26. package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +0 -15
  27. package/appUtils/playerManager/useChapterMarker.tsx +0 -1
  28. package/appUtils/playerManager/usePlayerControllerSetup.tsx +16 -0
  29. package/arrayUtils/__tests__/isEmptyArray.test.ts +63 -0
  30. package/arrayUtils/__tests__/isFilledArray.test.ts +1 -1
  31. package/arrayUtils/index.ts +8 -3
  32. package/audioPlayerUtils/__tests__/getArtworkImage.test.ts +144 -0
  33. package/audioPlayerUtils/__tests__/getBackgroundImage.test.ts +72 -0
  34. package/audioPlayerUtils/__tests__/getImageFromEntry.test.ts +110 -0
  35. package/audioPlayerUtils/assets/index.ts +2 -0
  36. package/audioPlayerUtils/index.ts +242 -0
  37. package/componentsUtils/__tests__/isTabsScreen.test.ts +38 -0
  38. package/componentsUtils/index.ts +4 -1
  39. package/conf/player/__tests__/selectors.test.ts +34 -0
  40. package/conf/player/selectors.ts +10 -0
  41. package/configurationUtils/__tests__/configurationUtils.test.js +0 -31
  42. package/configurationUtils/__tests__/getMediaItems.test.ts +65 -0
  43. package/configurationUtils/__tests__/imageSrcFromMediaItem.test.ts +34 -0
  44. package/configurationUtils/__tests__/manifestKeyParser.test.ts +546 -0
  45. package/configurationUtils/index.ts +63 -34
  46. package/configurationUtils/manifestKeyParser.ts +57 -32
  47. package/focusManager/FocusManager.ts +89 -20
  48. package/focusManager/Tree.ts +25 -21
  49. package/focusManager/__tests__/FocusManager.test.ts +50 -8
  50. package/focusManager/aux/index.ts +167 -0
  51. package/focusManager/utils.ts +12 -6
  52. package/index.d.ts +0 -9
  53. package/manifestUtils/_internals/getDefaultConfiguration.js +28 -0
  54. package/manifestUtils/{_internals.js → _internals/index.js} +2 -25
  55. package/manifestUtils/createConfig.js +4 -1
  56. package/manifestUtils/defaultManifestConfigurations/player.js +1239 -200
  57. package/manifestUtils/progressBar/__tests__/mobileProgressBar.test.js +0 -30
  58. package/navigationUtils/__tests__/mapContentTypesToRivers.test.ts +130 -0
  59. package/navigationUtils/index.ts +7 -5
  60. package/package.json +2 -3
  61. package/playerUtils/__tests__/configurationUtils.test.ts +1 -65
  62. package/playerUtils/__tests__/getPlayerActionButtons.test.ts +54 -0
  63. package/playerUtils/_internals/__tests__/utils.test.ts +71 -0
  64. package/playerUtils/_internals/index.ts +1 -0
  65. package/playerUtils/_internals/utils.ts +31 -0
  66. package/playerUtils/configurationUtils.ts +0 -44
  67. package/playerUtils/getPlayerActionButtons.ts +17 -0
  68. package/playerUtils/index.ts +2 -0
  69. package/playerUtils/useValidatePlayerConfig.tsx +22 -19
  70. package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +15 -14
  71. package/reactHooks/cell-click/__tests__/index.test.js +3 -0
  72. package/reactHooks/cell-click/index.ts +8 -1
  73. package/reactHooks/debugging/__tests__/index.test.js +0 -1
  74. package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +47 -90
  75. package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +71 -31
  76. package/reactHooks/feed/index.ts +2 -0
  77. package/reactHooks/feed/useBatchLoading.ts +17 -10
  78. package/reactHooks/feed/useFeedLoader.tsx +36 -34
  79. package/reactHooks/feed/useLoadPipesDataDispatch.ts +63 -0
  80. package/reactHooks/feed/usePipesCacheReset.ts +3 -3
  81. package/reactHooks/flatList/useSequentialRenderItem.tsx +3 -3
  82. package/reactHooks/layout/__tests__/index.test.tsx +3 -1
  83. package/reactHooks/layout/isTablet/index.ts +12 -5
  84. package/reactHooks/layout/useDimensions/__tests__/useDimensions.test.ts +34 -36
  85. package/reactHooks/layout/useDimensions/useDimensions.ts +2 -3
  86. package/reactHooks/layout/useLayoutVersion.ts +5 -5
  87. package/reactHooks/navigation/index.ts +7 -5
  88. package/reactHooks/navigation/useIsScreenActive.ts +9 -5
  89. package/reactHooks/navigation/useRoute.ts +7 -2
  90. package/reactHooks/navigation/useScreenStateStore.ts +8 -0
  91. package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +4 -0
  92. package/reactHooks/screen/useScreenContext.ts +1 -1
  93. package/reactHooks/state/__tests__/ZStoreProvider.test.tsx +2 -1
  94. package/reactHooks/state/index.ts +1 -1
  95. package/reactHooks/state/useHomeRiver.ts +4 -2
  96. package/reactHooks/state/useRivers.ts +7 -8
  97. package/riverComponetsMeasurementProvider/index.tsx +1 -1
  98. package/screenPickerUtils/index.ts +13 -0
  99. package/services/js2native.ts +1 -0
  100. package/storage/ScreenSingleValueProvider.ts +204 -0
  101. package/storage/ScreenStateMultiSelectProvider.ts +293 -0
  102. package/storage/StorageMultiSelectProvider.ts +192 -0
  103. package/storage/StorageSingleSelectProvider.ts +108 -0
  104. package/testUtils/index.tsx +7 -8
  105. package/time/BackgroundTimer.ts +6 -4
  106. package/utils/__tests__/endsWith.test.ts +30 -0
  107. package/utils/__tests__/equals.test.ts +65 -0
  108. package/utils/__tests__/find.test.ts +36 -0
  109. package/utils/__tests__/max.test.ts +36 -0
  110. package/utils/__tests__/omit.test.ts +19 -0
  111. package/utils/__tests__/path.test.ts +33 -0
  112. package/utils/__tests__/pathOr.test.ts +37 -0
  113. package/utils/__tests__/startsWith.test.ts +30 -0
  114. package/utils/__tests__/take.test.ts +40 -0
  115. package/utils/__tests__/toLower.test.ts +39 -0
  116. package/utils/endsWith.ts +9 -0
  117. package/utils/equals.ts +5 -0
  118. package/utils/find.ts +3 -0
  119. package/utils/index.ts +37 -1
  120. package/utils/max.ts +5 -0
  121. package/utils/omit.ts +5 -0
  122. package/utils/path.ts +5 -0
  123. package/utils/pathOr.ts +5 -0
  124. package/utils/startsWith.ts +9 -0
  125. package/utils/take.ts +5 -0
  126. package/utils/toLower.ts +5 -0
  127. package/playerUtils/configurationGenerator.ts +0 -2572
@@ -0,0 +1,108 @@
1
+ import { bridgeLogger } from "../../zapp-react-native-bridge/logger";
2
+ import { BehaviorSubject } from "rxjs";
3
+ import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
4
+ import { getNamespaceAndKey } from "../appUtils/contextKeysManager/utils";
5
+ import { createLogger } from "../logger";
6
+
7
+ export const { log_verbose, log_debug, log_warning, log_info, log_error } =
8
+ createLogger({
9
+ category: "StorageSingleValueProvider",
10
+ subsystem: "zapp-react-native-bridge",
11
+ parent: bridgeLogger,
12
+ });
13
+
14
+ export interface SingleValueProvider {
15
+ getObservable(): BehaviorSubject<string | null>;
16
+
17
+ setValue(value: string): Promise<void>;
18
+ getValue(): string | null;
19
+ getValueAsync(): Promise<string | null>;
20
+ clearValue(): Promise<void>;
21
+ }
22
+
23
+ interface StorageListenerArgs {
24
+ value: string | null;
25
+ }
26
+
27
+ export class StorageSingleValueProvider implements SingleValueProvider {
28
+ // Static cache of providers by namespace
29
+ private static singleValueProviders: Record<
30
+ string,
31
+ StorageSingleValueProvider
32
+ > = {};
33
+
34
+ public static getProvider(keyNamespace: string): SingleValueProvider {
35
+ if (!this.singleValueProviders[keyNamespace]) {
36
+ this.singleValueProviders[keyNamespace] = new StorageSingleValueProvider(
37
+ keyNamespace
38
+ );
39
+ }
40
+
41
+ return this.singleValueProviders[keyNamespace];
42
+ }
43
+
44
+ private valueSubject: BehaviorSubject<string | null>;
45
+
46
+ private readonly key: string;
47
+ private readonly namespace: string;
48
+
49
+ private constructor(keyNamespace: string) {
50
+ const { namespace, key } = getNamespaceAndKey(keyNamespace);
51
+
52
+ if (!key) {
53
+ throw new Error("StorageSingleValueProvider: Key is required");
54
+ }
55
+
56
+ this.key = key;
57
+ this.namespace = namespace;
58
+ localStorage.addListener?.({ key, namespace }, this.reloadValue);
59
+
60
+ this.valueSubject = new BehaviorSubject<string | null>(null);
61
+
62
+ void this.getValueAsync();
63
+ log_debug("StorageSingleValueProvider: Initializing");
64
+ }
65
+
66
+ private reloadValue = async ({ value }: StorageListenerArgs) => {
67
+ log_debug(`reloadValue: request to reload value: ${value}`);
68
+
69
+ this.valueSubject.next(value);
70
+ };
71
+
72
+ public getObservable = (): BehaviorSubject<string | null> =>
73
+ this.valueSubject;
74
+
75
+ private async updateValueAndNotifyObservers(value: string | null) {
76
+ if (value === null) {
77
+ await localStorage.removeItem(this.key, this.namespace);
78
+ } else {
79
+ await localStorage.setItem(this.key, value, this.namespace);
80
+ }
81
+
82
+ this.valueSubject.next(value);
83
+ }
84
+
85
+ public setValue = async (value: string): Promise<void> => {
86
+ log_debug(`setValue: Setting new value: ${value}`);
87
+
88
+ await this.updateValueAndNotifyObservers(value);
89
+ };
90
+
91
+ public clearValue = async (): Promise<void> => {
92
+ await localStorage.removeItem(this.key, this.namespace);
93
+ log_debug("clearValue: Removing value");
94
+
95
+ this.valueSubject.next(null);
96
+ };
97
+
98
+ public getValueAsync = async (): Promise<string | null> => {
99
+ const value = await localStorage.getItem(this.key, this.namespace);
100
+ this.valueSubject.next(value);
101
+
102
+ return value;
103
+ };
104
+
105
+ public getValue = (): string | null => {
106
+ return this.valueSubject.getValue();
107
+ };
108
+ }
@@ -1,17 +1,16 @@
1
- import * as R from "ramda";
2
-
1
+ import { SafeAreaProvider } from "react-native-safe-area-context";
2
+ import { render } from "@testing-library/react-native";
3
3
  import React, { PropsWithChildren } from "react";
4
- import { View } from "react-native";
5
-
4
+ import configureStore from "redux-mock-store";
6
5
  import { Provider } from "react-redux";
6
+ import { View } from "react-native";
7
7
  import thunk from "redux-thunk";
8
- import configureStore from "redux-mock-store";
9
- import { SafeAreaProvider } from "react-native-safe-area-context";
8
+ import * as R from "ramda";
9
+
10
10
  import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
11
11
 
12
- import { render } from "@testing-library/react-native";
13
- import { AnalyticsProvider } from "../analyticsUtils";
14
12
  import { ThemeContext } from "../theme";
13
+ import { AnalyticsProvider } from "../analyticsUtils";
15
14
 
16
15
  export { getByTestId } from "./getByTestId";
17
16
 
@@ -11,15 +11,17 @@ class BackgroundTimer {
11
11
  this.uniqueId = 0;
12
12
  this.callbacks = {};
13
13
 
14
- const EventEmitter = platformSelect({
14
+ const EventEmitter: typeof DeviceEventEmitter | undefined = platformSelect({
15
15
  android: DeviceEventEmitter,
16
- ios: undefined,
16
+ android_tv: DeviceEventEmitter,
17
+ amazon: DeviceEventEmitter, // probably does not exist and uses android_tv
17
18
  default: undefined,
18
19
  });
19
20
 
20
21
  EventEmitter?.addListener("BackgroundTimer.timer.fired", (id: number) => {
21
- if (this.callbacks[id]) {
22
- const callback = this.callbacks[id];
22
+ const callback = this.callbacks[id];
23
+
24
+ if (callback) {
23
25
  delete this.callbacks[id];
24
26
  callback();
25
27
  }
@@ -0,0 +1,30 @@
1
+ import { endsWith } from "../endsWith";
2
+
3
+ describe("endsWith", () => {
4
+ it("returns false when str is null", () => {
5
+ expect(endsWith("a", null)).toBe(false);
6
+ });
7
+
8
+ it("returns false when str is undefined", () => {
9
+ expect(endsWith("a", undefined)).toBe(false);
10
+ });
11
+
12
+ it("returns true when string ends with target", () => {
13
+ expect(endsWith("lo", "hello")).toBe(true);
14
+ expect(endsWith("", "hello")).toBe(true); // empty target always matches
15
+ });
16
+
17
+ it("returns false when string does not end with target", () => {
18
+ expect(endsWith("yo", "hello")).toBe(false);
19
+ });
20
+
21
+ it("works with single character target", () => {
22
+ expect(endsWith("o", "hello")).toBe(true);
23
+ expect(endsWith("x", "hello")).toBe(false);
24
+ });
25
+
26
+ it("is case-sensitive", () => {
27
+ expect(endsWith("Lo", "hello")).toBe(false);
28
+ expect(endsWith("lo", "hello")).toBe(true);
29
+ });
30
+ });
@@ -0,0 +1,65 @@
1
+ import { equals } from "../equals";
2
+
3
+ describe("equals", () => {
4
+ it("returns true for two identical primitive values", () => {
5
+ expect(equals(5, 5)).toBe(true);
6
+ expect(equals("hello", "hello")).toBe(true);
7
+ expect(equals(true, true)).toBe(true);
8
+ });
9
+
10
+ it("returns false for two different primitive values", () => {
11
+ expect(equals(5, 10)).toBe(false);
12
+ expect(equals("hello", "world")).toBe(false);
13
+ expect(equals(true, false)).toBe(false);
14
+ });
15
+
16
+ it("returns true for deeply equal objects", () => {
17
+ const a = { x: 1, y: { z: 2 } };
18
+ const b = { x: 1, y: { z: 2 } };
19
+ expect(equals(a, b)).toBe(true);
20
+ });
21
+
22
+ it("returns false for objects with different values", () => {
23
+ const a = { x: 1, y: { z: 2 } };
24
+ const b = { x: 1, y: { z: 3 } };
25
+ expect(equals(a, b)).toBe(false);
26
+ });
27
+
28
+ it("returns true for arrays with same elements", () => {
29
+ expect(equals([1, 2, 3], [1, 2, 3])).toBe(true);
30
+ });
31
+
32
+ it("returns false for arrays with different elements", () => {
33
+ expect(equals([1, 2, 3], [1, 2, 4])).toBe(false);
34
+ });
35
+
36
+ it("returns true for nested arrays/objects", () => {
37
+ const a = [{ id: 1, data: [1, 2, 3] }];
38
+ const b = [{ id: 1, data: [1, 2, 3] }];
39
+ expect(equals(a, b)).toBe(true);
40
+ });
41
+
42
+ it("returns false for arrays with different order", () => {
43
+ expect(equals([1, 2, 3], [3, 2, 1])).toBe(false);
44
+ });
45
+
46
+ it("handles null and undefined correctly", () => {
47
+ expect(equals(null, null)).toBe(true);
48
+ expect(equals(undefined, undefined)).toBe(true);
49
+ expect(equals(null, undefined)).toBe(false);
50
+ });
51
+
52
+ it("handles dates correctly", () => {
53
+ const d1 = new Date("2020-01-01");
54
+ const d2 = new Date("2020-01-01");
55
+ const d3 = new Date("2021-01-01");
56
+
57
+ expect(equals(d1, d2)).toBe(true);
58
+ expect(equals(d1, d3)).toBe(false);
59
+ });
60
+
61
+ it("handles regex correctly", () => {
62
+ expect(equals(/abc/, /abc/)).toBe(true);
63
+ expect(equals(/abc/i, /abc/)).toBe(false);
64
+ });
65
+ });
@@ -0,0 +1,36 @@
1
+ import { find } from "../find";
2
+
3
+ test("example 1", () => {
4
+ const predicate = <T>(_: T, index: number): boolean => index === 0;
5
+ const xs = ["1", "2", "2", "3", "4"];
6
+
7
+ expect(find(predicate, xs)).toBe("1");
8
+ });
9
+
10
+ test("example 2", () => {
11
+ const predicate = <T>(_: T, index: number): boolean => index === 0;
12
+ const xs: string[] = [];
13
+
14
+ expect(find(predicate, xs)).toBe(undefined);
15
+ });
16
+
17
+ test("example 3", () => {
18
+ const predicate = () => false;
19
+ const xs = ["1", "2", "2", "3"];
20
+
21
+ expect(find(predicate, xs)).toBe(undefined);
22
+ });
23
+
24
+ test("example 4", () => {
25
+ const predicate = <T>(_: T, index: number): boolean => index === 1;
26
+ const xs = ["1", "2", "2", "3"];
27
+
28
+ expect(find(predicate, xs)).toBe("2");
29
+ });
30
+
31
+ test("example 5", () => {
32
+ const predicate = <T>(_: T, index: number): boolean => index === 2;
33
+ const xs = ["1", "2.1", "2", "3", "2", "4"];
34
+
35
+ expect(find(predicate, xs)).toBe("2");
36
+ });
@@ -0,0 +1,36 @@
1
+ import { max } from "../max";
2
+
3
+ describe("max", () => {
4
+ describe("for numbers", () => {
5
+ it("returns the first argument when it is greater", () => {
6
+ expect(max(10, 5)).toBe(10);
7
+ });
8
+
9
+ it("returns the second argument when it is greater", () => {
10
+ expect(max(3, 7)).toBe(7);
11
+ });
12
+
13
+ it("returns the same value when both arguments are equal", () => {
14
+ expect(max(4, 4)).toBe(4);
15
+ });
16
+
17
+ it("works with negative numbers", () => {
18
+ expect(max(-2, -5)).toBe(-2);
19
+ });
20
+
21
+ it("works when one argument is negative and the other is positive", () => {
22
+ expect(max(-10, 20)).toBe(20);
23
+ });
24
+
25
+ it("works with zeros", () => {
26
+ expect(max(0, 0)).toBe(0);
27
+ expect(max(0, -1)).toBe(0);
28
+ });
29
+ });
30
+
31
+ describe("for letters", () => {
32
+ it("returns the second argument when it is greater", () => {
33
+ expect(max("a", "b")).toBe("b");
34
+ });
35
+ });
36
+ });
@@ -0,0 +1,19 @@
1
+ import { omit } from "../omit";
2
+
3
+ test("example 1", () => {
4
+ const path = ["a", "b", "c"];
5
+ const record = { a: 1, b: 2, c: 3 };
6
+
7
+ const output = {};
8
+
9
+ expect(omit(path, record)).toEqual(output);
10
+ });
11
+
12
+ test("example 2", () => {
13
+ const path = ["a", "b"];
14
+ const record = { a: 1, b: 2, c: 3 };
15
+
16
+ const output = { c: 3 };
17
+
18
+ expect(omit(path, record)).toEqual(output);
19
+ });
@@ -0,0 +1,33 @@
1
+ import { path } from "../path";
2
+
3
+ test("example 1", () => {
4
+ const route = ["a", "b", "c"];
5
+ const xs = { a: { b: { c: 1 } } };
6
+
7
+ const output = 1;
8
+
9
+ expect(path(route, xs)).toEqual(output);
10
+ });
11
+
12
+ test("example 2", () => {
13
+ const route = ["a", "b"];
14
+ const xs = { a: { b: { c: 1 } } };
15
+
16
+ const output = { c: 1 };
17
+
18
+ expect(path(route, xs)).toEqual(output);
19
+ });
20
+
21
+ test("example 3", () => {
22
+ const route = ["a", "b", "x"];
23
+ const xs = { a: { b: { c: 1 } } };
24
+
25
+ expect(path(route, xs)).toBeUndefined();
26
+ });
27
+
28
+ test("example 4", () => {
29
+ const route = ["a", "b", "c"];
30
+ const xs = undefined;
31
+
32
+ expect(path(route, xs)).toBeUndefined();
33
+ });
@@ -0,0 +1,37 @@
1
+ import { pathOr } from "../pathOr";
2
+
3
+ test("example 1", () => {
4
+ const defaultValue = "defaultValue";
5
+ const path = ["a", "b", "c"];
6
+ const xs = { a: { b: { c: 1 } } };
7
+
8
+ const output = 1;
9
+
10
+ expect(pathOr(defaultValue, path, xs)).toEqual(output);
11
+ });
12
+
13
+ test("example 2", () => {
14
+ const defaultValue = "defaultValue";
15
+ const path = ["a", "b"];
16
+ const xs = { a: { b: { c: 1 } } };
17
+
18
+ const output = { c: 1 };
19
+
20
+ expect(pathOr(defaultValue, path, xs)).toEqual(output);
21
+ });
22
+
23
+ test("example 3", () => {
24
+ const defaultValue = "defaultValue";
25
+ const path = ["a", "b", "x"];
26
+ const xs = { a: { b: { c: 1 } } };
27
+
28
+ expect(pathOr(defaultValue, path, xs)).toBe(defaultValue);
29
+ });
30
+
31
+ test("example 4", () => {
32
+ const defaultValue = "defaultValue";
33
+ const path = ["a", "b", "c"];
34
+ const xs = undefined;
35
+
36
+ expect(pathOr(defaultValue, path, xs)).toBe(defaultValue);
37
+ });
@@ -0,0 +1,30 @@
1
+ import { startsWith } from "../startsWith";
2
+
3
+ describe("startsWith", () => {
4
+ it("returns false when str is null", () => {
5
+ expect(startsWith("a", null)).toBe(false);
6
+ });
7
+
8
+ it("returns false when str is undefined", () => {
9
+ expect(startsWith("a", undefined)).toBe(false);
10
+ });
11
+
12
+ it("returns true when string starts with target", () => {
13
+ expect(startsWith("he", "hello")).toBe(true);
14
+ expect(startsWith("", "hello")).toBe(true); // empty target always matches
15
+ });
16
+
17
+ it("returns false when string does not start with target", () => {
18
+ expect(startsWith("yo", "hello")).toBe(false);
19
+ });
20
+
21
+ it("works with single character target", () => {
22
+ expect(startsWith("h", "hello")).toBe(true);
23
+ expect(startsWith("x", "hello")).toBe(false);
24
+ });
25
+
26
+ it("is case-sensitive", () => {
27
+ expect(startsWith("He", "hello")).toBe(false);
28
+ expect(startsWith("he", "hello")).toBe(true);
29
+ });
30
+ });
@@ -0,0 +1,40 @@
1
+ import { take } from "../take";
2
+
3
+ describe("take", () => {
4
+ it("takes n elements from the beginning", () => {
5
+ expect(take(2, [1, 2, 3])).toEqual([1, 2]);
6
+ });
7
+
8
+ it("returns the whole array if n is larger than length", () => {
9
+ expect(take(5, [1, 2, 3])).toEqual([1, 2, 3]);
10
+ });
11
+
12
+ it("returns empty array if n is 0", () => {
13
+ expect(take(0, [1, 2, 3])).toEqual([]);
14
+ });
15
+
16
+ it("returns empty array for empty input array", () => {
17
+ expect(take(2, [])).toEqual([]);
18
+ });
19
+
20
+ it("returns empty array if n is negative", () => {
21
+ expect(take(-1, [1, 2, 3])).toEqual([]);
22
+ });
23
+
24
+ it("works with strings in array", () => {
25
+ expect(take(2, ["a", "b", "c"])).toEqual(["a", "b"]);
26
+ });
27
+
28
+ it("works with objects in array", () => {
29
+ const arr = [{ id: 1 }, { id: 2 }];
30
+ expect(take(1, arr)).toEqual([{ id: 1 }]);
31
+ });
32
+
33
+ it("returns empty array if input is not an array", () => {
34
+ // @ts-expect-error testing non-array input
35
+ expect(take(2, null)).toEqual([]);
36
+
37
+ // @ts-expect-error testing non-array input
38
+ expect(take(2, undefined)).toEqual([]);
39
+ });
40
+ });
@@ -0,0 +1,39 @@
1
+ import { toLower } from "../toLower";
2
+
3
+ describe("toLower", () => {
4
+ it("converts mixed case string to lowercase", () => {
5
+ expect(toLower("--Foo-Bar--")).toBe("--foo-bar--");
6
+ expect(toLower("fooBar")).toBe("foobar");
7
+ expect(toLower("__FOO_BAR__")).toBe("__foo_bar__");
8
+ });
9
+
10
+ it("returns the same string if already lowercase", () => {
11
+ expect(toLower("hello")).toBe("hello");
12
+ });
13
+
14
+ it("converts uppercase string to lowercase", () => {
15
+ expect(toLower("HELLO")).toBe("hello");
16
+ });
17
+
18
+ it("handles empty string", () => {
19
+ expect(toLower("")).toBe("");
20
+ });
21
+
22
+ it("converts string with numbers and symbols", () => {
23
+ expect(toLower("123-ABC-xyz")).toBe("123-abc-xyz");
24
+ });
25
+
26
+ it("handles null and undefined gracefully", () => {
27
+ // @ts-expect-error testing null input
28
+ expect(toLower(null)).toBe("");
29
+ // @ts-expect-error testing undefined input
30
+ expect(toLower(undefined)).toBe("");
31
+ });
32
+
33
+ it("coerces non-string values to string before lowercasing", () => {
34
+ // @ts-expect-error testing number input
35
+ expect(toLower(12345)).toBe("12345");
36
+ // @ts-expect-error testing boolean input
37
+ expect(toLower(true)).toBe("true");
38
+ });
39
+ });
@@ -0,0 +1,9 @@
1
+ import { isNil } from "lodash";
2
+
3
+ export const endsWith = (target, str) => {
4
+ if (isNil(str)) {
5
+ return false;
6
+ }
7
+
8
+ return str.endsWith(target);
9
+ };
@@ -0,0 +1,5 @@
1
+ import { isEqual } from "lodash";
2
+
3
+ export function equals(a: any, b: any): boolean {
4
+ return isEqual(a, b);
5
+ }
package/utils/find.ts ADDED
@@ -0,0 +1,3 @@
1
+ export const find = (predicate, xs) => {
2
+ return (xs || []).find((x, index) => predicate(x, index));
3
+ };
package/utils/index.ts CHANGED
@@ -2,4 +2,40 @@ export { chunk } from "./chunk";
2
2
 
3
3
  export { times } from "./times";
4
4
 
5
- export { cloneDeep as clone, flatten, drop, size, isNil } from "lodash";
5
+ export { startsWith } from "./startsWith";
6
+
7
+ export { find } from "./find";
8
+
9
+ export { pathOr } from "./pathOr";
10
+
11
+ export { path } from "./path";
12
+
13
+ export { omit } from "./omit";
14
+
15
+ export { endsWith } from "./endsWith";
16
+
17
+ export { max } from "./max";
18
+
19
+ export { equals } from "./equals";
20
+
21
+ export { take } from "./take";
22
+
23
+ export { toLower } from "./toLower";
24
+
25
+ export {
26
+ cloneDeep as clone,
27
+ flatten,
28
+ drop,
29
+ size,
30
+ isNil,
31
+ isEmpty,
32
+ get,
33
+ has,
34
+ flatMap,
35
+ difference,
36
+ pick,
37
+ map,
38
+ trim,
39
+ toString,
40
+ last,
41
+ } from "lodash";
package/utils/max.ts ADDED
@@ -0,0 +1,5 @@
1
+ type Value = number | string;
2
+
3
+ export function max(a: Value, b: Value): Value {
4
+ return a > b ? a : b;
5
+ }
package/utils/omit.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { omit as Lodash_omit } from "lodash";
2
+
3
+ export const omit = (path, record) => {
4
+ return Lodash_omit(record, path);
5
+ };
package/utils/path.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { get } from "lodash";
2
+
3
+ export const path = (route, record) => {
4
+ return get(record, route, undefined);
5
+ };
@@ -0,0 +1,5 @@
1
+ import { get } from "lodash";
2
+
3
+ export const pathOr = (defaultValue, path, record) => {
4
+ return get(record, path, defaultValue);
5
+ };
@@ -0,0 +1,9 @@
1
+ import { isNil } from "lodash";
2
+
3
+ export const startsWith = (target, str) => {
4
+ if (isNil(str)) {
5
+ return false;
6
+ }
7
+
8
+ return str.startsWith(target);
9
+ };
package/utils/take.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { take as Ltake } from "lodash";
2
+
3
+ export function take<T>(n: number, xs: T[]): T[] {
4
+ return Ltake(xs, n);
5
+ }
@@ -0,0 +1,5 @@
1
+ import { toLower as L_toLower } from "lodash";
2
+
3
+ export function toLower(value: Option<string>): string {
4
+ return L_toLower(value);
5
+ }