How to mock a state value in jest - react-hooks

A react native 0.70 component displays items in FlatList. The array items is a state and is assigned value in hook useEffect. I would like to jest (0.29) it to see if a item's name is shown up on screen so a mock of state items is needed. Here is the render item code for FlatList:
return (
<TouchableOpacity testID={'listselltrade.detail'} onPress={() => {item.sell ? navigation.navigate("Deal Detail", {item:item}) : navigation.navigate("Trade Detail", item={item})} } >. <<== here is the testID
<View style={{flex:1, flexDirection:"row", alignItems:"center", width:wp("100%"), height:wp("17%")}}>
<View style={{width:wp("17%"), padding:wp("0.1%")}}>
{sourceUri && sourceAltUri ? <CachedImage source={{uri:sourceUri}} sourceAlt={{uri:sourceAltUri}} style={styles.itemImage} /> : null}
</View>
<View style={{width:wp("25%")}}>
<Text style={{fontSize:wp("4.2%"), flexWrap:"wrap"}}>{item.artwork.item_name}</Text>
</View>
<View style={{width:wp("8%")}}>
<Text style={{fontSize:wp("4.2%"), flexWrap:"wrap"}}>{helper.returnStatus(item.status)}</Text>
</View>
<View style={{width:wp("7%")}}>
<Text style={{fontSize:wp("4.2%"), flexWrap:"wrap"}}>{item.price+item.shipping_cost}</Text>
</View>
<View style={{width:wp("3%"),flexDirection:"row"}}>
{item.sell ? <Icon name='logo-usd' size={_size} /> : <Icon name='sync' size={_size} />}
</View>
<View style={{width:wp("8%")}}>
<Text style={{fontSize:wp("4.2%"), flexWrap:"wrap"}}>
{item.artwork.category.category_name}
</Text>
</View>
{item.sell && _me.id !== data.uploader_id && <View style={{width:wp("15%")}}>
<TouchableOpacity onPress={() => {navigation.push('ListBuyExg', {uploader_id:item.artwork.uploader_id, title:_sellerposter_name})} }>
<Text style={{fontSize:wp("4.2%"), flexWrap:"wrap"}}>
{_sellerposter_name}
</Text>
</TouchableOpacity>
</View> }
{!item.sell && _me.id !== data.uploader_id && <View style={{width:wp("15%")}}>
<TouchableOpacity onPress={() => {navigation.push('ListBuyExg', {uploader_id:item.artwork.uploader_id, title:_sellerposter_name})} } >
<Text style={{fontSize:wp("4.2%"), flexWrap:"wrap"}}>
{_sellerposter_name}
</Text>
</TouchableOpacity>
</View> }
<View style={{width:wp("20%")}}>
<Text style={{fontSize:wp("3%"), flexWrap:"wrap"}}>{item.active_date.substring(0,10)}</Text>
</View>
</View>
</TouchableOpacity>
);
Here is jest code:
test ('has screen detail', async () => {
const route = {params: {title:"my title", uploader_id:8}}; //8 is a test user. all is listed
const navigation = {navigate:jest.fn()};
const propsVal = {device_id:"a device id"};
const authVal = {result:"fdkfjdsl;fjdsafkl", myself:{id:1,name:"me"}};
const stateVal = {name:"me", alias:"akkus", aka:"aka"};
const items = [{artwork:{item_name:"work name"}}]; //<<==mock state items
const component = (
<NavigationContainer>
<propsContext.Provider value={propsVal}>
<authContext.Provider value={authVal}>
<stateContext.Provider value={stateVal}>
<ListSellTrade route={route} navigation={navigation}/>
</stateContext.Provider>
</authContext.Provider>
</propsContext.Provider>
</NavigationContainer>);
const wrapper = render(component);
expect(screen.getByText('my title')).toBeTruthy(); //<<==pass
expect(screen.getByText('work name')).toBeTruthy(); //<<==failed
expect(screen.getByTestId('listselltrade.detail')).toBeTruthy(); //<<==failed
})
But there is no item was rendered on screen and 2 expects were failed. What is the right way to mock state items so it can be fed into the jest for further test?

You should start by using screen.debug() to output the host element tree your component is rendering. This should help you understand what actually gets rendered in your RNTL tests.
You might also want to consult official RNTL + React Navigation example how to properly test screen components when using React Navigation.

Related

