How to test my Material UI component function handlers? - reactjs

Hi I'm having troubles testing my component function handlers.
I have this component:
class MUIToolbar extends React.Component {
state = {
title: this.props.title,
anchorEl: null,
value: 0,
open: false
};
handleMenu = event => {
this.setState({ anchorEl: event.currentTarget });
};
handleClose = () => {
this.setState({ anchorEl: null });
};
handleChange = (event, value) => {
this.setState({ value });
};
homeRedirect = () => {
this.props.history.push('/groups');
}
render() {
const { classes } = this.props;
const { anchorEl, value, title } = this.state;
const open = Boolean(anchorEl);
return (
<div className={classes.root}>
<AppBar position="fixed" className={classes.root}>
<Toolbar>
<Typography variant="h6" color="inherit" className={classes.grow} onClick={this.homeRedirect}>
{title}
</Typography>
<Tabs
value={value}
onChange={this.handleChange}
classes={{ root: classes.tabsRoot, indicator: classes.tabsIndicator }}>
<Tab
classes={{ root: classes.tabRoot, selected: classes.tabSelected }}
label="Groups"
component={Link} to="/groups"/>
</Tabs>
<div>
<IconButton
aria-owns={open ? 'menu-appbar' : undefined}
aria-haspopup="true"
onClick={this.handleMenu}
color="inherit">
<AccountCircle />
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={open}
onClose={this.handleClose}>
<MenuItem onClick={this.handleClose}>Profile</MenuItem>
<MenuItem onClick={this.handleClose}>Log out</MenuItem>
</Menu>
</div>
</Toolbar>
</AppBar>
<Switch>
<Route path="/groups" component={Groups} />
</Switch>
</div>
);
}
}
const enhance = compose(
defaultProps({
title: 'Daily Track',
}),
withStyles(styles),
withRouter,
);
export default enhance(MUIToolbar);
So, I want to test handleMenu, handleClose, handleChange and homeRedirect and I'm doing it like this way:
describe('Testing Toolbar component', () => {
let mount;
let shallow;
let mountWrapper;
let shallowWrapper;
let props = {};
beforeEach(() => {
props.onClick = jest.fn();
mount = createMount();
shallow = createShallow();
mountWrapper = mount(<Router><MUIToolbar /></Router>);
shallowWrapper = shallow(<MUIToolbar />);
});
it('should simulate user IconButton click.', () => {
const instance = mountWrapper.instance();
const userIconButton = shallow(<IconButton />);
userIconButton.simulate('click');
jest.spyOn(instance, 'handleMenu');
expect(instance.handleMenu).toHaveBeenCalled();
});
});
And I got this error
Cannot spy the handleMenu property because it is not a function; undefined given instead
I tried many ways to do that but I couldn't.
Thanks for the help

Related

React component doesn't render on MUI MenuItem onClick event

