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.
I would expect the component to be just
<Text>
Hello World
</Text>
React Native 0.59.5
| library | version
| ------------------- | -------
| enzyme | 3.6.0
| react | 16.8.6
| react-dom | 16.0.6
| react-test-renderer | 16.8.6
| adapter (below) |
enzyme won't work properly with react-native without a react-native adapter. Follow #1436 for that.
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;
};
What i mean is, those adapters are for react web. There isn’t an adapter for react native atm.
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?
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.
@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.
It’s likely that to “fix” this, RN itself would have to explicitly set the display name on their now-forward-reffed components.
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>
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.
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"]`)
Most helpful comment
@ljharb thank you! You just dropped the nugget of gold that I needed. In my example above,
wrapper.find("Text")doesn't work, butwrapper.find(Text)does. I should have read the docs here better: https://airbnb.io/enzyme/docs/api/selector.html.