Having issue to create navigation between screen

I have a splash screen which goes to the Intro page after a few seconds and from the Intro page, I have a link for sign in and sign up. I tried to set up navigation from the Intro page to/from Login but encounter an error which says:
undefined is not an object (evaluating '_this.props.navigation.navigate').
I have tried to check the documentation on react-navigation but not clear with the explanation as they muddled up all screens in App.js.
App.js
import React, {Component} from 'react';
import { StyleSheet, StatusBar, Text, View} from 'react-native';
import { createStackNavigator, createAppContainer } from 'react-navigation';
import Intro from './screen/Intro';
import Login from './components/Login';
const RootStack = createStackNavigator(
{
IntroScreen: Intro,
LoginScreen: Login,
},
{
initialRouteName: 'IntroScreen',
}
);
const AppNavigator = createAppContainer(RootStack);
export default class App extends Component {
render() {
return (
<View style={styles.container}>
<StatusBar
barStyle="light-content"
backgroundColor="#fefefe"
/>
<Intro/>
</View>
);
}
}
Intro.js
The page follows the splash screen display and from the page I can select login or sign up.
import React, { Component } from 'react';
import {
Text,
Image,
View,
ImageBackground,
StyleSheet,
TouchableOpacity
}
from 'react-native';
import SplashScreen from 'react-native-splash-screen';
export default class Intro extends Component{
componentDidMount(){
SplashScreen.hide();
}
render(){
return(
<ImageBackground source={require('../images/signup-background.jpg')} style={{width: '100%', height: '100%'}}>
<View style={styles.container}>
<Image
source ={ require('../images/logo.png')}
/>
<Text style={styles.simpleText}>WELCOME</Text>
<Text style={styles.literal}>Thank you for your interest in the APG app. </Text>
<Text style={styles.literal}>How can we help you?</Text>
<TouchableOpacity
style= {styles.signinCont}
onPress={() => this.props.navigation.navigate('IntroScreen')}
>
<Text style= {styles.signinText}>
I am already an APG Patient
</Text>
</TouchableOpacity>
<TouchableOpacity style= {styles.signupCont}>
<Text style= {styles.signupText}>
I am not an APG Patient
</Text>
</TouchableOpacity>
</View>
</ImageBackground>
);
}
}
Login.js
On the Login, I have a button to link to Signup and forgot password in case the user doesn't have an account
import React, { Component } from 'react';
import {
Text,
TouchableOpacity,
Label,
View,
TextInput,
StyleSheet,
}
from 'react-native';
export default class Login extends Component {
render(){
return(
<View style={styles.container}>
<View>
<Text>Login</Text>
</View>
<Label>Email</Label>
<TextInput
style={Styles.textbox}
placeholder="Email"
value= {this.state.email}
/>
<Label>Password</Label>
<TextInput
style={Styles.textbox}
placeholder="Password"
value ={this.state.password}
secureTextEntry={true}
/>
<View>
<Text>Forgot password</Text>
</View>
<TouchableOpacity style={styles.signin}>
<Text style ={styles.signinText}>Sign In</Text>
</TouchableOpacity>
<View>
<Text>Not an APG Member? </Text>
<TouchableOpacity style={styles.signup}
onPress ={() => this.props.navigation.navigate('SignUp') }
>
<Text>Sign Up</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
I want to be able to choose either the login or signup as the case may be from the Intro.js and on login.js to be able to click on signup for registration and from signup to be able to click login.
Solution
onPress={this.props.navigation.navigate('SignUp')} do not invoke this.props.navigation.navigate function.
Change your onPress property as below.
Use es6 arrow function for making anonymous function.
<TouchableOpacity
style={styles.signup}
onPress={() => this.props.navigation.navigate('SignUp')}
>
or you can reference your function in onPress. It makes performance better when render again.
navigate = () => {
this.props.navigation.navigate('SignUp')
}
return (
...
<TouchableOpacity
style={styles.signup}
onPress={this.navigate}
>
)
Why
props only references value even if it is function. So make props reference anonymous function or funtion in class you predefined.
And do not forget bind(this) when you use not arrow function.
navigate() {
this.props.navigation.navigate('SignUp')
}
...
onPress={this.navigate.bind(this)}
...

React Native: optimizing list of items

I have a list of items, each having a body and a source. Currently it renders like this:
const ListItem = (props) => {
const {body, source} = props.context;
return (
<View style={styles.item}>>
<View style={{backgroundColor: 'lightblue'}}>
<Text style={styles.body}>{body}</Text>
</View>
<View style={{backgroundColor: 'lightyellow'}}>
<Text style={styles.source}>{source}</Text>
</View>
</View>
);
}
That's a lot of nesting and containers. Could it be done more optimally?
I am going to post my comment as an answer.
Previous comment:
I guess it depends on your design, AFAIK this is fine within React Native assuming you're using an optimized way of rendering your list (e.g. using a FlatList or similar)
As per your following comment, I don't think it's monstrous at all.
Here's an alternative. However, for readability I would much prefer the snippet you posted in your question.
const ListItem = props => {
const items = [
{ key: 'body', backgroundColor: 'lightblue' },
{ key: 'source', backgroundColor: 'lightyellow' }
];
return (
<View style={styles.item}>
{
items.map(({ key, backgroundColor }) =>
<View style={{ backgroundColor }}>
<Text style={styles[key]}>
{ props[key] }
</Text>
</View>
)
}
</View>
)
}
The Text component does take backgroundColor so you can let go of the two Views:
<View style={styles.item}>
<Text style={[styles.body, {backgroundColor: '...'}]}>{body}</Text>
<Text style={[styles.source, {backgroundColor: '...'}]}>{source}</Text>
</View>
Additionally, I don't know what the styles.item consists of, but if you want to go all the way, you might replace the other container View with React.Fragment.

React-Native refs undefined on text input

we are currently running React-Native 0.33
We are trying to use the refs to go from 1 text input to another when they hit the next button. Classic username to password.
Anyone have any idea? Below is the code we are using. From other posts I've found on stack this is what they've done; however, it's telling us that this.refs is undefined.
UPDATE
So I've narrowed the problem down to
render() {
return (
<Navigator
renderScene={this.renderScene.bind(this)}
navigator={this.props.navigator}
navigationBar={
<Navigator.NavigationBar style={{backgroundColor: 'transparent'}}
routeMapper={NavigationBarRouteMapper} />
} />
);
}
If I just render the code below in the renderScene function inside of the original render it works, however with the navigator it won't work. Does anyone know why? Or how to have the navigator show as well as render the code in renderScene to appear in the original render?
class LoginIOS extends Component{
constructor(props) {
super(props);
this.state={
username: '',
password: '',
myKey: '',
};
}
render() {
return (
<Navigator
renderScene={this.renderScene.bind(this)}
navigator={this.props.navigator}
navigationBar={
<Navigator.NavigationBar style={{backgroundColor: 'transparent'}}
routeMapper={NavigationBarRouteMapper} />
} />
);
}
renderScene() {
return (
<View style={styles.credentialContainer}>
<View style={styles.inputContainer}>
<Icon style={styles.inputPassword} name="person" size={28} color="#FFCD00" />
<View style={{flexDirection: 'row', flex: 1, marginLeft: 2, marginRight: 2, borderBottomColor: '#e0e0e0', borderBottomWidth: 2}}>
<TextInput
ref = "FirstInput"
style={styles.input}
placeholder="Username"
autoCorrect={false}
autoCapitalize="none"
returnKeyType="next"
placeholderTextColor="#e0e0e0"
onChangeText={(text) => this.setState({username: text})}
value={this.state.username}
onSubmitEditing={(event) => {
this.refs.SecondInput.focus();
}}
>
</TextInput>
</View>
</View>
<View style={styles.inputContainer}>
<Icon style={styles.inputPassword} name="lock" size={28} color="#FFCD00" />
<View style={{flexDirection: 'row', flex: 1, marginLeft: 2, marginRight: 2, borderBottomColor: '#e0e0e0', borderBottomWidth: 2}}>
<TextInput
ref = "SecondInput"
style={styles.input}
placeholder="Password"
autoCorrect={false}
secureTextEntry={true}
placeholderTextColor="#e0e0e0"
onChangeText={(text) => this.setState({password: text})}
value={this.state.password}
returnKeyType="done"
onSubmitEditing={(event)=> {
this.login();
}}
focus={this.state.focusPassword}
>
</TextInput>
</View>
</View>
</View>
);
}
Try setting the reference using a function. Like this:
<TextInput ref={(ref) => { this.FirstInput = ref; }} />
Then you can access to the reference with this.FirstInput instead of this.refs.FirstInput
For a functional component using the useRef hook. You can use achieve this easily, with...
import React, { useRef } from 'react';
import { TextInput, } from 'react-native';
function MyTextInput(){
const textInputRef = useRef<TextInput>(null);;
return (
<TextInput ref={textInputRef} />
)
}
Try changing the Navigator's renderScene callback to the following (based on Navigator documentation) cause you will need the navigator object later.
renderScene={(route, navigator) => this.renderScene(route, navigator)}
Then, use 'navigator' instead of 'this' to get the refs.
navigator.refs.SecondInput.focus()

Avoid react-native navigator scene overlapping

I have a ScrollView with a list of items. When I click on one item I navigate to a new scene (slide in from the right). However, during the transition period the two scenes overlap; they are fine after the animation/transition is done.
Here's an example:
and heres the related code:
render() {
return (
<Navigator
initialRoute={routes.frontpage()}
renderScene={(route, navigator) => {
if(route.index === 1) {
return (
<TouchableHighlight
onPress={() => navigator.pop()}>
<View style={{flex: 1}}>
<Text style={styles.header}> other </Text>
<Text> {route.post.title} </Text>
</View>
</TouchableHighlight>
)
}
return (
<View style={{flex: 1}}>
<Text style={styles.header}> Frontpage </Text>
<ScrollView>
{
this.props.posts.map(post => (
<TouchableHighlight
key={post.created}
onPress={() => {
if (route.index === 0) {
return navigator.push(routes.post(post))
}
navigator.pop();
}}>
<View>
<PostSummary
title={post.title}
upvotes={post.ups}
author={post.author}
subreddit={post.subreddit}
created={post.created_utc}
/>
</View>
</TouchableHighlight>
))
}
</ScrollView>
</View>
)
}}
/>
)
}
Any ideas on how to solve this?
Navigator uses animations during transitions between two scenes. In your case it uses fade during the transition. How about using a different animation. Navigator has Scene Transitions feature that you may try to change the animation.

Displaying Parse.com data via SDK in React Native

I've just started with React Native and I'm sure I'm missing something small. However, for the life of me I can't figure out how to display this.data.mission.XX anywhere. I can console log the data and see it in Xcode, but that's it.
var React = require('react-native');
var Parse = require('parse/react-native');
var ParseReact = require('parse-react/react-native');
var {
Component,
StyleSheet,
Image,
Text,
View,
StatusBarIOS,
} = React;
var CountdownClock = require('./CountdownClock');
var CountdownScreen = React.createClass({
mixins: [ParseReact.Mixin],
getInitialState: function() {
return{
mission: null,
};
},
componentDidMount: function() {
StatusBarIOS.setStyle('light-content');
},
observe: function() {
return {
mission: (new Parse.Query('mission')).equalTo('featuredMission', true)
};
},
render: function() {
console.log(this.data.mission);
return (
<Image source={{uri: '#'}} style={styles.backgroundImage}>
<View style={styles.mainContainer}>
<View style={styles.clock}>
<CountdownClock />
</View>
<View style={styles.featured}>
<Text style={styles.header}> Name: {this.data.mission.missionName} </Text>
<Text style={styles.header}> Estimated Start: {this.data.mission.windowStart}</Text>
<Text style={styles.header}> Site: {this.data.mission.missionSite}</Text>
<Text style={styles.header}> Vehicle: {this.data.mission.missionVehicle}</Text>
<Text style={styles.description}>{this.data.mission.missionDescription}</Text>
</View>
</View>
</Image>
);
}
});
Taking into account that this.data is logging out, you probably need to do something like this:
Set the data property in the state:
getInitialState(){
data: {}
}
Attach this.data to the state:
componentWillUpdate() {
this.setState({
data: this.data
})
}
Use this.state.data vs this.data
<View style={styles.featured}>
<Text style={styles.header}> Name: {this.state.data.mission.missionName} </Text>
<Text style={styles.header}> Estimated Start: {this.state.data.mission.windowStart}</Text>
<Text style={styles.header}> Site: {this.state.data.mission.missionSite}</Text>
<Text style={styles.header}> Vehicle: {this.state.data.mission.missionVehicle}</Text>
<Text style={styles.description}>{this.state.data.mission.missionDescription}</Text>
</View>

Resources