@applicaster/zapp-react-native-utils 15.0.0-rc.10 → 15.0.0-rc.101
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/actionsExecutor/ActionExecutorContext.tsx +3 -6
- package/actionsExecutor/feedDecorator.ts +6 -6
- package/adsUtils/__tests__/createVMAP.test.ts +419 -0
- package/adsUtils/index.ts +2 -2
- package/analyticsUtils/README.md +1 -1
- package/analyticsUtils/analyticsMapper.ts +10 -2
- 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 +10 -10
- package/appUtils/RiverFocusManager/{index.js → index.ts} +25 -18
- package/appUtils/accessibilityManager/__tests__/utils.test.ts +360 -0
- package/appUtils/accessibilityManager/const.ts +4 -0
- package/appUtils/accessibilityManager/hooks.ts +20 -13
- package/appUtils/accessibilityManager/index.ts +28 -1
- package/appUtils/accessibilityManager/utils.ts +59 -8
- 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/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +4 -0
- package/appUtils/focusManager/index.ios.ts +59 -3
- 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 +19 -1
- package/appUtils/focusManagerAux/utils/utils.ios.ts +231 -0
- package/appUtils/keyCodes/keys/keys.web.ts +1 -4
- package/appUtils/orientationHelper.ts +2 -4
- package/appUtils/platform/platformUtils.ts +117 -18
- package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +94 -4
- package/appUtils/playerManager/OverlayObserver/utils.ts +32 -20
- package/appUtils/playerManager/player.ts +4 -0
- package/appUtils/playerManager/playerNative.ts +31 -17
- package/appUtils/playerManager/usePlayerState.tsx +14 -2
- package/arrayUtils/__tests__/allTruthy.test.ts +24 -0
- package/arrayUtils/__tests__/anyThruthy.test.ts +24 -0
- package/arrayUtils/index.ts +5 -0
- package/cellUtils/index.ts +32 -0
- package/cloudEventsUtils/__tests__/index.test.ts +529 -0
- package/cloudEventsUtils/index.ts +65 -1
- package/configurationUtils/__tests__/imageSrcFromMediaItem.test.ts +38 -0
- package/configurationUtils/__tests__/manifestKeyParser.test.ts +26 -26
- package/configurationUtils/index.ts +17 -11
- package/dateUtils/__tests__/dayjs.test.ts +330 -0
- 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/defaultManifestConfigurations/player.js +109 -11
- package/manifestUtils/keys.js +21 -0
- package/manifestUtils/platformIsTV.js +13 -0
- package/manifestUtils/sharedConfiguration/screenPicker/utils.js +1 -0
- package/manifestUtils/tvAction/container/index.js +1 -1
- package/navigationUtils/index.ts +15 -5
- package/numberUtils/__tests__/toNumber.test.ts +12 -0
- package/numberUtils/__tests__/toPositiveNumber.test.ts +165 -0
- package/numberUtils/index.ts +19 -1
- package/package.json +4 -4
- package/playerUtils/usePlayerTTS.ts +8 -3
- 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/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/events/index.ts +20 -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__/useFeedRefresh.test.tsx +1 -1
- package/reactHooks/feed/__tests__/{useInflatedUrl.test.ts → useInflatedUrl.test.tsx} +62 -7
- package/reactHooks/feed/useBatchLoading.ts +7 -1
- package/reactHooks/feed/useEntryScreenId.ts +2 -2
- package/reactHooks/feed/useInflatedUrl.ts +43 -17
- package/reactHooks/feed/usePipesCacheReset.ts +3 -1
- package/reactHooks/flatList/useLoadNextPageIfNeeded.ts +13 -16
- package/reactHooks/hookModal/hooks/useHookModalScreenData.ts +12 -8
- package/reactHooks/index.ts +2 -0
- package/reactHooks/layout/__tests__/index.test.tsx +1 -1
- package/reactHooks/layout/__tests__/useLayoutVersion.test.tsx +1 -1
- package/reactHooks/layout/index.ts +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 +40 -9
- package/reactHooks/navigation/index.ts +27 -11
- package/reactHooks/navigation/useRoute.ts +11 -7
- 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__/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/useRefWithInitialValue.ts +10 -0
- 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/rectUtils/__tests__/index.test.ts +549 -0
- package/rectUtils/index.ts +2 -2
- package/screenPickerUtils/__tests__/index.test.ts +333 -0
- package/screenState/__tests__/index.test.ts +1 -1
- package/searchUtils/const.ts +7 -0
- package/searchUtils/index.ts +3 -0
- package/services/storageServiceSync.web.ts +1 -1
- 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__/mapAccum.test.ts +73 -0
- package/utils/__tests__/mergeRight.test.ts +48 -0
- package/utils/__tests__/selectors.test.ts +124 -0
- package/utils/index.ts +20 -0
- package/utils/mapAccum.ts +23 -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 +49 -12
- package/zappFrameworkUtils/HookCallback/hookCallbackManifestExtensions.config.js +1 -1
- package/reactHooks/componentsMap/index.ts +0 -55
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { Timer } from "../Timer";
|
|
2
|
+
import BackgroundTimer from "../BackgroundTimer";
|
|
3
|
+
|
|
4
|
+
// Mock BackgroundTimer
|
|
5
|
+
jest.mock("../BackgroundTimer", () => ({
|
|
6
|
+
__esModule: true,
|
|
7
|
+
default: {
|
|
8
|
+
setTimeout: jest.fn((callback, delay) => {
|
|
9
|
+
return setTimeout(callback, delay);
|
|
10
|
+
}),
|
|
11
|
+
clearTimeout: jest.fn((id) => {
|
|
12
|
+
clearTimeout(id as NodeJS.Timeout);
|
|
13
|
+
}),
|
|
14
|
+
},
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
describe("Timer", () => {
|
|
18
|
+
let mockCallback: jest.Mock;
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
jest.clearAllMocks();
|
|
22
|
+
jest.useFakeTimers();
|
|
23
|
+
mockCallback = jest.fn();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
jest.useRealTimers();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("constructor", () => {
|
|
31
|
+
it("should create timer with callback and remaining time", () => {
|
|
32
|
+
const timer = new Timer(mockCallback, 1000);
|
|
33
|
+
|
|
34
|
+
expect(timer).toBeInstanceOf(Timer);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should create timer with callback only (optional remaining param)", () => {
|
|
38
|
+
const timer = new Timer(mockCallback);
|
|
39
|
+
|
|
40
|
+
expect(timer).toBeInstanceOf(Timer);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should not start automatically", () => {
|
|
44
|
+
// eslint-disable-next-line no-new
|
|
45
|
+
new Timer(mockCallback, 1000);
|
|
46
|
+
|
|
47
|
+
expect(BackgroundTimer.setTimeout).not.toHaveBeenCalled();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("start", () => {
|
|
52
|
+
it("should call setTimeout with callback and remaining time", () => {
|
|
53
|
+
const timer = new Timer(mockCallback, 1000);
|
|
54
|
+
|
|
55
|
+
timer.start();
|
|
56
|
+
|
|
57
|
+
expect(BackgroundTimer.setTimeout).toHaveBeenCalledWith(
|
|
58
|
+
mockCallback,
|
|
59
|
+
1000
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should reset remaining time to 0 after starting", () => {
|
|
64
|
+
const timer = new Timer(mockCallback, 5000);
|
|
65
|
+
|
|
66
|
+
timer.start();
|
|
67
|
+
timer.clear();
|
|
68
|
+
timer.start();
|
|
69
|
+
|
|
70
|
+
expect(BackgroundTimer.setTimeout).toHaveBeenCalledTimes(2);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("pause", () => {
|
|
75
|
+
it("should clear timeout and calculate remaining time correctly", () => {
|
|
76
|
+
const startTime = 1000;
|
|
77
|
+
const pauseTime = 1500;
|
|
78
|
+
|
|
79
|
+
jest
|
|
80
|
+
.spyOn(Date, "now")
|
|
81
|
+
.mockReturnValueOnce(startTime) // start
|
|
82
|
+
.mockReturnValueOnce(pauseTime); // pause
|
|
83
|
+
|
|
84
|
+
const timer = new Timer(mockCallback, 2000);
|
|
85
|
+
|
|
86
|
+
timer.start();
|
|
87
|
+
timer.pause();
|
|
88
|
+
|
|
89
|
+
expect(BackgroundTimer.clearTimeout).toHaveBeenCalled();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should be safe to call pause multiple times", () => {
|
|
93
|
+
const timer = new Timer(mockCallback, 1000);
|
|
94
|
+
|
|
95
|
+
timer.start();
|
|
96
|
+
timer.pause();
|
|
97
|
+
timer.pause();
|
|
98
|
+
|
|
99
|
+
expect(BackgroundTimer.clearTimeout).toHaveBeenCalledTimes(2);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("resume", () => {
|
|
104
|
+
it("should restart timer with remaining time after pause", () => {
|
|
105
|
+
const startTime = 1000;
|
|
106
|
+
const pauseTime = 1500;
|
|
107
|
+
|
|
108
|
+
jest
|
|
109
|
+
.spyOn(Date, "now")
|
|
110
|
+
.mockReturnValueOnce(startTime)
|
|
111
|
+
.mockReturnValueOnce(pauseTime);
|
|
112
|
+
|
|
113
|
+
const timer = new Timer(mockCallback, 2000);
|
|
114
|
+
|
|
115
|
+
timer.start();
|
|
116
|
+
timer.pause();
|
|
117
|
+
timer.resume();
|
|
118
|
+
|
|
119
|
+
expect(BackgroundTimer.setTimeout).toHaveBeenCalledTimes(2);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should be callable without prior pause", () => {
|
|
123
|
+
const timer = new Timer(mockCallback, 1000);
|
|
124
|
+
|
|
125
|
+
timer.resume();
|
|
126
|
+
|
|
127
|
+
expect(BackgroundTimer.setTimeout).toHaveBeenCalled();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("clear", () => {
|
|
132
|
+
it("should clear timeout and be safe to call without starting timer", () => {
|
|
133
|
+
const timer = new Timer(mockCallback, 1000);
|
|
134
|
+
|
|
135
|
+
expect(() => {
|
|
136
|
+
timer.clear();
|
|
137
|
+
}).not.toThrow();
|
|
138
|
+
|
|
139
|
+
expect(BackgroundTimer.clearTimeout).toHaveBeenCalled();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("should be callable multiple times", () => {
|
|
143
|
+
const timer = new Timer(mockCallback, 1000);
|
|
144
|
+
|
|
145
|
+
timer.start();
|
|
146
|
+
timer.clear();
|
|
147
|
+
timer.clear();
|
|
148
|
+
|
|
149
|
+
expect(BackgroundTimer.clearTimeout).toHaveBeenCalledTimes(2);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe("timer lifecycle", () => {
|
|
154
|
+
it("should support complete lifecycle: start -> pause -> resume -> clear", () => {
|
|
155
|
+
const timer = new Timer(mockCallback, 1000);
|
|
156
|
+
|
|
157
|
+
timer.start();
|
|
158
|
+
expect(BackgroundTimer.setTimeout).toHaveBeenCalledTimes(1);
|
|
159
|
+
|
|
160
|
+
timer.pause();
|
|
161
|
+
expect(BackgroundTimer.clearTimeout).toHaveBeenCalledTimes(1);
|
|
162
|
+
|
|
163
|
+
timer.resume();
|
|
164
|
+
expect(BackgroundTimer.setTimeout).toHaveBeenCalledTimes(2);
|
|
165
|
+
|
|
166
|
+
timer.clear();
|
|
167
|
+
expect(BackgroundTimer.clearTimeout).toHaveBeenCalledTimes(2);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe("edge cases", () => {
|
|
172
|
+
it("should handle zero delay", () => {
|
|
173
|
+
const timer = new Timer(mockCallback, 0);
|
|
174
|
+
|
|
175
|
+
timer.start();
|
|
176
|
+
|
|
177
|
+
expect(BackgroundTimer.setTimeout).toHaveBeenCalledWith(mockCallback, 0);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("should handle very long delays", () => {
|
|
181
|
+
const longDelay = Number.MAX_SAFE_INTEGER;
|
|
182
|
+
const timer = new Timer(mockCallback, longDelay);
|
|
183
|
+
|
|
184
|
+
timer.start();
|
|
185
|
+
|
|
186
|
+
expect(BackgroundTimer.setTimeout).toHaveBeenCalledWith(
|
|
187
|
+
mockCallback,
|
|
188
|
+
longDelay
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should handle negative delays gracefully", () => {
|
|
193
|
+
const timer = new Timer(mockCallback, -1000);
|
|
194
|
+
|
|
195
|
+
expect(() => {
|
|
196
|
+
timer.start();
|
|
197
|
+
}).not.toThrow();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("should maintain callback reference through lifecycle", () => {
|
|
201
|
+
const specificCallback = jest.fn();
|
|
202
|
+
const timer = new Timer(specificCallback, 1000);
|
|
203
|
+
|
|
204
|
+
timer.start();
|
|
205
|
+
|
|
206
|
+
expect(BackgroundTimer.setTimeout).toHaveBeenCalledWith(
|
|
207
|
+
specificCallback,
|
|
208
|
+
1000
|
|
209
|
+
);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe("callback execution safety", () => {
|
|
214
|
+
it("should not execute callback immediately on start", () => {
|
|
215
|
+
const timer = new Timer(mockCallback, 1000);
|
|
216
|
+
|
|
217
|
+
timer.start();
|
|
218
|
+
|
|
219
|
+
expect(mockCallback).not.toHaveBeenCalled();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("should not execute callback when paused or cleared", () => {
|
|
223
|
+
const timer1 = new Timer(mockCallback, 100);
|
|
224
|
+
const timer2 = new Timer(mockCallback, 100);
|
|
225
|
+
|
|
226
|
+
timer1.start();
|
|
227
|
+
timer1.pause();
|
|
228
|
+
timer2.start();
|
|
229
|
+
timer2.clear();
|
|
230
|
+
|
|
231
|
+
jest.advanceTimersByTime(200);
|
|
232
|
+
|
|
233
|
+
expect(mockCallback).not.toHaveBeenCalled();
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { isString } from "../index";
|
|
2
|
+
|
|
3
|
+
describe("isString", () => {
|
|
4
|
+
it("should return true for string primitives", () => {
|
|
5
|
+
expect(isString("hello")).toBe(true);
|
|
6
|
+
expect(isString("")).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("should return true for String object instances", () => {
|
|
10
|
+
// eslint-disable-next-line no-new-wrappers
|
|
11
|
+
expect(isString(new String("test"))).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should return false for non-string values", () => {
|
|
15
|
+
expect(isString(123)).toBe(false);
|
|
16
|
+
expect(isString(null)).toBe(false);
|
|
17
|
+
expect(isString(undefined)).toBe(false);
|
|
18
|
+
expect(isString({})).toBe(false);
|
|
19
|
+
expect(isString([])).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
});
|
package/typeGuards/index.ts
CHANGED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { mapAccum } from "../mapAccum";
|
|
2
|
+
|
|
3
|
+
describe("mapAccum", () => {
|
|
4
|
+
it("using standard ramda test", () => {
|
|
5
|
+
const digits = ["1", "2", "3", "4"];
|
|
6
|
+
const appender = (a, b) => [a + b, a + b];
|
|
7
|
+
|
|
8
|
+
const [acc, result] = mapAccum(appender, 0, digits); //= > ['01234', ['01', '012', '0123', '01234']]
|
|
9
|
+
expect(acc).toBe("01234");
|
|
10
|
+
expect(result).toEqual(["01", "012", "0123", "01234"]);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("maps and accumulates over an array", () => {
|
|
14
|
+
const fn = (acc, x) => [acc + x, x * 2];
|
|
15
|
+
const [acc, result] = mapAccum(fn, 0, [1, 2, 3]);
|
|
16
|
+
|
|
17
|
+
expect(acc).toBe(6); // final accumulator (0 + 1 + 2 + 3)
|
|
18
|
+
expect(result).toEqual([2, 4, 6]); // mapped values (acc + x*2 at each step)
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("returns initial accumulator for empty array", () => {
|
|
22
|
+
const fn = (acc, x) => [acc + x, acc * x];
|
|
23
|
+
const [acc, result] = mapAccum(fn, 10, []);
|
|
24
|
+
|
|
25
|
+
expect(acc).toBe(10);
|
|
26
|
+
expect(result).toEqual([]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("accumulates strings correctly", () => {
|
|
30
|
+
const fn = (acc, x) => [acc + x, acc + x];
|
|
31
|
+
const [acc, result] = mapAccum(fn, "A", ["B", "C", "D"]);
|
|
32
|
+
|
|
33
|
+
expect(acc).toBe("ABCD");
|
|
34
|
+
expect(result).toEqual(["AB", "ABC", "ABCD"]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("works with objects as accumulator", () => {
|
|
38
|
+
const fn = (acc, x) => {
|
|
39
|
+
const newAcc = { sum: acc.sum + x };
|
|
40
|
+
|
|
41
|
+
return [newAcc, newAcc.sum];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const [acc, result] = mapAccum(fn, { sum: 0 }, [1, 2, 3]);
|
|
45
|
+
|
|
46
|
+
expect(acc).toEqual({ sum: 6 });
|
|
47
|
+
expect(result).toEqual([1, 3, 6]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("is curried", () => {
|
|
51
|
+
const fn = (acc, x) => [acc + x, x * 2];
|
|
52
|
+
const mapWithFn = mapAccum(fn);
|
|
53
|
+
const withInit = mapWithFn(2);
|
|
54
|
+
const [acc, result] = withInit([1, 2, 3]);
|
|
55
|
+
|
|
56
|
+
expect(acc).toBe(8);
|
|
57
|
+
expect(result).toEqual([2, 4, 6]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("does not mutate the original array", () => {
|
|
61
|
+
const arr = [1, 2, 3];
|
|
62
|
+
mapAccum((acc, x) => [acc + x, acc + x], 0, arr);
|
|
63
|
+
expect(arr).toEqual([1, 2, 3]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("handles mixed types in accumulator and result", () => {
|
|
67
|
+
const fn = (acc, x) => [acc + x.length, acc + "-" + x];
|
|
68
|
+
const [acc, result] = mapAccum(fn, 0, ["a", "bb", "ccc"]);
|
|
69
|
+
|
|
70
|
+
expect(acc).toBe(6);
|
|
71
|
+
expect(result).toEqual(["0-a", "1-bb", "3-ccc"]);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { mergeRight } from "../mergeRight";
|
|
2
|
+
|
|
3
|
+
describe("mergeRight", () => {
|
|
4
|
+
test("merges two objects with no overlapping keys", () => {
|
|
5
|
+
const a = { x: 1, y: 2 };
|
|
6
|
+
const b = { z: 3 };
|
|
7
|
+
|
|
8
|
+
const result = mergeRight(a, b);
|
|
9
|
+
|
|
10
|
+
expect(result).toEqual({ x: 1, y: 2, z: 3 });
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("overwrites keys from the second object", () => {
|
|
14
|
+
const a = { x: 1, y: 2 };
|
|
15
|
+
const b = { y: 10, z: 3 };
|
|
16
|
+
|
|
17
|
+
const result = mergeRight(a, b);
|
|
18
|
+
|
|
19
|
+
expect(result).toEqual({ x: 1, y: 10, z: 3 });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("does not mutate the original objects", () => {
|
|
23
|
+
const a = { x: 1 };
|
|
24
|
+
const b = { y: 2 };
|
|
25
|
+
|
|
26
|
+
const result = mergeRight(a, b);
|
|
27
|
+
|
|
28
|
+
expect(result).not.toBe(a);
|
|
29
|
+
expect(result).not.toBe(b);
|
|
30
|
+
expect(a).toEqual({ x: 1 });
|
|
31
|
+
expect(b).toEqual({ y: 2 });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("works with empty objects", () => {
|
|
35
|
+
expect(mergeRight({}, { a: 1 })).toEqual({ a: 1 });
|
|
36
|
+
expect(mergeRight({ a: 1 }, {})).toEqual({ a: 1 });
|
|
37
|
+
expect(mergeRight({}, {})).toEqual({});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("works with nested objects (shallow merge only)", () => {
|
|
41
|
+
const a = { x: { nested: 1 }, y: 2 };
|
|
42
|
+
const b = { x: { nested: 10 }, z: 3 };
|
|
43
|
+
|
|
44
|
+
const result = mergeRight(a, b);
|
|
45
|
+
|
|
46
|
+
expect(result).toEqual({ x: { nested: 10 }, y: 2, z: 3 });
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createPluginsByPathSelector,
|
|
3
|
+
createPluginsByModuleSelector,
|
|
4
|
+
combinePluginSelectors,
|
|
5
|
+
} from "../selectors";
|
|
6
|
+
|
|
7
|
+
describe("Plugin Selectors", () => {
|
|
8
|
+
const mockPlugins = [
|
|
9
|
+
{
|
|
10
|
+
identifier: "plugin1",
|
|
11
|
+
module: {
|
|
12
|
+
urlScheme: { host: "test" },
|
|
13
|
+
player: { type: "default" },
|
|
14
|
+
},
|
|
15
|
+
customField: true,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
identifier: "plugin2",
|
|
19
|
+
module: {
|
|
20
|
+
urlScheme: { host: "other" },
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
identifier: "plugin3",
|
|
25
|
+
module: {
|
|
26
|
+
player: { type: "custom" },
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const mockState = {
|
|
32
|
+
plugins: mockPlugins,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
describe("createPluginsByPathSelector", () => {
|
|
36
|
+
it("filters plugins by string path", () => {
|
|
37
|
+
const selector = createPluginsByPathSelector("customField");
|
|
38
|
+
const result = selector(mockState);
|
|
39
|
+
|
|
40
|
+
expect(result).toHaveLength(1);
|
|
41
|
+
expect(result[0].identifier).toBe("plugin1");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("filters plugins by array path", () => {
|
|
45
|
+
const selector = createPluginsByPathSelector(["module", "urlScheme"]);
|
|
46
|
+
const result = selector(mockState);
|
|
47
|
+
|
|
48
|
+
expect(result).toHaveLength(2);
|
|
49
|
+
expect(result.map((p) => p.identifier)).toEqual(["plugin1", "plugin2"]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("handles missing plugins array", () => {
|
|
53
|
+
const selector = createPluginsByPathSelector("customField");
|
|
54
|
+
const result = selector({});
|
|
55
|
+
|
|
56
|
+
expect(result).toEqual([]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("handles non-existent path", () => {
|
|
60
|
+
const selector = createPluginsByPathSelector("nonexistent");
|
|
61
|
+
const result = selector(mockState);
|
|
62
|
+
|
|
63
|
+
expect(result).toEqual([]);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("createPluginsByModuleSelector", () => {
|
|
68
|
+
it("filters plugins by module path", () => {
|
|
69
|
+
const selector = createPluginsByModuleSelector("player");
|
|
70
|
+
const result = selector(mockState);
|
|
71
|
+
|
|
72
|
+
expect(result).toHaveLength(2);
|
|
73
|
+
expect(result.map((p) => p.identifier)).toEqual(["plugin1", "plugin3"]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("handles non-existent module", () => {
|
|
77
|
+
const selector = createPluginsByModuleSelector("nonexistent");
|
|
78
|
+
const result = selector(mockState);
|
|
79
|
+
|
|
80
|
+
expect(result).toEqual([]);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("combinePluginSelectors", () => {
|
|
85
|
+
it("combines multiple selectors with AND logic", () => {
|
|
86
|
+
const urlSchemeSelector = createPluginsByModuleSelector("urlScheme");
|
|
87
|
+
const playerSelector = createPluginsByModuleSelector("player");
|
|
88
|
+
|
|
89
|
+
const combinedSelector = combinePluginSelectors(
|
|
90
|
+
urlSchemeSelector,
|
|
91
|
+
playerSelector
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const result = combinedSelector(mockState);
|
|
95
|
+
|
|
96
|
+
expect(result).toHaveLength(1);
|
|
97
|
+
expect(result[0].identifier).toBe("plugin1");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("returns empty array when no plugins match all conditions", () => {
|
|
101
|
+
const urlSchemeSelector = createPluginsByModuleSelector("urlScheme");
|
|
102
|
+
const customSelector = createPluginsByPathSelector("nonexistent");
|
|
103
|
+
|
|
104
|
+
const combinedSelector = combinePluginSelectors(
|
|
105
|
+
urlSchemeSelector,
|
|
106
|
+
customSelector
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const result = combinedSelector(mockState);
|
|
110
|
+
|
|
111
|
+
expect(result).toEqual([]);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("handles empty state", () => {
|
|
115
|
+
const selector = combinePluginSelectors(
|
|
116
|
+
createPluginsByModuleSelector("urlScheme")
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const result = selector({});
|
|
120
|
+
|
|
121
|
+
expect(result).toEqual([]);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
package/utils/index.ts
CHANGED
|
@@ -16,6 +16,10 @@ export { endsWith } from "./endsWith";
|
|
|
16
16
|
|
|
17
17
|
export { take } from "./take";
|
|
18
18
|
|
|
19
|
+
export { mapAccum } from "./mapAccum";
|
|
20
|
+
|
|
21
|
+
export { mergeRight } from "./mergeRight";
|
|
22
|
+
|
|
19
23
|
export {
|
|
20
24
|
cloneDeep as clone,
|
|
21
25
|
flatten,
|
|
@@ -34,4 +38,20 @@ export {
|
|
|
34
38
|
last,
|
|
35
39
|
toLower,
|
|
36
40
|
isEqual as equals,
|
|
41
|
+
uniq,
|
|
42
|
+
uniqWith,
|
|
43
|
+
flowRight as compose,
|
|
44
|
+
partial,
|
|
45
|
+
clamp,
|
|
46
|
+
reverse,
|
|
47
|
+
takeRight,
|
|
48
|
+
fromPairs,
|
|
49
|
+
sortBy,
|
|
50
|
+
merge,
|
|
51
|
+
values,
|
|
52
|
+
head,
|
|
53
|
+
findIndex,
|
|
54
|
+
set,
|
|
55
|
+
compact,
|
|
56
|
+
identity,
|
|
37
57
|
} from "lodash";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { curry } from "lodash";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A native reimplementation of Ramda's mapAccum.
|
|
5
|
+
*
|
|
6
|
+
* @template A, B, C
|
|
7
|
+
* @param {(acc: A, value: B) => [A, C]} fn - Function returning [newAcc, mappedValue]
|
|
8
|
+
* @param {A} acc - Initial accumulator
|
|
9
|
+
* @param {B[]} list - List to process
|
|
10
|
+
* @returns {[A, C[]]} - Tuple of [final accumulator, mapped array]
|
|
11
|
+
*/
|
|
12
|
+
export const mapAccum = curry((fn, acc, list) => {
|
|
13
|
+
const result = [];
|
|
14
|
+
let currentAcc = acc;
|
|
15
|
+
|
|
16
|
+
for (let i = 0; i < list.length; i++) {
|
|
17
|
+
const [nextAcc, mapped] = fn(currentAcc, list[i]);
|
|
18
|
+
currentAcc = nextAcc;
|
|
19
|
+
result.push(mapped);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return [currentAcc, result];
|
|
23
|
+
});
|
package/utils/path.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { pathOr } from "./pathOr";
|
|
2
2
|
|
|
3
|
-
export const path =
|
|
4
|
-
|
|
3
|
+
export const path = <T = any>(
|
|
4
|
+
route: (string | number) | (string | number)[],
|
|
5
|
+
record: any
|
|
6
|
+
) => {
|
|
7
|
+
return pathOr<T>(undefined, route, record);
|
|
5
8
|
};
|
package/utils/pathOr.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { get } from "lodash";
|
|
2
2
|
|
|
3
|
-
export const pathOr =
|
|
3
|
+
export const pathOr = <T = any>(
|
|
4
|
+
defaultValue: T,
|
|
5
|
+
path: (number | string) | (number | string)[],
|
|
6
|
+
record: any
|
|
7
|
+
): T => {
|
|
4
8
|
return get(record, path, defaultValue);
|
|
5
9
|
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { get } from "lodash";
|
|
2
|
+
|
|
3
|
+
export type Selector<T> = (state: any) => T;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a selector for filtering plugins by path
|
|
7
|
+
* @param path - Path to check in plugin. Can be a dot notation string or array
|
|
8
|
+
* @returns A selector function that returns matching plugins
|
|
9
|
+
*/
|
|
10
|
+
export const createPluginsByPathSelector = (
|
|
11
|
+
path: string | string[]
|
|
12
|
+
): Selector<any[]> => {
|
|
13
|
+
return (state: any) => {
|
|
14
|
+
const plugins = state.plugins || [];
|
|
15
|
+
|
|
16
|
+
return plugins.filter((plugin: any) => get(plugin, path));
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Creates a selector for filtering plugins by module path
|
|
22
|
+
* @param modulePath - Module path to check in plugin (e.g., "urlScheme", "player")
|
|
23
|
+
* @returns A selector function that returns plugins with specified module
|
|
24
|
+
*/
|
|
25
|
+
export const createPluginsByModuleSelector = (
|
|
26
|
+
modulePath: string
|
|
27
|
+
): Selector<any[]> => {
|
|
28
|
+
return createPluginsByPathSelector(["module", modulePath]);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Creates a selector that combines multiple plugin selectors
|
|
33
|
+
* @param selectors - Array of plugin selectors to combine
|
|
34
|
+
* @returns A selector function that returns plugins matching all conditions
|
|
35
|
+
*/
|
|
36
|
+
export const combinePluginSelectors = (
|
|
37
|
+
...selectors: Selector<any[]>[]
|
|
38
|
+
): Selector<any[]> => {
|
|
39
|
+
return (state: any) => {
|
|
40
|
+
return selectors.reduce((filtered, selector) => {
|
|
41
|
+
const selected = selector(state);
|
|
42
|
+
|
|
43
|
+
return filtered.filter((plugin) => selected.includes(plugin));
|
|
44
|
+
}, state.plugins || []);
|
|
45
|
+
};
|
|
46
|
+
};
|