I have a simple mui Menu, where one MenuItem should render another React component. The problem is that my Menu is rendered in another file, where close and handleClick functions are defined.
Problem: The component doesn't render on the MenuItem click. Seems like it is because setAnchor(null); in the App component sets the anchor to null always. Does this mean I need to use a different anchor? If yes, how?
The Menu component code is as follows:
interface Props {
handler: Handler;
anchor: HTMLButtonElement | null;
onClose: () => void;
}
const AddDataMenu = ({ handler,anchor, onClose }: Props) => {
const renderDataPopOver = () => {
console.log('this is clicked'); <<<<<<<<<< I can see this function is accessed
<AddDataPopover handler={handler} anchor={anchor} onClose={onClose} />;
};
return (
<div>
<Menu
anchorEl={anchor}
open={Boolean(anchor)}
onClose={onClose}
sx={{ width: 320, maxWidth: '100%' }}
>
<MenuItem onClick={renderDataPopOver}>
<ListItemIcon>
<DataIcon />
</ListItemIcon>
<Typography>item 1</Typography>
</MenuItem>
</Menu>
</div>
);
};
export default AddDataMenu;
This is the Main Component where my Menu is rendered.
const App = ({ scope }) => {
const ref = useRef<HTMLButtonElement>(null);
const [anchor, setAnchor] = useState<HTMLButtonElement | null>(null);
const [handler, setHandler] = useState<Handler>();
const close = () => { <<<<<<< this is accessed before MenuItem click
setAnchor(null);
};
const handleClick = () => { <<<<<<< this is accessed before MenuItem click
setAnchor(ref.current);
};
return showAdd && handler ? (
<MessageContainer
message={'test'}
actions={
<Box ml={1}>
<Button ref={ref} color="primary" variant="contained" onClick={handleClick}>
{t('Visualization.chart-initial-states.AddColumns')}
</Button>
<AddDataMenu handler={handler} anchor={anchor} onClose={close} />
</Box>
}
/>
) : (
<DisplayError />
);
};
export default App;
Assuming that the goal is to render a secondary Popover beside Menu on click of MenuItem, perhaps indeed the component would need to assign any MenuItem that triggers it as anchorEl to the rendered Popover.
Basic live example on: stackblitz (this and below examples omitted everything except for the Menu from the original posted code for simplicity).
In AddDataMenu, add AddDataPopover to the output with its initial anchorEl as null so it would not render immediately. Matching anchorEl can be assigned in the event of handleOpen.
A ref array is used to reference multiple MenuItem here, but this is an optional approach.
const AddDataMenu = ({ anchor, onClose }) => {
const [itemAnchor, setItemAnchor] = useState(null);
const itemRef = useRef([]);
const handleClose = () => {
setItemAnchor(null);
};
const handleOpen = (index) => {
setItemAnchor(itemRef.current[index]);
};
return (
<>
<Menu
anchorEl={anchor}
open={Boolean(anchor)}
onClose={onClose}
sx={{
width: 320,
maxWidth: '100%',
}}
>
{[1, 2, 3].map((item, index) => (
<MenuItem
ref={(node) => (itemRef.current[index] = node)}
key={index}
onClick={() => handleOpen(index)}
sx={{ p: 2 }}
>
<ListItemIcon>
<DataIcon />
</ListItemIcon>
<Typography>{`item ${item}`}</Typography>
</MenuItem>
))}
</Menu>
<AddDataPopover anchor={itemAnchor} onClose={handleClose} />
</>
);
};
In AddDataPopover, wire up the anchorEl to the Popover and style the component to be rendered beside active MenuItem.
const AddDataPopover = ({ anchor, onClose }) => {
return (
<Popover
id={'my-popover'}
open={Boolean(anchor)}
anchorEl={anchor}
onClose={onClose}
anchorOrigin={{
vertical: 'center',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'center',
horizontal: 'left',
}}
>
<Typography sx={{ p: 2 }}>The content of Data Popover.</Typography>
</Popover>
);
};

Show component when onClick method is called

