@applicaster/zapp-react-native-utils 14.0.0-alpha.6391068513 → 14.0.0-alpha.6893149866
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 +0 -1
- package/actionsExecutor/ScreenActions.ts +20 -19
- package/analyticsUtils/__tests__/analyticsUtils.test.js +0 -11
- package/analyticsUtils/playerAnalyticsTracker.ts +2 -1
- package/appUtils/accessibilityManager/const.ts +13 -0
- package/appUtils/accessibilityManager/hooks.ts +35 -1
- package/appUtils/accessibilityManager/index.ts +151 -30
- package/appUtils/accessibilityManager/utils.ts +24 -0
- package/appUtils/contextKeysManager/contextResolver.ts +29 -1
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +5 -0
- package/appUtils/focusManager/__tests__/focusManager.test.js +1 -1
- package/appUtils/focusManager/index.ios.ts +10 -0
- package/appUtils/focusManager/index.ts +82 -11
- package/appUtils/focusManagerAux/utils/index.ts +106 -3
- package/appUtils/platform/platformUtils.ts +31 -1
- package/configurationUtils/__tests__/manifestKeyParser.test.ts +0 -1
- package/configurationUtils/index.ts +1 -1
- package/index.d.ts +1 -1
- package/manifestUtils/defaultManifestConfigurations/player.js +16 -2
- package/navigationUtils/index.ts +1 -1
- package/package.json +2 -3
- package/playerUtils/PlayerTTS/PlayerTTS.ts +359 -0
- package/playerUtils/PlayerTTS/index.ts +1 -0
- package/playerUtils/usePlayerTTS.ts +21 -0
- package/reactHooks/cell-click/__tests__/index.test.js +3 -0
- package/reactHooks/debugging/__tests__/index.test.js +0 -1
- package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +8 -2
- package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +57 -37
- package/reactHooks/feed/index.ts +2 -0
- package/reactHooks/feed/useBatchLoading.ts +14 -9
- package/reactHooks/feed/useFeedLoader.tsx +39 -50
- package/reactHooks/feed/useLoadPipesDataDispatch.ts +63 -0
- package/reactHooks/navigation/useScreenStateStore.ts +3 -3
- package/reactHooks/state/index.ts +1 -1
- package/reactHooks/state/useHomeRiver.ts +4 -2
- package/screenPickerUtils/index.ts +7 -0
- package/storage/ScreenSingleValueProvider.ts +25 -22
- package/storage/ScreenStateMultiSelectProvider.ts +26 -23
- package/utils/__tests__/find.test.ts +36 -0
- package/utils/__tests__/pathOr.test.ts +37 -0
- package/utils/__tests__/startsWith.test.ts +30 -0
- package/utils/find.ts +3 -0
- package/utils/index.ts +7 -0
- package/utils/pathOr.ts +5 -0
- package/utils/startsWith.ts +9 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import uuidv4 from "uuid/v4";
|
|
2
|
+
import { AccessibilityManager } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager";
|
|
3
|
+
import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
4
|
+
import { Player } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/player";
|
|
5
|
+
|
|
6
|
+
const { log_debug, log_error } = createLogger({
|
|
7
|
+
subsystem: "Player",
|
|
8
|
+
category: "PlayerTTS",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
enum SEEK_DIRECTION {
|
|
12
|
+
FORWARD = "forward",
|
|
13
|
+
REWIND = "back",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const hasPrerollAds = (entry: ZappEntry): boolean => {
|
|
17
|
+
const videoAds = entry?.extensions?.video_ads;
|
|
18
|
+
|
|
19
|
+
if (!videoAds) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// If it's a string (VMAP URL), assume it might have preroll
|
|
24
|
+
if (typeof videoAds === "string") {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// If it's an array, check for preroll offset
|
|
29
|
+
if (Array.isArray(videoAds)) {
|
|
30
|
+
return videoAds.some(
|
|
31
|
+
(ad: ZappVideoAdExtension) => ad.offset === "preroll" || ad.offset === 0
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return false;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export class PlayerTTS {
|
|
39
|
+
private player: Player;
|
|
40
|
+
private accessibilityManager: AccessibilityManager;
|
|
41
|
+
private seekStartPosition: number | null = null;
|
|
42
|
+
private isSeeking: boolean = false;
|
|
43
|
+
private listenerId: string;
|
|
44
|
+
private isInitialPlayerOpen: boolean = true;
|
|
45
|
+
private isPrerollActive: boolean = false;
|
|
46
|
+
private hasPrerollAds: boolean = false; // Track if preroll ads are expected
|
|
47
|
+
|
|
48
|
+
constructor(player: Player, accessibilityManager: AccessibilityManager) {
|
|
49
|
+
this.player = player;
|
|
50
|
+
this.accessibilityManager = accessibilityManager;
|
|
51
|
+
this.listenerId = `player-tts-${uuidv4()}`;
|
|
52
|
+
this.hasPrerollAds = hasPrerollAds(player.entry);
|
|
53
|
+
|
|
54
|
+
log_debug("PlayerTTS initialized", {
|
|
55
|
+
hasPrerollAds: this.hasPrerollAds,
|
|
56
|
+
listenerId: this.listenerId,
|
|
57
|
+
entryTitle: player.entry.title,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private numberToWords(num: number): string {
|
|
62
|
+
const ones = [
|
|
63
|
+
"",
|
|
64
|
+
"one",
|
|
65
|
+
"two",
|
|
66
|
+
"three",
|
|
67
|
+
"four",
|
|
68
|
+
"five",
|
|
69
|
+
"six",
|
|
70
|
+
"seven",
|
|
71
|
+
"eight",
|
|
72
|
+
"nine",
|
|
73
|
+
"ten",
|
|
74
|
+
"eleven",
|
|
75
|
+
"twelve",
|
|
76
|
+
"thirteen",
|
|
77
|
+
"fourteen",
|
|
78
|
+
"fifteen",
|
|
79
|
+
"sixteen",
|
|
80
|
+
"seventeen",
|
|
81
|
+
"eighteen",
|
|
82
|
+
"nineteen",
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
const tens = [
|
|
86
|
+
"",
|
|
87
|
+
"",
|
|
88
|
+
"twenty",
|
|
89
|
+
"thirty",
|
|
90
|
+
"forty",
|
|
91
|
+
"fifty",
|
|
92
|
+
"sixty",
|
|
93
|
+
"seventy",
|
|
94
|
+
"eighty",
|
|
95
|
+
"ninety",
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
if (num === 0) return "zero";
|
|
99
|
+
if (num < 20) return ones[num];
|
|
100
|
+
|
|
101
|
+
const ten = Math.floor(num / 10);
|
|
102
|
+
const one = num % 10;
|
|
103
|
+
|
|
104
|
+
return one === 0 ? tens[ten] : `${tens[ten]} ${ones[one]}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private secondsToTime(
|
|
108
|
+
seconds: number,
|
|
109
|
+
format: "natural" | "standard" = "natural"
|
|
110
|
+
): string {
|
|
111
|
+
if (seconds < 0) return format === "natural" ? "zero" : "0";
|
|
112
|
+
|
|
113
|
+
const minutes = Math.floor(seconds / 60);
|
|
114
|
+
const remainingSeconds = Math.floor(seconds % 60);
|
|
115
|
+
|
|
116
|
+
if (format === "standard") {
|
|
117
|
+
const parts = [];
|
|
118
|
+
|
|
119
|
+
if (minutes > 0) {
|
|
120
|
+
parts.push(`${minutes} minute${minutes !== 1 ? "s" : ""}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (remainingSeconds > 0) {
|
|
124
|
+
parts.push(
|
|
125
|
+
`${remainingSeconds} second${remainingSeconds !== 1 ? "s" : ""}`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return parts.length > 0 ? parts.join(" ") : "0";
|
|
130
|
+
} else {
|
|
131
|
+
if (minutes === 0) {
|
|
132
|
+
if (remainingSeconds === 0) return "zero";
|
|
133
|
+
|
|
134
|
+
if (remainingSeconds < 10) {
|
|
135
|
+
return `zero o ${this.numberToWords(remainingSeconds)}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return `zero ${this.numberToWords(remainingSeconds)}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (remainingSeconds === 0) {
|
|
142
|
+
return `${this.numberToWords(minutes)}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (remainingSeconds < 10) {
|
|
146
|
+
return `${this.numberToWords(minutes)} o ${this.numberToWords(remainingSeconds)}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return `${this.numberToWords(minutes)} ${this.numberToWords(remainingSeconds)}`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private announcePause = () => {
|
|
154
|
+
if (!this.isSeeking) {
|
|
155
|
+
this.accessibilityManager.addHeading(
|
|
156
|
+
`Paused - ${this.secondsToTime(this.player.playerState.contentPosition, "standard")}`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
private announceContentStart(
|
|
162
|
+
options: {
|
|
163
|
+
currentTime?: number;
|
|
164
|
+
duration?: number;
|
|
165
|
+
useReadText?: boolean;
|
|
166
|
+
} = {}
|
|
167
|
+
): void {
|
|
168
|
+
const { currentTime, duration, useReadText = false } = options;
|
|
169
|
+
const state = this.player.playerState;
|
|
170
|
+
|
|
171
|
+
const timeRemaining =
|
|
172
|
+
(duration || state?.contentDuration || 0) -
|
|
173
|
+
(currentTime || state?.contentPosition || 0);
|
|
174
|
+
|
|
175
|
+
const title = (this.player.entry.title as string) || "";
|
|
176
|
+
const summary = (this.player.entry.summary as string) || "";
|
|
177
|
+
|
|
178
|
+
log_debug("Announcing content start", {
|
|
179
|
+
title,
|
|
180
|
+
currentTime: currentTime || state?.contentPosition || 0,
|
|
181
|
+
duration: duration || state?.contentDuration || 0,
|
|
182
|
+
timeRemaining,
|
|
183
|
+
useReadText,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
this.accessibilityManager.addHeading(`Playing - ${title}`);
|
|
187
|
+
if (summary) this.accessibilityManager.addHeading(summary);
|
|
188
|
+
|
|
189
|
+
this.accessibilityManager.addHeading(
|
|
190
|
+
`Playing from ${this.secondsToTime(currentTime || state?.contentPosition || 0, "standard")}`
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const remainingText = `${this.secondsToTime(Math.max(0, Math.floor(timeRemaining)), "standard")} remaining.`;
|
|
194
|
+
|
|
195
|
+
if (useReadText) {
|
|
196
|
+
this.accessibilityManager.readText({ text: remainingText });
|
|
197
|
+
} else {
|
|
198
|
+
this.accessibilityManager.addHeading(remainingText);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
this.accessibilityManager.setInitialPlayerAnnouncementReady();
|
|
202
|
+
this.isInitialPlayerOpen = false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private announceBufferComplete = (event: any) => {
|
|
206
|
+
// If preroll ads are expected, wait for them to finish before announcing content
|
|
207
|
+
if (this.hasPrerollAds && this.isInitialPlayerOpen) {
|
|
208
|
+
log_debug("Waiting for preroll ads to finish", {
|
|
209
|
+
hasPrerollAds: this.hasPrerollAds,
|
|
210
|
+
isInitialPlayerOpen: this.isInitialPlayerOpen,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Gate content announcement until preroll finishes
|
|
217
|
+
if (this.isInitialPlayerOpen && !this.isPrerollActive) {
|
|
218
|
+
log_debug("Buffer complete - announcing content", {
|
|
219
|
+
currentTime: event.currentTime,
|
|
220
|
+
duration: event.duration,
|
|
221
|
+
isPrerollActive: this.isPrerollActive,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
this.announceContentStart({
|
|
225
|
+
currentTime: event.currentTime,
|
|
226
|
+
duration: event.duration,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
private announceResume = () => {
|
|
232
|
+
if (!this.isSeeking && !this.isInitialPlayerOpen) {
|
|
233
|
+
log_debug("Player resumed", {
|
|
234
|
+
contentPosition: this.player.playerState.contentPosition,
|
|
235
|
+
isSeeking: this.isSeeking,
|
|
236
|
+
isInitialPlayerOpen: this.isInitialPlayerOpen,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
this.accessibilityManager.addHeading(
|
|
240
|
+
`Playing - ${this.secondsToTime(this.player.playerState.contentPosition, "standard")}`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
private handleVideoProgress = (event: any) => {
|
|
246
|
+
if (event.currentTime > 0) {
|
|
247
|
+
this.seekStartPosition = event.currentTime;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
private handleSeekComplete = (event: any) => {
|
|
252
|
+
if (this.seekStartPosition !== null) {
|
|
253
|
+
const seekDirection =
|
|
254
|
+
event.currentTime > this.seekStartPosition
|
|
255
|
+
? SEEK_DIRECTION.FORWARD
|
|
256
|
+
: SEEK_DIRECTION.REWIND;
|
|
257
|
+
|
|
258
|
+
const seekAmount = Math.round(
|
|
259
|
+
Math.abs(event.currentTime - this.seekStartPosition)
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
log_debug("Seek completed", {
|
|
263
|
+
seekDirection,
|
|
264
|
+
seekAmount,
|
|
265
|
+
fromPosition: this.seekStartPosition,
|
|
266
|
+
toPosition: event.currentTime,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
this.accessibilityManager.readText({
|
|
270
|
+
text: `Skipped ${seekDirection} ${this.secondsToTime(seekAmount, "standard")}`,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
this.seekStartPosition = event.currentTime;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
this.isSeeking = false;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
private handleSeekStart = () => {
|
|
280
|
+
log_debug("Seek started");
|
|
281
|
+
this.isSeeking = true;
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
private handlePlayerClose = () => {
|
|
285
|
+
log_debug("Player closed - resetting state");
|
|
286
|
+
this.isInitialPlayerOpen = true;
|
|
287
|
+
this.accessibilityManager.resetInitialPlayerAnnouncementReady();
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
private announceAdBegin = (event: any) => {
|
|
291
|
+
this.isPrerollActive = true;
|
|
292
|
+
|
|
293
|
+
log_debug("Ad started", {
|
|
294
|
+
adDuration: event?.ad?.data?.duration,
|
|
295
|
+
isPrerollActive: this.isPrerollActive,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
if (event?.ad?.data?.duration) {
|
|
299
|
+
this.accessibilityManager.readText({
|
|
300
|
+
text: `Sponsored. Ends in ${this.secondsToTime(event.ad.data.duration, "standard")}`,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
private handleAdEnd = (_event: any) => {
|
|
306
|
+
this.isPrerollActive = false;
|
|
307
|
+
|
|
308
|
+
log_debug("Ad ended", {
|
|
309
|
+
isPrerollActive: this.isPrerollActive,
|
|
310
|
+
isInitialPlayerOpen: this.isInitialPlayerOpen,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// If initial entry still pending, trigger content announcement using latest player state
|
|
314
|
+
if (this.isInitialPlayerOpen) {
|
|
315
|
+
this.announceContentStart({ useReadText: true });
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
public init(): () => void {
|
|
320
|
+
if (!this.player) {
|
|
321
|
+
log_error("Failed to initialize PlayerTTS - no player provided");
|
|
322
|
+
|
|
323
|
+
return () => {};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
log_debug("Initializing PlayerTTS listeners", {
|
|
327
|
+
listenerId: this.listenerId,
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
return this.player.addListener({
|
|
331
|
+
id: this.listenerId,
|
|
332
|
+
listener: {
|
|
333
|
+
onBufferComplete: this.announceBufferComplete,
|
|
334
|
+
onPlayerResume: this.announceResume,
|
|
335
|
+
onPlayerPause: this.announcePause,
|
|
336
|
+
onVideoProgress: this.handleVideoProgress,
|
|
337
|
+
onPlayerSeekStart: this.handleSeekStart,
|
|
338
|
+
onPlayerSeekComplete: this.handleSeekComplete,
|
|
339
|
+
onPlayerClose: this.handlePlayerClose,
|
|
340
|
+
onAdBegin: this.announceAdBegin,
|
|
341
|
+
onAdEnd: this.handleAdEnd,
|
|
342
|
+
onAdBreakEnd: this.handleAdEnd,
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
public destroy(): void {
|
|
348
|
+
log_debug("Destroying PlayerTTS", {
|
|
349
|
+
listenerId: this.listenerId,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
if (this.player) {
|
|
353
|
+
this.player.removeListener(this.listenerId);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
this.seekStartPosition = null;
|
|
357
|
+
this.handlePlayerClose();
|
|
358
|
+
}
|
|
359
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { PlayerTTS } from "./PlayerTTS";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { usePlayer } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer";
|
|
3
|
+
import { useAccessibilityManager } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager/hooks";
|
|
4
|
+
import { PlayerTTS } from "@applicaster/zapp-react-native-utils/playerUtils/PlayerTTS";
|
|
5
|
+
|
|
6
|
+
export const usePlayerTTS = () => {
|
|
7
|
+
const player = usePlayer();
|
|
8
|
+
const accessibilityManager = useAccessibilityManager({});
|
|
9
|
+
|
|
10
|
+
React.useEffect(() => {
|
|
11
|
+
if (player && accessibilityManager) {
|
|
12
|
+
const playerTTS = new PlayerTTS(player, accessibilityManager);
|
|
13
|
+
const unsubscribe = playerTTS.init();
|
|
14
|
+
|
|
15
|
+
return () => {
|
|
16
|
+
unsubscribe();
|
|
17
|
+
playerTTS.destroy();
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}, [player, accessibilityManager]);
|
|
21
|
+
};
|
|
@@ -26,6 +26,9 @@ jest.mock("@applicaster/zapp-react-native-utils/analyticsUtils/", () => ({
|
|
|
26
26
|
}));
|
|
27
27
|
|
|
28
28
|
jest.mock("@applicaster/zapp-react-native-utils/reactHooks/screen", () => ({
|
|
29
|
+
...jest.requireActual(
|
|
30
|
+
"@applicaster/zapp-react-native-utils/reactHooks/screen"
|
|
31
|
+
),
|
|
29
32
|
useTargetScreenData: jest.fn(() => ({})),
|
|
30
33
|
useCurrentScreenData: jest.fn(() => ({})),
|
|
31
34
|
}));
|
|
@@ -12,7 +12,6 @@ describe("Debug utils", () => {
|
|
|
12
12
|
// Clear the timers object
|
|
13
13
|
Object.keys(timers).forEach((key) => delete timers[key]);
|
|
14
14
|
|
|
15
|
-
// Mock performance.now()
|
|
16
15
|
// eslint-disable-next-line no-undef
|
|
17
16
|
performanceNowMock = jest.spyOn(performance, "now");
|
|
18
17
|
performanceNowMock.mockReturnValue(0); // Initial value
|
|
@@ -2,12 +2,16 @@ import { renderHook } from "@testing-library/react-hooks";
|
|
|
2
2
|
import { allFeedsIsReady, useBatchLoading } from "../useBatchLoading";
|
|
3
3
|
import { WrappedWithProviders } from "@applicaster/zapp-react-native-utils/testUtils";
|
|
4
4
|
import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
|
|
5
|
+
import { waitFor } from "@testing-library/react-native";
|
|
5
6
|
|
|
6
7
|
jest.mock("../../navigation");
|
|
7
8
|
|
|
8
9
|
jest.mock(
|
|
9
10
|
"@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext",
|
|
10
11
|
() => ({
|
|
12
|
+
...jest.requireActual(
|
|
13
|
+
"@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext"
|
|
14
|
+
),
|
|
11
15
|
useScreenContext: jest.fn().mockReturnValue({ screen: {}, entry: {} }),
|
|
12
16
|
})
|
|
13
17
|
);
|
|
@@ -33,7 +37,7 @@ describe("useBatchLoading", () => {
|
|
|
33
37
|
jest.clearAllMocks();
|
|
34
38
|
});
|
|
35
39
|
|
|
36
|
-
it("loadPipesData start loading not started requests", () => {
|
|
40
|
+
it("loadPipesData start loading not started requests", async () => {
|
|
37
41
|
const store = {
|
|
38
42
|
zappPipes: {
|
|
39
43
|
url1: {
|
|
@@ -65,7 +69,9 @@ describe("useBatchLoading", () => {
|
|
|
65
69
|
|
|
66
70
|
const actions = (appStore.getStore() as any).getActions();
|
|
67
71
|
|
|
68
|
-
|
|
72
|
+
await waitFor(() => {
|
|
73
|
+
expect(actions).toHaveLength(2);
|
|
74
|
+
});
|
|
69
75
|
|
|
70
76
|
expect(actions[0]).toMatchObject({
|
|
71
77
|
type: "ZAPP_PIPES_REQUEST_START",
|
|
@@ -2,15 +2,12 @@ import { renderHook } from "@testing-library/react-hooks";
|
|
|
2
2
|
import * as R from "ramda";
|
|
3
3
|
import * as zappPipesModule from "@applicaster/zapp-react-native-redux/ZappPipes";
|
|
4
4
|
import * as reactReduxModules from "react-redux";
|
|
5
|
-
import { Provider } from "react-redux";
|
|
6
5
|
import * as React from "react";
|
|
7
|
-
import configureStore from "redux-mock-store";
|
|
8
|
-
import thunk from "redux-thunk";
|
|
9
6
|
import * as useRouteHook from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useRoute";
|
|
10
7
|
import * as useNavigationHooks from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useNavigation";
|
|
11
8
|
import { useFeedLoader } from "../useFeedLoader";
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
import { WrappedWithProviders } from "../../../testUtils";
|
|
10
|
+
import { ScreenStateResolver } from "../../../appUtils/contextKeysManager/contextResolver";
|
|
14
11
|
|
|
15
12
|
jest.useFakeTimers({ legacyFakeTimers: true });
|
|
16
13
|
|
|
@@ -55,13 +52,15 @@ const mockZappPipesData = {
|
|
|
55
52
|
|
|
56
53
|
describe("useFeedLoader", () => {
|
|
57
54
|
describe("with cached feed url", () => {
|
|
58
|
-
const store =
|
|
55
|
+
const store = {
|
|
59
56
|
plugins: [],
|
|
60
57
|
zappPipes: { "test://testfakeurl": mockZappPipesData },
|
|
61
|
-
}
|
|
58
|
+
};
|
|
62
59
|
|
|
63
|
-
const wrapper: React.FC<any> = ({ children }) => (
|
|
64
|
-
<
|
|
60
|
+
const wrapper: React.FC<any> = ({ children, ...props }) => (
|
|
61
|
+
<WrappedWithProviders store={props.store || store}>
|
|
62
|
+
{children}
|
|
63
|
+
</WrappedWithProviders>
|
|
65
64
|
);
|
|
66
65
|
|
|
67
66
|
it("returns cached feed", () => {
|
|
@@ -110,8 +109,10 @@ describe("useFeedLoader", () => {
|
|
|
110
109
|
describe("without cached feeds", () => {
|
|
111
110
|
const feedUrl = "test://testfakeurl2";
|
|
112
111
|
|
|
113
|
-
const wrapper: React.FC<any> = ({ children,
|
|
114
|
-
<
|
|
112
|
+
const wrapper: React.FC<any> = ({ children, ...props }) => (
|
|
113
|
+
<WrappedWithProviders store={props.store}>
|
|
114
|
+
{children}
|
|
115
|
+
</WrappedWithProviders>
|
|
115
116
|
);
|
|
116
117
|
|
|
117
118
|
it("It loads data for new url and returns it", () => {
|
|
@@ -123,10 +124,10 @@ describe("useFeedLoader", () => {
|
|
|
123
124
|
.spyOn(zappPipesModule, "loadPipesData")
|
|
124
125
|
.mockImplementation(jest.fn());
|
|
125
126
|
|
|
126
|
-
const initialStore =
|
|
127
|
+
const initialStore = {
|
|
127
128
|
plugins: [],
|
|
128
129
|
zappPipes: { "test://testfakeurl": "foobar" },
|
|
129
|
-
}
|
|
130
|
+
};
|
|
130
131
|
|
|
131
132
|
const { result, rerender } = renderHook(
|
|
132
133
|
() => useFeedLoader({ feedUrl: "test://testfakeurl2" }),
|
|
@@ -135,20 +136,19 @@ describe("useFeedLoader", () => {
|
|
|
135
136
|
|
|
136
137
|
expect(result.current.data).toBeNull();
|
|
137
138
|
|
|
138
|
-
expect(loadPipesDataSpy).
|
|
139
|
+
expect(loadPipesDataSpy).toHaveBeenCalledWith(feedUrl, {
|
|
139
140
|
clearCache: true,
|
|
140
141
|
riverId: undefined,
|
|
142
|
+
callback: expect.any(Function),
|
|
141
143
|
resolvers: {
|
|
142
|
-
screen:
|
|
143
|
-
screenStateStore: undefined,
|
|
144
|
-
},
|
|
144
|
+
screen: expect.any(ScreenStateResolver),
|
|
145
145
|
},
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
-
const store2 =
|
|
148
|
+
const store2 = {
|
|
149
149
|
plugins: [],
|
|
150
150
|
zappPipes: { "test://testfakeurl2": mockZappPipesData },
|
|
151
|
-
}
|
|
151
|
+
};
|
|
152
152
|
|
|
153
153
|
rerender({ store: store2 });
|
|
154
154
|
|
|
@@ -169,10 +169,10 @@ describe("useFeedLoader", () => {
|
|
|
169
169
|
.spyOn(reactReduxModules, "useDispatch")
|
|
170
170
|
.mockImplementation(() => jest.fn());
|
|
171
171
|
|
|
172
|
-
const initialStore =
|
|
172
|
+
const initialStore = {
|
|
173
173
|
plugins: [],
|
|
174
174
|
zappPipes: { "test://testfakeurl": "foobar" },
|
|
175
|
-
}
|
|
175
|
+
};
|
|
176
176
|
|
|
177
177
|
const { result, rerender } = renderHook(
|
|
178
178
|
() => useFeedLoader({ feedUrl: "test://testfakeurl2" }),
|
|
@@ -181,20 +181,22 @@ describe("useFeedLoader", () => {
|
|
|
181
181
|
|
|
182
182
|
expect(result.current.data).toBeNull();
|
|
183
183
|
|
|
184
|
-
expect(loadPipesDataSpy).
|
|
184
|
+
expect(loadPipesDataSpy.mock.calls[0][0]).toBe(feedUrl);
|
|
185
|
+
|
|
186
|
+
expect(loadPipesDataSpy.mock.calls[0][1]).toMatchObject({
|
|
185
187
|
clearCache: true,
|
|
186
188
|
riverId: undefined,
|
|
187
189
|
resolvers: {
|
|
188
190
|
screen: {
|
|
189
|
-
screenStateStore:
|
|
191
|
+
screenStateStore: expect.any(Function),
|
|
190
192
|
},
|
|
191
193
|
},
|
|
192
194
|
});
|
|
193
195
|
|
|
194
|
-
const store2 =
|
|
196
|
+
const store2 = {
|
|
195
197
|
plugins: [],
|
|
196
198
|
zappPipes: { "test://testfakeurl2": mockZappPipesData },
|
|
197
|
-
}
|
|
199
|
+
};
|
|
198
200
|
|
|
199
201
|
rerender({ store: store2 });
|
|
200
202
|
|
|
@@ -207,8 +209,10 @@ describe("useFeedLoader", () => {
|
|
|
207
209
|
const feedUrl = "test://testfakeurl";
|
|
208
210
|
const feedUrlWithNext = "test://withnexttestfakeurl";
|
|
209
211
|
|
|
210
|
-
const wrapper: React.FC<any> = ({ children,
|
|
211
|
-
<
|
|
212
|
+
const wrapper: React.FC<any> = ({ children, ...props }) => (
|
|
213
|
+
<WrappedWithProviders store={props.store || {}}>
|
|
214
|
+
{children}
|
|
215
|
+
</WrappedWithProviders>
|
|
212
216
|
);
|
|
213
217
|
|
|
214
218
|
describe("reloadData", () => {
|
|
@@ -221,10 +225,10 @@ describe("useFeedLoader", () => {
|
|
|
221
225
|
.spyOn(reactReduxModules, "useDispatch")
|
|
222
226
|
.mockImplementation(() => jest.fn());
|
|
223
227
|
|
|
224
|
-
const initialStore =
|
|
228
|
+
const initialStore = {
|
|
225
229
|
plugins: [],
|
|
226
230
|
zappPipes: { [feedUrl]: "foobar" },
|
|
227
|
-
}
|
|
231
|
+
};
|
|
228
232
|
|
|
229
233
|
const { result } = renderHook(() => useFeedLoader({ feedUrl }), {
|
|
230
234
|
wrapper,
|
|
@@ -233,14 +237,22 @@ describe("useFeedLoader", () => {
|
|
|
233
237
|
|
|
234
238
|
const { reloadData } = result.current;
|
|
235
239
|
|
|
236
|
-
reloadData();
|
|
240
|
+
reloadData?.();
|
|
241
|
+
|
|
242
|
+
expect(loadPipesDataSpy).toHaveBeenCalled();
|
|
243
|
+
|
|
244
|
+
expect(
|
|
245
|
+
loadPipesDataSpy.mock.calls[loadPipesDataSpy.mock.calls.length - 1][0]
|
|
246
|
+
).toBe(feedUrl);
|
|
237
247
|
|
|
238
|
-
expect(
|
|
248
|
+
expect(
|
|
249
|
+
loadPipesDataSpy.mock.calls[loadPipesDataSpy.mock.calls.length - 1][1]
|
|
250
|
+
).toMatchObject({
|
|
239
251
|
clearCache: true,
|
|
240
252
|
silentRefresh: true,
|
|
241
253
|
resolvers: {
|
|
242
254
|
screen: {
|
|
243
|
-
screenStateStore:
|
|
255
|
+
screenStateStore: expect.any(Function),
|
|
244
256
|
},
|
|
245
257
|
},
|
|
246
258
|
});
|
|
@@ -262,10 +274,10 @@ describe("useFeedLoader", () => {
|
|
|
262
274
|
.spyOn(reactReduxModules, "useDispatch")
|
|
263
275
|
.mockImplementation(() => jest.fn());
|
|
264
276
|
|
|
265
|
-
const initialStore =
|
|
277
|
+
const initialStore = {
|
|
266
278
|
plugins: [],
|
|
267
279
|
zappPipes: { [feedUrlWithNext]: { data: { next: nextUrl } } },
|
|
268
|
-
}
|
|
280
|
+
};
|
|
269
281
|
|
|
270
282
|
const { result } = renderHook(
|
|
271
283
|
() => useFeedLoader({ feedUrl: feedUrlWithNext }),
|
|
@@ -277,14 +289,22 @@ describe("useFeedLoader", () => {
|
|
|
277
289
|
|
|
278
290
|
const { loadNext } = result.current;
|
|
279
291
|
|
|
280
|
-
loadNext();
|
|
292
|
+
loadNext?.();
|
|
293
|
+
|
|
294
|
+
expect(loadPipesDataSpy).toHaveBeenCalled();
|
|
295
|
+
|
|
296
|
+
expect(
|
|
297
|
+
loadPipesDataSpy.mock.calls[loadPipesDataSpy.mock.calls.length - 1][0]
|
|
298
|
+
).toBe(nextUrl);
|
|
281
299
|
|
|
282
|
-
expect(
|
|
300
|
+
expect(
|
|
301
|
+
loadPipesDataSpy.mock.calls[loadPipesDataSpy.mock.calls.length - 1][1]
|
|
302
|
+
).toMatchObject({
|
|
283
303
|
parentFeed: feedUrlWithNext,
|
|
284
304
|
silentRefresh: true,
|
|
285
305
|
resolvers: {
|
|
286
306
|
screen: {
|
|
287
|
-
screenStateStore:
|
|
307
|
+
screenStateStore: expect.any(Function),
|
|
288
308
|
},
|
|
289
309
|
},
|
|
290
310
|
});
|
package/reactHooks/feed/index.ts
CHANGED