@applicaster/zapp-react-native-ui-components 14.0.0-rc.35 → 14.0.0-rc.37

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 (25) hide show
  1. package/Components/AnimatedInOut/index.tsx +5 -3
  2. package/Components/Focusable/FocusableTvOS.tsx +3 -3
  3. package/Components/Focusable/FocusableiOS.tsx +1 -1
  4. package/Components/FocusableList/index.tsx +4 -0
  5. package/Components/HandlePlayable/HandlePlayable.tsx +14 -8
  6. package/Components/MasterCell/elementMapper.tsx +1 -2
  7. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +13 -18
  8. package/Components/OfflineHandler/__tests__/__snapshots__/index.test.tsx.snap +9 -0
  9. package/Components/PlayerContainer/PlayerContainer.tsx +26 -22
  10. package/Components/River/ComponentsMap/ComponentsMap.tsx +0 -1
  11. package/Components/River/TV/withTVEventHandler.tsx +1 -1
  12. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +2 -0
  13. package/Components/ScreenRevealManager/ScreenRevealManager.ts +76 -0
  14. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +107 -0
  15. package/Components/ScreenRevealManager/__tests__/withScreenRevealManager.test.tsx +96 -0
  16. package/Components/ScreenRevealManager/index.ts +1 -0
  17. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +79 -0
  18. package/Components/Tabs/TV/Tabs.android.tsx +0 -2
  19. package/Components/Touchable/__tests__/__snapshots__/touchable.test.tsx.snap +34 -0
  20. package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +15 -9
  21. package/Components/VideoLive/animationUtils.ts +3 -3
  22. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +12 -16
  23. package/Components/Viewport/ViewportTracker/__tests__/viewportTracker.test.js +84 -24
  24. package/Decorators/ConfigurationWrapper/withConfigurationProvider.tsx +2 -2
  25. package/package.json +5 -6
@@ -4,9 +4,11 @@ import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/uti
4
4
  import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
5
5
  import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
6
6
 
7
- type AnimatedInterpolatedStyle =
8
- | Animated.AnimatedInterpolation
9
- | [{ [Key: string]: Animated.AnimatedInterpolation }];
7
+ type AnimatedInterpolatedStyle = any;
8
+
9
+ // type AnimatedInterpolatedStyle =
10
+ // | Animated.AnimatedInterpolation
11
+ // | [{ [Key: string]: Animated.AnimatedInterpolation }];
10
12
 