I have imported a schedular component from devexpress and have modified the appointments in this with the constant MyAppointment. Now i want to be able to delete and modify apointment data with a dialog when clicking on these. Therefore i added an onClick method to the MyAppointment const and tried to return the dialog however nothing happens when pressing the appointments.
const MyAppointment = ({ children, style, ...restProps }) => {
return (
<Appointments.Appointment
{...restProps}
style={{
...style,
backgroundColor: "#a02d37",
borderRadius: "8px",
}}
onClick={()=>{
return (
<SimpleDialogDemo />
)
}}
>
{children}
</Appointments.Appointment>
);
}
const emails = ['username#gmail.com', 'user02#gmail.com'];
function SimpleDialog(props) {
const { onClose, selectedValue, open } = props;
const handleClose = () => {
onClose(selectedValue);
};
const handleListItemClick = (value) => {
onClose(value);
};
return (
<Dialog onClose={handleClose} open={open}>
<DialogTitle>Set backup account</DialogTitle>
<List sx={{ pt: 0 }}>
{emails.map((email) => (
<ListItem button onClick={() => handleListItemClick(email)} key={email}>
<ListItemAvatar>
<Avatar sx={{ bgcolor: blue[100], color: blue[600] }}>
<PersonIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={email} />
</ListItem>
))}
<ListItem autoFocus button onClick={() => handleListItemClick('addAccount')}>
<ListItemAvatar>
<Avatar>
<AddIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="Add account" />
</ListItem>
</List>
</Dialog>
);
}
SimpleDialog.propTypes = {
onClose: PropTypes.func.isRequired,
open: PropTypes.bool.isRequired,
selectedValue: PropTypes.string.isRequired,
};
export default function SimpleDialogDemo() {
const [open, setOpen] = React.useState(true);
const [selectedValue, setSelectedValue] = React.useState(emails[1]);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = (value) => {
setOpen(false);
setSelectedValue(value);
};
handleClickOpen();
return (
<div>
<SimpleDialog
selectedValue={selectedValue}
open={open}
onClose={handleClose}
/>
</div>
);
}
onClick={()=>{
return (
<SimpleDialogDemo />
)
}}
isn't actually doing anything. All you're doing is returning a component in an event handler that doesn't do anything with that return value. The onClick event handler is react-agnostic.
If you want to display a component when something is clicked, you'll need to use local state variables to show/hide it based on a click action like below:
const MyAppointment = ({ children, style, ...restProps }) => {
const [showDialog, setShowDialog] = useState(false);
return (
<Appointments.Appointment
{...restProps}
style={{
...style,
backgroundColor: "#a02d37",
borderRadius: "8px",
}}
onClick={()=> setShowDialog(true)}
>
{children}
{showDialog && <SimpleDialogDemo onClose=(() => setShowDialog(false))/>}
</Appointments.Appointment>
);
}
Note the onClose prop I added to your SimpleDialog component so that you have a way of setting the showDialog state back to hidden (you will need to add this propr if you use it).

How to get specific value from react Component

