@applicaster/zapp-react-native-ui-components 12.0.0-rc.127 → 12.0.0-rc.128

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,72 @@
1
+ import React from "react";
2
+ import { render, act } from "@testing-library/react-native";
3
+ import { Touchable } from "../../Touchable";
4
+
5
+ const mockUseIsRTL = jest.fn();
6
+
7
+ jest.mock("@applicaster/zapp-react-native-utils/localizationUtils", () => ({
8
+ useIsRTL: mockUseIsRTL,
9
+ }));
10
+
11
+ const mockUseFocusManager = jest.fn();
12
+
13
+ jest.mock("@applicaster/zapp-react-native-utils/focusManager", () => ({
14
+ useFocusManager: mockUseFocusManager,
15
+ }));
16
+
17
+ const mockUsePrevious = jest.fn();
18
+
19
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/utils", () => ({
20
+ usePrevious: mockUsePrevious,
21
+ }));
22
+
23
+ const { Focusable } = require("../index.android");
24
+
25
+ describe("Focusable", () => {
26
+ const mockFocusManager = {
27
+ isFocused: jest.fn(),
28
+ onDisableFocusChange: jest.fn(),
29
+ registerFocusable: jest.fn(),
30
+ focusedId: "test-id",
31
+ };
32
+
33
+ beforeEach(() => {
34
+ jest.clearAllMocks();
35
+ mockUseIsRTL.mockReturnValue(false);
36
+ mockUseFocusManager.mockReturnValue(mockFocusManager);
37
+ mockUsePrevious.mockReturnValue(false);
38
+ });
39
+
40
+ it("updates disableFocus state when disableFocus prop changes", () => {
41
+ const { rerender } = render(
42
+ <Focusable id="test-id" disableFocus={false}>
43
+ <Touchable testID="touchable" />
44
+ </Focusable>
45
+ );
46
+
47
+ rerender(
48
+ <Focusable id="test-id" disableFocus={true}>
49
+ <Touchable testID="touchable" />
50
+ </Focusable>
51
+ );
52
+
53
+ expect(mockFocusManager.onDisableFocusChange).toHaveBeenCalledWith(
54
+ "test-id"
55
+ );
56
+ });
57
+
58
+ it("calls focusManager.registerFocusable on mount and unregisters on unmount", () => {
59
+ const unregister = jest.fn();
60
+ mockFocusManager.registerFocusable.mockReturnValue(unregister);
61
+
62
+ const { unmount } = render(
63
+ <Focusable id="test-id">
64
+ <Touchable testID="touchable" />
65
+ </Focusable>
66
+ );
67
+
68
+ expect(mockFocusManager.registerFocusable).toHaveBeenCalled();
69
+ act(() => unmount());
70
+ expect(unregister).toHaveBeenCalled();
71
+ });
72
+ });
@@ -128,7 +128,10 @@ function FocusableComponent(props: Props, forwardedRef) {
128
128
 
129
129
  const _onBlur = React.useCallback(
130
130
  (ref, options = {}) => {
131
- setFocused(false);
131
+ if (ref.props.id !== focusManager.focusedId) {
132
+ setFocused(false);
133
+ }
134
+
132
135
  onBlur?.(ref, options);
133
136
  },
134
137
  [onBlur]
@@ -148,15 +151,14 @@ function FocusableComponent(props: Props, forwardedRef) {
148
151
  [isRTL, nextFocusDown, nextFocusUp, nextFocusLeft, nextFocusRight]
149
152
  );
150
153
 
151
- const contextState = React.useMemo(
152
- () => ({
154
+ const contextState = React.useMemo(() => {
155
+ return {
153
156
  focused,
154
157
  ...parentFocus,
155
158
  ref: touchableRef,
156
159
  setIsFocusable,
157
- }),
158
- [parentFocus, focused]
159
- );
160
+ };
161
+ }, [parentFocus, focused]);
160
162
 
161
163
  return (
162
164
  <Touchable
@@ -173,9 +175,13 @@ function FocusableComponent(props: Props, forwardedRef) {
173
175
  {...parentFocus}
174
176
  >
175
177
  <FocusableContext.Provider value={contextState}>
176
- {typeof children === "function"
177
- ? children(focused, parentFocus, touchableRef)
178
- : children}
178
+ {React.useMemo(
179
+ () =>
180
+ typeof children === "function"
181
+ ? children(focused, parentFocus, touchableRef)
182
+ : children,
183
+ [children, focused, parentFocus]
184
+ )}
179
185
  </FocusableContext.Provider>
180
186
  </Touchable>
181
187
  );
@@ -1,7 +1,6 @@
1
1
  import * as React from "react";
2
2
  import { isEqual } from "@applicaster/zapp-react-native-utils/equalUtils";
3
3
  import { useFocusable } from "../Focusable/index.android";
4
- import { useCellState } from "./hooks/useCellState.android";
5
4
 
6
5
  /**
7
6
  * returns parent focus from prop or from context
@@ -30,15 +29,14 @@ const useParentFocus = (
30
29
  );
31
30
 
32
31
  function FocusableChildComponent(props) {
33
- const { setIsFocusable, ...focusableContext } = useFocusable();
32
+ const { setIsFocusable, focused, ...focusableContext } = useFocusable();
33
+
34
34
  const parentFocus = useParentFocus(props, focusableContext);
35
35
 
36
36
  const onLoadFailed = React.useCallback(() => {
37
37
  setIsFocusable(false);
38
38
  }, []);
39
39
 
40
- const focused = useCellState(props.id);
41
-
42
40
  return props.renderItem({
43
41
  ...props,
44
42
  focused,
@@ -88,6 +88,7 @@ function FocusableListComponent<ItemT>(props: Props<ItemT>, ref) {
88
88
  data = [],
89
89
  // eslint-disable-next-line unused-imports/no-unused-vars
90
90
  onAllComponentsLoaded, // TODO: re-implement it
91
+ omitPropsPropagation = [],
91
92
  } = props;
92
93
 
93
94
  useCheckItemIdsForUnique({ componentId: props.id, items: data });
@@ -252,9 +253,9 @@ function FocusableListComponent<ItemT>(props: Props<ItemT>, ref) {
252
253
  ]
253
254
  );
254
255
 
255
- const flatListProps = React.useMemo(
256
- () =>
257
- R.omit([
256
+ const getFlatListProps = React.useCallback(
257
+ R.omit(
258
+ [
258
259
  "onListElementFocus",
259
260
  "onListElementPress",
260
261
  "onListElementPressOut",
@@ -267,17 +268,17 @@ function FocusableListComponent<ItemT>(props: Props<ItemT>, ref) {
267
268
  "focusableItemProps",
268
269
  "renderItem",
269
270
  "extraChildrenData",
270
- "extraData",
271
- "keyExtractor",
272
271
  "onAllComponentsLoaded",
273
272
  "focused",
274
273
  "id",
275
274
  "initialFocusDirection",
276
275
  "withStateMemory",
277
276
  "useSequentialLoading",
278
- ...(props.omitPropsPropagation || []),
279
- ])(props),
280
- [props]
277
+ ...omitPropsPropagation,
278
+ ],
279
+ R.__
280
+ ),
281
+ [omitPropsPropagation]
281
282
  );
282
283
 
283
284
  const onEndReached = React.useCallback(
@@ -289,7 +290,7 @@ function FocusableListComponent<ItemT>(props: Props<ItemT>, ref) {
289
290
 
290
291
  const initialNumToRender = toFiniteNumberWithDefault(
291
292
  10,
292
- flatListProps.initialNumToRender
293
+ props.initialNumToRender
293
294
  );
294
295
 
295
296
  return (
@@ -298,12 +299,10 @@ function FocusableListComponent<ItemT>(props: Props<ItemT>, ref) {
298
299
  <FlatList
299
300
  focusable={false}
300
301
  scrollEnabled={false}
301
- extraData={props.extraData}
302
302
  ref={ref}
303
- {...flatListProps}
303
+ {...getFlatListProps(props)}
304
304
  renderItem={renderItem}
305
305
  onEndReached={onEndReached}
306
- keyExtractor={props.keyExtractor}
307
306
  initialNumToRender={initialNumToRender}
308
307
  />
309
308
  </ChildrenFocusDeactivatorView>
@@ -284,7 +284,7 @@ function FocusableScrollViewComponent(props: Props, ref) {
284
284
  };
285
285
 
286
286
  return (
287
- <View onLayout={onItemLayout}>
287
+ <View key={id} onLayout={onItemLayout}>
288
288
  <Focusable
289
289
  {...nextFocus}
290
290
  onFocus={(element, direction) =>
@@ -363,19 +363,11 @@ const PlayerContainerComponent = (props: Props) => {
363
363
  const { fullscreen = false } = event || {};
364
364
 
365
365
  if (fullscreen) {
366
- // in order to avoid UI glitches with player we have to start re-render ealier and then start real rotation
367
366
  navigator.fullscreenVideoModal();
368
-
369
- setTimeout(() => {
370
- playerEvent("onFullscreenPlayerWillPresent");
371
- });
367
+ playerEvent("onFullscreenPlayerWillPresent");
372
368
  } else {
373
- // in order to avoid UI glitches with player we have to start re-render ealier and then start real rotation
374
369
  navigator.maximiseVideoModal();
375
-
376
- setTimeout(() => {
377
- playerEvent("onFullscreenPlayerWillDismiss");
378
- });
370
+ playerEvent("onFullscreenPlayerWillDismiss");
379
371
  }
380
372
  };
381
373
 
@@ -471,6 +463,10 @@ const PlayerContainerComponent = (props: Props) => {
471
463
 
472
464
  if (mode === VideoModalMode.FULLSCREEN) {
473
465
  toggleFullscreen({ fullscreen: false });
466
+
467
+ if (!isModal) {
468
+ playerManager.closeNativePlayer();
469
+ }
474
470
  }
475
471
 
476
472
  return !!(
@@ -62,6 +62,7 @@ export const AnimationView = ({
62
62
  deviceInfo: { isTablet, isTabletLandscape },
63
63
  } = useDimensions("window", {
64
64
  deviceInfo: true,
65
+ updateForInactiveScreens: true,
65
66
  });
66
67
 
67
68
  const { videoModalState } = useNavigation();
@@ -45,7 +45,10 @@ const BarView = ({ screenStyles, barState = defaultState }: Props) => {
45
45
  );
46
46
 
47
47
  return (
48
- <SafeAreaView style={[style.view, isRTL ? style.rtlStyle : {}]}>
48
+ <SafeAreaView
49
+ style={[style.view, isRTL ? style.rtlStyle : {}]}
50
+ testID="BarView-safeAreaView"
51
+ >
49
52
  {backButton}
50
53
  <BarTitle title={barState.title} screenStyles={screenStyles} />
51
54
  {closeButton}
@@ -11,12 +11,16 @@ const renderEmptyView = () => {
11
11
  return <View style={emptyViewStyle} />;
12
12
  };
13
13
 
14
- type FloatingButtonProps = {
14
+ type FloatingButtonProps = Testable & {
15
15
  onPress: () => void;
16
16
  screenStyles: any;
17
17
  };
18
18
 
19
- const BackButton = ({ onPress, screenStyles }: FloatingButtonProps) => {
19
+ const BackButton = ({
20
+ onPress,
21
+ screenStyles,
22
+ testID = "backButton",
23
+ }: FloatingButtonProps) => {
20
24
  const containerStyle: any = {};
21
25
 
22
26
  if (!onPress) {
@@ -25,6 +29,7 @@ const BackButton = ({ onPress, screenStyles }: FloatingButtonProps) => {
25
29
 
26
30
  return (
27
31
  <ActionButton
32
+ testID={`${testID}-actionButton`}
28
33
  onPress={onPress}
29
34
  buttonStyles={containerStyle}
30
35
  imageUrl={screenStyles.back_button_image}
@@ -11,12 +11,16 @@ const renderEmptyView = () => {
11
11
  return <View style={emptyViewStyle} />;
12
12
  };
13
13
 
14
- type CloseButtonProps = {
14
+ type CloseButtonProps = Testable & {
15
15
  onPress: () => void;
16
16
  screenStyles?: any;
17
17
  };
18
18
 
19
- const CloseButton = ({ onPress, screenStyles }: CloseButtonProps) => {
19
+ const CloseButton = ({
20
+ onPress,
21
+ screenStyles,
22
+ testID = "closeButton",
23
+ }: CloseButtonProps) => {
20
24
  const containerStyle: any = {};
21
25
 
22
26
  if (!onPress) {
@@ -25,6 +29,7 @@ const CloseButton = ({ onPress, screenStyles }: CloseButtonProps) => {
25
29
 
26
30
  return (
27
31
  <ActionButton
32
+ testID={`${testID}-actionButton`}
28
33
  onPress={onPress}
29
34
  buttonStyles={containerStyle}
30
35
  imageUrl={screenStyles.close_button_image}
@@ -0,0 +1,87 @@
1
+ import React from "react";
2
+ import { renderWithProviders } from "@applicaster/zapp-react-native-utils/testUtils";
3
+ import { fireEvent } from "@testing-library/react-native";
4
+
5
+ const mockUseIsRTL = jest.fn(() => true);
6
+
7
+ jest.mock("@applicaster/zapp-react-native-utils/localizationUtils", () => ({
8
+ useIsRTL: mockUseIsRTL,
9
+ }));
10
+
11
+ const BarView = require("../BarView").default;
12
+
13
+ describe("BarView", () => {
14
+ const screenStyles = {
15
+ title_font_color: "#000",
16
+ title_font_android: "Roboto",
17
+ title_font_ios: "San Francisco",
18
+ title_font_size: "16",
19
+ background_color: "#fff",
20
+ };
21
+
22
+ const barState = {
23
+ backHandler: jest.fn(),
24
+ closeHandler: jest.fn(),
25
+ title: "Test Title",
26
+ };
27
+
28
+ beforeEach(() => {
29
+ jest.clearAllMocks();
30
+ });
31
+
32
+ it("renders without crashing", () => {
33
+ mockUseIsRTL.mockReturnValue(false);
34
+
35
+ const { getByText } = renderWithProviders(
36
+ <BarView screenStyles={screenStyles} barState={barState} />
37
+ );
38
+
39
+ expect(getByText("Test Title")).toBeTruthy();
40
+ });
41
+
42
+ it("applies RTL styles when isRTL is true", () => {
43
+ mockUseIsRTL.mockReturnValue(true);
44
+
45
+ const { getByTestId } = renderWithProviders(
46
+ <BarView screenStyles={screenStyles} barState={barState} />
47
+ );
48
+
49
+ expect(getByTestId("BarView-safeAreaView").props.style).toContainEqual({
50
+ transform: [{ scaleX: -1 }],
51
+ });
52
+ });
53
+
54
+ it("does not apply RTL styles when isRTL is false", () => {
55
+ mockUseIsRTL.mockReturnValue(false);
56
+
57
+ const { getByTestId } = renderWithProviders(
58
+ <BarView screenStyles={screenStyles} barState={barState} />
59
+ );
60
+
61
+ expect(getByTestId("BarView-safeAreaView").props.style).not.toContainEqual({
62
+ transform: [{ scaleX: -1 }],
63
+ });
64
+ });
65
+
66
+ it("calls closeHandler when CloseButton is pressed", () => {
67
+ mockUseIsRTL.mockReturnValue(false);
68
+
69
+ const { getByTestId } = renderWithProviders(
70
+ <BarView screenStyles={screenStyles} barState={barState} />
71
+ );
72
+
73
+ fireEvent.press(getByTestId("closeButton-actionButton-touchable"));
74
+ expect(barState.closeHandler).toHaveBeenCalled();
75
+ });
76
+
77
+ it("calls backHandler when BackButton is pressed", () => {
78
+ mockUseIsRTL.mockReturnValue(false);
79
+
80
+ const { getByTestId } = renderWithProviders(
81
+ <BarView screenStyles={screenStyles} barState={barState} />
82
+ );
83
+
84
+ fireEvent.press(getByTestId("backButton-actionButton-touchable"));
85
+ expect(barState.backHandler).toHaveBeenCalled();
86
+ });
87
+ });
@@ -28,7 +28,7 @@ styles: {
28
28
  }
29
29
  */
30
30
 
31
- type ActionButtonProps = {
31
+ type ActionButtonProps = Testable & {
32
32
  onPress: () => void;
33
33
  buttonStyles: any;
34
34
  imageUrl: string;
@@ -46,11 +46,16 @@ const ActionButton = ({
46
46
  buttonStyles,
47
47
  imageUrl,
48
48
  imageStyle = closeButtonStyle,
49
+ testID,
49
50
  }: ActionButtonProps) => {
50
51
  return (
51
- <View style={buttonStyles}>
52
- <TouchableOpacity onPress={onPress}>
53
- <Image source={{ uri: imageUrl }} style={imageStyle} />
52
+ <View style={buttonStyles} testID={testID}>
53
+ <TouchableOpacity onPress={onPress} testID={`${testID}-touchable`}>
54
+ <Image
55
+ source={{ uri: imageUrl }}
56
+ style={imageStyle}
57
+ testID={`${testID}-image`}
58
+ />
54
59
  </TouchableOpacity>
55
60
  </View>
56
61
  );
@@ -1,5 +1,6 @@
1
1
  import { create, useStore } from "zustand";
2
2
  import { produce } from "immer";
3
+ import { useCallback, useMemo } from "react";
3
4
 
4
5
  type Dimensions = {
5
6
  width: Option<number>;
@@ -31,16 +32,19 @@ export const useCachedDimensions = (id: string) => {
31
32
  dimensionsStore
32
33
  ) as DimensionsState;
33
34
 
34
- const getCachedDimensions = () => {
35
+ const getCachedDimensions = useCallback(() => {
35
36
  return dimensions[id] || initialDimensions();
36
- };
37
+ }, [dimensions?.[id], id]);
37
38
 
38
- const handleSetCachedDimensions = (newDimensions) => {
39
+ const handleSetCachedDimensions = useCallback((newDimensions) => {
39
40
  setCachedDimensions(id, newDimensions);
40
- };
41
-
42
- return {
43
- getCachedDimensions,
44
- setCachedDimensions: handleSetCachedDimensions,
45
- };
41
+ }, []);
42
+
43
+ return useMemo(
44
+ () => ({
45
+ getCachedDimensions,
46
+ setCachedDimensions: handleSetCachedDimensions,
47
+ }),
48
+ [getCachedDimensions, handleSetCachedDimensions]
49
+ );
46
50
  };
@@ -177,7 +177,12 @@ export function withScreenContext(Component: React.ComponentType<any>) {
177
177
  return function WithScreenContextWrapper(props) {
178
178
  return (
179
179
  <ScreenContext.Consumer>
180
- {(value) => <Component {...props} screenContext={value} />}
180
+ {useCallback(
181
+ (value) => (
182
+ <Component {...props} screenContext={value} />
183
+ ),
184
+ [Component, props]
185
+ )}
181
186
  </ScreenContext.Consumer>
182
187
  );
183
188
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-ui-components",
3
- "version": "12.0.0-rc.127",
3
+ "version": "12.0.0-rc.128",
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",
@@ -34,10 +34,10 @@
34
34
  "redux-mock-store": "^1.5.3"
35
35
  },
36
36
  "dependencies": {
37
- "@applicaster/applicaster-types": "12.0.0-rc.127",
38
- "@applicaster/zapp-react-native-bridge": "12.0.0-rc.127",
39
- "@applicaster/zapp-react-native-redux": "12.0.0-rc.127",
40
- "@applicaster/zapp-react-native-utils": "12.0.0-rc.127",
37
+ "@applicaster/applicaster-types": "12.0.0-rc.128",
38
+ "@applicaster/zapp-react-native-bridge": "12.0.0-rc.128",
39
+ "@applicaster/zapp-react-native-redux": "12.0.0-rc.128",
40
+ "@applicaster/zapp-react-native-utils": "12.0.0-rc.128",
41
41
  "promise": "^8.3.0",
42
42
  "react-router-native": "^5.1.2",
43
43
  "url": "^0.11.0",