software-mansion / react-native-reanimated

React Native's Animated library reimplemented
https://docs.swmansion.com/react-native-reanimated/
MIT License
9.09k stars 1.32k forks source link

Animated.createAnimatedComponent mock returns undefined instead of the Component when running jest #3489

Open mahdieh-dev opened 2 years ago

mahdieh-dev commented 2 years ago

Description

I have a screen that contains an animated FlatList and I need to use ref for it, so I created my Animated list like below:

const AnimatedList = Animated.createAnimatedComponent(FlatList)

I need to test an element in this list is working fine, however, I cannot get the test running. Because it fails on my AnimatedList component with the following error:

console.error
    Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

I have mocked the react-native-reanimated component like this:

jest.mock('react-native-reanimated', () => {
  return {
    ...jest.requireMock('react-native-reanimated/mock'),
    useSharedValue: jest.fn,
    useAnimatedStyle: jest.fn,
  };
});

So I tried to log what is really inside my AnimatedList and got undefined. I tried to log Animated.createAnimatedComponent itself since I knew it exists in the package mock, and this is what is logged in the console:

 [Function: createAnimatedComponent] {
      _isMockFunction: true,
      getMockImplementation: [Function (anonymous)],
      mock: [Getter/Setter],
      mockClear: [Function (anonymous)],
      mockReset: [Function (anonymous)],
      mockRestore: [Function (anonymous)],
      mockReturnValueOnce: [Function (anonymous)],
      mockResolvedValueOnce: [Function (anonymous)],
      mockRejectedValueOnce: [Function (anonymous)],
      mockReturnValue: [Function (anonymous)],
      mockResolvedValue: [Function (anonymous)],
      mockRejectedValue: [Function (anonymous)],
      mockImplementationOnce: [Function (anonymous)],
      mockImplementation: [Function (anonymous)],
      mockReturnThis: [Function (anonymous)],
      mockName: [Function (anonymous)],
      getMockName: [Function (anonymous)]
    }

But when I log Animated.createAnimatedComponent(FlatList) it returns undefined. I tried to mock this function after the line ...jest.requireMock('react-native-reanimated/mock'), like (Component) => Component but it didn't work. So I tried to remove this line ...jest.requireMock('react-native-reanimated/mock'), and try to mock the required components and functions myself. This time it worked and in the logs, I could see that the component is returned by the Animated.createAnimatedComponent(FlatList), however, the test failed again, because other reanimated components were not mocked correctly. So I believe the issue should be inside the mock file, I couldn't figure out what is the reason it is not returning undefined instead of the component. I would be grateful if you help me fix this issue, it took me 2 days but has not been resolved yet.

Steps to reproduce

  1. Clone the provided repo
  2. Run npm i to install the dependencies
  3. Run npx jest (it will fail on AnimatedList component and I provided the logs I described above which you can check on the console)

Snack or a link to a repository

https://github.com/mshavandi/reanimatedMockIssueWithJest

Reanimated version

3.0.0-rc.1

React Native version

0.68.2

Platforms

Android

JavaScript runtime

No response

Workflow

No response

Architecture

No response

Build type

No response

Device

No response

Device model

No response

Acknowledgements

Yes

mahdieh-dev commented 2 years ago

For those who need a solution to this problem: I ended up mocking the reanimated package like this:

jest.mock('react-native-reanimated', () => {
  return {
    ...jest.requireMock('react-native-reanimated/mock'),
    useSharedValue: jest.fn,
    useAnimatedStyle: jest.fn,
    useAnimatedRef: () => ({ current: null }),
    default: {
      ...jest.requireMock('react-native-reanimated/mock').default,
      createAnimatedComponent: (Component) => Component,
    },
  };
});

This solves the issue with Animated components, and also some features related to reanimated V2

Update:

Actually, the problem was with the requireMock, it works with requireActual. Changing the mock to:

jest.mock('react-native-reanimated', () => {
  return {
    ...jest.requireActual('react-native-reanimated/mock'),
    ...jest.requireActual('react-native-reanimated/src/reanimated2/mock'),
  };
});

solves the problem.

Michelalmeidasilva commented 2 years ago

For those who need a solution to this problem: I ended up mocking the reanimated package like this:

jest.mock('react-native-reanimated', () => {
  return {
    ...jest.requireMock('react-native-reanimated/mock'),
    useSharedValue: jest.fn,
    useAnimatedStyle: jest.fn,
    useAnimatedRef: () => ({ current: null }),
    default: {
      ...jest.requireMock('react-native-reanimated/mock').default,
      createAnimatedComponent: (Component) => Component,
    },
  };
});

This solves the issue with Animated components, and also some features related to reanimated V2

Update:

Actually, the problem was with the requireMock, it works with requireActual. Changing the mock to:

jest.mock('react-native-reanimated', () => {
  return {
    ...jest.requireActual('react-native-reanimated/mock'),
    ...jest.requireActual('react-native-reanimated/src/reanimated2/mock'),
  };
});