I am trying to get the ID of the specific input that is selected. I have a handleInputChange function that sets state that i want to send to the DB, what i need is to get the specific ID that is Selected.
handleInputChange = (event) => {
console.log(event.target.name)
console.log(event.target.value)
console.log(this.props.posts)
this.setState({
[event.target.name]: event.target.value,
postId: **What do I put here to get the value id**
})
}
In my render i would like to get the value of post.id whenever they type in the input field
render () {
const reversedProps = this.props.posts.reverse();
const {title, postBody} = this.state
const displayGifPicker = this.state.displayGifPicker
return (
<Grid item xl={8}>
{this.props.posts.map((post, index) =>
<PostBodyTemplate key={index} postId={post.id} onChange=
{this.handleInputChange.bind(this)} />
)}
</Grid>
)
}
}
const mt4 = {
marginTop: '40px',
height: '350px',
marginBottom: '40px'
};
const useStyles = makeStyles({
card: {
minWidth: 275,
},
bullet: {
display: 'inline-block',
margin: '0 2px',
transform: 'scale(0.8)',
},
title: {
fontSize: 14,
},
pos: {
marginBottom: 12,
}
});
class NewPostBody extends Component {
constructor(props){
super(props)
this.state = {
commentBody: null,
postId: null,
giphyUrl: null,
postPicture: null,
userId: null,
userIdto: null,
userIdName: null,
userIdtoName:null,
// postBody: null,
// giphyUrl: null,
// userIdto: null,
// userIdName: null,
// userIdtoName:'Julio',
displayGifPicker: false
}
}
componentWillMount(){
this.props.fetchPosts();
}
handleInputChange = (event, id) => {
console.log(event.target.name)
console.log(event.target.value)
console.log(this.props.posts)
this.setState({
[event.target.name]: event.target.value,
postId: id
})
}
displayGifPicker = () => {
this.setState({
displayGifPicker: !this.state.displayGifPicker
})
}
getGifState = (selectedUrl) => {
this.setState({ giphyUrl: selectedUrl})
}
render () {
const reversedProps = this.props.posts.reverse();
const {title, postBody} = this.state
const displayGifPicker = this.state.displayGifPicker
return (
<Grid item xl={8}>
{this.props.posts.map((post, index) =>
<PostBodyTemplate key={index} postId={post.id} onChange={e => this.handleInputChange(e,post.id)} />
)}
</Grid>
)
}
}
NewPostBody.propTypes = {
fetchPosts: PropTypes.func.isRequired,
// user: PropTypes.array.isRequired
posts: PropTypes.array.isRequired,
}
const mapStateToProps = state =>({
// user: state.user.items,
posts: state.posts.items,
})
export default connect(mapStateToProps, { fetchPosts })(NewPostBody);
This is the PostBodyTemplate
const useStyles = makeStyles(theme => ({
root: {
padding: theme.spacing(3, 2),
},
}));
const fr = {
float: 'right'
}
const giphyRes = {
width: '300px',
height: '300px'
}
export default function PostBodyTemplate(props, onChange, onSubmit) {
const classes = useStyles();
// render() {
return (
<Grid item xs={12} xl={8} lg={8} style={fr}>
<Card className={classes.card}>
<CardContent>
<Paper className={classes.root}>
<Typography variant="h5" component="h2" style={fr}>
{props.userWhoPosted} Gave A VH5 To Julio {props.postId}
</Typography>
<Typography variant="h5" component="h3">
{props.title}
</Typography>
<Typography component="p">
{props.postBody}
</Typography>
<img src={props.giphyUrl} style={giphyRes}/>
</Paper>
</CardContent>
<CardActions>
<IconButton aria-label="add to favorites">
<FavoriteIcon />
<div>Add Gif</div>
</IconButton>
<IconButton aria-label="share">
<EcoIcon />
<div>Add Photo</div>
</IconButton>
<form onSubmit={onSubmit}>
<div className={classes.container}>
<TextField
onChange = {onChange}
name='commentBody'
id="standard-full-width"
label="Reply To Post"
style={{ margin: 8 }}
placeholder="Reply to Post"
fullWidth
margin="normal"
InputLabelProps={{
shrink: true,
}}
/>
{/* <p><button>Send VH5</button></p> */}
<Button onSubmit={onSubmit} size="small">Submit</Button>
{/* <button onSubmit={onSubmit}>Submit Reply</button> */}
</div>
</form>
{/* <CommentInput onChange={onChange}/> */}
{/* <Button size="small">Submit</Button> */}
</CardActions>
<Paper className={classes.root} value={props.postId}>
<Typography variant="h5" component="h3">
{props.commentBody}
</Typography>
<Typography component="p">
{props.userIdName} replied to the post.
</Typography>
</Paper>
</Card>
</Grid>
)
// }
}
Just pass it to your handler
<PostBodyTemplate onChange={e => this.handleInputChange(e,post.id)} />
And inside handleChange
handleInputChange = (event, id) => {
console.log(event.target.name)
console.log(event.target.value)
console.log(this.props.posts)
this.setState({
[event.target.name]: event.target.value,
postId: id
})
}
Notice that You're already using arrow function notation, so you don't need to bind.
You're also receiving props incorrectly inside PostBodyTemplate. The following
export default function PostBodyTemplate(props, onChange, onSubmit)
Should be
export default function PostBodyTemplate({onChange, onSubmit}){}
Or
export default function PostBodyTemplate(props){
const { onChange, onSubmit} = props
}

React Display Separate Popover Component on Menu Click

