Testing chakra-ui using jest in a next-js app - javascript

I have a login page as
import { Flex, Stack, FormControl, FormLabel, Input, FormHelperText } from '#chakra-ui/react'
import type { NextPage } from 'next'
const Login: NextPage = () => {
return (
<Flex h="100vh" w="100vw" justifyContent="center" alignItems="center">
<Stack>
<FormControl>
<FormLabel>Email address</FormLabel>
<Input type='email' />
<FormHelperText>We'll never share your email.</FormHelperText>
</FormControl>
</Stack>
</Flex>
);
};
export default Login;
and I want to do a snapshot test for this page using jest.
I created Login.test.tsx as
import { render } from '#testing-library/react';
import Login from '../pages/Login';
describe('Login', () => {
it('renders correctly', () => {
const { container } = render(<Login />);
expect(container).toMatchSnapshot();
});
});
Is this a right approach to test web pages created using chakra-ui?

What you can do is create a customer render where you'll put all your providers.
https://testing-library.com/docs/react-testing-library/setup
If you want to do snapshot testing you can do it like that
https://testing-library.com/docs/react-testing-library/api/#render
import { render } from '#testing-library/react';
import Login from '../pages/Login';
describe('Login', () => {
it('renders correctly', () => {
const { asFragment } = render(<ChakraProvider><Login /></ChakraProvider>);
expect(asFragment()).toMatchSnapshot();
});
});

Related

Next page after loads in React Native

I'm newbie in React-native , I've just confused on how can I go to next page when the Google API successfully load to my app, mostly in the internet they used onPress but I don't have any onPress just to trigger the event instead i've implement after it successfully login data and will trigger the event, how should I do it?
From Login > Home
Apps.js
import React from 'react';
import Login from './components/login'
import SplashScreen from 'react-native-splash-screen';
const App = () => {
React.useEffect(() =>{
SplashScreen.hide()
},[])
return (
<Login/>
)
}
export default App;
Login.js
import React, { Component } from 'react';
import { View, StyleSheet, ToastAndroid, Button ,Text,Image} from "react-native";
import {
GoogleSignin,
GoogleSigninButton,
statusCodes,
} from '#react-native-community/google-signin';
GoogleSignin.configure({
webClientId: 'testttttttt.........apps.googleusercontent.com',
offlineAccess: true,
});
class Login extends Component {
constructor(props){
super(props)
this.state={
userGoogleInfo : {},
loaded: false
}}
signIn = async () => {
try {
console.log("Processing");
await GoogleSignin.hasPlayServices();
const userInfo = await GoogleSignin.signIn();
this.setState({
userGoogleInfo : userInfo,
loaded : true
})
// after this it goes to another page Home.js -----------------------------------------<<<<<<<<<
} catch (error) {
if (error.code === statusCodes.SIGN_IN_CANCELLED) {
console.log("e 1");
} else if (error.code === statusCodes.IN_PROGRESS) {
console.log("e 2");
} else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
console.log("e 3");
} else {
console.log(error.message);
}
}
};
render() {
return (
<View style={styles.container}>
<GoogleSigninButton
style={{ width: 222, height: 48 }}
size={GoogleSigninButton.Size.Wide}
color={GoogleSigninButton.Color.Dark}
onPress={this.signIn}
/>
{this.state.loaded ?
<View>
<Text>{this.state.userGoogleInfo.user.name}</Text>
<Text>{this.state.userGoogleInfo.user.email}</Text>
<Image
style={{ width: 100, height: 100 }}
source={{uri: this.state.userGoogleInfo.user.photo}}
/>
</View>
: <Text>Not SignedIn</Text> }
</View>
);
}
}
const styles = StyleSheet.create({
container:{
flex:1,
backgroundColor:'#000000',
padding:15,
},
});
export default Login;
Home.js
import React from 'react';
import Login from './components/login'
import SplashScreen from 'react-native-splash-screen';
import { View,Text} from "react-native";
const App = () => {
React.useEffect(() =>{
SplashScreen.hide()
},[])
return (
<View>
<Text>fsaf</Text>
</View>
)
}
export default App;
You can use React Navigation
And once you have a proper setup use push method
navigation.push('YourView')
First, you need to add a navigation component to your project. The React Native documentation has a section about it with some libraries suggestions.
The React Navigation is hugely used and has the CommonActions that resolve your problem. After installing the React Navigation you can see this section on the documentation to dispatch the CommonActions to navigate to the Home screen after login success on your try...catch.

Jest test fails because it doesn't recognise a props value

Having this React function, tried to run a test but it doesn't pass it:
import React, { useEffect } from 'react';
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
import { useIntl } from 'react-intl';
import {
PrimaryButton,
OutlineButton
} from 'react-komodo-design';
const Buttons = () => {
const dispatch = useDispatch();
const intl = useIntl();
const orderType = useSelector(
(state) => state.orderDetails.orderDetails.orderType,
shallowEqual
);
...
return (
<div>
<OutlineButton>
{intl.formatMessage({ id: 'buttons' })}
</OutlineButton>
{orderType.toLowerCase() !== 't9' && (
<PrimaryButton
onClick={clickReorder}
</PrimaryButton>
)}
</div>
);
};
export default Buttons;
The test file is this:
import React from 'react';
import { render } from '#testing-library/react';
import Buttons from './Buttons';
import { WrapIntlProvider, WrapStore } from '../../testsHelper';
describe('<Buttons />', function () {
it('should render <Buttons></Buttons>', () => {
const { container } = render(
<WrapStore>
<WrapIntlProvider>
<Buttons />
</WrapIntlProvider>
</WrapStore>
);
expect(container).toMatchSnapshot();
});
});
Error message: TypeError: Cannot read property 'toLowerCase' of undefined
What can be done to avoid this error?
I've tried to add values inside the test function like this:
<Buttons orderType="test" /> or <Buttons orderType={"test'} /> or send it as a variable:
describe('<Buttons />', function () {
it('should render <Buttons></Buttons>', () => {
const xx = "test"; // <--- added here
const { container } = render(
<WrapStore>
<WrapIntlProvider>
<Buttons orderType={xx} />
</WrapIntlProvider>
</WrapStore>
);
expect(container).toMatchSnapshot();
});
});
import * as redux from 'react-redux'
const spy = jest.spyOn(redux, 'useSelector')
spy.mockReturnValue({ orderType:'test' })
Try to mock useSelector like this.

Wrapping React Native app with Context Provider

I've been following some simple tutorial (full working source code) to get the idea how to use React's Context together with handling authentication in my React Native app.
This example is using stateful components for views and handling routing the app within component itself, for example, in SignInScreen.js:
/* SignInScreen.js */
export default class SignInScreen extends React.Component {
static navigationOptions = {
title: 'Please sign in',
};
_signInAsync = async (saveToken) => {
saveToken()
.then((data) => {
// ROUTE USER TO "PROTECTED" PART OF THE APP
this.props.navigation.navigate('App');
})
.catch((error) => {
this.setState({ error })
})
};
render() {
return (
<View style={styles.container}>
<MyContext.Consumer>
{context => ((
<Button title="Sign in!" onPress={() => this._signInAsync(context.saveToken)} />
))}
</MyContext.Consumer>
</View>
);
}
};
I tried to transform this component into function component and move the signing in logic into my Context Provider like this:
/* SignInScreen.js - MODIFIED */
import React from 'react';
import { Button, View } from 'react-native';
import { MyContext } from '../Provider';
export default const LoginScreen = () => {
return (
<View>
<MyContext.Consumer>
{context => {
return (
<Button
onPress={() => context.signIn()}
title="Sign In"
/>
)
}
}
</MyContext.Consumer>
</View>
)
};
/* Provider.js */
import React from 'react';
import { AsyncStorage } from 'react-native';
export const MyContext = React.createContext();
export default class MyProvider extends React.Component {
constructor(props) {
super(props);
this.getToken = () => AsyncStorage.getItem('userToken');
this.saveToken = () => AsyncStorage.setItem('userToken', 'abc');
this.removeToken = () => AsyncStorage.removeItem('userToken');
this.signIn = () => {
this.saveToken()
.then((data) => {
// this.props.navigation DOES NOT EXIST!!! :(
this.props.navigation.navigate('App');
})
.catch((error) => this.setState({ error }));
};
this.state = {
token: '',
signIn: this.signIn,
};
}
componentWillMount() {
AsyncStorage.getItem('userToken')
.then((token) => {
this.setState({ token })
})
.catch(error => {
this.setState({ error })
})
}
render() {
return (
<MyContext.Provider value={this.state}>
{this.props.children}
</MyContext.Provider>
);
}
}
When I press that "Sign In" button, my provider errors when I try to redirect user (this.props.navigation.navigate('App');) because this.props.navigation does not exist.
As far as I understood, this is happening because I didn't properly wrap my app with my Context.
This is my main App.js file:
/* App.js */
import React from 'react';
import { View } from 'react-native';
import MyContext from './Provider';
import AppNavigator from './navigation/AppNavigator';
export default class App extends React.Component {
render() {
return (
<MyContext>
<View>
<AppNavigator />
</View>
</MyContext>
);
}
}
and my AppNavigator.js:
/* AppNavigator.js */
import React from 'react';
import { createAppContainer, createSwitchNavigator } from 'react-navigation';
import AuthLoadingScreen from '../screens/AuthLoadingScreen';
import Auth from './AuthNavigator';
import App from './AppTabNavigator';
export default createAppContainer(createSwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
Auth,
App,
},
{
initialRouteName: 'AuthLoading',
}
));
(AuthNavigator and AppTabNavigator contain only createStackNavigator() with my screens defined in it.)
My question is: how can I wrap this app with my Context so that Context Provider is always aware of navigation prop and so I could handle logging in and out and routing user from the Context Provider itself?
I solved this by using NavigationActions, pretty helpful built-in module designed for this purpose.