11
13
  type AnimationConfig = {
12
14
  duration: number;
@@ -16,9 +16,9 @@ function noop() {}
16
16
  type Props = {
17
17
  id: string;
18
18
  groupId: string;
19
- onPress?: (nativeEvent: React.SyntheticEvent) => void;
20
- onFocus?: (nativeEvent: React.SyntheticEvent) => void;
21
- onBlur?: (nativeEvent: React.SyntheticEvent) => void;
19
+ onPress?: (nativeEvent: any) => void;
20
+ onFocus?: (nativeEvent: any) => void;
21
+ onBlur?: (nativeEvent: any) => void;
22
22
  children: (focused?: boolean) => React.ReactNode;
23
23
  isParallaxDisabled: boolean;
24
24
  preferredFocus?: boolean;
@@ -12,4 +12,4 @@ function FocusableiOSComponent({ children }: Props) {
12
12
  return children;
13
13
  }
14
14
 
15
- export const FocusableiOS = React.forwardRef(FocusableiOSComponent);
15
+ export const FocusableiOS = FocusableiOSComponent;
@@ -91,6 +91,7 @@ function FocusableListComponent<ItemT>(props: Props<ItemT>, ref) {
91
91
  // eslint-disable-next-line unused-imports/no-unused-vars
92
92
  omitPropsPropagation = [],
93
93
  useScrollView = false,
94
+ onScrollToIndexFailed = noop,
94
95
  } = props;
95
96
 
96
97
  useCheckItemIdsForUnique({ componentId: props.id, items: data });
@@ -277,6 +278,7 @@ function FocusableListComponent<ItemT>(props: Props<ItemT>, ref) {
277
278
  "withStateMemory",
278
279
  "useSequentialLoading",
279
280
  "useScrollView",
281
+ "onScrollToIndexFailed",
280
282
  ...omitPropsPropagation,
281
283
  ],
282
284
  R.__
@@ -305,6 +307,7 @@ function FocusableListComponent<ItemT>(props: Props<ItemT>, ref) {
305
307
  {...getFlatListProps(props)}
306
308
  onEndReached={onEndReached}
307
309
  initialNumToRender={initialNumToRender}
310
+ onScrollToIndexFailed={onScrollToIndexFailed}
308
311
  renderItem={renderItem}
309
312
  focused={focused}
310
313
  data={data}
@@ -319,6 +322,7 @@ function FocusableListComponent<ItemT>(props: Props<ItemT>, ref) {
319
322
  renderItem={renderItem}
320
323
  onEndReached={onEndReached}
321
324
  initialNumToRender={initialNumToRender}
325
+ onScrollToIndexFailed={onScrollToIndexFailed}
322
326
  />
323
327
  )}
324
328
  </ChildrenFocusDeactivatorView>
@@ -14,6 +14,7 @@ import {
14
14
  import { BufferAnimation } from "../PlayerContainer/BufferAnimation";
15
15
  import { PlayerContainer } from "../PlayerContainer";
16
16
  import { useModalSize } from "../VideoModal/hooks";
17
+ import { ViewStyle } from "react-native";
17
18
  import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
18
19
 
19
20
  type Props = {
@@ -150,14 +151,19 @@ export function HandlePlayable({
150
151
  const modalSize = useModalSize();
151
152
 
152
153
  const style = React.useMemo(
153
- () => ({
154
- width: isModal ? modalSize.width : mode === "PIP" ? "100%" : screenWidth,
155
- height: isModal
156
- ? modalSize.height
157
- : mode === "PIP"
158
- ? "100%"
159
- : screenHeight,
160
- }),
154
+ () =>
155
+ ({
156
+ width: isModal
157
+ ? modalSize.width
158
+ : mode === "PIP"
159
+ ? "100%"
160
+ : screenWidth,
161
+ height: isModal
162
+ ? modalSize.height
163
+ : mode === "PIP"
164
+ ? "100%"
165
+ : screenHeight,
166
+ }) as ViewStyle,
161
167
  [screenWidth, screenHeight, modalSize, isModal, mode]
162
168
  );
163
169
 
@@ -73,7 +73,6 @@ export function elementMapper(
73
73
  : {};
74
74
 
75
75
  const componentProps = {
76
- key,
77
76
  style,
78
77
  skipButtons: otherProps?.skipButtons,
79
78
  emitAsyncElementRegistrate: otherProps?.emitAsyncElementRegistrate,
@@ -91,7 +90,7 @@ export function elementMapper(
91
90
  const fn = mapElementWithKey(elementMapper(components, otherProps));
92
91
 
93
92
  return (
94
- <Component {...componentProps}>
93
+ <Component key={key} {...componentProps}>
95
94
  {focusableTypes.has(type) && elements.length > 0
96
95
  ? elements.map(fn)
97
96
  : null}
@@ -1,9 +1,12 @@
1
1
  import React from "react";
2
2
  import { Text, Animated } from "react-native";
3
+ import { render } from "@testing-library/react-native";
3
4
 
4
- import renderer from "react-test-renderer";
5
-
6
- jest.useFakeTimers();
5
+ import {
6
+ NotificationView,
7
+ onlinePhrase,
8
+ offlinePhrase,
9
+ } from "../NotificationView";
7
10
 
8
11
  jest.mock("@applicaster/zapp-react-native-redux/hooks", () => ({
9
12
  usePickFromState: () => ({
@@ -32,39 +35,31 @@ jest.mock("react-native-safe-area-context", () => ({
32
35
 
33
36
  const dismiss = jest.fn();
34
37
 
35
- const {
36
- NotificationView,
37
- onlinePhrase,
38
- offlinePhrase,
39
- } = require("../NotificationView");
40
-
41
38
  describe("NotificationView", () => {
42
39
  it("Show online message when Online", () => {
43
- const component = renderer.create(
44
- <NotificationView online dismiss={dismiss} />
45
- );
40
+ const component = render(<NotificationView online dismiss={dismiss} />);
46
41
 
47
- expect(component.root.findByType(Text).props.children).toBe(onlinePhrase);
42
+ expect(component.UNSAFE_getByType(Text).props.children).toBe(onlinePhrase);
48
43
  });
49
44
 
50
45
  it("Show offline message when Online", () => {
51
- const component = renderer.create(
46
+ const component = render(
52
47
  <NotificationView online={false} dismiss={dismiss} />
53
48
  );
54
49
 
55
- expect(component.root.findByType(Text).props.children).toBe(offlinePhrase);
50
+ expect(component.UNSAFE_getByType(Text).props.children).toBe(offlinePhrase);
56
51
  });
57
52
 
58
53
  it("When hidden is false to true notification is visible", () => {
59
- const component = renderer.create(
54
+ const component = render(
60
55
  <NotificationView online={false} hidden={false} dismiss={dismiss} />
61
56
  );
62
57
 
63
- component.update(
58
+ component.rerender(
64
59
  <NotificationView online={false} hidden={true} dismiss={dismiss} />
65
60
  );
66
61
 
67
- const animatedView = component.root.findByType(Animated.View);
62
+ const animatedView = component.UNSAFE_getByType(Animated.View);
68
63
  const animatedViewStyles = animatedView.props.style;
69
64
 
70
65
  expect(animatedViewStyles.opacity).toBe(1);
@@ -21,6 +21,15 @@ exports[`OfflineHandler renders 1`] = `
21
21
  }
22
22
  >
23
23
  <View
24
+ accessibilityState={
25
+ {
26
+ "busy": undefined,
27
+ "checked": undefined,
28
+ "disabled": undefined,
29
+ "expanded": undefined,
30
+ "selected": undefined,
31
+ }
32
+ }
24
33
  accessible={true}
25
34
  collapsable={false}
26
35
  focusable={true}
@@ -1,7 +1,6 @@
1
1
  import * as React from "react";
2
2
  import { useEffect, useReducer } from "react";
3
- // @ts-ignore
4
- import { TVMenuControl, View, ViewStyle } from "react-native";
3
+ import { View, ViewStyle } from "react-native";
5
4
  import * as R from "ramda";
6
5
  import uuid from "uuid/v4";
7
6
  import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager";
@@ -62,6 +61,11 @@ import {
62
61
  useModalAnimationContext,
63
62
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
64
63
 
64
+ import {
65
+ PlayerNativeCommandTypes,
66
+ PlayerNativeSendCommand,
67
+ } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/playerNativeCommand";
68
+
65
69
  type Props = {
66
70
  Player: React.ComponentType<any>;
67
71
  PlayerLoadingView?: React.ComponentType<any>; // 👀 we are not receiving this prop
@@ -259,9 +263,15 @@ const PlayerContainerComponent = (props: Props) => {
259
263
  return;
260
264
  }
261
265
 
266
+ // send command to clear and stop player
267
+ PlayerNativeSendCommand(
268
+ PlayerNativeCommandTypes.clearPlayerData,
269
+ state.playerId
270
+ );
271
+
262
272
  showNavBar(true);
263
273
  navigator.goBack();
264
- }, [isModal, navigator.goBack, showNavBar]);
274
+ }, [isModal, navigator.goBack, state.playerId, showNavBar]);
265
275
 
266
276
  const playEntry = (entry) => navigator.replaceTop(entry, { mode });
267
277
 
@@ -389,13 +399,17 @@ const PlayerContainerComponent = (props: Props) => {
389
399
  }
390
400
  };
391
401
 
392
- const playerRemoteHandler = (event, isLanguageOverlayVisible) => {
393
- const { eventType } = event;
402
+ const playerRemoteHandler = React.useCallback(
403
+ (isLanguageOverlayVisible = false) =>
404
+ (event) => {
405
+ const { eventType } = event;
394
406
 
395
- if (!isLanguageOverlayVisible && eventType === "menu") {
396
- close();
397
- }
398
- };
407
+ if (!isLanguageOverlayVisible && eventType === "menu") {
408
+ close();
409
+ }
410
+ },
411
+ [close]
412
+ );
399
413
 
400
414
  // Effects
401
415
  useEffect(() => {
@@ -508,16 +522,6 @@ const PlayerContainerComponent = (props: Props) => {
508
522
  }
509
523
  }, [isAudioContent]);
510
524
 
511
- // Needs to handle back button on Apple TV
512
- // https://github.com/facebook/react-native/issues/18930
513
- useEffect(() => {
514
- TVMenuControl?.enableTVMenuKey();
515
-
516
- return () => {
517
- TVMenuControl?.disableTVMenuKey();
518
- };
519
- }, []);
520
-
521
525
  useEffect(() => {
522
526
  playerEvent("source_changed", { item });
523
527
 
@@ -633,9 +637,9 @@ const PlayerContainerComponent = (props: Props) => {
633
637
  <PlayerContainerContext.Consumer>
634
638
  {(context) => (
635
639
  <TVEventHandlerComponent
636
- tvEventHandler={(_component, event) =>
637
- playerRemoteHandler(event, context.isLanguageOverlayVisible)
638
- }
640
+ tvEventHandler={playerRemoteHandler(
641
+ context.isLanguageOverlayVisible
642
+ )}
639
643
  >
640
644
  <FocusableGroup
641
645
  id={FocusableGroupMainContainerId}
@@ -286,7 +286,6 @@ function ComponentsMapComponent(props: Props) {
286
286
  initialNumToRender={3}
287
287
  maxToRenderPerBatch={10}
288
288
  windowSize={12}
289
- listKey={riverId}
290
289
  keyExtractor={keyExtractor}
291
290
  renderItem={renderRiverItem}
292
291
  data={riverComponents}
@@ -8,7 +8,7 @@ export const withTvEventHandler = (Component) => {
8
8
  return function WithTVEventHandler(props) {
9
9
  const navigator = useNavigation();
10
10
 
11
- const remoteHandler = (_, event) => {
11
+ const remoteHandler = (event) => {
12
12
  const { eventType } = event;
13
13
 
14
14
  const canGoBack = navigator.canGoBack();
@@ -159,6 +159,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
159
159
  >
160
160
  <View>
161
161
  <View
162
+ onFocusCapture={[Function]}
162
163
  onLayout={[Function]}
163
164
  style={null}
164
165
  >
@@ -174,6 +175,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
174
175
  </View>
175
176
  </View>
176
177
  <View
178
+ onFocusCapture={[Function]}
177
179
  onLayout={[Function]}
178
180
  style={null}
179
181
  >
@@ -0,0 +1,76 @@
1
+ import { makeListOf } from "@applicaster/zapp-react-native-utils/arrayUtils";
2
+ import { isFirstComponentGallery } from "@applicaster/zapp-react-native-utils/componentsUtils";
3
+ import { once } from "ramda";
4
+
5
+ const INITIAL_NUMBER_TO_LOAD = 3;
6
+
7
+ // Infer the values of COMPONENT_LOADING_STATE as a type
8
+ type ComponentLoadingState =
9
+ (typeof COMPONENT_LOADING_STATE)[keyof typeof COMPONENT_LOADING_STATE];
10
+
11
+ export const COMPONENT_LOADING_STATE = {
12
+ UNKNOWN: "UNKNOWN",
13
+ LOADED_WITH_SUCCESS: "LOADED_WITH_SUCCESS",
14
+ LOADED_WITH_FAILURE: "LOADED_WITH_FAILURE",
15
+ } as const;
16
+
17
+ // Function to get the number of loaded components
18
+ const getNumberOfLoaded = (states: ComponentLoadingState[]): number => {
19
+ return states.filter((value) => value !== COMPONENT_LOADING_STATE.UNKNOWN)
20
+ .length;
21
+ };
22
+
23
+ const getNumberOfComponentsWaitToLoadBeforePresent = (
24
+ componentsToRender: ZappUIComponent[]
25
+ ): number => {
26
+ // when Gallery is the first component, no need to wait the others
27
+ if (isFirstComponentGallery(componentsToRender)) {
28
+ return 1;
29
+ }
30
+
31
+ return Math.min(INITIAL_NUMBER_TO_LOAD, componentsToRender.length);
32
+ };
33
+
34
+ export class ScreenRevealManager {
35
+ public numberOfComponentsWaitToLoadBeforePresent: number;
36
+ private renderingState: Array<ComponentLoadingState>;
37
+ private callback: Callback;
38
+
39
+ constructor(componentsToRender: ZappUIComponent[], callback: Callback) {
40
+ this.numberOfComponentsWaitToLoadBeforePresent =
41
+ getNumberOfComponentsWaitToLoadBeforePresent(componentsToRender);
42
+
43
+ this.renderingState = makeListOf<ComponentLoadingState>(
44
+ COMPONENT_LOADING_STATE.UNKNOWN,
45
+ this.numberOfComponentsWaitToLoadBeforePresent
46
+ );
47
+
48
+ this.callback = once(callback);
49
+ }
50
+
51
+ onLoadFinished = (index: number): void => {
52
+ this.renderingState[index] = COMPONENT_LOADING_STATE.LOADED_WITH_SUCCESS;
53
+
54
+ if (
55
+ getNumberOfLoaded(this.renderingState) >=
56
+ this.numberOfComponentsWaitToLoadBeforePresent
57
+ ) {
58
+ this.setIsReadyToShow();
59
+ }
60
+ };
61
+
62
+ onLoadFailed = (index: number): void => {
63
+ this.renderingState[index] = COMPONENT_LOADING_STATE.LOADED_WITH_FAILURE;
64
+
65
+ if (
66
+ getNumberOfLoaded(this.renderingState) >=
67
+ this.numberOfComponentsWaitToLoadBeforePresent
68
+ ) {
69
+ this.setIsReadyToShow();
70
+ }
71
+ };
72
+
73
+ setIsReadyToShow = (): void => {
74
+ this.callback();
75
+ };
76
+ }
@@ -0,0 +1,107 @@
1
+ import {
2
+ ScreenRevealManager,
3
+ COMPONENT_LOADING_STATE,
4
+ } from "../ScreenRevealManager";
5
+
6
+ describe("ScreenRevealManager", () => {
7
+ const mockCallback = jest.fn();
8
+
9
+ beforeEach(() => {
10
+ jest.clearAllMocks();
11
+ });
12
+
13
+ it("should initialize with the correct number of components to wait for", () => {
14
+ const componentsToRender: ZappUIComponent[] = [
15
+ { component_type: "component1" },
16
+ { component_type: "component2" },
17
+ { component_type: "component3" },
18
+ ];
19
+
20
+ const manager = new ScreenRevealManager(componentsToRender, mockCallback);
21
+
22
+ expect(manager["numberOfComponentsWaitToLoadBeforePresent"]).toBe(3);
23
+
24
+ expect(manager["renderingState"]).toEqual([
25
+ COMPONENT_LOADING_STATE.UNKNOWN,
26
+ COMPONENT_LOADING_STATE.UNKNOWN,
27
+ COMPONENT_LOADING_STATE.UNKNOWN,
28
+ ]);
29
+ });
30
+
31
+ it("should call the callback when the required number of components are loaded successfully", () => {
32
+ const componentsToRender: ZappUIComponent[] = [
33
+ { component_type: "component1" },
34
+ { component_type: "component2" },
35
+ { component_type: "component3" },
36
+ ];
37
+
38
+ const manager = new ScreenRevealManager(componentsToRender, mockCallback);
39
+
40
+ manager.onLoadFinished(0);
41
+ manager.onLoadFinished(1);
42
+ manager.onLoadFinished(2);
43
+
44
+ expect(mockCallback).toHaveBeenCalledTimes(1);
45
+ });
46
+
47
+ it("should call the callback when the required number of components fail to load", () => {
48
+ const componentsToRender: ZappUIComponent[] = [
49
+ { component_type: "component1" },
50
+ { component_type: "component2" },
51
+ { component_type: "component3" },
52
+ ];
53
+
54
+ const manager = new ScreenRevealManager(componentsToRender, mockCallback);
55
+
56
+ manager.onLoadFailed(0);
57
+ manager.onLoadFailed(1);
58
+ manager.onLoadFailed(2);
59
+
60
+ expect(mockCallback).toHaveBeenCalledTimes(1);
61
+ });
62
+
63
+ it("should call the callback when a mix of successful and failed loads meet the required number", () => {
64
+ const componentsToRender: ZappUIComponent[] = [
65
+ { component_type: "component1" },
66
+ { component_type: "component2" },
67
+ { component_type: "component3" },
68
+ ];
69
+
70
+ const manager = new ScreenRevealManager(componentsToRender, mockCallback);
71
+
72
+ manager.onLoadFinished(0);
73
+ manager.onLoadFailed(1);
74
+ manager.onLoadFinished(2);
75
+
76
+ expect(mockCallback).toHaveBeenCalledTimes(1);
77
+ });
78
+
79
+ it("should not call the callback if the required number of components are not loaded", () => {
80
+ const componentsToRender: ZappUIComponent[] = [
81
+ { component_type: "component1" },
82
+ { component_type: "component2" },
83
+ { component_type: "component3" },
84
+ ];
85
+
86
+ const manager = new ScreenRevealManager(componentsToRender, mockCallback);
87
+
88
+ manager.onLoadFinished(0);
89
+ manager.onLoadFailed(1);
90
+
91
+ expect(mockCallback).not.toHaveBeenCalled();
92
+ });
93
+
94
+ it("should call the callback when the when first component is gallery and it was loaded successfully", () => {
95
+ const componentsToRender: ZappUIComponent[] = [
96
+ { component_type: "gallery-qb" },
97
+ { component_type: "component2" },
98
+ { component_type: "component3" },
99
+ ];
100
+
101
+ const manager = new ScreenRevealManager(componentsToRender, mockCallback);
102
+
103
+ manager.onLoadFinished(0);
104
+
105
+ expect(mockCallback).toHaveBeenCalledTimes(1);
106
+ });
107
+ });
@@ -0,0 +1,96 @@
1
+ /* eslint-disable react/prop-types */
2
+
3
+ import * as React from "react";
4
+ import { render, screen, act } from "@testing-library/react-native";
5
+ import { View } from "react-native";
6
+ import {
7
+ withScreenRevealManager,
8
+ SHOWN,
9
+ TIMEOUT,
10
+ } from "../withScreenRevealManager";
11
+
12
+ // jest.mock("react-native/Libraries/Animated/NativeAnimatedHelper");
13
+
14
+ const MockComponent = ({
15
+ initialNumberToLoad,
16
+ onLoadFinishedFromScreenRevealManager,
17
+ onLoadFailedFromScreenRevealManager,
18
+ }) => {
19
+ React.useEffect(() => {
20
+ // Simulate loading components
21
+ for (let i = 0; i < initialNumberToLoad; i++) {
22
+ onLoadFinishedFromScreenRevealManager(i);
23
+ }
24
+ }, [initialNumberToLoad, onLoadFinishedFromScreenRevealManager]);
25
+
26
+ return (
27
+ <View
28
+ testID="mock-component"
29
+ initialNumberToLoad={initialNumberToLoad}
30
+ onLoadFinishedFromScreenRevealManager={
31
+ onLoadFinishedFromScreenRevealManager
32
+ }
33
+ onLoadFailedFromScreenRevealManager={onLoadFailedFromScreenRevealManager}
34
+ />
35
+ );
36
+ };
37
+
38
+ const WrappedComponent = withScreenRevealManager(MockComponent);
39
+
40
+ describe.skip("withScreenRevealManager", () => {
41
+ beforeEach(() => {
42
+ jest.clearAllMocks();
43
+ jest.useFakeTimers();
44
+ });
45
+
46
+ afterEach(() => {
47
+ jest.runOnlyPendingTimers();
48
+ jest.useRealTimers();
49
+ });
50
+
51
+ it("should render the wrapped component", () => {
52
+ render(
53
+ <WrappedComponent
54
+ componentsToRender={[{ id: "1" }, { id: "2" }, { id: "3" }]}
55
+ />
56
+ );
57
+
58
+ expect(screen.getByTestId("mock-component")).toBeTruthy();
59
+ });
60
+
61
+ it("should animate opacity when ready to show", () => {
62
+ render(
63
+ <WrappedComponent
64
+ componentsToRender={[{ id: "1" }, { id: "2" }, { id: "3" }]}
65
+ />
66
+ );
67
+
68
+ const animatedView = screen.getByTestId("animated-component");
69
+
70
+ act(() => {
71
+ jest.advanceTimersByTime(TIMEOUT + 100);
72
+ });
73
+
74
+ expect(animatedView.props.style.opacity).toBe(SHOWN);
75
+ });
76
+
77
+ it("should pass initialNumberToLoad, onLoadFinishedFromScreenRevealManager, and onLoadFailedFromScreenRevealManager to the wrapped component", () => {
78
+ render(
79
+ <WrappedComponent
80
+ componentsToRender={[{ id: "1" }, { id: "2" }, { id: "3" }]}
81
+ />
82
+ );
83
+
84
+ const mockComponent = screen.getByTestId("mock-component");
85
+
86
+ expect(mockComponent.props.initialNumberToLoad).toBe(3);
87
+
88
+ expect(
89
+ mockComponent.props.onLoadFinishedFromScreenRevealManager
90
+ ).toBeInstanceOf(Function);
91
+
92
+ expect(
93
+ mockComponent.props.onLoadFailedFromScreenRevealManager
94
+ ).toBeInstanceOf(Function);
95
+ });
96
+ });
@@ -0,0 +1 @@
1
+ export { withScreenRevealManager } from "./withScreenRevealManager";
@@ -0,0 +1,79 @@
1
+ import * as React from "react";
2
+ import { Animated } from "react-native";
3
+ import { isFirstComponentScreenPicker } from "@applicaster/zapp-react-native-utils/componentsUtils";
4
+ import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
5
+ import { useRefWithInitialValue } from "@applicaster/zapp-react-native-utils/reactHooks/state/useRefWithInitialValue";
6
+
7
+ import { ScreenRevealManager } from "./ScreenRevealManager";
8
+
9
+ const flex = platformSelect({
10
+ tvos: 1,
11
+ android_tv: 1,
12
+ web: undefined,
13
+ samsung_tv: undefined,
14
+ lg_tv: undefined,
15
+ default: undefined,
16
+ });
17
+
18
+ export const TIMEOUT = 500; // 500 ms
19
+
20
+ const HIDDEN = 0; // opacity = 0
21
+
22
+ export const SHOWN = 1; // opacity = 1
23
+
24
+ type Props = {
25
+ componentsToRender: ZappUIComponent[];
26
+ };
27
+
28
+ export const withScreenRevealManager = (Component) => {
29
+ return function WithScreenRevealManager(props: Props) {
30
+ const { componentsToRender } = props;
31
+
32
+ const [isReadyToShow, setIsReadyToShow] = React.useState(false);
33
+
34
+ const handleSetIsReadyToShow = React.useCallback(() => {
35
+ setIsReadyToShow(true);
36
+ }, []);
37
+
38
+ const managerRef = useRefWithInitialValue<ScreenRevealManager>(
39
+ () => new ScreenRevealManager(componentsToRender, handleSetIsReadyToShow)
40
+ );
41
+
42
+ const opacityRef = useRefWithInitialValue<Animated.Value>(
43
+ () => new Animated.Value(HIDDEN)
44
+ );
45
+
46
+ React.useEffect(() => {
47
+ if (isReadyToShow) {
48
+ Animated.timing(opacityRef.current, {
49
+ toValue: SHOWN,
50
+ duration: TIMEOUT,
51
+ useNativeDriver: true,
52
+ }).start();
53
+ }
54
+ }, [isReadyToShow]);
55
+
56
+ if (isFirstComponentScreenPicker(componentsToRender)) {
57
+ // for screen-picker with have additional internal ComponentsMap, no need to add this wrapper
58
+ return <Component {...props} />;
59
+ }
60
+
61
+ return (
62
+ <Animated.View
63
+ style={{ opacity: opacityRef.current, flex }}
64
+ testID="animated-component"
65
+ >
66
+ <Component
67
+ {...props}
68
+ initialNumberToLoad={
69
+ managerRef.current.numberOfComponentsWaitToLoadBeforePresent
70
+ }
71
+ onLoadFinishedFromScreenRevealManager={
72
+ managerRef.current.onLoadFinished
73
+ }
74
+ onLoadFailedFromScreenRevealManager={managerRef.current.onLoadFailed}
75
+ />
76
+ </Animated.View>
77
+ );
78
+ };
79
+ };
@@ -7,7 +7,6 @@ import { isEmptyOrNil } from "@applicaster/zapp-react-native-utils/cellUtils";
7
7
 
8
8
  import Tab from "./Tab";
9
9
  import { Gutter } from "../Gutter";
10
- import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
11
10
  import { ImageBackground, View } from "react-native";
12
11
  import { getStyles } from "./styles";
13
12
  import {
@@ -100,7 +99,6 @@ const TabsComponent = ({
100
99
  >
101
100
  <FocusableList
102
101
  horizontal
103
- onScrollToIndexFailed={noop}
104
102
  onLayout={onLayoutChange}
105
103
  contentContainerStyle={tabsListContentContainer}
106
104
  ref={flatListRef}
@@ -3,6 +3,23 @@
3
3
  exports[`<Touchable /> when not running in automated tests environment renders correctly 1`] = `
4
4
  <View
5
5
  accessibilityLabel="some-test-id"
6
+ accessibilityState={
7
+ {
8
+ "busy": undefined,
9
+ "checked": undefined,
10
+ "disabled": undefined,
11
+ "expanded": undefined,
12
+ "selected": undefined,
13
+ }
14
+ }
15
+ accessibilityValue={
16
+ {
17
+ "max": undefined,
18
+ "min": undefined,
19
+ "now": undefined,
20
+ "text": undefined,
21
+ }
22
+ }
6
23
  accessible={true}
7
24
  collapsable={false}
8
25
  focusable={true}
@@ -29,6 +46,23 @@ exports[`<Touchable /> when not running in automated tests environment renders c
29
46
  exports[`<Touchable /> when running in automated tests environment has accessible flag set to false 1`] = `
30
47
  <View
31
48
  accessibilityLabel="some-test-id"
49
+ accessibilityState={
50
+ {
51
+ "busy": undefined,
52
+ "checked": undefined,
53
+ "disabled": undefined,
54
+ "expanded": undefined,
55
+ "selected": undefined,
56
+ }
57
+ }
58
+ accessibilityValue={
59
+ {
60
+ "max": undefined,
61
+ "min": undefined,
62
+ "now": undefined,
63
+ "text": undefined,
64
+ }
65
+ }
32
66
  accessible={false}
33
67
  collapsable={false}
34
68
  focusable={true}
@@ -6,15 +6,21 @@ exports[`<Scene /> renders correctly 1`] = `
6
6
  collapsable={false}
7
7
  pointerEvents="auto"
8
8
  style={
9
- {
10
- "flex": 1,
11
- "fontScale": 2,
12
- "height": 1334,
13
- "paddingBottom": 49,
14
- "scale": 2,
15
- "statusBarHeight": null,
16
- "width": 750,
17
- }
9
+ [
10
+ {
11
+ "flex": 1,
12
+ },
13
+ {
14
+ "paddingBottom": 49,
15
+ },
16
+ {
17
+ "fontScale": 2,
18
+ "height": 1334,
19
+ "scale": 2,
20
+ "statusBarHeight": null,
21
+ "width": 750,
22
+ },
23
+ ]
18
24
  }
19
25
  />
20
26
  </View>
@@ -1,8 +1,8 @@
1
1
  import { Animated, Easing, EasingFunction, StyleProp } from "react-native";
2
2
 
3
3
  type AnimatedInterpolatedStyle =
4
- | Animated.AnimatedInterpolation
5
- | [{ [Key: string]: Animated.AnimatedInterpolation }];
4
+ | Animated.AnimatedInterpolation<number>
5
+ | [{ [Key: string]: Animated.AnimatedInterpolation<number> }];
6
6
 
7
7
  type AnimationConfig = {
8
8
  duration: number;
@@ -31,7 +31,7 @@ const interpolate = (
31
31
  animatedValue: Animated.Value,
32
32
  from: number = 0,
33
33
  to: number = 1
34
- ): Animated.AnimatedInterpolation =>
34
+ ): Animated.AnimatedInterpolation<number> =>
35
35
  animatedValue.interpolate({
36
36
  inputRange: [0, 1],
37
37
  outputRange: [from, to],
@@ -2,7 +2,7 @@
2
2
 
3
3
  import * as React from "react";
4
4
 
5
- import { act, create as render } from "react-test-renderer";
5
+ import { act, render } from "@testing-library/react-native";
6
6
 
7
7
  import { ViewportAware } from "../";
8
8
  import { ViewportTracker } from "../../ViewportTracker";
@@ -12,21 +12,17 @@ import ReactNative from "react-native";
12
12
 
13
13
  jest.useFakeTimers();
14
14
 
15
- jest.mock("react-native/Libraries/ReactNative/UIManager", () => {
16
- return {
17
- ...jest.requireActual("react-native/Libraries/ReactNative/NativeUIManager"),
18
- measureLayout: (handle, parent, error, success) => {
19
- success(100, 100, 400, 400);
20
- },
21
- };
22
- });
15
+ const { ScrollView } = ReactNative;
23
16
 
24
- jest.mock("react-native/Libraries/Renderer/shims/ReactNative", () => ({
25
- ...jest.requireActual("react-native/Libraries/Renderer/shims/ReactNative"),
26
- findNodeHandle: () => 1234,
27
- }));
17
+ jest.spyOn(ReactNative, "findNodeHandle").mockImplementation(() => 1234);
28
18
 
29
- const { ScrollView } = ReactNative;
19
+ ReactNative.UIManager.measureLayout = jest.fn(
20
+ (handle, parent, error, success) => {
21
+ success(100, 100, 400, 400);
22
+ }
23
+ );
24
+
25
+ ReactNative.findNodeHandle = () => 1234;
30
26
 
31
27
  const viewportEventsManager = new ViewportEvents(true);
32
28
 
@@ -138,7 +134,7 @@ describe("<ViewportAware />", () => {
138
134
  expect(wrapper.toJSON()).toMatchSnapshot();
139
135
  expect(onViewportEnter).toHaveBeenCalled();
140
136
 
141
- const scrollviews = wrapper.root.findAllByType(ScrollView);
137
+ const scrollviews = wrapper.UNSAFE_getAllByType(ScrollView);
142
138
  expect(scrollviews).toBeArray();
143
139
  expect(scrollviews).toHaveProperty("length", 2);
144
140
 
@@ -179,7 +175,7 @@ describe("<ViewportAware />", () => {
179
175
 
180
176
  expect(wrapper.toJSON()).toMatchSnapshot();
181
177
 
182
- const scrollviews = wrapper.root.findAllByType(ScrollView);
178
+ const scrollviews = wrapper.UNSAFE_getAllByType(ScrollView);
183
179
  expect(scrollviews).toBeArray();
184
180
  expect(scrollviews).toHaveProperty("length", 2);
185
181
 
@@ -1,14 +1,19 @@
1
1
  import * as React from "react";
2
- import { ScrollView } from "react-native";
3
- import TestRenderer, { act } from "react-test-renderer";
2
+ import { act, render } from "@testing-library/react-native";
4
3
  import { ViewportEvents } from "../../ViewportEvents";
4
+ import ReactNative from "react-native";
5
+
6
+ const { ScrollView } = ReactNative;
5
7
 
6
8
  const TestComponent = () => <ScrollView />;
7
9
 
8
- jest.mock("react-native/Libraries/Renderer/shims/ReactNative", () => ({
9
- ...jest.requireActual("react-native/Libraries/Renderer/shims/ReactNative"),
10
- findNodeHandle: () => 1234,
11
- }));
10
+ jest.spyOn(ReactNative, "findNodeHandle").mockImplementation(() => 1234);
11
+
12
+ ReactNative.UIManager.measureLayout = jest.fn(
13
+ (handle, parent, error, success) => {
14
+ success(100, 100, 400, 400);
15
+ }
16
+ );
12
17
 
13
18
  const viewportEventsManager = new ViewportEvents(true);
14
19
 
@@ -23,32 +28,49 @@ const event = {
23
28
  };
24
29
 
25
30
  describe("<ViewportTracker />", () => {
26
- // eslint-disable-next-line react/prop-types
27
- const ReactWrapper = ({ children }) => (
28
- <ViewportTracker viewportEventsManager={viewportEventsManager}>
29
- <ScrollView>{children}</ScrollView>
30
- </ViewportTracker>
31
- );
32
-
33
- const wrapper = TestRenderer.create(
34
- <ReactWrapper>
35
- <TestComponent />
36
- </ReactWrapper>
37
- );
38
-
39
- const scrollView = wrapper.root.findByType(ScrollView);
40
-
41
31
  beforeEach(() => {
42
32
  viewportEventSpy.mockClear();
43
33
  });
44
34
 
45
35
  it("renders correctly", () => {
36
+ // eslint-disable-next-line react/prop-types
37
+ const ReactWrapper = ({ children }) => (
38
+ <ViewportTracker viewportEventsManager={viewportEventsManager}>
39
+ <ScrollView>{children}</ScrollView>
40
+ </ViewportTracker>
41
+ );
42
+
43
+ const wrapper = render(
44
+ <ReactWrapper>
45
+ <TestComponent />
46
+ </ReactWrapper>
47
+ );
48
+
49
+ const scrollView = wrapper.UNSAFE_getByType(ScrollView);
50
+
46
51
  expect(wrapper.toJSON()).toMatchSnapshot();
47
52
  expect(scrollView.props).toMatchSnapshot();
48
53
  });
49
54
 
50
55
  it("notifies viewport listeners when layout changes", () => {
51
- act(() => scrollView.props.onLayout(event));
56
+ // eslint-disable-next-line react/prop-types
57
+ const ReactWrapper = ({ children }) => (
58
+ <ViewportTracker viewportEventsManager={viewportEventsManager}>
59
+ <ScrollView>{children}</ScrollView>
60
+ </ViewportTracker>
61
+ );
62
+
63
+ const wrapper = render(
64
+ <ReactWrapper>
65
+ <TestComponent />
66
+ </ReactWrapper>
67
+ );
68
+
69
+ const scrollView = wrapper.UNSAFE_getByType(ScrollView);
70
+
71
+ act(() => {
72
+ scrollView.props.onLayout(event);
73
+ });
52
74
 
53
75
  expect(viewportEventSpy).toHaveBeenCalledWith(
54
76
  expect.objectContaining({
@@ -63,12 +85,50 @@ describe("<ViewportTracker />", () => {
63
85
  });
64
86
 
65
87
  it("notifies viewport listeners when content scrolls", () => {
66
- act(() => scrollView.props.onScroll(event));
88
+ // eslint-disable-next-line react/prop-types
89
+ const ReactWrapper = ({ children }) => (
90
+ <ViewportTracker viewportEventsManager={viewportEventsManager}>
91
+ <ScrollView>{children}</ScrollView>
92
+ </ViewportTracker>
93
+ );
94
+
95
+ const wrapper = render(
96
+ <ReactWrapper>
97
+ <TestComponent />
98
+ </ReactWrapper>
99
+ );
100
+
101
+ const scrollView = wrapper.UNSAFE_getByType(ScrollView);
102
+
103
+ act(() => {
104
+ scrollView.props.onLayout(event);
105
+ scrollView.props.onScroll(event);
106
+ });
107
+
67
108
  expect(viewportEventSpy).toHaveBeenCalled();
68
109
  });
69
110
 
70
111
  it("notifies viewport listeners when content size changes", () => {
71
- act(() => scrollView.props.onContentSizeChange(100, 100));
112
+ // eslint-disable-next-line react/prop-types
113
+ const ReactWrapper = ({ children }) => (
114
+ <ViewportTracker viewportEventsManager={viewportEventsManager}>
115
+ <ScrollView>{children}</ScrollView>
116
+ </ViewportTracker>
117
+ );
118
+
119
+ const wrapper = render(
120
+ <ReactWrapper>
121
+ <TestComponent />
122
+ </ReactWrapper>
123
+ );
124
+
125
+ const scrollView = wrapper.UNSAFE_getByType(ScrollView);
126
+
127
+ act(() => {
128
+ scrollView.props.onLayout(event);
129
+ scrollView.props.onContentSizeChange(100, 100);
130
+ });
131
+
72
132
  expect(viewportEventSpy).toHaveBeenCalled();
73
133
  });
74
134
  });
@@ -21,13 +21,13 @@ const prepareConfiguration = (
21
21
  keys: [string]
22
22
  ) => R.compose(R.evolve(keysMap), R.pickAll(keys))(configuration);
23
23
 
24
+ const configurationKeys = R.keys(keysMap);
25
+
24
26
  export function withConfigurationProvider(Component: React.ComponentType<any>) {
25
27
  return function WithConfigurationProvider(props: Props) {
26
28
  const styles = props?.screenData?.styles;
27
29
  const general = props?.screenData?.general;
28
30
 
29
- const configurationKeys = React.useMemo(() => R.keys(keysMap), []);
30
-
31
31
  const configuration = React.useMemo(
32
32
  () => prepareConfiguration({ ...general, ...styles }, configurationKeys),
33
33
  []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-ui-components",
3
- "version": "14.0.0-rc.35",
3
+ "version": "14.0.0-rc.37",
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": "14.0.0-rc.35",
32
- "@applicaster/zapp-react-native-bridge": "14.0.0-rc.35",
33
- "@applicaster/zapp-react-native-redux": "14.0.0-rc.35",
34
- "@applicaster/zapp-react-native-utils": "14.0.0-rc.35",
31
+ "@applicaster/applicaster-types": "14.0.0-rc.37",
32
+ "@applicaster/zapp-react-native-bridge": "14.0.0-rc.37",
33
+ "@applicaster/zapp-react-native-redux": "14.0.0-rc.37",
34
+ "@applicaster/zapp-react-native-utils": "14.0.0-rc.37",
35
35
  "promise": "^8.3.0",
36
36
  "url": "^0.11.0",
37
37
  "uuid": "^3.3.2"
@@ -42,7 +42,6 @@
42
42
  "immer": "*",
43
43
  "react": "*",
44
44
  "react-native": "*",
45
- "react-native-safe-area-context": "*",
46
45
  "react-native-svg": "*",
47
46
  "uglify-js": "*",
48
47
  "validate-color": "*",