@applicaster/zapp-react-native-utils 16.0.0-alpha.3112220329 → 16.0.0-alpha.3202041045

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 (82) hide show
  1. package/actionUtils/index.ts +3 -1
  2. package/actionsExecutor/ActionExecutor.ts +10 -1
  3. package/actionsExecutor/ActionExecutorContext.tsx +3 -0
  4. package/actionsExecutor/__tests__/ActionExecutor.test.ts +71 -0
  5. package/actionsExecutor/actions/__tests__/navigateToScreen.test.ts +139 -5
  6. package/actionsExecutor/actions/__tests__/openBottomSheet.customContent.test.ts +24 -0
  7. package/actionsExecutor/actions/__tests__/openBottomSheet.inlineItems.test.ts +88 -0
  8. package/actionsExecutor/actions/__tests__/showAlert.test.ts +49 -0
  9. package/actionsExecutor/actions/__tests__/showToast.test.ts +2 -2
  10. package/actionsExecutor/actions/index.ts +2 -0
  11. package/actionsExecutor/actions/navigateToScreen.ts +29 -45
  12. package/actionsExecutor/actions/openBottomSheet.ts +20 -6
  13. package/actionsExecutor/actions/showAlert.ts +51 -0
  14. package/actionsExecutor/actions/showToast.ts +2 -2
  15. package/actionsExecutor/consts.ts +1 -0
  16. package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +4 -11
  17. package/appUtils/playerManager/OverlayObserver/__tests__/getUpcomingQueue.test.ts +67 -0
  18. package/appUtils/playerManager/OverlayObserver/__tests__/utils.test.ts +9 -2
  19. package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.ts +15 -2
  20. package/appUtils/playerManager/OverlayObserver/utils.ts +2 -5
  21. package/appUtils/playerManager/__tests__/playerDiagnostics.test.ts +62 -0
  22. package/appUtils/playerManager/__tests__/playerNative.test.ts +76 -0
  23. package/appUtils/playerManager/__tests__/playerNativeCommand.test.ts +62 -0
  24. package/appUtils/playerManager/__tests__/playerToolbox.test.ts +281 -0
  25. package/appUtils/playerManager/__tests__/usePlayerControllerSetup.test.tsx +62 -0
  26. package/appUtils/playerManager/const.ts +13 -0
  27. package/appUtils/playerManager/index.ts +7 -0
  28. package/appUtils/playerManager/player.ts +25 -0
  29. package/appUtils/playerManager/playerNative.ts +22 -0
  30. package/appUtils/playerManager/playerNativeCommand.ts +3 -6
  31. package/appUtils/playerManager/playerToolbox.ts +196 -0
  32. package/appUtils/playerManager/usePlayerControllerSetup.tsx +6 -0
  33. package/arrayUtils/__tests__/reorderByIds.test.ts +13 -0
  34. package/arrayUtils/index.ts +26 -43
  35. package/cellUtils/__tests__/getButtonsEnabled.test.ts +28 -0
  36. package/cellUtils/__tests__/getCellNavigationEnabled.test.ts +36 -0
  37. package/cellUtils/__tests__/getIsCellNavigationEnabled.test.ts +35 -0
  38. package/cellUtils/__tests__/isCellNavigationEnabled.test.ts +21 -0
  39. package/cellUtils/index.ts +40 -0
  40. package/configurationUtils/__tests__/modalBlocksParser.test.ts +178 -46
  41. package/configurationUtils/modalBlocksParser.ts +57 -101
  42. package/entryActions/__tests__/buildEntryActions.test.ts +97 -18
  43. package/entryActions/aliasPresentation.ts +26 -42
  44. package/entryActions/buildEntryActions.ts +1 -0
  45. package/entryActions/index.ts +0 -2
  46. package/focusManager/FocusManager.ts +26 -0
  47. package/focusManager/__tests__/FocusManager.test.ts +9 -0
  48. package/localizationUtils/index.ts +12 -6
  49. package/logger/__tests__/xrayToolbox.test.ts +69 -0
  50. package/logger/xrayToolbox.ts +67 -0
  51. package/manifestUtils/defaultManifestConfigurations/player.js +8 -0
  52. package/manifestUtils/keys.js +7 -2
  53. package/manifestUtils/mobileAction/container/__tests__/mobileActionButtonsContainer.test.js +21 -0
  54. package/manifestUtils/mobileAction/groups/__tests__/buildMobileActionButtonGroups.test.js +1 -1
  55. package/manifestUtils/mobileAction/groups/defaults.js +2 -1
  56. package/modalState/ModalOrchestrator.ts +1 -0
  57. package/modalState/RemoteEditableCollection.ts +9 -1
  58. package/modalState/__tests__/ModalOrchestrator.phase3.test.ts +16 -0
  59. package/modalState/__tests__/RemoteEditableCollection.test.ts +90 -1
  60. package/modalState/__tests__/useModalStoreState.test.ts +142 -0
  61. package/modalState/store.ts +9 -2
  62. package/modalState/types.ts +3 -0
  63. package/modalState/useBottomSheetContent.ts +17 -12
  64. package/navigationUtils/xrayNavigator.ts +12 -0
  65. package/package.json +3 -2
  66. package/pluginUtils/__tests__/pluginUtils.test.js +32 -0
  67. package/pluginUtils/index.ts +29 -0
  68. package/quickBrickAssets/__tests__/index.test.ts +18 -0
  69. package/quickBrickAssets/index.ts +35 -0
  70. package/reactHooks/__tests__/getPluginConfiguration.test.ts +82 -0
  71. package/reactHooks/actions/index.ts +29 -23
  72. package/reactHooks/cell-click/__tests__/index.test.js +64 -0
  73. package/reactHooks/cell-click/index.ts +21 -14
  74. package/reactHooks/connection/index.tsx +8 -5
  75. package/reactHooks/feed/__tests__/useMarkPipesDataStale.test.tsx +243 -0
  76. package/reactHooks/feed/useMarkPipesDataStale.ts +68 -23
  77. package/reactHooks/navigation/__tests__/index.test.tsx +128 -1
  78. package/reactHooks/navigation/useRoute.ts +8 -4
  79. package/reactHooks/usePluginConfiguration.ts +84 -46
  80. package/riverComponetsMeasurementProvider/index.tsx +3 -1
  81. package/uiActionsRegistrator/__tests__/subscribe.test.ts +26 -0
  82. package/uiActionsRegistrator/registry.ts +16 -0