I have the below menu:
So, ideally you'd click on those 3 dots which would open up another box next to it, I decided to use a popover(https://material-ui.com/components/popover/).. The problem is that when you click the 3 dots nothing happens. I assume this is because onClick function returns a functional popover component but that doesn't get displayed. I put in debuggers and alerts inside the functional component no hit at all.
This is those 3 dots
<IconButton
aria-describedby="simple-popover"
variant="contained"
onClick={e => this.moreClick(e, props.children)}
>
<More />
</IconButton>
This is moreClick function
moreClick = (e, officeAccount) => {
return (
<AccountFavoritesPopover element={e} officeAccount={officeAccount} />
);
};
This is the whole popover
import React from "react";
import Popover from "#material-ui/core/Popover";
export default function AccountFavoritesPopover(element, officeAccount) {
const anchorEl = element;
const open = Boolean(anchorEl);
const id = open ? "simple-popover" : undefined;
return (
<div>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
//onClose={alert}
anchorOrigin={{
vertical: "top",
horizontal: "right"
}}
transformOrigin={{
vertical: "top",
horizontal: "left"
}}
>
<div>{officeAccount}</div>
</Popover>
</div>
);
}
Does popover have to be inside the main file? Currently the 3 dots is in the main file and AccountFavoritesPopover is a whole separate file.
I attempted to put "AccountFavoritesPopover" code inside the main file but I cannot utilize useState in the main file because it's a class. Also, I cannot convert it to actual state and use setState because once setState kicks in, the menu will disappear...
Edit:
Below is the Menu
<Creatable
id="Select"
menuIsOpen={this.state.Focused}
components={{
Option: this.Option
}}
/>
Below is the Options inside menu
<div style={{ position: "relative" }}>
<components.Option {...props} />
<div id="MoreBox">
<IconButton
aria-describedby="simple-popover"
variant="contained"
onClick={e => this.moreClick(e, props.children)}
>
<More />
</IconButton>
</div>
</div>
Try this, this should work(Not tested)
Main.js
export default class Main extends Component {
constructor(props) {
this.state = {
selectedIndex: 0,
selectedId: 0,
anchorEl: null
};
}
moreClick = (anchorEl, selectedId, selectedIndex) => {
this.setState({
selectedId,
selectedIndex,
anchorEl,
open: true,
});
}
handleClose = () => {
this.setState({
open: false
});
}
render() {
const menu = [
{
id: 1,
text: '002',
more: 'more 003'
},
{
id: 2,
text: '003',
more: 'more 003'
},
{
id: 3,
text: '004',
more: 'more 003'
}
]
const menuDom = menu.map((m, index) => {
return (
<IconButton
key={m.id}
aria-describedby="simple-popover"
variant="contained"
onClick={e => this.moreClick(e.currentTarget, index, m.id)}>
{m.text}
</IconButton>
)
})
const more = (<More>{menu[this.state.selectedIndex].text}</More>)
return (
<div>
{menuDom}
<AccountFavoritesPopover open={this.state.open} anchorEl={this.state.anchorEl} onClose={this.handleClose}>
{more}
</AccountFavoritesPopover>
</div>
)
}
}
AccountFavoritesPopover.js
export default function AccountFavoritesPopover(open, anchorEl, onClose) {
const id = open ? "simple-popover" : undefined;
return (
<div>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={onClose}
anchorOrigin={{
vertical: "top",
horizontal: "right"
}}
transformOrigin={{
vertical: "top",
horizontal: "left"
}}
>
<div>{this.props.children}</div>
</Popover>
</div>
);
}

Show SnackBar Material UI when appear erron in Mutation

