@applicaster/zapp-react-native-ui-components 15.1.21-rc.2 → 15.1.22-rc.0

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.
@@ -24,7 +24,9 @@ const MemoizedImage = React.memo(QBImage, R.equals);
24
24
  const isValidExternalSource = (source): boolean =>
25
25
  source?.uri?.startsWith("http") ||
26
26
  source?.uri?.startsWith("data:image") ||
27
- source?.uri?.startsWith("file://");
27
+ source?.uri?.startsWith("file://") ||
28
+ source?.uri?.startsWith("asset:/") ||
29
+ source?.uri?.startsWith("node_modules_");
28
30
 
29
31
  export default function Image({
30
32
  style,
@@ -0,0 +1,47 @@
1
+ import React from "react";
2
+ import { render } from "@testing-library/react-native";
3
+ import { Image } from "react-native";
4
+ import AndroidImage from "../Image/Image.android";
5
+
6
+ jest.mock(
7
+ "@applicaster/zapp-react-native-ui-components/Components/Image",
8
+ () => ({
9
+ QBImage: require("react-native").Image,
10
+ })
11
+ );
12
+
13
+ jest.mock("../Image/hooks", () => ({
14
+ useImageSourceWithDefault: ({ uri }) => ({ uri }),
15
+ useCheckFailedUri: () => ({
16
+ appendFailedToLoadUri: jest.fn(),
17
+ isFailedToLoadUri: () => false,
18
+ }),
19
+ }));
20
+
21
+ jest.mock("../Image/hooks/useImageSize", () => ({
22
+ __esModule: true,
23
+ default: ({ source }) => source,
24
+ }));
25
+
26
+ describe("Android MasterCell image sources", () => {
27
+ test.each([
28
+ "asset:/icons/play.png",
29
+ "node_modules_plugin_assets_play",
30
+ "https://example.com/play.png",
31
+ "data:image/png;base64,test",
32
+ "file:///images/play.png",
33
+ ])("renders supported source %s", (uri) => {
34
+ const { UNSAFE_getByType } = render(<AndroidImage uri={uri} />);
35
+
36
+ expect(UNSAFE_getByType(Image).props.source).toEqual({ uri });
37
+ });
38
+
39
+ test.each(["unsupported://play.png", "", undefined])(
40
+ "does not render unsupported source %s",
41
+ (uri) => {
42
+ const { UNSAFE_queryByType } = render(<AndroidImage uri={uri} />);
43
+
44
+ expect(UNSAFE_queryByType(Image)).toBeNull();
45
+ }
46
+ );
47
+ });
@@ -272,7 +272,10 @@ const PlayerContainerComponent = (props: Props) => {
272
272
  const playEntry = (entry: ZappEntry) => navigator.replaceTop(entry, { mode });
273
273
 
274
274
  const onPlayNextPerformNextVideoPlay = React.useCallback(() => {
275
- if (!playNextOverlayState.entry) {
275
+ // The overlay's entry, or the player's when the overlay is not showing yet (X-Ray player.next)
276
+ const nextEntry = playNextOverlayState?.entry ?? player?.getPlayNextEntry();
277
+
278
+ if (!nextEntry) {
276
279
  log_warning(
277
280
  "onPlayNextPerformNextVideoPlay: playNextOverlayState.entry is not defined"
278
281
  );
@@ -281,16 +284,30 @@ const PlayerContainerComponent = (props: Props) => {
281
284
  }
282
285
 
283
286
  log_debug(
284
- `onPlayNextPerformNextVideoPlay: delay completed, replaceTop to item: ${playNextOverlayState.entry.title}, id: ${playNextOverlayState.entry.id}, outgoing item id: ${item?.id}`
287
+ `onPlayNextPerformNextVideoPlay: delay completed, replaceTop to item: ${nextEntry.title}, id: ${nextEntry.id}, outgoing item id: ${item?.id}`
285
288
  );
286
289
 
287
- playNextOverlayState.hidePlayNext();
290
+ (
291
+ playNextOverlayState?.hidePlayNext ??
292
+ player?.getOverlayObservable()?.hidePlayNext
293
+ )?.();
288
294
 
289
295
  playerEvent(PLAYER_EVENTS.PlayNextTriggered, { id: item?.id });
290
296
 
291
297
  setIsLoadingNextVideo(true);
292
- playEntry(playNextOverlayState.entry);
293
- }, [playNextOverlayState, item?.id]);
298
+ playEntry(nextEntry);
299
+ }, [playNextOverlayState, item?.id, player]);
300
+
301
+ // Lets the X-Ray player.next tool run the same Play Next path as the overlay.
302
+ useEffect(() => {
303
+ if (!player) return;
304
+
305
+ player.playNextHandler = onPlayNextPerformNextVideoPlay;
306
+
307
+ return () => {
308
+ player.playNextHandler = null;
309
+ };
310
+ }, [player, onPlayNextPerformNextVideoPlay]);
294
311
 
295
312
  const playNextData: Nullable<PlayNextData> = React.useMemo(() => {
296
313
  if (!playNextOverlayState) {
@@ -127,6 +127,8 @@ jest.mock(
127
127
  seekTo: jest.fn(),
128
128
  getPluginConfiguration: jest.fn(() => ({})),
129
129
  getOverlayObservable: jest.fn(() => ({ getPlayNextEntry: () => null })),
130
+ getPlayNextEntry: jest.fn(() => null),
131
+ playNextHandler: null,
130
132
  };
131
133
 
132
134
  return {
@@ -337,3 +339,52 @@ describe("PlayerContainer hardware back handling", () => {
337
339
  expect(mockNavigator.closeVideoModal).not.toHaveBeenCalled();
338
340
  });
339
341
  });
342
+
343
+ describe("PlayerContainer Play Next action for X-Ray player.next", () => {
344
+ beforeEach(() => {
345
+ jest.clearAllMocks();
346
+ mockIsTV.mockReturnValue(false);
347
+ mockPlayer.getPluginConfiguration.mockReturnValue({});
348
+ mockPlayerManager.getPluginConfiguration.mockReturnValue({});
349
+ mockPlayerManager.on.mockReturnThis();
350
+ mockPlayerManager.removeHandler.mockReturnThis();
351
+ });
352
+
353
+ it("sets its Play Next action on the player and clears it on unmount", () => {
354
+ const { unmount } = renderPlayerContainer();
355
+
356
+ expect(mockPlayer.playNextHandler).toEqual(expect.any(Function));
357
+
358
+ unmount();
359
+
360
+ expect(mockPlayer.playNextHandler).toBeNull();
361
+ });
362
+
363
+ it("plays the player's next entry once, with analytics and the overlay hidden, when the overlay is not showing", () => {
364
+ const next = { id: "entry-2", title: "Next" };
365
+ const hidePlayNext = jest.fn();
366
+ mockPlayer.getPlayNextEntry.mockReturnValue(next);
367
+
368
+ mockPlayer.getOverlayObservable.mockReturnValue({
369
+ getPlayNextEntry: () => next,
370
+ hidePlayNext,
371
+ });
372
+
373
+ renderPlayerContainer();
374
+
375
+ mockPlayer.playNextHandler();
376
+
377
+ expect(hidePlayNext).toHaveBeenCalledTimes(1);
378
+
379
+ expect(mockPlayerManager.invokeHandler).toHaveBeenCalledWith(
380
+ "play_next_triggered",
381
+ { id: "entry-1" }
382
+ );
383
+
384
+ expect(mockNavigator.replaceTop).toHaveBeenCalledTimes(1);
385
+
386
+ expect(mockNavigator.replaceTop).toHaveBeenCalledWith(next, {
387
+ mode: VideoModalMode.MAXIMIZED,
388
+ });
389
+ });
390
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-ui-components",
3
- "version": "15.1.21-rc.2",
3
+ "version": "15.1.22-rc.0",
4
4
  "description": "Applicaster Zapp React Native ui components for the Quick Brick App",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -28,10 +28,10 @@
28
28
  },
29
29
  "homepage": "https://github.com/applicaster/quickbrick#readme",
30
30
  "dependencies": {
31
- "@applicaster/applicaster-types": "15.1.21-rc.2",
32
- "@applicaster/zapp-react-native-bridge": "15.1.21-rc.2",
33
- "@applicaster/zapp-react-native-redux": "15.1.21-rc.2",
34
- "@applicaster/zapp-react-native-utils": "15.1.21-rc.2",
31
+ "@applicaster/applicaster-types": "15.1.22-rc.0",
32
+ "@applicaster/zapp-react-native-bridge": "15.1.22-rc.0",
33
+ "@applicaster/zapp-react-native-redux": "15.1.22-rc.0",
34
+ "@applicaster/zapp-react-native-utils": "15.1.22-rc.0",
35
35
  "fast-json-stable-stringify": "^2.1.0",
36
36
  "promise": "^8.3.0",
37
37
  "react-native-sortables": "1.7.1",