@@ -0,0 +1,51 @@
1
+ /// <reference types="@applicaster/applicaster-types" />
2
+ import { showAlertDialog } from "../../alertUtils";
3
+ import { ActionResult } from "../ActionExecutor";
4
+ import { ActionHandler } from "../types";
5
+ import { createLogger } from "../../logger";
6
+
7
+ const { log_info } = createLogger({
8
+ subsystem: "ActionExecutorContext",
9
+ category: "General",
10
+ });
11
+
12
+ /**
13
+ * Options for the showAlert action.
14
+ */
15
+ export interface ShowAlertOptions {
16
+ /** Dialog title (required) */
17
+ title: string;
18
+
19
+ /** Dialog message */
20
+ message?: string;
21
+
22
+ /** Dismiss button text. Defaults to "OK" */
23
+ okButtonText?: string;
24
+ }
25
+
26
+ /**
27
+ * Tells the user something and waits for them to acknowledge it.
28
+ *
29
+ * One button, because there is nothing to decide. Use `confirmDialog` when the
30
+ * user is choosing between going on and backing out; a `confirmDialog` without
31
+ * a cancel label still renders two buttons, the second one blank.
32
+ *
33
+ * Always resolves to Success: dismissing a notice is not a cancellation, so the
34
+ * rest of the chain runs.
35
+ */
36
+ export const showAlertAction: ActionHandler<ShowAlertOptions> = async (
37
+ action
38
+ ): Promise<ActionResult> => {
39
+ log_info("handleAction: showAlert event");
40
+
41
+ const options = action.options as ShowAlertOptions;
42
+
43
+ return new Promise<ActionResult>((resolve) => {
44
+ showAlertDialog({
45
+ title: options?.title,
46
+ message: options?.message,
47
+ okButtonText: options?.okButtonText || "OK",
48
+ completion: () => resolve(ActionResult.Success),
49
+ });
50
+ });
51
+ };
@@ -5,7 +5,7 @@ import { postEvent } from "../../reactHooks/useSubscriberFor";
5
5
 
