Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shallow Render has everything wrapped in ForwardRef #2190

Closed
2 of 13 tasks
gregkerzhner opened this issue Jul 15, 2019 · 10 comments
Closed
2 of 13 tasks

Shallow Render has everything wrapped in ForwardRef #2190

gregkerzhner opened this issue Jul 15, 2019 · 10 comments

Comments

@gregkerzhner
Copy link

gregkerzhner commented Jul 15, 2019

Current behavior

This is with react native. When I use shallow rendering with enzyme, all components seem to be wrapped in a forwardRef. For example, if I do this in a test

    const wrapper = shallow(
      <TestView/>
    );
    console.log(wrapper.debug())

where TestView is

export const TestView: FC<PropType> = (props: PropType) => {
  return (
    <Text>Hello World</Text>
  );
};

The resulting output is

 <ForwardRef(Text)>
    Hello World
 </ForwardRef(Text)>

The problem with this is that you cannot do wrapper.find("Text"), and instead have to do wrapper.find('ForwardRef(Text') which is a lot less clear and seems fragile.

Expected behavior

I would expect the component to be just

 <Text>
    Hello World
 </Text>

Your environment

React Native 0.59.5

API

  • shallow
  • mount
  • render

Version

library version
enzyme 3.6.0
react 16.8.6
react-dom 16.0.6
react-test-renderer 16.8.6
adapter (below)

Adapter

  • enzyme-adapter-react-16
  • enzyme-adapter-react-16.3
  • enzyme-adapter-react-16.2
  • enzyme-adapter-react-16.1
  • enzyme-adapter-react-15
  • enzyme-adapter-react-15.4
  • enzyme-adapter-react-14
  • enzyme-adapter-react-13
  • enzyme-adapter-react-helper
  • others ( )
@ljharb
Copy link
Member

ljharb commented Jul 16, 2019

enzyme won't work properly with react-native without a react-native adapter. Follow #1436 for that.

@ljharb ljharb closed this as completed Jul 16, 2019
@gregkerzhner
Copy link
Author

I am using the Adapter! mount works properly, but shallow does not. Here is my test-setup.js file

import { JSDOM } from "jsdom";

const jsdom = new JSDOM("<!doctype html><html><body></body></html>");
const { window } = jsdom;

const copyProps = (src, target) => {
  const props = Object.getOwnPropertyNames(src)
    .filter(prop => typeof target[prop] === "undefined")
    .map(prop => Object.getOwnPropertyDescriptor(src, prop));

  console.log("Props are" + props)  
  Object.defineProperties(target, props);
};

global.window = window;
global.document = window.document;
global.navigator = {
  userAgent: "node.js"
};
copyProps(window, global);

import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";

// Setup enzyme's react adapter
configure({ adapter: new Adapter() });

// Ignore React Web errors when using React Native
console.error = message => {
  return message;
};

@ljharb
Copy link
Member

ljharb commented Jul 16, 2019

What i mean is, those adapters are for react web. There isn’t an adapter for react native atm.

@gregkerzhner
Copy link
Author

Thanks for the quick response. I think what is misleading to me is the documentation then?

https://airbnb.io/enzyme/docs/guides/react-native.html

"As of v0.18, React Native uses React as a dependency rather than a forked version of the library, which means it is now possible to use enzyme's shallow with React Native components".

But if the behavior I am seeing is actually what is happening to everyone else, isn't this statement not really true? I guess you technically can use it, but it won't actually produce output thats predictable or usable.

Should I make a pull request to the documentation to say that shallow won't produce output that actually matches your component structure and is therefore not really usable?

@ljharb
Copy link
Member

ljharb commented Jul 16, 2019

I suspect that at some point RN started using forwardRef in all their primitives, and that doesn’t play well with shallow.

If you shallow render your own component, it should certainly be usable - you should be using .find(Text) and not finding by display name anyways.

@gregkerzhner
Copy link
Author

@ljharb thank you! You just dropped the nugget of gold that I needed. In my example above,
wrapper.find("Text") doesn't work, but wrapper.find(Text) does. I should have read the docs here better: https://airbnb.io/enzyme/docs/api/selector.html.

@ljharb
Copy link
Member

ljharb commented Jul 16, 2019

It’s likely that to “fix” this, RN itself would have to explicitly set the display name on their now-forward-reffed components.

