@applicaster/zapp-react-native-ui-components 12.0.0-alpha.4102526423 → 12.0.0-alpha.4173603919

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.
@@ -276,13 +276,20 @@ export class CellComponent extends React.Component<Props, State> {
276
276
  skipFocusManagerRegistration={skipFocusManagerRegistration}
277
277
  >
278
278
  {(focused, event) => {
279
+ const isFocused = this.isCellFocused(focused);
280
+
281
+ if (isFocused) {
282
+ const accessibilityManager = AccessibilityManager.getInstance();
283
+ accessibilityManager.readText({ text: item.title });
284
+ }
285
+
279
286
  return (
280
287
  <FocusableCell
281
288
  {...{
282
289
  index,
283
290
  CellRenderer,
284
291
  item,
285
- focused: this.isCellFocused(focused),
292
+ focused: isFocused,
286
293
  scrollTo: this.scrollToIndex(event),
287
294
  behavior,
288
295
  }}
@@ -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
  );
@@ -31,14 +31,14 @@ const useParentFocus = (
31
31
 
32
32
  function FocusableChildComponent(props) {
33
33
  const { setIsFocusable, ...focusableContext } = useFocusable();
34
+ const focused = useCellState(props.id);
35
+
34
36
  const parentFocus = useParentFocus(props, focusableContext);
35
37
 
36
38
  const onLoadFailed = React.useCallback(() => {
37
39
  setIsFocusable(false);
38
40
  }, []);
39
41
 
40
- const focused = useCellState(props.id);
41
-
42
42
  return props.renderItem({
43
43
  ...props,
44
44
  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) =>
@@ -362,21 +362,15 @@ const PlayerContainerComponent = (props: Props) => {
362
362
  const toggleFullscreen = (event) => {
363
363
  const { fullscreen = false } = event || {};
364
364
 
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
- navigator.fullscreenVideoModal();
368
-
369
- setTimeout(() => {
365
+ setTimeout(() => {
366
+ if (fullscreen) {
367
+ navigator.fullscreenVideoModal();
370
368
  playerEvent("onFullscreenPlayerWillPresent");
371
- });
372
- } else {
373
- // in order to avoid UI glitches with player we have to start re-render ealier and then start real rotation
374
- navigator.maximiseVideoModal();
375
-
376
- setTimeout(() => {
369
+ } else {
370
+ navigator.maximiseVideoModal();
377
371
  playerEvent("onFullscreenPlayerWillDismiss");
378
- });
379
- }
372
+ }
373
+ });
380
374
  };
381
375
 
382
376
  const onCellTap = (entry) => {
@@ -471,6 +465,10 @@ const PlayerContainerComponent = (props: Props) => {
471
465
 
472
466
  if (mode === VideoModalMode.FULLSCREEN) {
473
467
  toggleFullscreen({ fullscreen: false });
468
+
469
+ if (!isModal) {
470
+ playerManager.closeNativePlayer();
471
+ }
474
472
  }
475
473
 
476
474
  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-alpha.4102526423",
3
+ "version": "12.0.0-alpha.4173603919",
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-alpha.4102526423",
38
- "@applicaster/zapp-react-native-bridge": "12.0.0-alpha.4102526423",
39
- "@applicaster/zapp-react-native-redux": "12.0.0-alpha.4102526423",
40
- "@applicaster/zapp-react-native-utils": "12.0.0-alpha.4102526423",
37
+ "@applicaster/applicaster-types": "12.0.0-alpha.4173603919",
38
+ "@applicaster/zapp-react-native-bridge": "12.0.0-alpha.4173603919",
39
+ "@applicaster/zapp-react-native-redux": "12.0.0-alpha.4173603919",
40
+ "@applicaster/zapp-react-native-utils": "12.0.0-alpha.4173603919",
41
41
  "promise": "^8.3.0",
42
42
  "react-router-native": "^5.1.2",
43
43
  "url": "^0.11.0",