6
6
  export const CONFIRMATION_TOAST_SOURCE = "confirmation";
7
7
 
8
- export const CONFIRMATION_TOAST_DEFAULT_TIMEOUT = 1000;
8
+ export const CONFIRMATION_TOAST_DEFAULT_TIMEOUT = 5000;
9
9
 
10
10
  /**
11
11
  * Shows a mobile confirmation toast with Theme defaults from
@@ -13,7 +13,7 @@ export const CONFIRMATION_TOAST_DEFAULT_TIMEOUT = 1000;
13
13
  *
14
14
  * This action is confirmation-only by design: it always stamps
15
15
  * `source: "confirmation"` (so Theme confirmation styles apply) and defaults
16
- * `timeout` to 1000 ms. Non-confirmation toasts (e.g. Offline Experience)
16
+ * `timeout` to 5000 ms. Non-confirmation toasts (e.g. Offline Experience)
17
17
  * must post on the shared `"showToast"` event bus directly and omit `source`.
18
18
  * Callers may override individual style fields; Theme supplies the rest.
19
19
  *
@@ -23,6 +23,7 @@ export const ACTION_TYPES = {
23
23
  SWITCH_LAYOUT: "switchLayout",
24
24
  APP_RESTART: "appRestart",
25
25
  CONFIRM_DIALOG: "confirmDialog",
26
+ SHOW_ALERT: "showAlert",
26
27
  SCREEN_SET_VARIABLE: "screenSetVariable",
27
28
  SCREEN_TOGGLE_FLAG: "screenToggleFlag",
28
29
  } as const;
@@ -2,8 +2,7 @@ import { BehaviorSubject, Observable } from "rxjs";
2
2
  import { distinctUntilChanged } from "rxjs/operators";
3
3
  import { Player } from "../player";
4
4
  import { createLogger, utilsLogger } from "../../../logger";
5
- import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
6
- import { findPluginByIdentifier } from "../../../pluginUtils";
5
+ import { getPluginByIdentifier } from "@applicaster/zapp-react-native-utils/pluginUtils";
7
6
  import {
8
7
  loadFeedEntry,
9
8
  loadFeedAndPrefetchThumbnailImage,
@@ -237,11 +236,8 @@ export class OverlaysObserver {
237
236
 
238
237
  preparePlayNext = async () => {
239
238
  try {
240
- const plugins = appStore.get("plugins");
241
-
242
- const playNextPlugin: any = findPluginByIdentifier(
243
- "QuickBrickPlayNextOverlay",
244
- plugins
239
+ const playNextPlugin: any = getPluginByIdentifier(
240
+ "QuickBrickPlayNextOverlay"
245
241
  );
246
242
 
247
243
  if (!playNextPlugin) {
@@ -252,10 +248,7 @@ export class OverlaysObserver {
252
248
  return;
253
249
  }
254
250
 
255
- const playNextFeedUrl: string = await retrieveFeedUrl(
256
- this.entry,
257
- plugins
258
- );
251
+ const playNextFeedUrl: string = await retrieveFeedUrl(this.entry);
259
252
 
260
253
  if (!playNextFeedUrl || typeof playNextFeedUrl !== "string") {
261
254
  log_debug(
@@ -0,0 +1,67 @@
1
+ import { getUpcomingQueue } from "../getUpcomingQueue";
2
+
3
+ describe("getUpcomingQueue", () => {
4
+ it("returns null when plugin is not provided or has no module", async () => {
5
+ expect(await getUpcomingQueue(undefined)).toBeNull();
6
+ expect(await getUpcomingQueue({} as any)).toBeNull();
7
+ });
8
+
9
+ it("returns null when QueueManager is not present on module", async () => {
10
+ const plugin = {
11
+ identifier: "queue-action",
12
+ module: {},
13
+ } as any;
14
+
15
+ expect(await getUpcomingQueue(plugin)).toBeNull();
16
+ });
17
+
18
+ it("calls setupQuickBrickQueue when QueueManager is not initialized", async () => {
19
+ const setupQuickBrickQueue = jest.fn();
20
+ const mockUpcoming = [{ id: "item_1" }];
21
+
22
+ const QueueManager = {
23
+ isInitialized: jest
24
+ .fn()
25
+ .mockReturnValueOnce(false)
26
+ .mockReturnValueOnce(true),
27
+ instance: {
28
+ getUpcomingQueue: jest.fn().mockResolvedValue(mockUpcoming),
29
+ },
30
+ };
31
+
32
+ const plugin = {
33
+ identifier: "queue-action",
34
+ module: {
35
+ QueueManager,
36
+ setupQuickBrickQueue,
37
+ },
38
+ } as any;
39
+
40
+ const result = await getUpcomingQueue(plugin);
41
+
42
+ expect(setupQuickBrickQueue).toHaveBeenCalled();
43
+ expect(result).toEqual(mockUpcoming);
44
+ });
45
+
46
+ it("returns upcoming items when QueueManager is already initialized", async () => {
47
+ const mockUpcoming = [{ id: "item_1" }, { id: "item_2" }];
48
+
49
+ const QueueManager = {
50
+ isInitialized: jest.fn().mockReturnValue(true),
51
+ instance: {
52
+ getUpcomingQueue: jest.fn().mockResolvedValue(mockUpcoming),
53
+ },
54
+ };
55
+
56
+ const plugin = {
57
+ identifier: "queue-action",
58
+ module: {
59
+ QueueManager,
60
+ },
61
+ } as any;
62
+
63
+ const result = await getUpcomingQueue(plugin);
64
+
65
+ expect(result).toEqual(mockUpcoming);
66
+ });
67
+ });
@@ -1,5 +1,12 @@
1
1
  import { retrieveFeedUrl, retrievePlayPreviousFeedUrl } from "../utils";
2
2
 
3
+ // mock for import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
4
+ jest.mock("@applicaster/zapp-react-native-redux/AppStore", () => ({
5
+ appStore: {
6
+ get: jest.fn(() => []),
7
+ },
8
+ }));
9
+
3
10
  const entryWith = (extensions: Record<string, unknown>): ZappEntry =>
4
11
  ({ id: "1", type: { value: "video" }, extensions }) as ZappEntry;
5
12
 
@@ -7,14 +14,14 @@ describe("playlist feed url accessors", () => {
7
14
  it("returns the next feed url when present", async () => {
8
15
  const entry = entryWith({ play_next_feed_url: "https://feeds/next.json" });
9
16
 
10
- await expect(retrieveFeedUrl(entry, [])).resolves.toBe(
17
+ await expect(retrieveFeedUrl(entry)).resolves.toBe(
11
18
  "https://feeds/next.json"
12
19
  );
13
20
  });
14
21
 
15
22
  it("resolves to null for a missing entry", async () => {
16
23
  await expect(
17
- retrieveFeedUrl(undefined as unknown as ZappEntry, [])
24
+ retrieveFeedUrl(undefined as unknown as ZappEntry)
18
25
  ).resolves.toBeNull();
19
26
  });
20
27
 
@@ -1,9 +1,22 @@
1
1
  export const getUpcomingQueue = async (
2
2
  plugin?: QuickBrickPlugin | ZappPlugin
3
3
  ): Promise<unknown[] | null> => {
4
- const QueueManager = (plugin as any)?.module?.QueueManager;
4
+ const module = (plugin as any)?.module;
5
+ const QueueManager = module?.QueueManager;
6
+ const setupQuickBrickQueue = module?.setupQuickBrickQueue;
5
7
 
6
- if (!QueueManager?.instance) {
8
+ if (!QueueManager) {
9
+ return null;
10
+ }
11
+
12
+ if (
13
+ !QueueManager.isInitialized?.() &&
14
+ typeof setupQuickBrickQueue === "function"
15
+ ) {
16
+ setupQuickBrickQueue();
17
+ }
18
+
19
+ if (!QueueManager.isInitialized?.()) {
7
20
  return null;
8
21
  }
9
22
 
@@ -59,13 +59,10 @@ export const parseTimeToSeconds = (timeStr: string): number => {
59
59
  * on playerManager, decoupling Queue playback logic entirely from OverlaysObserver (which should
60
60
  * strictly focus on recommendation visuals and chapter markers).
61
61
  */
