@applicaster/zapp-react-native-utils 13.0.8 → 13.0.9-alpha.1049520314
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 +60 -84
- package/actionsExecutor/ScreenActions.ts +164 -0
- package/actionsExecutor/StorageActions.ts +110 -0
- package/actionsExecutor/feedDecorator.ts +171 -0
- package/actionsExecutor/screenResolver.ts +11 -0
- package/appUtils/HooksManager/Hook.ts +4 -4
- package/appUtils/HooksManager/index.ts +11 -1
- package/appUtils/accessibilityManager/const.ts +13 -0
- package/appUtils/accessibilityManager/hooks.ts +35 -1
- package/appUtils/accessibilityManager/index.ts +149 -28
- package/appUtils/accessibilityManager/utils.ts +24 -0
- package/appUtils/contextKeysManager/contextResolver.ts +42 -1
- package/appUtils/platform/platformUtils.ts +31 -1
- package/index.d.ts +3 -0
- package/package.json +2 -2
- 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/cell-click/index.ts +8 -1
- package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +3 -0
- package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +71 -31
- package/reactHooks/feed/index.ts +2 -0
- package/reactHooks/feed/useBatchLoading.ts +13 -6
- package/reactHooks/feed/useFeedLoader.tsx +35 -33
- package/reactHooks/feed/useLoadPipesDataDispatch.ts +55 -0
- package/reactHooks/navigation/useRoute.ts +7 -2
- package/reactHooks/navigation/useScreenStateStore.ts +8 -0
- package/storage/ScreenSingleValueProvider.ts +208 -0
- package/storage/ScreenStateMultiSelectProvider.ts +293 -0
- package/storage/StorageMultiSelectProvider.ts +192 -0
- package/storage/StorageSingleSelectProvider.ts +108 -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
|
}));
|
|
@@ -16,7 +16,8 @@ import { ActionExecutorContext } from "@applicaster/zapp-react-native-utils/acti
|
|
|
16
16
|
import { isFunction, noop } from "../../functionUtils";
|
|
17
17
|
import { useSendAnalyticsOnPress } from "../analytics";
|
|
18
18
|
import { logOnPress, warnEmptyContentType } from "./helpers";
|
|
19
|
-
import { useCurrentScreenData } from "../screen";
|
|
19
|
+
import { useCurrentScreenData, useScreenContext } from "../screen";
|
|
20
|
+
import { useScreenStateStore } from "../navigation/useScreenStateStore";
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* If onCellTap is defined execute the function and
|
|
@@ -42,10 +43,12 @@ export const useCellClick = ({
|
|
|
42
43
|
}: Props): onPressReturnFn => {
|
|
43
44
|
const { push, currentRoute } = useNavigation();
|
|
44
45
|
const { pathname } = useRoute();
|
|
46
|
+
const screenStateStore = useScreenStateStore();
|
|
45
47
|
|
|
46
48
|
const onCellTap: Option<Function> = React.useContext(CellTapContext);
|
|
47
49
|
const actionExecutor = React.useContext(ActionExecutorContext);
|
|
48
50
|
const screenData = useCurrentScreenData();
|
|
51
|
+
const screenState = useScreenContext()?.options;
|
|
49
52
|
|
|
50
53
|
const cellSelectable = toBooleanWithDefaultTrue(
|
|
51
54
|
component?.rules?.component_cells_selectable
|
|
@@ -83,6 +86,9 @@ export const useCellClick = ({
|
|
|
83
86
|
await actionExecutor?.handleEntryActions(selectedItem, {
|
|
84
87
|
component,
|
|
85
88
|
screenData,
|
|
89
|
+
screenState,
|
|
90
|
+
screenRoute: pathname,
|
|
91
|
+
screenStateStore,
|
|
86
92
|
});
|
|
87
93
|
}
|
|
88
94
|
|
|
@@ -117,6 +123,7 @@ export const useCellClick = ({
|
|
|
117
123
|
push,
|
|
118
124
|
sendAnalyticsOnPress,
|
|
119
125
|
screenData,
|
|
126
|
+
screenState,
|
|
120
127
|
]
|
|
121
128
|
);
|
|
122
129
|
|
|
@@ -9,6 +9,9 @@ jest.mock("../../navigation");
|
|
|
9
9
|
jest.mock(
|
|
10
10
|
"@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext",
|
|
11
11
|
() => ({
|
|
12
|
+
...jest.requireActual(
|
|
13
|
+
"@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext"
|
|
14
|
+
),
|
|
12
15
|
useScreenContext: jest.fn().mockReturnValue({ screen: {}, entry: {} }),
|
|
13
16
|
})
|
|
14
17
|
);
|
|
@@ -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,15 +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),
|
|
143
|
+
resolvers: {
|
|
144
|
+
screen: expect.any(ScreenStateResolver),
|
|
145
|
+
},
|
|
141
146
|
});
|
|
142
147
|
|
|
143
|
-
const store2 =
|
|
148
|
+
const store2 = {
|
|
144
149
|
plugins: [],
|
|
145
150
|
zappPipes: { "test://testfakeurl2": mockZappPipesData },
|
|
146
|
-
}
|
|
151
|
+
};
|
|
147
152
|
|
|
148
153
|
rerender({ store: store2 });
|
|
149
154
|
|
|
@@ -164,10 +169,10 @@ describe("useFeedLoader", () => {
|
|
|
164
169
|
.spyOn(reactReduxModules, "useDispatch")
|
|
165
170
|
.mockImplementation(() => jest.fn());
|
|
166
171
|
|
|
167
|
-
const initialStore =
|
|
172
|
+
const initialStore = {
|
|
168
173
|
plugins: [],
|
|
169
174
|
zappPipes: { "test://testfakeurl": "foobar" },
|
|
170
|
-
}
|
|
175
|
+
};
|
|
171
176
|
|
|
172
177
|
const { result, rerender } = renderHook(
|
|
173
178
|
() => useFeedLoader({ feedUrl: "test://testfakeurl2" }),
|
|
@@ -176,15 +181,22 @@ describe("useFeedLoader", () => {
|
|
|
176
181
|
|
|
177
182
|
expect(result.current.data).toBeNull();
|
|
178
183
|
|
|
179
|
-
expect(loadPipesDataSpy).
|
|
184
|
+
expect(loadPipesDataSpy.mock.calls[0][0]).toBe(feedUrl);
|
|
185
|
+
|
|
186
|
+
expect(loadPipesDataSpy.mock.calls[0][1]).toMatchObject({
|
|
180
187
|
clearCache: true,
|
|
181
188
|
riverId: undefined,
|
|
189
|
+
resolvers: {
|
|
190
|
+
screen: {
|
|
191
|
+
screenStateStore: expect.any(Function),
|
|
192
|
+
},
|
|
193
|
+
},
|
|
182
194
|
});
|
|
183
195
|
|
|
184
|
-
const store2 =
|
|
196
|
+
const store2 = {
|
|
185
197
|
plugins: [],
|
|
186
198
|
zappPipes: { "test://testfakeurl2": mockZappPipesData },
|
|
187
|
-
}
|
|
199
|
+
};
|
|
188
200
|
|
|
189
201
|
rerender({ store: store2 });
|
|
190
202
|
|
|
@@ -197,8 +209,10 @@ describe("useFeedLoader", () => {
|
|
|
197
209
|
const feedUrl = "test://testfakeurl";
|
|
198
210
|
const feedUrlWithNext = "test://withnexttestfakeurl";
|
|
199
211
|
|
|
200
|
-
const wrapper: React.FC<any> = ({ children,
|
|
201
|
-
<
|
|
212
|
+
const wrapper: React.FC<any> = ({ children, ...props }) => (
|
|
213
|
+
<WrappedWithProviders store={props.store || {}}>
|
|
214
|
+
{children}
|
|
215
|
+
</WrappedWithProviders>
|
|
202
216
|
);
|
|
203
217
|
|
|
204
218
|
describe("reloadData", () => {
|
|
@@ -211,10 +225,10 @@ describe("useFeedLoader", () => {
|
|
|
211
225
|
.spyOn(reactReduxModules, "useDispatch")
|
|
212
226
|
.mockImplementation(() => jest.fn());
|
|
213
227
|
|
|
214
|
-
const initialStore =
|
|
228
|
+
const initialStore = {
|
|
215
229
|
plugins: [],
|
|
216
230
|
zappPipes: { [feedUrl]: "foobar" },
|
|
217
|
-
}
|
|
231
|
+
};
|
|
218
232
|
|
|
219
233
|
const { result } = renderHook(() => useFeedLoader({ feedUrl }), {
|
|
220
234
|
wrapper,
|
|
@@ -223,11 +237,24 @@ describe("useFeedLoader", () => {
|
|
|
223
237
|
|
|
224
238
|
const { reloadData } = result.current;
|
|
225
239
|
|
|
226
|
-
reloadData();
|
|
240
|
+
reloadData?.();
|
|
241
|
+
|
|
242
|
+
expect(loadPipesDataSpy).toHaveBeenCalled();
|
|
243
|
+
|
|
244
|
+
expect(
|
|
245
|
+
loadPipesDataSpy.mock.calls[loadPipesDataSpy.mock.calls.length - 1][0]
|
|
246
|
+
).toBe(feedUrl);
|
|
227
247
|
|
|
228
|
-
expect(
|
|
248
|
+
expect(
|
|
249
|
+
loadPipesDataSpy.mock.calls[loadPipesDataSpy.mock.calls.length - 1][1]
|
|
250
|
+
).toMatchObject({
|
|
229
251
|
clearCache: true,
|
|
230
252
|
silentRefresh: true,
|
|
253
|
+
resolvers: {
|
|
254
|
+
screen: {
|
|
255
|
+
screenStateStore: expect.any(Function),
|
|
256
|
+
},
|
|
257
|
+
},
|
|
231
258
|
});
|
|
232
259
|
|
|
233
260
|
loadPipesDataSpy.mockRestore();
|
|
@@ -247,10 +274,10 @@ describe("useFeedLoader", () => {
|
|
|
247
274
|
.spyOn(reactReduxModules, "useDispatch")
|
|
248
275
|
.mockImplementation(() => jest.fn());
|
|
249
276
|
|
|
250
|
-
const initialStore =
|
|
277
|
+
const initialStore = {
|
|
251
278
|
plugins: [],
|
|
252
279
|
zappPipes: { [feedUrlWithNext]: { data: { next: nextUrl } } },
|
|
253
|
-
}
|
|
280
|
+
};
|
|
254
281
|
|
|
255
282
|
const { result } = renderHook(
|
|
256
283
|
() => useFeedLoader({ feedUrl: feedUrlWithNext }),
|
|
@@ -262,11 +289,24 @@ describe("useFeedLoader", () => {
|
|
|
262
289
|
|
|
263
290
|
const { loadNext } = result.current;
|
|
264
291
|
|
|
265
|
-
loadNext();
|
|
292
|
+
loadNext?.();
|
|
293
|
+
|
|
294
|
+
expect(loadPipesDataSpy).toHaveBeenCalled();
|
|
295
|
+
|
|
296
|
+
expect(
|
|
297
|
+
loadPipesDataSpy.mock.calls[loadPipesDataSpy.mock.calls.length - 1][0]
|
|
298
|
+
).toBe(nextUrl);
|
|
266
299
|
|
|
267
|
-
expect(
|
|
300
|
+
expect(
|
|
301
|
+
loadPipesDataSpy.mock.calls[loadPipesDataSpy.mock.calls.length - 1][1]
|
|
302
|
+
).toMatchObject({
|
|
268
303
|
parentFeed: feedUrlWithNext,
|
|
269
304
|
silentRefresh: true,
|
|
305
|
+
resolvers: {
|
|
306
|
+
screen: {
|
|
307
|
+
screenStateStore: expect.any(Function),
|
|
308
|
+
},
|
|
309
|
+
},
|
|
270
310
|
});
|
|
271
311
|
|
|
272
312
|
loadPipesDataSpy.mockRestore();
|
package/reactHooks/feed/index.ts
CHANGED