@bedrich-schindler
Copy link

bedrich-schindler commented Feb 28, 2020

We have similar problem when using mount. I fixed it this way:

import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import { createSerializer } from 'enzyme-to-json';

// Configure enzyme to use adapter for React
Enzyme.configure({ adapter: new Adapter() });

// Omit ForwardRef component in jest snapshot
// Bug: https://github.com/enzymejs/enzyme/issues/2190
const omitForwardRefInTree = (node) => {
  if (node.type && node.type.startsWith('ForwardRef')) {
    // ForwardRef have no child only when node is processed via `shallow` function,
    // otherwise `mount` is used.
    if (!node.children || node.children.length === 0) {
      return node;
    }

    if (node.children && node.children.length === 1) {
      return node.children[0];
    }

    return node.children;
  }

  return node;
};

// Configure jest to use json serializer for snapshot creation
expect.addSnapshotSerializer(createSerializer({
  map: omitForwardRefInTree,
  mode: 'deep',
  noKey: true,
}));

I use enzyme-to-json to render snapshots and it allows to change rendering of specific node, so I can omit it to temporarily fix the problem.

Snapshot before fix:

<ForwardRef(withForwardedRef(s))
            beforeLabel={
              <Icon
                icon="remote-connection"
                size="medium"
              />
            }
            clickHandler={[Function]}
            disabled={false}
            id="header__remoteAccessDialogButton"
            label="Remote access"
            labelVisibility="desktop"
            priority="default"
            variant="secondary"
          >
            <button
              className="Button__root__3zrxR
        Button__priorityDefault__hcWO8
        Button__sizeMedium__168V1
        Button__variantSecondary__1bYwq
        
        
        Button__withLabelHiddenMobile__3WSDW"
              disabled={false}
              id="header__remoteAccessDialogButton"
              onClick={[Function]}
              title="Remote access"
              type="button"
            >
              <span
                className="Button__beforeLabel__2TFTl"
              >
                <svgr-mock
                  height={16}
                  width={16}
                />
              </span>
              <span
                className="Button__label__3YN6e"
                id="header__remoteAccessDialogButton__label"
              >
                Remote access
              </span>
            </button>
          </ForwardRef(withForwardedRef(s))>

Snapshot after fix:

<button
            className="Button__root__3zrxR
        Button__priorityDefault__hcWO8
        Button__sizeMedium__168V1
        Button__variantSecondary__1bYwq
        
        
        Button__withLabelHiddenMobile__3WSDW"
            disabled={false}
            id="header__remoteAccessDialogButton"
            onClick={[Function]}
            title="Remote access"
            type="button"
          >
            <span
              className="Button__beforeLabel__2TFTl"
            >
              <svgr-mock
                height={16}
                width={16}
              />
            </span>
            <span
              className="Button__label__3YN6e"
              id="header__remoteAccessDialogButton__label"
            >
              Remote access
            </span>
          </button>

@roni-castro
Copy link

roni-castro commented Aug 19, 2020

After upgrading react-native to the 0.63.2, finding an element by testing id does not work anymore, because TouchableOpacity component is wrapped by ForwardRef and it returns two nodes now instead of one.
Is there any solution to find an element by testing ID that do not return this ForwardRef or do I need to handle it (generate a logic to filter it, add wrapper around TouchabeOpacity to avoid components that have forwardRef...)?

Now it is retuning two nodes when I run: wrapper.find(`[data-ref="thumbsUp"]`) and use mount

// First node with the `data-ref = thumbsUp` 
<ForwardRef data-ref="thumbsUp" onPress={[Function: onPress]}>
     <TouchableOpacity data-ref="thumbsUp" onPress={[Function: onPress]} hostRef={{...}}>
     </TouchableOpacity> 
</ForwardRef>

// Second node with the `data-ref = thumbsUp`
<TouchableOpacity data-ref="thumbsup" onPress={[Function: onPress]} hostRef={{...}}>
</TouchableOpacity> 

I had the same problem after upgrading react-native version, and I can't do find(TouchableOpacity) because it would return more than 5 different items.

@roni-castro
Copy link

roni-castro commented Aug 21, 2020

The solution I have figured out is to add the name of component (TouchableOpacity) in the find command to be more restricted. Do something like this:

wrapper.find(`TouchableOpacity[data-ref="idOfComponent"]`)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants