I am writing a test for a component that is wrapped in a withStyles() from Material UI using Jest. I have searched many examples but didn't get solution.
Below are the my files:
Login.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as Actions from 'auth/store/actions/index';
import { bindActionCreators } from 'redux';
import { withRouter } from 'react-router-dom';
import { withStyles } from '#material-ui/core/styles/index';
import { TextFieldFormsy } from '#fuse';
import Formsy from 'formsy-react';
import {
Button, Card, CardContent, Typography,
} from '#material-ui/core';
const styles = () => ({
card: {
width: '100%',
maxWidth: 400,
},
});
class Login extends Component {
state = {
email: '',
password: '',
};
form = React.createRef();
componentDidMount() {
this.props.resetErrorMessage();
}
componentDidUpdate() {
}
onSubmit = (model) => {
this.props.submitMerritosLogin(model);
};
render() {
const { classes } = this.props;
const { email, password } = this.state;
return (
<Card className={`${classes.card} mx-auto md:m-0 merritos-login-card merritos-mobile-register
justify-center items-center flex flex-col`}>
<CardContent className="flex flex-col p-44">
<img className="w-256 mb-32 ml-6 merritos-desktop-display merritos-mobile-image merritos-mobile-images self-center" src="assets/images/logos/merritos-blue-small.png" alt="logo" />
<Typography className="mt-16 font-bold text-24 merritos-login-subtitile merritos-theme-colour">Sign in</Typography>
<Typography className="mb-56 text-grey-darker text-16 font-bold ">to your professional world</Typography>
<Formsy
onValidSubmit={this.onSubmit}
ref={form => this.form = form}
className="flex flex-col justify-center w-full merritos-form"
>
<TextFieldFormsy
className="mb-16 merritos-border-color merritos-accountinfo-textinput"
type="email"
name="email"
label="Email"
value={email}
required
/>
<TextFieldFormsy
className="mb-16 merritos-border-color merritos-accountinfo-textinput"
type="password"
name="password"
label="Password"
value={password}
required
/>
<Button
color="primary"
size="small"
type="submit"
className="text-16 normal-case merritos-login-btn accountinfo-margin"
aria-label="LOG IN"
>
Sign in
</Button>
</Formsy>
</CardContent>
</Card>
);
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
submitMerritosLogin: Actions.submitMerritosLogin,
resetErrorMessage: Actions.resetErrorMessage,
}, dispatch);
}
function mapStateToProps({ auth }) {
return {
user: auth.user,
};
}
export default withStyles(styles, { withTheme: true })(withRouter(
connect(mapStateToProps, mapDispatchToProps)(Login),
));
login.test.js
import React from 'react';
import {render, fireEvent, screen} from '#testing-library/react';
import { shallow } from 'enzyme';
import Login from './Login';
describe('login', () => {
test('renders without crashing', () => {
const wrapper = shallow(<Login.WrappedComponent />);
expect(wrapper.find('Button').text()).toEqual('Sign in');
});
});
While running test I am getting error like bellow:
login › renders without crashing
TypeError: Cannot read property 'card' of undefined
I removed the classes.card and tried then also I got error below:
login › renders without crashing
TypeError: Cannot read property 'resetErrorMessage' of undefined
I am wanting the wrapper component to behave the same way as a wrapper without the withStyles() component.
Looks like you are testing the wrapped component without passing any props while the component requires some props having to be passed.
I think it would work as you pass some needed props to it, it would work as following:
Export Login class in your Login.js
export class Login {
// ...
}
Then import to the test:
// you should import `Login` class to test separately
import { Login } from "./Login";
test('renders without crashing', () => {
const props = {
classes: {
card: 'yourCardClass',
},
resetErrorMessage: jest.fn(),
submitMerritosLogin: jest.fn(),
};
const wrapper = shallow(<Login {...props} />);
// should get called as component did mount
expect(props.resetErrorMessage).toHaveBeenCalled();
expect(wrapper.find('Button').text()).toEqual('Sign in');
});
Related
I have been looking for several hours now but can't figure this out.
How would I go about testing the onClick?
This is my card component:
import { useRouter } from "next/router";
import React from "react";
interface Props {
url: string;
name: string;
}
const LargeCard = (props: Props) => {
const router = useRouter();
return (
<button
onClick={() => {
router.push(props.url);
}}
className="w-full h-96 grid place-items-center hover:border border-dark-3 bg-dark-5 hover:cursor-pointer"
>
<p className="text-2xl uppercase text-dark-1 font-thin">{props.name}</p>
</button>
);
};
export default LargeCard;
And this is the test file I've got up until now.
import { expect, it } from "#jest/globals";
import { render } from "#testing-library/react";
import LargeCard from "./largeCard";
describe("LargeCard", () => {
it("renders the button", () => {
const { queryByText } = render(
<LargeCard name={"test"} url={"/test"} />
);
expect(queryByText("test")).toBeTruthy();
});
});
I am going to post my question here and hopefully learn something! I've been following a tutorial and do not yet have the insight into React and Redux to effectively figure out what is going on. I want to create functionality that adds and removes businesses to global state in React and Redux. From what I've Googled I know the file structures vary depending on the project so I will post all of my files here. I have them divided into actions.js, reducers.js, state.js, and store.js. I have an add Listing view with React and would like to add remove functionality to my view listings view. Here are my files:
redux/actions.js:
export const addListing = (newBusiness) => {
return {
type: 'ADD_LISTING',
value: newBusiness
}
}
export const removeListing = (index) => {
return {
type: 'REMOVE_LISTING',
value: index
}
}
redux/reducers.js:
import { combineReducers } from 'redux';
import { addBusiness, removeBusiness } from './actions'
const user = (state = null) => state
// add switch statements in here
const businesses = (state = [], action) => {
switch(action.type) {
case 'ADD_BUSINESS':
return [ ...state, action.value ]
case 'REMOVE_BUSINESS':
const newState = [ ...state ]
newState.splice(action.value, 1);
return newState;
default: // need this for default case
return state
}
}
export default combineReducers({ user, businesses })
redux/state.js
export default {
user: {
username: 'test-user-1',
email: 'test-user#example.com'
},
businesses: [
{
"id": 15,
"name": "My pizza",
"description":"Homemade pizza shop",
"address": "123 Taco Street",
"hours": "9-5"
}
]
};
redux/store.js
import { createStore } from 'redux'
import reducers from './reducers'
import state from './state'
export default createStore(reducers, state)
containers/addListing.js
import { connect } from "react-redux";
import { addListing } from '../redux/actions';
import AddListing from '../components/addListing'
const mapDispatchToProps = (dispatch) => {
return {
addListing: (business) => dispatch(addListing(business)),
}
}
export default connect(null, mapDispatchToProps)(AddListing)
containers/removeListing.js
import { connect } from "react-redux";
import { removeListing } from '../redux/actions';
const mapDispatchToProps = (dispatch) => {
return {
removeCar: (business) => dispatch(removeListing(business)),
}
}
export default connect(null, mapDispatchToProps)(removeListing)
containers/Listing.js:
import { connect } from 'react-redux'
import Listing from '../components/Listing'
const mapStateToProps = (state) => {
return {
businesses: state.businesses,
user: state.user.username
}
}
export default connect(mapStateToProps)(Listing)
components/addListing.js
import React from 'react';
import { InputLabel } from '#material-ui/core'
import { Input } from '#material-ui/core'
import { FormControl } from '#material-ui/core';
import { Button } from '#material-ui/core';
import { TextField } from '#material-ui/core';
import '../redux/state';
class addListing extends React.Component {
state = {
name: '',
description: '',
address: '',
hours: ''
}
handleTextChange = (e) => {
const newState = { ...this.state }
newState[e.target.id] = e.target.value
this.setState(newState)
console.log(this.state)
}
handleSubmit = (e) => {
e.preventDefault()
const payload = { ...this.state }
console.log("THE BUSINESS", payload)
this.props.addListing(payload)
console.log(this.props)
}
componentDidUpdate = (prevProps, prevState) => {
if (prevState.open !== this.state.open) {
this.setState({
name: '',
description: '',
address: '',
hours: ''
})
}
}
render(){
return (
<div className="App">
<form
onSubmit={this.handleSubmit}
style={{ display: 'flex', flexDirection: 'column', width: '350px' }}>
<TextField
id="name"
name="name"
placeholder="Name"
value={this.state.name}
onChange={this.handleTextChange}
required />
<TextField
id="description"
name="description"
placeholder="Description"
value={this.state.description}
onChange={this.handleTextChange}
required />
<TextField
id="address"
name="address"
placeholder="Address"
value={this.state.address}
onChange={this.handleTextChange}
required />
<TextField
id="hours"
name="hours"
placeholder="Hours"
value={this.state.hours}
onChange={this.handleTextChange}
required />
<br />
<Button variant="contained" color="primary" type="submit">Submit</Button>
</form>
</div>
);
}
}
export default addListing;
components/Listing.js:
import React from 'react'
import {
Container,
Table,
TableBody,
TableCell,
TableHead,
TableRow
} from '#material-ui/core'
import DeleteIcon from '#material-ui/icons/Delete'
import addListing from '../containers/addListing'
import removeListing from '../containers/removeListing'
import businesses from '../redux/state'
import user from '../redux/state';
const Listing = (props) => {
console.log(props.businesses)
return (
<Container maxWidth="lg" className="car-container">
<h4>Welcome, {props.user.username}</h4>
<div className="flex-container">
</div>
<Table>
<TableHead>
<TableRow>
<TableCell>ID</TableCell>
<TableCell>Name</TableCell>
<TableCell>Description</TableCell>
<TableCell>Address</TableCell>
<TableCell>Hours</TableCell>
</TableRow>
</TableHead>
<TableBody>
{props.businesses.map((businesses, idx) => (
<TableRow key={businesses.id}>
<TableCell component="th" scope="row">
</TableCell>
<TableCell>{businesses["name"]}</TableCell>
<TableCell>{businesses["description"]}</TableCell>
<TableCell>{businesses["address"]}</TableCell>
<TableCell>{businesses["hours"]}</TableCell>
<TableCell>
<DeleteIcon
// add onClick method here
// onClick={props.removeCar(idx)}
className="icon text-red"
onClick={ () => this.props.removeListing(idx)}
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Container>
)
}
export default Listing
App.js:
import React from 'react';
import Navigation from './components/Navigation'
import './App.css'
import Router from './Router'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import store from './redux/store'
function App() {
return (
<Provider store={store}>
<BrowserRouter>
<Navigation />
<Router />
</BrowserRouter>
</Provider>
);
}
export default App;
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Login from './components/Login'
import App from './App';
import * as serviceWorker from './serviceWorker';
import Listing from '../src/components/Listing';
import { Provider } from 'react-redux';
ReactDOM.render(
<App />,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
serviceWorker.unregister();
So far I know it's something simple. I am getting no errors but it just doesn't seem to add the new listings to global state. When I get to the listing view it only displays the Business I included as a default in state.js. I will try to reply in a quick manner and please let me know if more info is needed. Thanks!
I see that the names of your actions are different ADD_LISTING vs ADD_BUSINESS, REMOVE_LISTING vs. REMOVE_BUSINESS.
In the addListing you have {type: 'ADD_LISTING', ...}, in your reducer you have case 'ADD_BUSINESS': The strings are different. They need to match. Try renaming ADD_BUSINESS => ADD_LISTING, and REMOVE_BUSINESS=>REMOVE_LISTING.
Regarding the crash on line 50. You don't need this. because your component is not a class type. Change it to onClick={ () => props.removeListing(idx)}.
You're missing removeListing in the mapDispatchToProps.
Also, Redux DevTools plugin for Chrome can help you a lot in debugging issues with redux..
What is wrong in this code? I put it together from a material-ui example and facebook login button react example.
import React, { Component } from 'react';
import { makeStyles } from '#material-ui/core/styles';
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 IconButton from '#material-ui/core/IconButton';
import MenuIcon from '#material-ui/core/Menu';
import Menu from '#material-ui/core/Menu';
import MenuItem from '#material-ui/core/MenuItem';
import FacebookLogin from 'react-facebook-login';
export default class Demo extends Component {
useStyles = makeStyles(theme => ({
root: {
flexGrow: 1,
},
menuButton: {
marginRight: theme.spacing(2),
},
title: {
flexGrow: 1,
},
}));
classes = this.useStyles();
handleClick = event => this.setState({ anchorEl: event.currentTarget })
handleClose = () => this.setState({ anchorEl: null })
state = {
isLoggedIn: false,
userID: '',
name: '',
email: '',
piture: '',
anchorEl: null,
setAnchorEl: null
};
componentClicked = () => {
console.log("componentClicked");
};
responseFacebook = (response) => {
console.log(response);
this.setState({
isLoggedIn: true,
userID: response.userID,
name: response.name,
email: response.email,
picture: response.picture
});
console.log(response);
};
render() {
let fbContent;
if (this.state.isLoggedIn) {
fbContent = "hello"; //this.state.name;
} else {
fbContent = (<FacebookLogin appId="2526636684068727"
autoLoad={true}
fields="name,email,picture"
onClick={this.componentClicked}
callback={this.responseFacebook}
cssClass="my-facebook-button-class"/>);
}
return (
<div>
<AppBar position="static">
<Toolbar>
<Typography variant="h6" className={this.classes.title}>
Tiket.hu
</Typography>
<Button color="inherit">Search</Button>
<Button color="inherit">Basket</Button>
{fbContent}
</Toolbar>
</AppBar>
{fbContent}
</div>
);
}
}
I have also faced a situation like this, and this is really strange what I did was I took out everything and put inside separate component.
In your case you might want to take out all your Fb login functionality into a separate component and just import this component into your main component
makeStyles is a high order function which returns a hook (usually named useStyles) and hooks cannot be called from within class based components. This is the part throwing the error
classes = this.useStyles()
Use connect instead of makeStyles.
connect is a high order component which serializes classes inside both functional and class based components props
import { connect } from '#material-ui/core/styles'
class Component extends React.Component{
render(){
const { classes } = this.props
return <div className={classes.foo} />
}
}
export default connect(styles)(Component)
//Works in functional components as well
const Component = connect(styles)(({ classes }) =>{
return <div className={classes.foo} />
})
In my file called app.jsx i have one import from the component "atividades":
import React from 'react'
import { Container } from 'semantic-ui-react'
import atividades from '../atividades/atividades'
export default props => (
<Container>
<h1>Teste</h1>
<atividades />
</Container>
)
But only the h1 is rendering...
This is my atividades component:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { Form } from 'semantic-ui-react'
import { addWorkout, searchWorkout } from './workOutActions'
import { Button, Icon } from 'semantic-ui-react'
const workoutOptions = [
{ text: 'correr', value: 'run' },
{ text: 'nadar', value: 'swimming' },
{ text: 'andar de bicicleta', value: 'bike' },
]
class atividades extends Component {
constructor(props){
super(props)
}
componentWillMount(){
this.props.searchWorkout()
}
render() {
const { addWorkout, searchWorkout, tempoGasto, tipoTarefa, data} = this.props
return (
<div role='form'>
<h6>Inserir atividade</h6>
<Form>
<Form.Group widths='equal'>
<Form.Input fluid placeholder='Tempo gasto' />
<Form.Select
fluid
label='Atividade'
options={workoutOptions}
/>
<Button animated='vertical'>
<Button.Content hidden>Shop</Button.Content>
<Button.Content visible>
<Icon name='shop' />
</Button.Content>
</Button>
</Form.Group>
</Form>
</div>
)
}
}
const mapStateToProps = state => ({tempoGasto: state.workout.tempoGasto, tipoTarefa: state.workout.tipoTarefa, data: state.workout.data})
const mapDispatchToProps = dispatch =>
bindActionCreators({ addWorkout, searchWorkout }, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(atividades)
It's not showing nothing in the console, but the element workout is not rendering, the visual studio code says that the import is not being used.
The first letter of React components must be capitalized or it thinks it’s a built in component like div or p or span.
This link has more info:
ReactJS component names must begin with capital letters?
I'm trying to insert a personalized theme into my component, however when using console.log() in properties in styles I get a return that the object is empty.
I do not get any kind of error warning, why is this happening?
// LIBRARY AND MODULES
import React from "react";
// STYLES
import GlobalStyle from "../../styles/global";
import { ContainerPage, FooterContainerPage, FormElement } from "./styles";
// COMPONENTS
import CardRepository from "../../components/stateless/specifics/Cards/Repository/Repository";
import Input from "../../components/stateless/generics/Input/Input";
import Button from "../../components/stateless/generics/Button/Button";
const themes = {
buttons: {
searchRepository: {
bgColor: "#73c894",
txtColor: "#ffffff",
hoverBgColor: "#218838"
}
}
};
export default class App extends React.Component {
state = {
buttonIsDisabled: false
};
searchRepository() {
alert("ae");
}
render() {
const { buttonIsDisabled } = this.state;
return (
<React.Fragment>
<GlobalStyle />
<FooterContainerPage>
<FormElement>
<Button
theme={themes.buttons.searchRepository}
type="button"
onClick={this.searchRepository}
disabled={buttonIsDisabled}
required={true}
>
BUSCAR
</Button>
</FormElement>
</FooterContainerPage>
</React.Fragment>
);
}
}