I use Snack bar from Materia-UI page (first example - Customized SnackBars)
const variantIcon = {
success: CheckCircleIcon,
warning: WarningIcon,
error: ErrorIcon,
info: InfoIcon,
};
const styles1 = theme => ({
success: {
backgroundColor: green[600],
},
error: {
backgroundColor: theme.palette.error.dark,
},
info: {
backgroundColor: theme.palette.primary.dark,
},
warning: {
backgroundColor: amber[700],
},
icon: {
fontSize: 20,
},
iconVariant: {
opacity: 0.9,
marginRight: theme.spacing.unit,
},
message: {
display: 'flex',
alignItems: 'center',
},
});
function MySnackbarContent(props) {
const { classes, className, message, onClose, variant, ...other } = props;
const Icon = variantIcon[variant];
return (
<SnackbarContent
className={classNames(classes[variant], className)}
aria-describedby="client-snackbar"
message={
<span id="client-snackbar" className={classes.message}>
<Icon className={classNames(classes.icon, classes.iconVariant)} />
{message}
</span>
}
action={[
<IconButton
key="close"
aria-label="Close"
color="inherit"
className={classes.close}
onClick={onClose}
>
<CloseIcon className={classes.icon} />
</IconButton>,
]}
{...other}
/>
);
}
MySnackbarContent.propTypes = {
classes: PropTypes.object.isRequired,
className: PropTypes.string,
message: PropTypes.node,
onClose: PropTypes.func,
variant: PropTypes.oneOf(['success', 'warning', 'error', 'info']).isRequired,
};
const MySnackbarContentWrapper = withStyles(styles1)(MySnackbarContent);
const styles2 = theme => ({
margin: {
margin: theme.spacing.unit,
},
});
class CustomizedSnackbar extends React.Component {
state = {
open: false,
};
handleClick = () => {
this.setState({ open: true });
};
handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
this.setState({ open: false });
};
render() {
return (
<div>
<Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
open={this.state.open}
autoHideDuration={2000}
onClose={this.handleClose}
>
<MySnackbarContentWrapper
onClose={this.handleClose}
variant="error"
message="This is an error message!"
/>
</Snackbar>
</div>
);
}
}
export default withStyles(styles2)(CustomizedSnackbar);
In the example the snack bar is shown when click on button "OPEN SUCCESS SNACKBAR"
I would like to show the error snack bar when Mutation from Apollo on my Form gives an error.
render(){
return(
<div>
<Mutation
mutation={this.mutationQuery}
onError={() =>
//here show Snack Bar
}
onCompleted={data => { console.log(data); }}
>
{mutation => (
//here is the form
)}
)}
The problem is I dont know how to trigger to show the SnackBar in the on Error function. How to change state of Snack Bar? I was trying the solution from here, but I receive an error that
openSnackbarFn is not a function
Thanks in advance.
Fundamentally, you want your Snackbar to be a sibling of your Mutation, and let their common parent (i.e. your component) handle the Snackbar open/closed state.
Class-style component
class FormWithMutationAndSnackbar extends React.Component {
state = {
open: false
}
handleOpen = () => this.setState({ open: true })
handleClose = () => this.setState({ open: false })
render() {
const { open } = this.state
return(
<React.Fragment>
<Mutation
mutation={this.mutationQuery}
onError={(err) => {
// use err to set Snackbar contents for example
this.handleOpen()
}
onCompleted={data => { console.log(data); }}
>
{mutation => (
//here is the form
)}
</Mutation>
<Snackbar
open={open}
onClose={this.handleClose}
// other Snackbar props
>
// Snackbar contents
</Snackbar>
</React.Fragment>
)
}
}
Functional component with Hooks
const FormWithMutationAndSnackbar = () => {
const [open, setOpen] = useState(false)
return(
<React.Fragment>
<Mutation
mutation={this.mutationQuery}
onError={(err) => {
// use err to set Snackbar contents for example
setOpen(true)
}
onCompleted={data => { console.log(data); }}
>
{mutation => (
//here is the form
)}
</Mutation>
<Snackbar
open={open}
onClose={() => setOpen(false)}
// other Snackbar props
>
// Snackbar contents
</Snackbar>
</React.Fragment>
)
}

Resources