Material UI React Test cases failing - JEST, ENZYME

I have a connected component and have integrated MaterialUI in my component. For some reason, the tests keep on failing and I am not able to find some article online to resolve this.
Please advice.
Below is my code.
import React, {Component} from 'react';
import {connect} from 'react-redux';
import SourceCurrencyInput from './components/SourceCurrencyInput';
import TargetCurrencyInput from './components/TargetCurrencyInput';
import {fetchCurrencyConverterRates} from './actions/currencyConverterActions';
import CurrencyConverterValue from './components/CurrencyConverterValue';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import Button from '#material-ui/core/Button';
import {withStyles} from '#material-ui/core/styles';
import './App.css';
import {
changeSourceCurrencyValue,
changeTargetCurrencyValue
} from './actions/actions';
const styles = {
root: {
flexGrow: 1,
},
grow: {
flexGrow: 1,
},
menuButton: {
marginLeft: -12,
marginRight: 20,
},
};
class App extends Component {
componentDidMount() {
this.props.fetchCurrencyConverterRates(
this.props.srcCurrencyType,
this.props.tgtCurrencyType
);
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevProps.srcCurrencyType !== this.props.srcCurrencyType
|| prevProps.tgtCurrencyType !== this.props.tgtCurrencyType) {
this.props.fetchCurrencyConverterRates(
this.props.srcCurrencyType,
this.props.tgtCurrencyType);
}
}
clearAll = () => {
this.props.sourceValue('');
this.props.targetValue('');
};
render() {
const {srcCurrencyType, tgtCurrencyType, srcCurrencyValue, tgtCurrencyValue, currencyConversionRate, classes} = this.props;
return (
<div className="App">
<AppBar position="static">
<Toolbar>
<Typography variant="h6" color="inherit" className={classes.grow}>
Currency Converter by Arun Manohar
</Typography>
</Toolbar>
</AppBar>
<header className="App-header">
<SourceCurrencyInput/>
<TargetCurrencyInput/>
<Button variant="contained" color="primary" className={classes.button}
onClick={() => this.clearAll()}>Clear</Button>
</header>
<CurrencyConverterValue srcCurrencyType={srcCurrencyType}
tgtCurrencyType={tgtCurrencyType}
srcCurrencyValue={srcCurrencyValue}
tgtCurrencyValue={tgtCurrencyValue}
currencyConversionRate={currencyConversionRate}
/>
<footer><a href={'https://currencyconverterapi.com'} target={'_blank'}>API from currencyconverterapi.com</a></footer>
</div>
);
}
}
const mapStateToProps = state => {
return {
srcCurrencyType: state.currencyConverterReducer.sourceCurrencyType,
tgtCurrencyType: state.currencyConverterReducer.targetCurrencyType,
srcCurrencyValue: state.currencyConverterReducer.sourceCurrencyValue,
tgtCurrencyValue: state.currencyConverterReducer.targetCurrencyValue,
currencyConversionRate: state.getConvertedRates.data[0]
};
};
const mapDispatchToProps = (dispatch) => ({
fetchCurrencyConverterRates: (src, tgt) => dispatch(fetchCurrencyConverterRates(src, tgt)),
sourceValue: (val) => dispatch(changeSourceCurrencyValue(val)),
targetValue: (val) => dispatch(changeTargetCurrencyValue(val)),
});
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(App));
Below is my test case.
import React from 'react';
import {Provider} from 'react-redux';
import configureStore from 'redux-mock-store';
import App from './App';
import {createMount} from '#material-ui/core/test-utils';
const mockStore = configureStore();
const initialState = {sourceCurrencyType: 'USD'};
const store = mockStore(initialState);
describe('<App />', () => {
let mount;
beforeEach(() => {
mount = createMount();
});
it('should work', () => {
const wrapper = mount(<Provider store={store}><App/></Provider>);
console.log(wrapper.debug());
});
});
This is the error I get.
TypeError: Cannot read property 'sourceCurrencyType' of undefined
I just need a way to access this component in my tests. Please help me out.
Your initial state must keep the same structure with the reducer object, such:
const initialState = {
currencyConverterReducer: {
sourceCurrencyType: 'USD',
// rest of attributes of currencyConverterReducer
}
};

