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

Cannot find custom component using selector with property value #951

Closed
andriichernenko opened this issue May 19, 2017 · 10 comments
Closed
Labels

Comments

@andriichernenko
Copy link

andriichernenko commented May 19, 2017

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.

@samit4me
Copy link
Contributor

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!

@andriichernenko
Copy link
Author

@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.

@samit4me
Copy link
Contributor

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?

@andriichernenko
Copy link
Author

andriichernenko commented May 22, 2017

@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).

@samit4me
Copy link
Contributor

samit4me commented May 22, 2017

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 ✨

@ljharb
Copy link
Member

ljharb commented May 22, 2017

Seems like this is resolved :-) thanks everyone!

@andriichernenko
Copy link
Author

andriichernenko commented May 23, 2017

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" />

@samit4me
Copy link
Contributor

samit4me commented Jun 3, 2017

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 ✨

@ljharb
Copy link
Member

ljharb commented Jun 3, 2017

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".

@andriichernenko
Copy link
Author

Okay, thanks for advice!

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

No branches or pull requests

3 participants