solves the problem.

I have the same problem, but this mock doesn't work for me.

Invariant Violation:createAnimatedComponent` does not support stateless functional components; use a class component instead.

  46 | }
  47 |
> 48 | const AnimatedColumn = Animated.createAnimatedComponent(Column);
     |                                 ^
  49 | const defaultAnimationDuration = 0;
  50 |
  51 | const Overlay: ForwardRefRenderFunction<OverlayRef, OverlayProps> = (

  at invariant (node_modules/invariant/invariant.js:40:15)
  at Object.createAnimatedComponent (node_modules/react-native/Libraries/Animated/src/createAnimatedComponent.js:28:3)`

i`m tryng testing with a styled component library, maybe my error is because styled components doesnt works with animated components, but idk.

tonibardina commented 2 years ago

@Michelalmeidasilva You can use styled-components using a dumb class component for that:

class ColumnKlass extends React.Component {
  render() {
    return <Column />; //Styled component
  }
}

const AnimatedColumn = Animated.createAnimatedComponent(ColumnKlass);

Btw seems that some mocks are missing for the Layout components (on both reanimated and reanimated2 mocks). You can solve that issue by mocking the missing stuff yourself 😄

jest.mock('react-native-reanimated', () => {
  const Reanimated = require('react-native-reanimated/mock');

  // The mock for `call` immediately calls the callback which is incorrect
  // So we override it with a no-op
  Reanimated.default.call = () => {};

  const DefaultLayoutAnimation: unknown = {
    delay: (_: number) => DefaultLayoutAnimation,
    duration: (_: number) => DefaultLayoutAnimation,
    withCallback: () => DefaultLayoutAnimation,
    withInitialValues: (_: unknown) => DefaultLayoutAnimation,
    randomDelay: (_: number) => DefaultLayoutAnimation,
  };

  return {
    ...Reanimated,
    FadeIn: DefaultLayoutAnimation,
    BounceIn: DefaultLayoutAnimation,
  };
});

NOTE: In my case I'm only using FadeIn and BounceIn default animations so I only mocked those two

moribaleta commented 1 year ago

I am encountering an issue where I want the withCallback to be called but even if I remove the line with that Reanimated.default.call its still not being called

"react-native-reanimated": "^2.14.4",

component

 const [openState, setOpenState] = useState(true);

  const onWillClose = () => {
    setOpenState(false);
  };

  const onExit = (finished: boolean) => {
    'worklet';
    console.log('on exit');
    if (finished) {
      runOnJS(onCloseModal)();
    }
  };

  const onShown = (finished: boolean) => {
    'worklet';
    console.log('on open');
    if (finished && onModalShown) {
      runOnJS(onModalShown)();
    }
  };

  return (
    <ReAnimated.View {...testProps(testID)} style={styles.main}>
      {openState && (
        <>
          <ReAnimated.View
            style={styles.background}
            entering={FadeIn}
            exiting={FadeOut}
          />
          <ReAnimated.View
            style={styles.containerStyle}
            entering={SlideInDown.withCallback(onShown)}
            exiting={SlideOutDown.withCallback(onExit)}
          >
            <Pressable
              {...testProps(testID + '.pressable')}
              style={styles.pressable}
              onPress={onWillClose}
            />
            <View style={styles.contents}>
              {children}
              <SafeAreaView edges={['bottom']} />
            </View>
          </ReAnimated.View>
        </>
      )}
    </ReAnimated.View>
);

test


jest.mock('react-native-reanimated', () => {
  // eslint-disable-next-line @typescript-eslint/no-var-requires
  const Reanimated = require('react-native-reanimated/mock');

  // The mock for `call` immediately calls the callback which is incorrect
  // So we override it with a no-op
  //Reanimated.default.call = () => {};

  const DefaultLayoutAnimation: unknown = {
    delay: (_: number) => DefaultLayoutAnimation,
    duration: (_: number) => DefaultLayoutAnimation,
    withCallback: () => DefaultLayoutAnimation,
    withInitialValues: (_: unknown) => DefaultLayoutAnimation,
    randomDelay: (_: number) => DefaultLayoutAnimation,
  };

  return {
    ...Reanimated,
    SlideInDown: DefaultLayoutAnimation,
    SlideOutDown: DefaultLayoutAnimation,
  };
});

it('invokes open', () => {
    render(
      <Component
        testID="sample"
        onCloseModal={mockOnClose}
        onModalShown={mockOnOpen}
      >
        <Text>Sample Text</Text>
        <Button title="Sample Button" />
      </Component>,
    );
    expect(mockOnOpen).toHaveBeenCalled();
  });
github-actions[bot] commented 1 year ago

Hey! 👋

The issue doesn't seem to contain a minimal reproduction.

Could you provide a snack or a link to a GitHub repository under your username that reproduces the problem?