I have a custom text input component in my React Native app which wraps TextInput:
export class FnTextInput extends Component {
static propTypes = {
...TextInput.propTypes
};
static defaultProps = {
style: {}
};
props: {
style?: any,
};
textInput: TextInput;
focus() {
this.textInput.focus();
}
render() {
return (
<TextInput
{...this.props}
id="textInput"
ref={e => (this.textInput = e)}
style={[styles.textInput, this.props.style]}
selectionColor={colors.fortnoxGreen}
placeholderTextColor={colors.secondaryText}
autoCapitalize="none"
autoCorrect={false}
clearButtonMode="while-editing"
/>
);
}
}
I have 2 such text inputs in my login view:
<...>
<FnTextInput
id="usernameInput"
placeholder={strings.t('login.usernamePlaceholder')}
/>
<FnTextInput
id="passwordInput"
placeholder={strings.t('login.passwordPlaceholder')}
/>
</...>
I mount() login view in tests and try to find the text fields by their ID:
const loginView = mount(<LoginView />);
const usernameInput = loginView.find('FnTextInput[id="usernameInput"]');
Unfortunately, this lookup fails, usernameInput is empty.
However, the following lookups work as expected:
const textInputs = loginView.find('FnTextInput');
const usernameInput = loginView.find('TextInput[id="textInput"]');
I use react-native-mock-render to enable deep rendering, if it matters. My React Native version is 0.42.3.
Hi @andriichernenko, I'm not sure you can find your component using a string unless the component has a displayName set. So you can find using the actual component or you could use the id as it's unique.
// using id only
loginView.find('[id="usernameInput"]');
// OR
// using the component (it will require FnTextInput to be imported into your test)
loginView.find(FnTextInput).find('[id="usernameInput"]');
I'm not sure why the `TextInput[id="textInput"]' works (I'm not familiar with RN) but I can only assume it has a displayName set.
See this page for more info. I hope that helps!
@samit4me
I don't think displayName is the problem. Whether I set it or not, loginView.find('FnTextInput') works and gives exactly the same result as loginView.find(FnTextInput).
Both loginView.find('[id="usernameInput"]'); and loginView.find(FnTextInput).find('[id="usernameInput"]');, however, fail.
I also tried using loginView.find({ id: 'usernameInput' }), but it doesn't work either.
Ah that's very interesting! To replicate the issue you mentioned, I setup a small test with plain react and the suggestions I made earlier actually worked, so maybe it's a react native specific issue? I'm not sure as I've never used it. Do you have a repo or some code you can share that demonstrates the issue?
@samit4me
Sure, here it is: https://github.com/andriichernenko/enzyme-951-repro. Run the test case in ./js/__tests__/FnTextInput.test.js. Jest setup logic is in ./jest/setup.js.
Note that I am using RN 0.42.3 with React 15.4.1 and Enzyme 2.8.0 due to #893 (the latest stable version is 0.44.0).
Not sure how I didn't spot this earlier, but in the example repo and the code you posted above, the id prop is passed in from the unit test and spread to <TextInput {...this.props} /> but then, on the very next line the id is overridden (e.g. id="textInput"), removing that resolves the problem.
You're 💯 correct about the displayName, it makes zero difference, it works with or without it.
I also opened a PR on your repo so you can see it working if you want, I hope that resolves the issue. Thanks for sharing the code, much appreciated ✨
Seems like this is resolved :-) thanks everyone!
Yeah, the tests work now, but I am afraid the issue has not been resolved (or maybe I don't quite understand how lookup is supposed to work?).
Lookup by id still does not find the wrapper component (FnTextInput), finding the wrapped TextInput instead (since {...this.props} now gives it the same id).
Here are the lookup results I get:
loginView.find('[id="usernameInput"]').debug();
<TextInput id="usernameInput" style={{...}} selectionColor="#00aa00" placeholderTextColor="#000000" autoCapitalize="none" autoCorrect={false} clearButtonMode="while-editing" />
loginView.find(FnTextInput).debug();
<FnTextInput id="usernameInput" style={{...}}>
<TextInput id="usernameInput" style={{...}} selectionColor="#00aa00" placeholderTextColor="#000000" autoCapitalize="none" autoCorrect={false} clearButtonMode="while-editing">
<TextInput id="usernameInput" style={{...}} selectionColor="#00aa00" placeholderTextColor="#000000" autoCapitalize="none" autoCorrect={false} clearButtonMode="while-editing" />
</TextInput>
</FnTextInput>
<FnTextInput id="passwordInput" style={{...}}>
<TextInput id="passwordInput" style={{...}} selectionColor="#00aa00" placeholderTextColor="#000000" autoCapitalize="none" autoCorrect={false} clearButtonMode="while-editing">
<TextInput id="passwordInput" style={{...}} selectionColor="#00aa00" placeholderTextColor="#000000" autoCapitalize="none" autoCorrect={false} clearButtonMode="while-editing" />
</TextInput>
</FnTextInput>
loginView.find(FnTextInput).find('[id="usernameInput"]').debug();
<TextInput id="usernameInput" style={{...}} selectionColor="#00aa00" placeholderTextColor="#000000" autoCapitalize="none" autoCorrect={false} clearButtonMode="while-editing" />
I did a little investigation into this and it turns out the CSS selectors _(e.g. .find('[id="usernameInput"]'))_ only work for natively rendered elements like TextInput and not your own react components like FnTextInput. In the docs it states:
Enzyme supports a subset of valid CSS selectors to find nodes inside a render tree.
I think the keywords "render tree" can be translated to "only the native elements". This would make sense as this is the actual output. The components we write in JSX are essentially a way to compose state/props to native elements.
If you remove the id out of this example and do something like this <FnTextInput someProp="someValue" /> and try and use a CSS selector like .find(FnTextInput).find('[someProp="someValue"]').debug() it returns nothing.
Does this help at all? I know I certainly learned something looking into this, thanks for sharing ✨
I'd recommend using findWhere, and implementing your find logic in JS, instead of as string selectors - both because enzyme is currently very limited in its string selector capability, but also because it's more declarative than "magic strings".
Okay, thanks for advice!
Most helpful comment
I'd recommend using
findWhere, and implementing your find logic in JS, instead of as string selectors - both because enzyme is currently very limited in its string selector capability, but also because it's more declarative than "magic strings".