I have a TextInput component, which calls a custom function when its value changes through its onValueChange property.
Is it possible to simulate the change of the text of this TexInput with Jest, so that I test if that the custom function (which is passed as a prop) is called with correct parameters and produces correct results?
Yes its possible with jest and enzyme. Below is the definition of SearchBar component which receives a function as a prop. The function is called with the text typed.
const SearchBar = ({onSearch}) => {
return (
<View>
<TextInput
onChangeText={text => onSearch(text)}
/>
</View>
);
};
The testing can be carried as follows
const onSearch = jest.fn();
const wrapper = shallow(<SearchBar onSearch={onSearch} />);
wrapper.find('TextInput').simulate('changeText', 'test search text');
expect(onSearch).toHaveBeenCalledWith('test search text');
Related
I have a component. It has a button. Upon pressing the button, I am changing the style of the button text (color) using setState function. When I am testing the changed component, the test is failing because the change happens asynchronously. I want to do something as is given here (https://testing-library.com/docs/dom-testing-library/api-async/)
const button = screen.getByRole('button', { name: 'Click Me' })
fireEvent.click(button)
await screen.findByText('Clicked once')
fireEvent.click(button)
await screen.findByText('Clicked twice')
But rather than waiting for the text to change. I want to wait for the text color to change. Thanks
This is the code for my button
<Button onPress = {() => {this.setState({state : 1});}}>
<Text style = {style}>Button Text</Text>
</Button>
So when this button is pressed. state is set to 1. And in render :
if(this.state.state === 1) style = style1
else style = style2;
But it can be seen from logs that render is called after the test checks for the styles. So How can I wait for the render to complete before checking if the font color has been changed?
Here is the testing code
test('The button text style changes after press', () => {
const {getByText} = render(<Component/>);
fireEvent.press(getByText('button'));
expect(getByText('button')).toHaveStyle({
color : '#ffffff'
});
})
It looks like you have a custom button, not a native button. I'm guessing your component is something like this:
import React from "react";
import {Text, TouchableOpacity} from "react-native";
const Button = ({pressHandler, children}) => (
<TouchableOpacity onPress={pressHandler}>
{children}
</TouchableOpacity>
);
const ColorChangingButton = ({text}) => {
const [color, setColor] = React.useState("red");
const toggleColor = () => setTimeout(() =>
setColor(color === "green" ? "red" : "green"), 1000
);
return (
<Button pressHandler={toggleColor}>
<Text style={{color}}>{text}</Text>
</Button>
);
};
export default ColorChangingButton;
If so, you can test it with waitFor as described here:
import React from "react";
import {
fireEvent,
render,
waitFor,
} from "#testing-library/react-native";
import ColorChangingButton from "../src/components/ColorChangingButton";
it("should change the button's text color", async () => {
const text = "foobar";
const {getByText} = render(<ColorChangingButton text={text} />);
fireEvent.press(getByText(text));
await waitFor(() => {
expect(getByText(text)).toHaveStyle({color: "green"});
});
});
For a native button which has rigid semantics for changing colors and doesn't accept children, instead using title="foo", a call to debug() shows that it expands to a few nested elements. You can use
const text = within(getByRole("button")).getByText(/./);
expect(text).toHaveStyle({color: "green"});
inside the waitFor callback to dip into the button's text child and wait for it to have the desired color.
I used the same packages/versions for this post as shown in React Testing Library: Test if Elements have been mapped/rendered.
You can try
<Text style = {this.state.state === 1 ? style1 : style2}>Button Text</Text>
This will consequently lead to the style being defined all time. So you don't have to wait for the setState to complete.
Edit
You can use the callback provided by setState function to perform your tests for styles.
this.setState({
state : 1
} , () => {
//this is called only after the state is changed
//perform your test here
})
I have a custom component like this
const MyCustomComponent = ({ value, style }) => {
// I can access value & style with value and style
return <View style={style}>
<Text>{value}</Text>
</View>
}
I can called it with
<MyCustomComponent value="123" style={{ color: "blue" }} />
My question is how to get arguments or alyelse to get all props passed to my component?
In native function, i can use arguments to get allProps as an Array and set it in a new variable like const allProps = arguments[0]
What about in arrow function?
What you have here is a functional component, it is built as such that it only receives one object - that is props. When you did this: const MyComponent({value, style}) you destructured the prop object, extracting only the two variables.
You should instead do this:
const MyCustomComponent = (props) => {
//you can access the values like this
console.log(props.style, props.value)
//or you can access them like this which is the same thing you did
//earlier
const {style, value} = props;
console.log(style, value)
return (
...
)
}
Keep in mind that you need to have React in scope, so be sure to import it at the top:
import React from 'react';
Functional components are very well explained in React documentation: https://reactjs.org/docs/components-and-props.html
Do you want to get 'value' and 'style' props using a single variable?
You can use:
const MyCustomComponent = (props) => {
// I can access value & style with value and style
return <View style={props.style}>
<Text>{props.value}</Text>
</View>
}
I want to change the state of a parent component from a child's TextInput.
The problem is every time it changes the parent state the component re-renders, the TextInput is blurred and the keyboard disappears.
I tried keeping all the logic in the some component and changing the parent state without passing props. Now I've tried extracting the InputText container and putting it into a new file, changing the parents' state and receiving the value through the props, that's not working either.
Here is the text field component:
export default (ProfileTextInput = ({
placeholder,
label,
handleChange,
name,
value
}) => {
return (
<View style={styles.inputComponent}>
<Text style={styles.labelText}>{label.toUpperCase()}</Text>
<TextInput
key={Math.random()}
placeholder={placeholder || ""}
value={value}
onChangeText={val => handleChange(val, name)}
/>
</View>
);
});
and this is how it's being used:
const [newUserData, setNewUserData] = useState({ ...userData });
const changeHandler = (value, name) => {
setNewUserData({ ...newUserData, [name]: value });
};
return(
<ProfileTextInput
label="Username"
defaultValue={newUserData.username}
name="username"
value={newUserData.username}
handleChange={changeHandler}
/>
)
I expected it to continue letting me type like a normal TextInput, but it's only typing one letter and losing focus.
Try removing the defaultValue props from your components.
defaultValue is just the initial value passed to an uncontrolled component. Since you're setting the input value with a change handler, that makes your input a controlled component, and so you should just set the value explicitly.
Read these articles for more details on the differences between these cases:
React forms and controlled components
Uncontrolled components
I am trying to do a jest unit test which simply confirms that the refreshControl prop is active in my ScrollView.
I currently have a component that essentially renders the following
<ScrollView
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this._onRefresh}
/>
}
{children}
/>
It works fine in practice, and in my test, I am checking to confirm that the prop is as expected.
import { RefreshControl } from 'react-native'
const layout = shallow(<Element />)
const refreshControl = <RefreshControl
onRefresh={jest.fn()}
refreshing={false}
/>
expect(layout.prop('refreshControl')).toEqual(refreshControl)
I then get this error when running my test:
Expected value to equal:
<RefreshControlMock onRefresh={[Function mockConstructor]} refreshing={false} />
Received:
<RefreshControlMock onRefresh={[Function mockConstructor]} refreshing={false} />
I assume it's because the instance of the onRefresh function differs, but not sure how to control that. Does anyone perhaps know?
Please find below steps to call Pull to Refresh to cover coverage.
First, get access to the scrollView component through testID
Fetch refresh control present in the scroll view component
call onRefresh from test case
I have added the below Example for reference.
test('refresh control', async () => {
const props = createTestProps();
const component = render(
<Provider store={store}>
<ScrollViewComponent {...props} />
</Provider>
);
const scrollView = component.getByTestId('refreshControl');
expect(scrollView).toBeDefined();
const { refreshControl } = scrollView.props;
await act(async () => {
refreshControl.props.onRefresh();
});
});
Just realised that I need to check that the function is the same one I pass into the component, in the test, so basically, the function in the component that matches.
this._onRefresh
MW UMESH's answer helped me quite a lot (and I therefore upvoted it) but since you get the refreshControl prop synchronously, you can remove the async test here.
So replace
await act(async () => {
refreshControl.props.onRefresh();
});
With
refreshControl.props.onRefresh();
And the reload mechanism will trigger.
In React Native, I want to pass the value of the TextInput in the onBlur event handler.
onBlur={(e) => this.validateText(e.target.value)}
e.target.value works for plain React. But, in react-native, e.target.value is undefined. What is the structure of event args available in React Native?
You should use the 'onEndEditing' method instead of the 'onBlur'
onEndEditing?: function Callback that is called when text input ends.
onBlur is a component function where onEndEditing is specific for TextInput
onEndEditing
This approach works for both multiline and single line.
<TextInput
onEndEditing={(e: any) =>
{
this.setState({textValue: e.nativeEvent.text})
}
}/>
In React Native, you can get the value of the TextInput from e.nativeEvent.text.
Unfortunately, this doesn't work for multiline={true}. One hack around this is to maintain a ref to your TextInput and access the text value through the _lastNativeText property of the component. For example (assuming you've assigned your TextInput component a ref of "textInput"):
onBlur={() => console.log(this.refs.textInput._lastNativeText)}
Simple solution:
This way onBlur your state's email will always have the last value changed by the user.
validate = () => {
const { email } = this.state
console.log('Validating Email Here!', email)
}
<TextInput
style={styles.input}
placeholder='E-mail'
onChangeText={email => this.setState({email})}
onBlur={e => this.validate()}
/>