React Native jest testing: "Cannot read properties of undefined" - reactjs

I am struggling to get my jest test going.
My Component:
//Login.js
import Dialog from "react-native-dialog";
import { View, TextInput } from "react-native";
export default class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: "",
};
}
render() {
return (
<View>
<View>
<TextInput
testID="email"
value={this.state.email}
onChangeText={(text) => this.setState({ email: text })}
/>
</View>
<View>
<TextInput
testID="password"
value={this.state.password}
onChangeText={(text) => this.setState({ password: text })}
/>
</View>
<View>
<Dialog.Container>
<Dialog.Button label="hi" />
</Dialog.Container>
</View>
</View>
);
}
}
My Test file
//Login.test.js
import React from "react";
import { render, fireEvent } from "react-native-testing-library";
import Login from "./Login";
const mockedModule_dialog = jest.mock("react-native-dialog");
module.exports = mockedModule_dialog;
jest.mock("react-native-dialog", () => mockedModule_dialog);
describe("Login", () => {
describe("change text login", () => {
it("change text username and password", () => {
const { getByTestId } = render(<Login />);
// use fireEvent change value TextInput
fireEvent.changeText(getByTestId("email"), "admin");
fireEvent.changeText(getByTestId("password"), "admin#123");
// use toEqual check value TextInput
expect(getByTestId("email").props.value).toEqual("admin");
expect(getByTestId("password").props.value).toEqual(
"admin#123"
);
});
});
});
But every time I run yarn test, I am getting the following error Cannot read properties of undefined (Reading 'Container')
When I remove the <Dialog.Container> .... </Dialog.Container> then test passed.
What am I doing wrong? Mocking the library react-native-dialog is not done correctly?

Related

Expo - React Native: Firebase Phone Number Authentication is failing

I need help. I am trying to send a verification code with phone number authentication in EXPO and It works well in development. But I don't know why the same code is not working in production. I am getting this error,
verifyphonenumber failed: second argument “applicationVerifier” must be an implementation of firebase.auth.ApplicationVerifier
The FirebaseRecaptchaVerifierModal is not showing up when I click to send the verification code. I think, it is failing in the second argument of verifyPhoneNumber() but I can’t see how I can correct this.recaptchaVerifier.current using the Doc. Here is my code
import React, {Component} from 'react';
import { View, TextInput, TouchableOpacity } from 'react-native';
import firebase from 'firebase';
import config from '../../firebase.config';
import { FirebaseRecaptchaVerifierModal } from 'expo-firebase-recaptcha';
import {ToastError, ToastSuccess} from '../../utils/Toast';
import styles from './styles';
class PhoneAuthScreen extends Component {
constructor(props) {
super(props);
this.state = {
phoneNumber: '',
},
this.recaptchaVerifier = React.createRef();
}
onChangeText(key, value) {
this.setState({
[key]: value
})
}
signInWithPhoneNumber = async () => {
const { phoneNumber } = this.state;
const phoneProvider = new firebase.auth.PhoneAuthProvider();
try {
const verificationId = await phoneProvider.verifyPhoneNumber(
phoneNumber, this.recaptchaVerifier.current);
this.setState({ verificationId: verificationId });
ToastSuccess('Verification code has been sent to your phone.');
} catch (err) {
ToastError(err.message);
}
}
render() {
return (
<View style={styles.verifyPage}>
<View>
<View style={styles.telSection}>
<TextInput
style={styles.telInput}
placeholder='+1 201-555-0123'
placeholderTextColor='#080040'
keyboardType={'phone-pad'}
returnKeyType='done'
autoCapitalize='none'
autoCorrect={false}
secureTextEntry={false}
ref='PhoneInput'
autoCompleteType="tel"
value={this.state.phoneNumber}
onChangeText={(val) => {
this.onChangeText('phoneNumber', val)
}
}
/>
</View>
</View>
<FirebaseRecaptchaVerifierModal
ref={this.recaptchaVerifier}
firebaseConfig={config}
/>
<TouchableOpacity style={styles.confirmBtn} onPress={this.signInWithPhoneNumber}>
<Text style={styles.confirmText}>Send Verification Code</Text>
</TouchableOpacity>
</View>
)
};
}
export default PhoneAuthScreen;

how to make a 'number' component in react native

I want to make a 'number' component which accepts numbers on input.
I tried to make it, but it is not working.
Here the code-
import React, { Component } from 'react';
import { TextInput } from 'react-native';
constructor(props)
{
super(props);
this.state = {
text: ''
};}
handleInputChange = (text) => {
if (/^\d+$/.test(text)) {
this.setState({
text: text
});
}
}
const NumberInput = (props) => {
return (
<TextInput
keyboardType='numeric'
onChangeText={this.handleInputChange}
value={this.state.text}
/>
)
}
export { NumberInput };
You don't have access to this in functional component, you need to define it as class based component,
class NumberInput extends Component{
constructor(props){
super(props);
this.state = {
text: ''
};
}
handleInputChange = (text) => {
if (/^\d+$/.test(text)) {
this.setState({
text: text
});
}
}
render(){
return (
<TextInput
keyboardType='numeric'
onChangeText={this.handleInputChange}
value={this.state.text}
/>
)
}
}
Update
You can also try this,
<TextInput
keyboardType='numeric'
onChange={this.handleInputChange} //onChange instead of onChangeText
value={this.state.text}
/>
And your function should be,
handleInputChange = (e) => {
if (/^\d+$/.test(e.target.value)) {
this.setState({
text: e.target.value
});
}
}
Reference to this change.
Also, you can use Number() function to check if the input is a number. It not, it will return NaN
you should use the class component when to use the constructor and use super or use and used hock function with useState in react
class NumberInput extends Component{
constructor(props){
super(props);
this.state = {
text: ''
};
}
handleInputChange = (text) => {
if (/^\d+$/.test(text)) {
this.setState({
text: text
});
}
}
render(){
return (
<TextInput
keyboardType='numeric'
onChangeText={this.handleInputChange}
value={this.state.text}
/>
)
}
}
or using the following shape when used the function component
import useState from'react'
function NumberInput (){
const [text, setText] = useState('');
handleInputChange = (text) => {
if (/^\d+$/.test(text))(setText(text)) ;
}
}
return (
<TextInput
keyboardType='numeric'
onChangeText={this.handleInputChange}
value={text}
/>
)
}
}

Button not displaying fetch results to the component?

I am creating a currency converter app and it will retrieve currency value from the API and multiply with the text input for the result. Both the API result and Text input are stored in State and passing as props to the Component
import React from 'react';
import { StyleSheet, Text, View,TextInput,Button } from 'react-native';
import DisplayResult from './src/DisplayResult'
export default class App extends React.Component {
state = {
currency:'',
pokeList: '',
}
placeNameChangeHandler=(val)=>{
this.setState({currency:val});
}
// console.log(this.state.currency);
async findCurrency () {
try {
//Assign the promise unresolved first then get the data using the json method.
const pokemonApiCall = await fetch('https://free.currconv.com/api/v7/convert?q=KWD_INR&compact=ultra&apiKey={my_api_Key}');
const pokemon = await pokemonApiCall.json();
this.setState({pokeList: pokemon['KWD_INR']});
// console.log(pokemon);
} catch(err) {
console.log("Error fetching data-----------", err);
};
<DisplayResult convert={this.state.pokeList} result={this.state.currency} />
}
render() {
return (
<View style={styles.container}>
<TextInput
placeholder="Currency"
value = {this.state.currency}
onChangeText={this.placeNameChangeHandler}
/>
<Button
title="Search"
onPress={this.findCurrency.bind(this)}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
DisplayResult
const DisplayResult =(props)=>{
const {convert,result} = props
console.log(convert);
return (
<View>
<Text>{result*convert}</Text>
</View>
)
}
export default DisplayResult;
I am trying to pass the API result and text input to the display component and this will multiply the values and will give the result.
Now this is not functioning or giving result
why this is not showing and where it's going wrong?
In your findCurrency method you just "call" the DisplayResult without returning it, but I don't think this is the good method to display your result.
You can use your component directly within the render method by testing your state variables, like this :
findCurrency = async () => {
try {
const pokemonApiCall = await fetch(
"https://free.currconv.com/api/v7/convert?q=KWD_INR&compact=ultra&apiKey={my_api_Key}"
);
const pokemon = await pokemonApiCall.json();
this.setState({ pokeList: pokemon["KWD_INR"] }); // You set your "pokeList" variable up
} catch (err) {
console.log("Error fetching data-----------", err);
}
}
Note that you remove the DisplayResult call here and the function became an arrowed function, then in your render method use the test to make your result appear only if pokeList isn't empty :
render() {
return (
<View style={styles.container}>
<TextInput
placeholder="Currency"
value={this.state.currency}
onChangeText={this.placeNameChangeHandler}
/>
<Button title="Search" onPress={this.findCurrency.bind(this)} />
{this.state.pokeList !== "" && (
<DisplayResult
convert={this.state.pokeList}
result={this.state.currency}
/>
)}
</View>
);
}
Then, you don't have to bind your function in the onPress method like this, JavaScript immediately calls the function if you do this, instead, use arrow functions, you can access this by doing so in your function AND the onPress method doesn't call it if you don't click on the button, you just have to specify which function to execute when clicked :
<Button title="Search" onPress={this.findCurrency} />
If you have parameters in your function, use an arrow function instead :
<Button title="Search" onPress={() => yourFunction(param)} />
This should do the trick.
Try writing your function like that :
const findCurrency = async() => {
// ...
};
and call it like that
<Button
title="Search"
onPress={() => this.findCurrency()}
/>
I personnaly never use .bind because I think this is very unclear.
try using conditional rendering,
if data fetched, then only render.
import React from 'react';
import { StyleSheet, Text, View,TextInput,Button } from 'react-native';
import DisplayResult from './src/DisplayResult'
export default class App extends React.Component {
state = {
currency: '',
pokeList: '',
}
placeNameChangeHandler=(val)=>{
this.setState({currency:val});
}
// console.log(this.state.currency);
this.findCurrency.bind(this);
async findCurrency () {
try {
//Assign the promise unresolved first then get the data using the json method.
const pokemonApiCall = await fetch('https://free.currconv.com/api/v7/convert?q=KWD_INR&compact=ultra&apiKey={my_api_Key}');
const pokemon = await pokemonApiCall.json();
this.setState({pokeList: pokemon['KWD_INR']});
// console.log(pokemon);
} catch(err) {
console.log("Error fetching data-----------", err);
};
}
render() {
return (
<View style={styles.container}>
<TextInput
placeholder="Currency"
value = {this.state.currency}
onChangeText={this.placeNameChangeHandler}
/>
<Button
title="Search"
onPress={this.findCurrency()}
/>
</View>
{
if(this.state.pokeList !== '' || this.state.currency !== '') ?
<DisplayResult convert={this.state.pokeList} result={this.state.currency} /> : <div></div>
}
);
}
}

How to modify state of a component from a routed component?

I'm building a react native application where users can create event and invite people.
But I'm having some problems modifying the state of the component from a routed component.
There is a createEvent screen where user tries to create event...on clicking on invite people button a new screen is rendered let's name it as invitePeopleScreen.
If i'm not wrong...I think I need to use redux.
The createEvent screen:
import React from 'react';
import {Button, Container, Form, Input, Item, Text, Textarea} from "native-base";
export default class createEventScreen extends React.Component{
constructor(props) {
super(props);
this.state = {
title: '',
description: '',
createdBy: '',
invites: []
};
}
createEvent () {}
render() {
return (
<Container>
<Text>Create An Event </Text>
<Form>
<Item rounded>
<Input keyboardType="default"
placeholder="Enter the event title"
onChangeText={ title => this.setState({ title: title }) } />
</Item>
<Item rounded>
<Textarea keyboardType="default"
placeholder="Enter the event description"
onChangeText={ description => this.setState({ description: description }) } />
</Item>
</Form>
<Button rounded onPress={ () => { this.props.navigation.navigate('invitePeople') }}>
<Text>Invite People</Text>
</Button>
<Button rounded onPress={this.createEvent}>
<Text>Create Event</Text>
</Button>
</Container>
)
}
}
Here is the invitePeople Screen:
import React from 'react';
import { Container } from "native-base";
export default class inviteUsersScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
username: ''
}
}
addInvite = (username) => {
// push the username into the invites array of createEventScreen
}
render() {
return (
<Container>
<Text>Invite People </Text>
<Form>
<Item rounded>
<Input keyboardType="default"
placeholder="Enter the username"
onChangeText={ (username) => {this.setState({ username: username)})} />
</Item>
</Form>
<Button rounded onPress={ () => { this.addInvite(this.state.username) }}>
<Text>Submit</Text>
</Button>
);
}
}
I am exactly not sure what code will go into addInvite function.
You have three options to achieve this, option 1: pass a function as a prop to the next screen, option 2: use async storage, option 3: Redux.
Option: 1.
in your CreateEventScreen pass a function to the second screen as prop.
addInvite = username => {
this.setState({
invites: [...this.state.invites, username],
});
};
<Button
rounded
onPress={() => {
this.props.navigation.navigate('InvitePeople', {
addInvite: this.addInvite,
});
}}>
<Text>Invite People</Text>
</Button>
In your InviteUsersScreen, get the function and pass username.
addInvite = username => {
// push the username into the invites array of createEventScreen
const addInvite = this.props.navigation.getParam('addInvite', () => {
alert('No function was passed');
});
addInvite(username);
};
Option 2: AsyncStorage
import {AsyncStorage} from 'react-native';
// create function for saving items to storage
export const SaveItem = async (key, value) => {
try {
await AsyncStorage.setItem(key, value);
console.log('saved')
} catch(e) {
console.log(e)
}
};
// create function for saving items to storage
export const ReadItem = async key => {
try {
var result = await AsyncStorage.getItem(key);
return result;
} catch (e) {
return e;
}
};
Then you can add items to the storage in your InviteUsersScreen.
addInviteWithStorge = username => {
const usernames = [...this.state.storageUsernames, username];
this.setState({
storageUsernames: usernames,
username: '',
});
const invites = JSON.stringify(usernames);
SaveItem('invites', invites)
.then(res => {
console.log('saved', res);
})
.catch(e => console.warn(e));
};
Retrieve items in your CreateEventScreen.
ReadItem('invites')
.then(res => {
if (res) {
const invites = JSON.parse(res);
this.setState({
invites: invites,
});
}
})
.catch(e => console.warn(e));
Demo
Something like that perhaps:
addInvite = (e, username) => {
e.preventDefault();
this.setState({
list: [...this.state.list, username]
})
}
Assuming you add to your state a list prop

React Native Update picker value except a component with ref

I'm new to React/Native, I've being breaking my head with this issue. I'm using a library called react-native-image-picker-form which is so good, it let's use your phone camera or you can choose a picture from your album.
Let me show you the code
import React, {Component} from 'react'
import {Text, View, TouchableOpacity, Picker} from 'react-native'
import t from 'tcomb-form-native'
import ImageFactory from 'react-native-image-picker-form';
const Form = t.form.Form;
const DocumentFormStruct = t.struct({
image: t.String
});
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
form: null,
value: null,
language: 'PERMISO ESPECIAL',
options: {
fields: {
image: {
config: {
title: 'Select image',
options: ['Open camera', 'Select from gallery', 'Cancel'],
// Used on Android to style BottomSheet
style: {
titleFontFamily: 'Roboto'
}
},
error: 'No image provided',
factory: ImageFactory
}
}
},
changed: false
};
}
onPress = () =>{
if (this.form == undefined){
console.log("UNDEFINED");
}else {
console.log(this.form.getValue());
}
}
componentDidMount() {
// var value = this.form.getValue();
// console.log(value);
}
shouldComponentUpdate(nextProps, nextState) {
return false;
}
render() {
return (
<View>
<Form
ref={(ref: any) => {
this.form = ref
}}
type={DocumentFormStruct}
value={this.state.value}
options={this.state.options}
/>
<Picker
selectedValue={this.state.language}
onValueChange={(itemValue, itemIndex) => this.setState({language: itemValue})}>
<Picker.Item label="PERMISO ESPECIAL"
value="PERMISO ESPECIAL"/>
<Picker.Item label="PERMISO HOJA CLARO" value="HOJA CLARO"/>
<Picker.Item label="COORDINAR CON PROPIETARIO"
value="COORDINA CON PROPIETARIO"/>
</Picker>
{this.state.language == 'HOJA CLARO'
?
<View>
<Text>Hello World!</Text>
</View>
:
null
}
<TouchableOpacity
onPress={this.onPress}
>
<Text> Touch Here </Text>
</TouchableOpacity>
</View>
)
}
}
I know that with shouldcomponentupdate, you can prevent unnecessary re renders. The thing is that this library has to use this ref property that when on state change of any other value the selected picture gets a null. Is there a way or workaround for this. If I use the shouldcomponentupdate then the pickers value wont change but at least I'm getting the current value of the image-picker.
An workaround would be to take advantage of onChange props of Form. (See docs)
<Form
ref={(ref: any) => {
this.form = ref
}}
type={DocumentFormStruct}
value={this.state.value}
options={this.state.options}
onChange={this.onChange}
/>
In your onChange method, save the value in local state.
onChange = (value) => {
this.setState({value});
}
Now, even if it reinitialized as you said, it will get the value from local state and will not become null.

Resources