Mocking React higher-order components (HOC) with Jest - reactjs

i am trying to run a unit testing for my react-native component. My component seems complex. i tired all possible ways to get it done.
My CheckBox component wrapped with HOC withTheme
import { View, Text, Pressable, StyleSheet } from 'react-native'
import React, { useState } from 'react'
import { scale } from 'react-native-size-matters'
import MaterialIcons from 'react-native-vector-icons/dist/MaterialIcons'
import withTheme from 'Theme/withTheme'
import Label from 'Components/Label'
import testID from 'Utils/common'
function CheckBox({ theme: { colors }, label }) {
const styles = style(colors)
const [checked, setChecked] = useState(false)
const onPress = () => {
setChecked(!checked)
}
return (
<Pressable onPress={onPress} style={styles.wrapper} {...testID(`CheckBox wrapper`)}>
<View style={styles.iconWrapper} {...testID(`CheckBox wrapper check`)}>
{checked && <MaterialIcons name="done" size={scale(15)} color={colors.black} />}
</View>
<View style={{ paddingHorizontal: scale(10) }} {...testID(`CheckBox Label Wrapper`)}>
<Label text={label} style={{ fontSize: scale(14) }} {...testID(`CheckBox label ${label}`)}/>
</View>
</Pressable>
)
}
export default withTheme(CheckBox)
const style = StyleSheet.create((colors) => ({
wrapper: {
flexDirection: 'row',
alignItems: 'center'
},
iconWrapper: {
backgroundColor: colors.primary,
height: scale(25),
width: scale(25), borderRadius: scale(4),
justifyContent: "center",
alignItems: 'center'
}
}))
withTheme HOC Code
withTheme has hook useTheme
import React, { forwardRef } from 'react';
import { useTheme } from './ThemeProvider';
function withTheme(WrappedComponent, props) {
return forwardRef(function (someProps, ref) {
const { theme } = useTheme();
return (
<WrappedComponent ref={ref} {...someProps} {...props} theme={theme} />
);
});
}
export default withTheme;
What i tried
Tried mockcing HOC
Tried mockcing withTheme hook
Refered this Blog
test written
/* ---------- Packages Import ---------- */
import React from 'react';
import {render, cleanup, fireEvent} from '#testing-library/react-native';
import CheckBox from '../index';
import testID from 'Utils/common';
/* ---------- Components Import ---------- */
jest.mock('#react-native-async-storage/async-storage', () =>
require('#react-native-async-storage/async-storage/jest/async-storage-mock'),
);
jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter');
jest.mock('Theme/__mock__/withTheme');
jest.mock('Theme/__mock__/useTheme');
afterEach(cleanup);
describe('<CheckBox />', () => {
it('should match the snapshot', () => {
const rendered = render(<CheckBox />).toJSON();
expect(rendered).toMatchSnapshot();
});
});
Error i am getting
Any help would be appricated, Thank in advance

Related

Mobx not updating react native element

I'm new to react and mobx.
I'm trying to use mobx to update a simple counter and display the count number.
When I click on the button "Add" I can see in the logs that counterStore.count is increasing but counter shown in the <Text></Text> remains equal to 0.
Can you please me tell me what is wrong?
index.tsx
import { observer } from "mobx-react";
import React from "react";
import { Button, StyleSheet, Text, View } from "react-native";
import CounterStore from './stores/CounterStore';
export function App() {
const counterStore = new CounterStore(0);
return (
<View style={styles.container}>
<View style={styles.wrapper}>
<Text>{counterStore.count}</Text>
<Button
title="Add"
onPress={() => {
counterStore.addToCount();
console.log("count = ", counterStore.count);
}}
/>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
height: "100%"
},
wrapper: {
backgroundColor: "#F5FCFF",
width: "100%",
maxWidth: 425
}
});
export default observer(App);
CounterStore.ts
import { action, makeObservable, observable } from "mobx";
class CounterStore {
#observable count: number;
constructor(count: number){
this.count = count
makeObservable(this);
}
#action
addToCount(){
this.count++;
}
}
export default CounterStore;
Output & Logs
output
I tried to improve my code based on Jay Vaghasiya's answer, but still got the same behaviour.
CounterStore.ts
import { action, makeObservable, observable } from "mobx";
import React from "react";
class CounterStore {
count = 0;
constructor(){
makeObservable(this), {count: observable};
}
#action
addToCount(){
this.count++;
}
}
export const CounterStoreContext = React.createContext(new CounterStore());
index.tsx
import { observer } from "mobx-react";
import React from "react";
import { Button, StyleSheet, Text, View } from "react-native";
import { CounterStoreContext } from './stores/CounterStore';
const App = observer(() => {
const counterStore = React.useContext(CounterStoreContext);
return (
<View style={styles.container}>
<Text>{counterStore.count}</Text>
<Button
title="Add"
onPress={() => {
counterStore.addToCount();
console.log("count = ", counterStore.count);
}}
/>
</View>
);
});
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
height: "100%",
},
wrapper: {
backgroundColor: "#F5FCFF",
width: "100%",
maxWidth: 425
}
});
export default App;