Spy on onClick for Button - React-redux

I'm trying to get this form working for the first time and would just like to know that my onclick is at least working. I'd like to inject a spy to replace the handler that my dispatchToProps is referencing as well.
So in other words I'd like to replace this:
AsyncActions.login
with loginSpy
I can't just do button.props().login = loginSpy because props are immutable at that point. I get TypeError: Can't add property login, object is not extensible
So is there a way to use restructuring through an ES6 class, specifically an ES6 react component via its constructor or something like that?
I know you can do {prop1, prop2} as a parameter in a stateless function, for example:
function FieldGroup({ id, label, help, ...props }) {
but what about ES6 classes in React?
Test
it.only('can log in successfully', async () => {
const container = shallow(<LoginContainer store={store} />),
loginContainer = shallow(<LoginContainer store={store} />),
login = loginContainer.dive().find(Login),
loginForm = login.dive().find(LoginForm),
loginFormLogin = await loginForm.props().login(),
button = loginForm.dive().find('.ft-login-button'),
loginSpy = spy()
button.props().login = loginSpy
button.simulate('click')
expect(loginSpy.calledOnce).to.be.true
})
Container
import { connect } from 'react-redux'
import React, { Component } from 'react'
import * as AsyncActions from '../actions/User/UserAsyncActions'
import Login from '../components/Login/Login'
class LoginContainer extends Component {
componentWillMount(){
// const requested = this.user.requested
}
render(){
return( <Login login={this.props.login} /> )
}
}
const mapStateToProps = state => {
return {
requesting: state.user.requesting,
token: state.user.token,
session: state.user.session
}
}
export const mapDispatchToProps = {
login: AsyncActions.login
}
export { Login }
export default connect(mapStateToProps, mapDispatchToProps)(LoginContainer)
LoginForm
import React, { Component } from 'react'
import { Button, FormControl, FormGroup, ControlLabel, PageHeader } from 'react-bootstrap'
class LoginForm extends Component {
render(){
return (
<div className='ft-login-form'>
<PageHeader className='ft-header'>Login</PageHeader>
<form>
<FormGroup controlId="formBasicText" >
<ControlLabel>Email</ControlLabel>
<FormControl
bsSize="small"
className="ft-username"
componentClass="input"
placeholder="Enter mail"
style={{ width: 300}}
type="text"
/>
<ControlLabel>Password</ControlLabel>
<FormControl
bsSize="small"
className="ft-password"
componentClass="input"
placeholder="Enter Password"
style={{ width: 300}}
type="text"
/>
</FormGroup>
<Button
className='ft-login-button'
onClick={this.props.login}
type='submit'>Login</Button>
</form>
</div>)
}
}
export default LoginForm
You should shallow render LoginForm instead of LoginContainer and simply pass loginSpy as a prop to LoginForm to test the button...
it.only('can log in successfully', async () => {
const loginSpy = spy(),
loginForm = shallow(<LoginForm login={loginSpy} />),
button = loginForm.dive().find('.ft-login-button')
button.simulate('click')
expect(loginSpy.calledOnce).to.be.true
})

Categories