@applicaster/zapp-react-native-utils 16.0.0-rc.110 → 16.0.0-rc.112

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.
@@ -0,0 +1,62 @@
1
+ import { renderHook } from "@testing-library/react-native";
2
+
3
+ import { usePlayerControllerSetup } from "../usePlayerControllerSetup";
4
+ import { PlayerAnalyticsManager } from "../../../analyticsUtils/PlayerAnalyticsManager";
5
+
6
+ const calls: string[] = [];
7
+
8
+ jest.mock("../index", () => ({
9
+ playerManager: {
10
+ isPlayerRegistered: jest.fn(() => false),
11
+ registerPlayer: jest.fn(() => calls.push("registerPlayer")),
12
+ unregisterPlayer: jest.fn(),
13
+ },
14
+ }));
15
+
16
+ jest.mock("../../../analyticsUtils/PlayerAnalyticsManager", () => ({
17
+ PlayerAnalyticsManager: {
18
+ getInstance: jest.fn(() => calls.push("analyticsManager")),
19
+ },
20
+ }));
21
+
22
+ jest.mock("../../../reactHooks/useConfiguration", () => ({
23
+ useConfiguration: () => ({}),
24
+ }));
25
+
26
+ jest.mock("../../../playerUtils/useValidatePlayerConfig", () => ({
27
+ useValidatePlayerConfig: jest.fn(),
28
+ }));
29
+
30
+ jest.mock("../../../reactUtils", () => ({
31
+ isTV: () => false,
32
+ }));
33
+
34
+ jest.mock(
35
+ "@applicaster/zapp-react-native-ui-components/Helpers/Platform",
36
+ () => ({
37
+ isAppleTV: () => false,
38
+ })
39
+ );
40
+
41
+ class Controller {
42
+ destroy = jest.fn();
43
+ }
44
+
45
+ describe("usePlayerControllerSetup", () => {
46
+ it("creates the analytics manager before the player registers", () => {
47
+ renderHook(() =>
48
+ usePlayerControllerSetup({
49
+ props: { playerId: "player-1", entry: { id: "entry-1" } },
50
+ ControllerType: Controller,
51
+ ref: null,
52
+ manifest: {},
53
+ pluginId: "plugin",
54
+ })
55
+ );
56
+
57
+ // The manager attaches analytics to players as they register, so a player
58
+ // registered before it exists would send no player analytics events
59
+ expect(PlayerAnalyticsManager.getInstance).toHaveBeenCalled();
60
+ expect(calls).toEqual(["analyticsManager", "registerPlayer"]);
61
+ });
62
+ });
@@ -8,6 +8,7 @@ import { isAppleTV } from "@applicaster/zapp-react-native-ui-components/Helpers/
8
8
  import { TVSeekController } from "../../reactHooks/player/TVSeekControlller/TVSeekController";
9
9
  import { KeyInputHandler } from "../keyInputHandler/KeyInputHandler";
10
10
  import { isTV } from "../../reactUtils";
11
+ import { PlayerAnalyticsManager } from "../../analyticsUtils/PlayerAnalyticsManager";
11
12
 
12
13
  // TODO: Rename to ControllerType
13
14
  export const usePlayerControllerSetup = ({
@@ -68,6 +69,11 @@ export const usePlayerControllerSetup = ({
68
69
  return;
69
70
  }
70
71
 
72
+ // The analytics manager attaches its listener when a player registers, so it
73
+ // must exist first; on web the Player wrapper that also creates it renders
74
+ // only after the lazily loaded engine chunk, i.e. after registration.
75
+ PlayerAnalyticsManager.getInstance();
76
+
71
77
  playerManager.registerPlayer({
72
78
  id: playerId,
73
79
  playerController,
@@ -224,12 +224,38 @@ class FocusManager {
224
224
 
225
225
  unregisterFocusable(focusableId: string) {
226
226
  const node = this.tree.find(focusableId);
227
+ const parentNode = this.tree.findParent(focusableId);
227
228
 
228
229
  this.focusableComponents = this.focusableComponents.filter(
229
230
  (c) => c !== node?.component
230
231
  );
231
232
 
232
233
  this.tree.remove(focusableId);
234
+
235
+ if (this._focusedId === focusableId) {
236
+ this._focusedId = null;
237
+ this.retainFocus(parentNode);
238
+ }
239
+ }
240
+
241
+ // Empty groups drop their focusable list after the feed fails, which is
242
+ // after Layout's one-shot focus pass. Land on the nearest ancestor that is
243
+ // still mounted so key handlers keep a target.
244
+ private retainFocus(node: TreeNode | null) {
245
+ let current = node;
246
+
247
+ while (current) {
248
+ const ref = current.component;
249
+
250
+ if (ref?.current?.props?.id) {
251
+ this.setFocusLocal(ref);
252
+ ref.current.onFocus?.(ref.current, {}, undefined);
253
+
254
+ return;
255
+ }
256
+
257
+ current = current.parentId ? this.tree.find(current.parentId) : null;
258
+ }
233
259
  }
234
260
 
235
261
  private setNextFocus(
@@ -231,6 +231,15 @@ describe("FocusManager", () => {
231
231
  ).toBeFalsy();
232
232
  });
233
233
 
234
+ it("moves focus to the mounted parent when the focused element unregisters", () => {
235
+ focusManager.setFocus("child1");
236
+
237
+ focusManager.unregisterFocusable("child1");
238
+
239
+ expect(focusManager.focusedId).toBe("group");
240
+ expect(focusManager.focused).toBe(group.current);
241
+ });
242
+
234
243
  it("focusManager registerFocusable should be defined", () => {
235
244
  expect(focusManager.registerFocusable).toBeDefined();
236
245
  });
@@ -472,7 +472,6 @@ const MOBILE_ACTION_BUTTON_FIELDS = [
472
472
  suffix: "assign action",
473
473
  options: [
474
474
  { text: "primary navigation", value: "navigation_action" },
475
- { text: "secondary navigation", value: "secondary_navigation" },
476
475
  { text: "favorite", value: "local_storage_favourites_action" },
477
476
  { text: "more", value: "open-modal-bottom-sheet-cell-action" },
478
477
  { text: "add to calendar", value: "add_to_calendar_action" },
@@ -67,7 +67,7 @@ describe("buildMobileActionButtonGroups", () => {
67
67
  );
68
68
 
69
69
  expect(button2EnabledField.initial_value).toBe(false);
70
- expect(button2AssignActionField.initial_value).toBe("secondary_navigation");
70
+ expect(button2AssignActionField.initial_value).toBe("trailer_action");
71
71
 
72
72
  const button3Fields = groups[3].fields;
73
73
 
@@ -63,7 +63,7 @@ const DEFAULT_MOBILE_ACTION_BUTTON_PRESETS = {
63
63
  },
64
64
  2: {
65
65
  buttonEnabled: false,
66
- assignAction: "secondary_navigation",
66
+ assignAction: "trailer_action",
67
67
  },
68
68
  3: {
69
69
  buttonEnabled: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "16.0.0-rc.110",
3
+ "version": "16.0.0-rc.112",
4
4
  "description": "Applicaster Zapp React Native utilities package",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "homepage": "https://github.com/applicaster/quickbrick#readme",
29
29
  "dependencies": {
30
- "@applicaster/applicaster-types": "16.0.0-rc.110",
30
+ "@applicaster/applicaster-types": "16.0.0-rc.112",
31
31
  "buffer": "^5.2.1",
32
32
  "camelize": "^1.0.0",
33
33
  "dayjs": "^1.11.10",