@applicaster/zapp-react-dom-app 15.1.17 → 15.1.18-alpha.5403005121

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.
@@ -1,9 +1,25 @@
1
1
  import { DISPLAY_STATES } from "@applicaster/zapp-react-native-ui-components/Contexts/DisplayStateContext";
2
2
  import { confirmationDialogStore } from "@applicaster/zapp-react-native-ui-components/Contexts/ConfirmDialogState";
3
- import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils";
3
+ import {
4
+ focusManager,
5
+ KEYS,
6
+ playerManager,
7
+ } from "@applicaster/zapp-react-native-utils/appUtils";
8
+ import { KeyInputHandler } from "@applicaster/zapp-react-native-utils/appUtils/keyInputHandler/KeyInputHandler";
9
+ import { TVSeekController } from "@applicaster/zapp-react-native-utils/reactHooks/player/TVSeekControlller/TVSeekController";
10
+ import { SKIP_TIME_BASE } from "@applicaster/zapp-react-native-utils/reactHooks/player/const";
4
11
 
5
12
  import { InteractionManagerClass } from "..";
6
13
 
14
+ // The web app runs with the web key map
15
+ jest.mock(
16
+ "@applicaster/zapp-react-native-utils/appUtils/keyCodes/keys/keys",
17
+ () =>
18
+ jest.requireActual(
19
+ "@applicaster/zapp-react-native-utils/appUtils/keyCodes/keys/keys.web"
20
+ )
21
+ );
22
+
7
23
  jest.mock("@applicaster/zapp-react-native-utils/alertUtils", () => ({
8
24
  showConfirmationDialog: jest.fn(),
9
25
  }));
@@ -28,6 +44,8 @@ jest.mock("@applicaster/zapp-react-native-utils/logger", () => {
28
44
  };
29
45
  });
30
46
 
47
+ const globalAny: any = global;
48
+
31
49
  const createBackEvent = () => ({
32
50
  code: "Escape",
33
51
  preventDefault: jest.fn(),
@@ -98,4 +116,128 @@ describe("InteractionManager (web)", () => {
98
116
  expect(props.navigator.canGoBack).toHaveBeenCalled();
99
117
  });
100
118
  });
119
+
120
+ describe("Forward / Rewind keys", () => {
121
+ const POSITION = 100;
122
+
123
+ const createPlayer = (supportsNativeControls: boolean) => ({
124
+ supportsNativeControls: () => supportsNativeControls,
125
+ isPaused: () => false,
126
+ isLive: () => false,
127
+ getPosition: () => POSITION,
128
+ getSeekableDuration: () => 1000,
129
+ pause: jest.fn(),
130
+ play: jest.fn(),
131
+ seekTo: jest.fn(),
132
+ forward: jest.fn(),
133
+ rewind: jest.fn(),
134
+ });
135
+
136
+ let removeListener = () => {};
137
+
138
+ // Same wiring as usePlayerControllerSetup
139
+ const setup = ({ supportsNativeControls, ...props }: any) => {
140
+ const player = createPlayer(supportsNativeControls);
141
+
142
+ jest
143
+ .spyOn(playerManager, "getInstanceController")
144
+ .mockReturnValue(player as any);
145
+
146
+ removeListener = KeyInputHandler.getInstance().addListener(
147
+ new TVSeekController(player as any)
148
+ );
149
+
150
+ return { player, ...createManager(props) };
151
+ };
152
+
153
+ const press = (manager, key) => {
154
+ manager.onKeyDown(KEYS[key]);
155
+ manager.onKeyUp(KEYS[key]);
156
+ jest.runAllTimers();
157
+ };
158
+
159
+ beforeEach(() => jest.useFakeTimers());
160
+
161
+ afterEach(() => {
162
+ removeListener();
163
+ jest.useRealTimers();
164
+ jest.restoreAllMocks();
165
+ });
166
+
167
+ describe("with a player rendering its own controls", () => {
168
+ const expectUntouched = (player, props) => {
169
+ expect(player.seekTo).not.toHaveBeenCalled();
170
+ expect(player.forward).not.toHaveBeenCalled();
171
+ expect(player.rewind).not.toHaveBeenCalled();
172
+ expect(player.pause).not.toHaveBeenCalled();
173
+ expect(props.setDisplayState).not.toHaveBeenCalled();
174
+ };
175
+
176
+ // the deferral logs the focused element
177
+ beforeEach(() => {
178
+ globalAny.document = {};
179
+ });
180
+
181
+ afterEach(() => {
182
+ delete globalAny.document;
183
+ });
184
+
185
+ it.each(["Forward", "Rewind"])("%s is left to the player", (key) => {
186
+ const { player, manager, props } = setup({
187
+ supportsNativeControls: true,
188
+ });
189
+
190
+ const event = { ...KEYS[key], stopPropagation: jest.fn() };
191
+
192
+ manager.onKeyDown(event);
193
+ manager.onKeyUp(event);
194
+ jest.runAllTimers();
195
+
196
+ expectUntouched(player, props);
197
+ // the key has to reach the player's own handler
198
+ expect(event.stopPropagation).not.toHaveBeenCalled();
199
+ });
200
+
201
+ it("does not seek the player while the play-next overlay is visible", () => {
202
+ const { player, manager } = setup({ supportsNativeControls: true });
203
+
204
+ (manager.props.navigator as any).getPlayNextOverlay = () => ({});
205
+
206
+ press(manager, "Forward");
207
+
208
+ expect(player.seekTo).not.toHaveBeenCalled();
209
+ expect(player.forward).not.toHaveBeenCalled();
210
+ });
211
+
212
+ it("keeps the keys while a dialog is visible", () => {
213
+ const { player, manager } = setup({ supportsNativeControls: true });
214
+
215
+ confirmationDialogStore.getState().showDialog();
216
+
217
+ press(manager, "Forward");
218
+
219
+ expect(player.seekTo).toHaveBeenCalledWith(POSITION + SKIP_TIME_BASE);
220
+ });
221
+ });
222
+
223
+ describe("with QuickBrick's transport controls", () => {
224
+ it.each([
225
+ ["Forward", POSITION + SKIP_TIME_BASE],
226
+ ["Rewind", POSITION - SKIP_TIME_BASE],
227
+ ])("%s keeps the delayed seek", (key, expectedPosition) => {
228
+ const { player, manager, props } = setup({
229
+ supportsNativeControls: false,
230
+ displayState: DISPLAY_STATES.PLAYER,
231
+ });
232
+
233
+ press(manager, key);
234
+
235
+ expect(props.setDisplayState).toHaveBeenCalledWith(DISPLAY_STATES.HUD);
236
+ expect(player.seekTo).toHaveBeenCalledWith(expectedPosition);
237
+ expect(player.play).toHaveBeenCalled();
238
+ expect(player.forward).not.toHaveBeenCalled();
239
+ expect(player.rewind).not.toHaveBeenCalled();
240
+ });
241
+ });
242
+ });
101
243
  });
