Testing a scroll on an Animated.ScrollView - reactjs

I am currently writing tests for a component I implemented using the react-test-renderer and an issue I've come across is trying to test the callback function that is called when I scroll on my Animated.ScrollView.
Here's a succint version of what I currently have with generic naming:
Component
export class MyComponent extends React.Component<MyComponentProps, MyComponentState> {
public componentWillMount() {
this.index = 0;
this.routeSelectionAnimation = new Animated.Value(0);
}
public componentDidMount() {
this.routeSelectionAnimation.addListener(({ value }) => {
// some work is done before the following line
// the function I want to test is being called
this.props.onIndexUpdate(index);
});
}
public render() {
const scrollAnimation = Animated.event(
[{
nativeEvent: {
contentOffset: {
x: this.routeSelectionAnimation,
},
},
},
],
{ useNativeDriver: true },
);
// * more processing *
return (
<Animated.ScrollView
horizontal={true}
scrollEventThrottle={1}
showsHorizontalScrollIndicator={false}
snapToInterval={...}
pagingEnabled={true}
onScroll={scrollAnimation} // what matters is this
style={styles.scrollView}
>
{other components}
</Animated.ScrollView>
);
}
This works correctly on the device, the function passed by props onIndexUpdate(int) is called when the ScrollView scrolls.
I would like to test this behaviour, however I can't seem to simulate the scroll on the ScrollView. I was thinking of doing something like:
const props = {
onIndexUpdate: jest.fn(),
};
const getTestInstance = () => {
return TestRenderer.create(
<MyComponent onIndexUpdate={props.onIndexUpdate} />
).root;
};
describe("Animated.ScrollView", () => {
it("calls function", () => {
const instance = getTestInstance();
const scrollView = instance.findByType(Animated.ScrollView);
scrollView.instance.scrollToEnd({animated: false});
expect(props.onIndexUpdate).toHaveBeenCalledTimes(3);
});
});
But it does not work. What I get as a result is TypeError: scrollView.instance.scrollToEnd is not a function which clearly shows I'm looking at this wrong...
I'd greatly appreciate input on how to fix this or approach the testing in a more correct way.

You missed out a print statement, thats why it wont work. You should have put:
print scrollView.instance.scrollToEnd({animated: false});

Related

setState not returned from render when using Axios

I'm using axios to get data from an endpoint. I'm trying to store this data inside the state of my React component, but I keep getting this error:
Error: Results(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
I've struggled with many approaches: arrow functions etc., but without luck.
export default class Map extends Component {
constructor() {
super();
this.state = {
fillColor: {},
selectedCounty: "",
dbResponse: null,
};
}
getCounty(e) {
axios.get("/getWeatherData?county=" + e.target.id)
.then((response) => {
this.setState(prevState => {
let fillColor = {...prevState.fillColor};
fillColor[prevState.selectedCounty] = '#81AC8B';
fillColor[e.target.id] = '#425957';
const selectedCounty = e.target.id;
const dbResponse = response.data;
return { dbResponse, selectedCounty, fillColor };
})
}).catch((error) => {
console.log('Could not connect to the backend');
console.log(error)
});
}
render() {
return (
<div id="map">
<svg>big svg file</svg>
{this.state.selectedCounty ? <Results/> : null}
</div>
)
}
I need to set the state using prevState in order to update the fillColor dictionary.
Should this be expected? Is there a workaround?

React testing error "Cannot read get property of undefined"

I am trying to test the function searchTrigger in my CardMain component.
export default class CardMain extends Component {
state = {
Pools : [],
loading: false,
}
componentDidMount(){
axios.get('/pools')
.then (res => {
//console.log(res.data.data);
this.setState({
Pools: res.data.data,
loading: true,
message: "Loading..."
},()=>{
if (res && isMounted){
this.setState({
loading: false
});
}
})
}
)
.catch(err=>{
console.log(err.message);
})
}
// the function is for search method
// upon search, this function is called and the state of the pools is changed
searchTrigger = (search) => {
Search = search.toLowerCase();
SearchList = this.state.Pools.filter((e)=> {
if (e.name.toLowerCase().includes(Search)){
this.setState({
loading: false
})
return e
}
})
if (SearchList.length === 0){
this.setState({
loading: true,
message: "No pools found"
})
}
}
render() {
return (
<div>
<Searchbar trigger={this.searchTrigger}/>
{ this.state.loading ?
<div className="d-flex justify-content-center">{this.state.message}</div>
:<div>
{Search === "" ? <Card1 pools={this.state.Pools}/> : <Card1 pools={SearchList}/> }
</div>
}
</div>
)
}
}
The function searchTrigger is passed to another class component called Searchbar which basically displays the search bar. Upon searching something, the function searchTrigger is called and the searched value is passed as an argument to this function.
So, I am trying to test this function and I am new to react and testing. I found some examples online and tried a simple testing whether the function is called or not. My CardMain.test.js code looks like this:
describe("callback function test", ()=> {
it("runs it", () => {
//const spy = jest.spyOn(CardMain.prototype,"searchTrigger");
const cardmain = shallow(<CardMain/>)
const spy = jest.spyOn(cardmain.instance(), "searchTrigger");
expect(spy).toHaveBeenCalled()
})
});
I get the TypeError: Cannot read property 'get' of undefined pointing to the axios.get("/pools") in the CardMain component inside componentDidMount. axios is being imported from another component api.js which creates the instance of axios using axios.create. I have no idea what the problem is. I am very new to react. I have absolutely no idea, how do I test these components? Could somebody help me?
Update:
So, i tried mocking axios call:
let Wrapper;
beforeEach(() => {
Wrapper = shallow( <CardMain/>);
});
describe("Card Main", ()=> {
it("returns data when called", done => {
let mock = new MockAdapter(axios);
const data = [{
name: "Test",
response: true
}];
mock.onGet('My_URL')
.reply(200,data);
const instance = Wrapper.instance();
instance.componentDidMount().then(response => {
expect(response).toEqual(data);
done();
});
});
});
It says "cannot read property .then of undefined"

React app won't render info from props even though I can console log it

So ideally my parent component is mapping through a database and rendering them based on the user's choice. Right now right now the information is being passed correctly and the app is rendering what I need it too (the card component) in the correct amount however it is full of dummy info. (Someone clicks beer, there are three beer types in the database, the app renders three card components full of dummy info).
Here is the parent component:
class Render extends React.Component {
constructor(props) {
super(props);
console.log("Here are your props", props);
}
componentDidMount() {
let options = {
method: 'GET',
url: 'http://localhost:8080/drinks',
};
let drinks = [];
console.log("this is",this);
axios.request(options)
.then( (response) => {
console.log(response);
this.setState({ drinks: response.data })
})
.catch(function (error) {
console.log(error);
});
}
render() {
console.log("this.state is",[this.state])
let stateArray = [this.state]
if (stateArray[0] == null) {
console.log("returning with nothing")
return <div></div>
}
let firstElement = stateArray[0];
let drinks = firstElement.drinks;
let drinkChoice = this.props.reduxState.drinkChoice
console.log("drinkchoice is here" , drinkChoice)
// const props = this.props
console.log("just drinks", drinks)
let drinkInfo = {
type: this.state.drinks.type,
name: this.state.drinks.name,
manufacturer: this.state.drinks.manufacturer,
rating: this.state.drinks.rating,
date: this.state.drinks.date,
description: this.state.drinks.description,
favorite: this.state.drinks.favorite
}
let cardComponents = drinks.map((drink) =>{
if (drink.type === drinkChoice) {
return (<InfoCard props={this.state.drinks} />)
} else {
return <div>Nothing to Report</div>
}})
return (
<div>
<div>{cardComponents}</div>
</div>
)
}
}
export default Render
Now I need it to render the actual database information for each entry. In the child/cardcomponent- I can console.log the props and it will correctly show the right information. It's getting through. But anytime I try to be more specific ( props.name ) it turns to undefined.
I have been at this for days and i'm so confused. The information is right there! I just need to grab it!
Here is the code for the child/card component:
const useStyles = makeStyles(theme => ({
root: {
maxWidth: 345,
},
media: {
height: 0,
paddingTop: '56.25%', // 16:9
},
expand: {
transform: 'rotate(0deg)',
marginLeft: 'auto',
transition: theme.transitions.create('transform', {
duration: theme.transitions.duration.shortest,
}),
},
expandOpen: {
transform: 'rotate(180deg)',
},
avatar: {
backgroundColor: red[500],
},
}));
export default function InfoCard(props) {
const classes = useStyles();
if( props.length <= 0 ) {
return (<div></div>);
} else {
console.log("props are here")
console.log( props )
console.log("props dot name")
console.log ( props.name )
}
props.each(function (drink) {
console.log(drink.name);
});
return (
<Card className={classes.root}>
title = { props.name }
</Card>
);
}
Where have I gone wrong? I feel like i've tried every possible iteration of console.log and drink.name. I'm at the end of my rope.
Thanks for any and all guidance.
sccreengrab of console log
What you're seeing in your log of props is an array of objects. Those objects have names, but the array itself does not, so when you console.log(props.name) it doesn't work and you see undefined. If you try console.log(props[0].name), for instance, you should see a name.
But what's strange here is that props should NOT be an array: it should be an object (whose keys map to the JSX element's attributes). For instance:
<InfoCard name="Bob"/>
would create a props object of:
{name: 'Bob'}
But when you log your props, you see an array, and that means you've somehow/somewhere replaced the actual props object with an array. Without seeing the code where you actually create <Infocard>, I can't speak to the details.
P.S. It might be possible to do this if you did something like:
`<MyComponent {...[{ name: 'Bob']} />`
... but honestly I'm not sure if that even works, and it seems like a bad idea even if it does.

Unable to pass params successfully to another .js file/screen

I'm trying to pass params from one screen to another screen using react-navigation, the problem I'm encountering is that when I console.log the param itself, the console returns 'undefined'. I can't seem to pinpoint what I'm doing wrong exactly. Any help or guidance would be much appreciated.
I tried the following, with no success:
-this.props.navigation.getParam('biometryStatus')
-this.props.navigation.state.params('biometryStatus')
This is my AuthenticationEnroll screen where my param is being initialised as the state of the component:
export default class AuthenticationEnroll extends Component {
constructor() {
super()
this.state = {
biometryType: null
};
}
async _clickHandler() {
if (TouchID.isSupported()){
console.log('TouchID is supported');
return TouchID.authenticate()
.then(success => {
AlertIOS.alert('Authenticated Successfuly');
this.setState({biometryType: true })
this.props.navigation.navigate('OnboardingLast', {
pin: this.props.pin,
biometryStatus: this.state.biometryType,
});
})
.catch(error => {
console.log(error)
AlertIOS.alert(error.message);
});
} else {
this.setState({biometryType: false });
console.log('TouchID is not supported');
// AlertIOS.alert('TouchID is not supported in this device');
}
}
_navigateOnboardingLast() {
this.props.navigation.navigate('OnboardingLast', {pin: this.props.pin})
}
render () {
return (
<View style={{flex: 1}}>
<Slide
icon='fingerprint'
headline='Secure authentication'
subhead='To make sure you are the one using this app we use authentication using your fingerprints.'
buttonIcon='arrow-right'
buttonText='ENROLL'
buttonAction={() => this._clickHandler()}
linkText={'Skip for now.'}
linkAction={() => this._navigateOnboardingLast()}
slideMaxCount={4}
slideCount={2}
subWidth={{width: 220}}
/>
</View>
)
}
}
And this is my OnboardingLast Screen where my param is being passed down and printed through console.log:
class OnboardingLast extends Component {
async _createTokenAndGo () {
let apiClient = await this._createToken(this.props.pin)
this.props.setClient(apiClient)
AsyncStorage.setItem('openInApp', 'true')
const { navigation } = this.props;
const biometryStatus = navigation.getParam('biometryStatus', this.props.biometryStatus);
console.log(biometryStatus);
resetRouteTo(this.props.navigation, 'Home')
}
/**
* Gets a new token from the server and saves it locally
*/
async _createToken (pin) {
const tempApi = new ApiClient()
let token = await tempApi.createToken(pin)
console.log('saving token: ' + token)
AsyncStorage.setItem('apiToken', token)
return new ApiClient(token, this.props.navigation)
}
render () {
return (
<View style={{flex: 1}}>
<Slide
icon='checkbox-marked-circle-outline'
headline={'You\'re all set up!'}
subhead='Feel free to start using MyUros.'
buttonIcon='arrow-right'
buttonText='BEGIN'
buttonAction={() => this._createTokenAndGo()}
slideMaxCount={4}
slideCount={3}
/>
</View>
)
}
}
Expected Result is that console.log(biometryStatus); returns 'true' or 'false', however it returns 'undefined'.
Since setState is asynchron, you send null (declared in your constructor) to your next page. By doing so, you will send true:
this.setState({ biometryType: true })
this.props.navigation.navigate('OnboardingLast', {
pin: this.props.pin,
biometryStatus: true,
});
You could also do this, since setState can take a callback as param:
this.setState({ biometryType: true }, () => {
this.props.navigation.navigate('OnboardingLast', {
pin: this.props.pin,
biometryStatus: true,
});
})
In your second page this.props.biometryStatus is undefined.
The second argument of getParam is the default value. You should change it like that
const biometryStatus = navigation.getParam('biometryStatus', false);

ComponentDidMount() not working

I am making a game using matter.js in reactjs.
I am suffering a problem where I cannot return anything in the render().
Probably this is because matter.js has its own renderer.
So I did not retun anytihng into the render section.
If i don't return anything in the render section componentDidMount do not run.
I am Unable to run componentDidMount() and i cannot call any function even though i bind it.
I am calling a start_game() function to start game but any other function that I call within start_game() an error comes which says that no such function exist even though I have declared it within the class.
import React, { Component } from 'react';
import Matter from 'matter-js';
const World = Matter.World;
const Engine = Matter.Engine;
const Renderer = Matter.Render;
const Bodies = Matter.Bodies;
const Events = Matter.Events;
const Mouse = Matter.Mouse;
const MouseConstraint = Matter.MouseConstraint;
const Body = Matter.Body;
class Walley extends Component
{
constructor(props)
{
super(props);
this.world = World;
this.bodies = Bodies;
this.engine = Engine.create(
{
options:
{
gravity: { x: 0, y: 0,}
}
});
this.renderer = Renderer.create(
{ element: document.getElementById('root'),
engine: this.engine,
options:
{
width: window.innerWidth,
height: window.innerHeight,
background: 'black',
wireframes: false,
}
});
this.state = {
play_game: false,
score:0,
};
this.play_game=this.play_game.bind(this);
}
play_game(){
this.setState
({
score: 50,
});
}
startGame()
{
console.log(this.state.play_game);
//don't know how here the value of play_game is set to true even it was false in componentWillMount()
if(this.state.play_game){
this.play_game();
//unable to access play_game
}
}
componentWillMount()
{
if (confirm("You want to start game?"))
{
this.setState({play_game: true});
}
console.log(this.state.play_game);
//even though play_game is set true above yet output at this console.log remains false
}
render()
{
if(this.state.play_game)
this.startGame();
}
componentDidMount()
{
//unable to access
console.log('Did Mount');
}
}
export default Walley;
render needs to be a pure function, meaning you should not call startGame from inside render. I think you want something closer to this:
// remove this
// componentWillMount() {}
componentDidMount() {
if (confirm("You want to start game?")) {
this.setState({play_game: true}, () => {
console.log(this.state.play_game);
});
startGame();
}
}
render() {
// eventually you will want to return a div where you can put your matter.js output
return null;
}

Resources