ReferenceError: Can't find variable: styles

In my react-native project I experienced the above error
My App.js file:
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
//Screens
import Header from './Shared/Header'
import ProductContainer from './Screens/Products/ProductContainer'
export default function App() {
return (
<View style={styles.container}>
<Header />
<ProductContainer />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
My ProductCotainer.js file:
import React, { useState, useEffect } from 'react'
import { View, StyleSheet, ActivityIndicator, FlatList} from 'react-native'
import ProductList from './ProductList';
const data = require('../../assets/data/products.json');
const ProductContainer = () => {
const [products, setProducts ] = useState([]);
useEffect(() => {
setProducts(data);
return () => {
setProducts([])
}
}, [])
return (
<View style={styles.container}>
<Text>Product Container</Text>
<View style={styles.listContainer}>
<FlatList
data={products}
numColumns={2}
renderItem={({item}) => <ProductList
key={item.id}
item={item}/>}
keyExtractor={item => item.name}
/>
</View>
</View>
)
}
export default ProductContainer;
Please help me to solve this issue
Thanks in advance
You did not define the styles in the ProductCotainer.js file.
In order to use the styles you have 2 options:
Define it in each file that uses it. Which can be pretty hard to maintain in case of a medium/large app that has lots of common styles.
Define a style file that gets imported in each file that uses the styles. This would be a good starting point

Why do i get "Could not find "store" in the context of "Connect(App)"" error when I connect my component to store

I have a component which I'm trying to connect with the global redux store. But when I do, I got an error which says that "Could not find "store" in the context of "Connect(App)"".
Here is my code:
App.js
import React from 'react';
import { StyleSheet, Text, View, TextInput, Button } from 'react-native';
import {connect} from 'react-redux'
function App() {
const geo = navigator.geolocation
if(!geo) {
} else {
geo.getCurrentPosition((response)=>{console.log(response)}, (response) => {console.log(response)})
}
return (
<View style={{padding: 40}}>
<View style={styles.form}>
<TextInput placeholder="Example text" style={styles.cityInput}/>
<Button title="Press ME!" onPress={(e)=> {console.log(e)}}/>
</View>
</View>
);
}
const styles = StyleSheet.create({
form: {
flexDirection: 'row',
justifyContent: 'space-between',
maxWidth: '100%'
},
cityInput: {
borderColor: 'black',
borderWidth: 2,
borderRadius: 5,
padding: 5,
width: '80%'
}
});
export default connect()(App)
index.js
import React from 'react'
import {AppRegistry} from 'react-native';
import App from './App'
import { name as appName } from './app.json'
import {Provider} from 'react-redux'
import configureStore from './src/reducers/store'
const store = configureStore();
const SunApp = () =>
<Provider store={store}>
<App />
</Provider>
AppRegistry.registerComponent(appName, () => SunApp)
store.js
import positionReducer from './positionReducer';
import sunReducer from './sunReducer';
import cityReducer from './cityReducer';
import {createStore, combineReducers} from 'redux';
const reducers = combineReducers({
positionReducer,
sunReducer,
cityReducer,
})
const configureStore = () => createStore(
reducers,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
export default configureStore;
Here's the error
I can't figure out what I have to do to get rid of this error, so any feedback will be much appreciated
That error seems to come from your react native web entry point where the App is not wrapped in a <Provider (visible in the error message of the screenshot), not from the entry point you shared here.
So, indeed, the problem was in the entry point of my App. Because I was using managed workflow, the entry point of my application was registered to the App component, and my index.js file didn't make anything. So I managed to change my project structure a little bit so now my files looks like this:
App.js
import React from 'react';
import Main from './src/components/Main'
import {Provider} from 'react-redux'
import configureStore from './src/reducers/store'
const store = configureStore();
function App() {
return (
<Provider store={store}>
<Main />
</Provider>
);
}
export default App
Main.js
import React from 'react';
import { StyleSheet, Text, View, TextInput, Button } from 'react-native';
import {useEffect} from 'react'
import {connect, useSelector, useDispatch} from 'react-redux'
import axios from 'axios'
import {setPosition, setSun, setCity, setUndef, setInput} from '../actions'
function Main() {
const position = useSelector(state => state.positionReducer)
const sun = useSelector(state => state.sunReducer)
const city = useSelector(state => state.cityReducer)
const input = useSelector(state => state.inputReducer)
const dispatch = useDispatch();
function fail(error) {
alert(`Currently we can't get your location because: "${error.message}"`)
}
function success(response) {
dispatch(setPosition(response.coords));
}
function findCity(e) {
e.preventDefault()
dispatch(setCity(input))
axios.get('https://api.openweathermap.org/geo/1.0/direct', {
params: {
q: input,
limit: 1,
appid: 'a913b85241698a00b1014abe62a5ca0e'
}
})
.then(response => {
console.log(response);
if(response.data[0]) {
dispatch(setPosition(response.data[0]))
} else {
dispatch(setUndef())
}
})
}
useEffect(() => {
const geo = navigator.geolocation;
if(!geo) {
fail()
} else {
geo.getCurrentPosition(success, fail)
}
// eslint-disable-next-line
},[])
useEffect(() => {
if(position.lat && position.lat !== 'Not Found') {
axios.get('https://api.openweathermap.org/data/2.5/weather', {
params: {
lat: position.lat,
lon: position.lon,
appid: 'a913b85241698a00b1014abe62a5ca0e'
}
})
.then((response) => {
const sunriseTime = new Date(response.data.sys.sunrise * 1000)
const sunsetTime = new Date(response.data.sys.sunset * 1000)
const sun = {
sunrise: `${sunriseTime.getHours()}:${sunriseTime.getMinutes()}`,
sunset: `${sunsetTime.getHours()}:${sunsetTime.getMinutes()}`
}
dispatch(setSun(sun))
})
axios.get('https://api.openweathermap.org/geo/1.0/reverse', {
params: {
lat: position.lat,
lon: position.lon,
limit: 1,
appid: 'a913b85241698a00b1014abe62a5ca0e'
}
})
.then((response) => {
dispatch(setCity(response.data[0].name))
})
}
// eslint-disable-next-line
},[position])
return (
<View style={{padding: 40}}>
<View style={styles.form}>
<TextInput
placeholder="Example text"
style={styles.cityInput}
onChangeText={(e) => {dispatch(setInput(e))}}
onSubmitEditing={(e) => {dispatch(setInput(e.nativeEvent.text)); findCity(e)}}
/>
<Button title="Press ME!" onPress={findCity}/>
</View>
<View style={styles.info}>
<Text style={styles.infoText}>City: {city}</Text>
<Text style={styles.infoText}>Longitude :{position.lon}</Text>
<Text style={styles.infoText}>Latitude: {position.lat}</Text>
<Text style={styles.infoText}>Sunrise time: {sun.sunrise}</Text>
<Text style={styles.infoText}>Sunset time: {sun.sunset}</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
form: {
flexDirection: 'row',
justifyContent: 'space-between',
maxWidth: '100%'
},
cityInput: {
borderColor: 'black',
borderWidth: 2,
borderRadius: 5,
padding: 5,
width: '80%'
},
info: {
marginTop: 0,
marginBottom: 0,
marginLeft: 'auto',
marginRight: 'auto',
width: 'max-content'
},
infoText: {
fontSize: 20,
fontWeight: 700
}
});
export default connect()(Main)

React: Slider with custom hook not working properly

I am trying to create a custom hook with a slider ui element. My goal is to be able to access the slider value from the parent element so as to update some other ui parts.
However, it seems that the slider values do not update correctly: when the user tries to drag the slider tooltip it only moves one step. It seems like the mouse events stop being tracked after useEffect gets called.
What can I do to fix this and have a smooth dragging behaviour?
Here is my code (sandbox):
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Demo from './demo';
ReactDOM.render(<Demo />, document.querySelector('#root'));
demo.js
import React, { useEffect } from "react";
import useSlider from "./slider";
function CustomizedSlider() {
const [CustomSlider, sliderValue] = useSlider("Slider", 50);
useEffect(() => {
console.log("Slider value: " + sliderValue);
}, [sliderValue]);
return <CustomSlider />;
}
export default CustomizedSlider;
slider.js
import React, { useState } from "react";
import { withStyles, makeStyles } from "#material-ui/core/styles";
import Paper from "#material-ui/core/Paper";
import Slider from "#material-ui/core/Slider";
import Typography from "#material-ui/core/Typography";
const useStyles = makeStyles(theme => ({...
}));
const PrettoSlider = withStyles({...
})(Slider);
export default function useSlider(label, defaultState) {
const classes = useStyles();
const [state, setState] = useState(defaultState);
const CustomSlider = () => {
return (
<Paper className={classes.root}>
<div className={classes.margin} />
<Typography gutterBottom>{label}</Typography>
<PrettoSlider
valueLabelDisplay="auto"
aria-label="pretto slider"
defaultValue={50}
value={state}
onChange={(event, v) => {
setState(v);
}}
/>
</Paper>
);
};
return [CustomSlider, state];
}
Thanks a lot for your help!
The issue is that your CustomSlider is a new component type for each render due to it being a unique function each time. This causes it to unmount/remount with each render rather than just re-render which will cause all sorts of issues (as you've seen).
Rather than a custom hook, I think you really just want a custom component. Below is one way you could structure it with only minimal changes to your initial code.
demo.js
import React, { useEffect } from "react";
import CustomSlider from "./CustomSlider";
function CustomizedSlider() {
const [value, setValue] = React.useState(50);
useEffect(() => {
console.log("Slider value: " + value);
}, [value]);
return <CustomSlider label="Slider" value={value} setValue={setValue} />;
}
export default CustomizedSlider;
CustomSlider.js
import React from "react";
import { withStyles, makeStyles } from "#material-ui/core/styles";
import Paper from "#material-ui/core/Paper";
import Slider from "#material-ui/core/Slider";
import Typography from "#material-ui/core/Typography";
const useStyles = makeStyles(theme => ({
root: {
width: 300 + 24 * 2,
padding: 24
},
margin: {
height: theme.spacing(1)
}
}));
const PrettoSlider = withStyles({
root: {
color: "#a2df77",
height: 8
},
thumb: {
height: 24,
width: 24,
backgroundColor: "#fff",
border: "2px solid currentColor",
marginTop: -8,
marginLeft: -12,
"&:focus,&:hover,&$active": {
boxShadow: "inherit"
}
},
active: {},
valueLabel: {
left: "calc(-50% + 4px)"
},
track: {
height: 8,
borderRadius: 4
},
rail: {
height: 8,
borderRadius: 4
}
})(Slider);
const CustomSlider = ({ label, value, setValue }) => {
const classes = useStyles();
return (
<Paper className={classes.root}>
<div className={classes.margin} />
<Typography gutterBottom>{label}</Typography>
<PrettoSlider
valueLabelDisplay="auto"
aria-label="pretto slider"
defaultValue={50}
value={value}
onChange={(event, v) => {
setValue(v);
}}
/>
</Paper>
);
};
export default CustomSlider;

Redux thunk with rendering using App class

Redux thunk with rendering using App class - I am using react-native and redux thunk to call the dispatcher via componentDidMount of App class and receiving errors "props is not defined" and "Unable to find module for EventDispatcher".
Would request for further help on this from experts.
index.js
import React from 'react';
import {AppRegistry} from 'react-native';
import {Provider} from 'react-redux';
import configureStore from './configureStore';
import App from './App';
import {name as appName} from './app.json';
const store = configureStore();
const rnredux = () => (
<Provider store={store}>
<App />
</Provider>
)
AppRegistry.registerComponent(appName, () => rnredux);
app.js
import React from 'react';
import {Platform, TouchableHighlight, StyleSheet, Text, View} from 'react-native';
import {connect} from 'react-redux'
import {fetchPeopleFromAPI} from './actions'
class App extends React.Component {
componentDidMount() {
this.props.getPeople();
}
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>Welcome to React Native! & Redux</Text>
<Text style={styles.instructions}>To get started, edit App.js</Text>
<TouchableHighlight onPress={props.getPeople} style={styles.buttonText}>
<Text>Fetch Data</Text>
</TouchableHighlight>
{
isFetching && <Text>Loading</Text>
}
{
people.length? (
people.map((person,index) => {
return (
<View key={index}>
<Text>Name: {person.breedName}</Text>
</View>
)
})
) : null
}
</View>
);
}
}
const {people, isFetching} = props.people
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
buttonText: {
backgroundColor: 'red',
height:60,
justifyContent: 'center',
alignItems: 'center',
}
});
function mapStateToProps (state) {
return {
people: state.people
}
}
function mapDispatchToProps (dispatch) {
return {
getPeople: () => dispatch(fetchPeopleFromAPI())
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App)
Remove this part from your code completely:
function mapDispatchToProps (dispatch) {
return {
getPeople: () => dispatch(fetchPeopleFromAPI())
}
}
in your export statement amend into:
export default connect(
mapStateToProps,
{ fetchPeopleFromAPI }
)(App)
and finally in your componentDidMount:
componentDidMount() {
this.props.fetchPeopleFromAPI();
}

Resources