@@ -369,6 +369,10 @@ export class InteractionManagerClass extends React.Component<Props, State> {
369
369
  focusManager.pressOut();
370
370
  }
371
371
 
372
+ if (this.isDeferredToNativeControls(event)) {
373
+ return;
374
+ }
375
+
372
376
  if (
373
377
  keyCode(event).matches(KEYS.Forward) ||
374
378
  (__DEV__ && keyCode(event).matches(KEYS.C))
@@ -568,12 +572,22 @@ export class InteractionManagerClass extends React.Component<Props, State> {
568
572
  return (
569
573
  this.isNativePlayerControlsEnabled() &&
570
574
  !this.isPlayNextOverlayVisible() &&
571
- (keyCode(event).matchesAny(...ARROW_KEYS) ||
572
- keyCode(event).matches(KEYS.Enter) ||
573
- (__DEV__ && keyCode(event).matches(KEYS.X)))
575
+ (keyCode(event).matchesAny(
576
+ ...ARROW_KEYS,
577
+ KEYS.Enter,
578
+ // The player seeks on its own with these keys
579
+ KEYS.Forward,
580
+ KEYS.Rewind
581
+ ) ||
582
+ (__DEV__ && keyCode(event).matchesAny(KEYS.X, KEYS.C, KEYS.Z)))
574
583
  );
575
584
  };
576
585
 
586
+ // An open dialog owns the remote, whatever the player renders.
587
+ isDeferredToNativeControls = (event: KeyboardEvent) =>
588
+ !confirmationDialogStore.getState().isDialogVisible &&
589
+ this.shouldDeferPlaybackNavigationToNativeControls(event);
590
+
577
591
  onKeyDown(event) {
578
592
  const { setDisplayState, navigator, xray } = this.props;
579
593
  const displayState = this.getEffectiveDisplayState();
@@ -612,11 +626,8 @@ export class InteractionManagerClass extends React.Component<Props, State> {
612
626
  // Only handle repeat events for arrow keys
613
627
  if (event.repeat && !keyCode(event).matchesAny(...ARROW_KEYS)) return;
614
628
 
615
- if (
616
- !isDialogVisible &&
617
- this.shouldDeferPlaybackNavigationToNativeControls(event)
618
- ) {
619
- // The player's own spatial navigation handles these keys.
629
+ if (this.isDeferredToNativeControls(event)) {
630
+ // The player's own key handling takes these keys.
620
631
  // TEMP(native-controls): remove after device verification
621
632
  log_debug("[NativeControls] key deferred to player", {
622
633
  key: event.key,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-dom-app",
3
- "version": "15.1.17",
3
+ "version": "15.1.18-alpha.5403005121",
4
4
  "description": "Zapp App Component for Applicaster's Quick Brick React Native App",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -22,11 +22,11 @@
22
22
  },
23
23
  "homepage": "https://github.com/applicaster/zapp-react-dom-app#readme",
24
24
  "dependencies": {
25
- "@applicaster/zapp-react-dom-ui-components": "15.1.17",
26
- "@applicaster/zapp-react-native-bridge": "15.1.17",
27
- "@applicaster/zapp-react-native-redux": "15.1.17",
28
- "@applicaster/zapp-react-native-ui-components": "15.1.17",
29
- "@applicaster/zapp-react-native-utils": "15.1.17",
25
+ "@applicaster/zapp-react-dom-ui-components": "15.1.18-alpha.5403005121",
26
+ "@applicaster/zapp-react-native-bridge": "15.1.18-alpha.5403005121",
27
+ "@applicaster/zapp-react-native-redux": "15.1.18-alpha.5403005121",
28
+ "@applicaster/zapp-react-native-ui-components": "15.1.18-alpha.5403005121",
29
+ "@applicaster/zapp-react-native-utils": "15.1.18-alpha.5403005121",
30
30
  "abortcontroller-polyfill": "^1.7.5",
31
31
  "typeface-montserrat": "^0.0.54",
32
32
  "video.js": "7.14.3",