62
- export const retrieveFeedUrl = async (
63
- entry: ZappEntry,
64
- plugins?: ZappPlugin[]
65
- ): Promise<string> => {
62
+ export const retrieveFeedUrl = async (entry: ZappEntry): Promise<string> => {
66
63
  if (!entry) return null;
67
64
 
68
- const availablePlugins = plugins || appStore.get("plugins");
65
+ const availablePlugins = appStore.get("plugins");
69
66
 
70
67
  const queuePlugin = findPluginByIdentifier(
71
68
  "queue-action",
@@ -0,0 +1,62 @@
1
+ import { Player } from "../player";
2
+
3
+ const entry = (id: string) => ({ id, title: id }) as unknown as ZappEntry;
4
+
5
+ const makePlayer = () => {
6
+ const player = new Player({ playerId: "test-player" });
7
+ player.entry = entry("first");
8
+
9
+ return player;
10
+ };
11
+
12
+ describe("Player per-entry diagnostics", () => {
13
+ it("keeps the latest of onError and onVideoError, and clears it when a new entry loads", () => {
14
+ const player = makePlayer();
15
+ const second = { message: "second" };
16
+
17
+ player.onError({ message: "first" } as any);
18
+ player.onVideoError(second as any);
19
+
20
+ expect(player.lastError).toBe(second);
21
+
22
+ player.entry = entry("second");
23
+
24
+ expect(player.lastError).toBeNull();
25
+ });
26
+
27
+ it("reads the next entry without creating the overlay observer", () => {
28
+ const player = makePlayer();
29
+
30
+ expect(player.getPlayNextEntry()).toBeNull();
31
+
32
+ jest
33
+ .spyOn(player.getOverlayObservable(), "getPlayNextEntry")
34
+ .mockReturnValue(entry("next"));
35
+
36
+ expect(player.getPlayNextEntry()).toEqual(entry("next"));
37
+ });
38
+
39
+ it("keeps the content clock while a client-side ad plays", () => {
40
+ const player = makePlayer();
41
+
42
+ player.onVideoProgress({
43
+ currentTime: 300,
44
+ duration: 1800,
45
+ seekableDuration: 1800,
46
+ });
47
+
48
+ player.onAdBegin({});
49
+
50
+ player.onVideoProgress({
51
+ currentTime: 5,
52
+ duration: 15,
53
+ seekableDuration: 15,
54
+ });
55
+
56
+ expect(player.getPosition()).toBe(5);
57
+ expect(player.getDuration()).toBe(15);
58
+ expect(player.getContentPosition()).toBe(300);
59
+ expect(player.getContentDuration()).toBe(1800);
60
+ expect(player.getSeekableDuration()).toBe(1800);
61
+ });
62
+ });
@@ -36,3 +36,79 @@ describe("PlayerNative.isAudioItem", () => {
36
36
  expect(playerWithEntry(null).isAudioItem()).toBe(false);
37
37
  });
38
38
  });
39
+
40
+ describe("PlayerNative.command", () => {
41
+ it("invokes native function with playerId on iOS platform", () => {
42
+ const player = new PlayerNative({
43
+ entry: null,
44
+ player: { current: {} } as any,
45
+ playerId: "test-player",
46
+ playerPluginId: "QuickBrickPlayerPlugin",
47
+ });
48
+
49
+ const commandSpy = jest.fn();
50
+
51
+ jest.spyOn(player, "getPlayerModule").mockReturnValue({
52
+ command: commandSpy,
53
+ } as any);
54
+
55
+ player.command("setPlayerControls", { transport: ["play"] });
56
+
57
+ expect(commandSpy).toHaveBeenCalledWith(
58
+ "setPlayerControls",
59
+ "test-player",
60
+ {
61
+ transport: ["play"],
62
+ }
63
+ );
64
+ });
65
+
66
+ it("invokes native function with view tag on Android when method exists", () => {
67
+ const reactUtils = require("../../../reactUtils");
68
+ jest.spyOn(reactUtils, "isApplePlatform").mockReturnValue(false);
69
+
70
+ const player = new PlayerNative({
71
+ entry: null,
72
+ player: { current: {} } as any,
73
+ playerId: "test-player",
74
+ playerPluginId: "QuickBrickPlayerPlugin",
75
+ });
76
+
77
+ const commandSpy = jest.fn();
78
+
79
+ jest.spyOn(player, "getPlayerModule").mockReturnValue({
80
+ command: commandSpy,
81
+ } as any);
82
+
83
+ jest.spyOn(player, "getComponentNode").mockReturnValue(12345 as any);
84
+
85
+ player.command("setPlayerControls", { transport: ["play"] });
86
+
87
+ expect(commandSpy).toHaveBeenCalledWith(12345, "setPlayerControls", {
88
+ transport: ["play"],
89
+ });
90
+
91
+ (reactUtils.isApplePlatform as jest.Mock).mockRestore();
92
+ });
93
+
94
+ it("handles when native module does not have command method on Android", () => {
95
+ const reactUtils = require("../../../reactUtils");
96
+ jest.spyOn(reactUtils, "isApplePlatform").mockReturnValue(false);
97
+
98
+ const player = new PlayerNative({
99
+ entry: null,
100
+ player: { current: {} } as any,
101
+ playerId: "test-player",
102
+ playerPluginId: "KalturaPlayerPlugin",
103
+ });
104
+
105
+ jest.spyOn(player, "getPlayerModule").mockReturnValue({} as any);
106
+ jest.spyOn(player, "getComponentNode").mockReturnValue(12345 as any);
107
+
108
+ expect(() => {
109
+ player.command("setPlayerControls", { transport: ["play"] });
110
+ }).not.toThrow();
111
+
112
+ (reactUtils.isApplePlatform as jest.Mock).mockRestore();
113
+ });
114
+ });
@@ -0,0 +1,62 @@
1
+ import { NativeModules } from "react-native";
2
+ import {
3
+ PlayerNativeCommandTypes,
4
+ PlayerNativeSendCommand,
5
+ } from "../playerNativeCommand";
6
+
7
+ describe("PlayerNativeSendCommand", () => {
8
+ const reactUtils = require("../../../reactUtils");
9
+
10
+ afterEach(() => {
11
+ jest.restoreAllMocks();
12
+ });
13
+
14
+ it("calls NativeModules.PlayerModule.command on Apple platform with playerId", () => {
15
+ jest.spyOn(reactUtils, "isApplePlatform").mockReturnValue(true);
16
+
17
+ NativeModules.PlayerModule = {
18
+ command: jest.fn(),
19
+ };
20
+
21
+ PlayerNativeSendCommand(
22
+ PlayerNativeCommandTypes.setPlayerControls,
23
+ "player-123",
24
+ { transport: ["play"] }
25
+ );
26
+
27
+ expect(NativeModules.PlayerModule.command).toHaveBeenCalledWith(
28
+ "setPlayerControls",
29
+ "player-123",
30
+ { transport: ["play"] }
31
+ );
32
+ });
33
+
34
+ it("does not call NativeModules.PlayerModule.command on non-Apple platform", () => {
35
+ jest.spyOn(reactUtils, "isApplePlatform").mockReturnValue(false);
36
+
37
+ NativeModules.PlayerModule = {
38
+ command: jest.fn(),
39
+ };
40
+
41
+ PlayerNativeSendCommand(
42
+ PlayerNativeCommandTypes.setPlayerControls,
43
+ "player-123",
44
+ { transport: ["play"] }
45
+ );
46
+
47
+ expect(NativeModules.PlayerModule.command).not.toHaveBeenCalled();
48
+ });
49
+
50
+ it("does nothing when playerId or command is missing", () => {
51
+ jest.spyOn(reactUtils, "isApplePlatform").mockReturnValue(true);
52
+
53
+ NativeModules.PlayerModule = {
54
+ command: jest.fn(),
55
+ };
56
+
57
+ PlayerNativeSendCommand("", "player-123");
58
+ PlayerNativeSendCommand("setPlayerControls", "");
59
+
60
+ expect(NativeModules.PlayerModule.command).not.toHaveBeenCalled();
61
+ });
62
+ });