I'm trying to test if a button toggle works as expected.
This is my code.
<>
<IconButton color="secondary" data-testid="HelpIconButton" ref={anchorRef} onClick={handleToggle}>
<Help />
</IconButton>
<Popper {...{ open }} anchorEl={anchorRef.current} transition disablePortal data-testid="HelpIconPopper">
{({ TransitionProps, placement }) => (
<Grow {...TransitionProps} style={{ transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList autoFocusItem={open} onKeyDown={handleListKeyDown}>
{menuItems.map(({ onClick, label }, index) => (
<MenuItem key={index} {...{ onClick }}>
{label}
</MenuItem>
))}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</>
In the browser, clicking the browser works as expected. Clicking on the IconButton toggles the Popper, removing and adding it into the DOM.
On test however, firing a click event placed the Popper in the DOM but its not removed when fired again. Hence, the popper isn't toggled.
Here is the test Code.
it('Ensures the help menu toggles when the help button is clicked.', () => {
const { getByTestId, queryByTestId } = render(<HelpButton />);
expect(queryByTestId('HelpIconPopper')).not.toBeInTheDocument();
fireEvent.click(getByTestId('HelpIconButton'));
expect(queryByTestId('HelpIconPopper')).toBeInTheDocument();
fireEvent.click(getByTestId('HelpIconButton'));
expect(queryByTestId('HelpIconPopper')).not.toBeInTheDocument();
});
A work around currently in use is using fireEvent.doubleClick
it('Ensures the help menu toggles when the help button is clicked.', () => {
const { getByTestId, queryByTestId } = render(<HelpButton />);
expect(queryByTestId('HelpIconPopper')).not.toBeInTheDocument();
fireEvent.doubleClick(getByTestId('HelpIconButton'));
expect(queryByTestId('HelpIconPopper')).not.toBeInTheDocument();
});
The second test passes.
Related
I am using #material-ui Accordion, the onChange function is not taking current value. Ex - when i am clicking on panel1 icon then its opening panel2 its not taking current value on icon click. I am sharing panel1 code? Am i missing something here?
const handleAccordionChange = (panel) => (event, newExpanded) => {
setExpanded(newExpanded ? panel : false);
};
<Accordion
expanded={expanded === 'panel1'}
onChange={handleAccordionChange('panel1')}
classes={{
root: classes.MuiAccordionroot
}}
>
<AccordionSummary
className="accrdSummary"
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1a-content"
id="panel1a-header"
classes={{ expandIcon: classes.MuiAccordionSummaryIcon }}
>
<Typography><div id="deployment" style={{ fontSize: "14px", fontWeight: "600" }}>Deploy</div></Typography>
</AccordionSummary>
<AccordionDetails className="accrdDetails">
<CreateButton
id="addEnvironmentButton"
variant="outlined"
startIcon={<AddIcon />}
className="envButtons"
onClick={addHandeler}
>
Add Environment
</CreateButton>
<br />
{environmentVar.map((element, index) => (
<Environment
onChangeEnv={changeEnv}
environmentVar={element}
index={index}
deleteHandeler={() => deleteHandeler(index)}
/>
))}
</AccordionDetails>
</Accordion>
Can somebody please help on same?
I am trying to pass different id, name to each accordion but this din't work out. In event.target i don't see name, ids.
Here is a working example of MUI Accordian https://codesandbox.io/s/qgk40c?file=/demo.tsx
const [expanded, setExpanded] = React.useState<string | false>(false);
const handleChange =
(panel: string) => (event: React.SyntheticEvent, isExpanded: boolean) => {
setExpanded(isExpanded ? panel : false);
};
Your code looks fine to me.
The above shared example was taken from MUI https://mui.com/material-ui/react-accordion/#controlled-accordion
I'm building a mobile version of a web app using Material UI and having trouble matching designs. I'm using MUI App-Bar together with MUI Modal to create something similar to the mockups shown below.
The expected behavior is that the user selects the button in the top right of the header to open the modal, and has the option to use the top right button again to close the modal. The user should also be able to select My App logo in the header to navigate to the home page.
The actual behavior is that the header bar is covered by the modal's backdrop; I can add styling like marginTop: 150px to the backdrop to visually achieve the expected result, but the button and the logo are still not usable.
Is there a way to override the backdrop component so that the header will be usable?
const useStyles = makeStyles((theme: Theme) => ({
card: {
position: "absolute",
top: "160px",
width: "90vw",
borderTopLeftRadius: "0px",
borderTopRightRadius: "0px",
},
backDrop: {
marginTop: "160px",
},
}));
const Header = (props) => {
const classes = useStyles();
const [menuOpen, setMenuOpen] = React.useState(false);
const handleOpenUserMenu = () => {
setMenuOpen(true);
};
const handleCloseUserMenu = () => {
setMenuOpen(false);
};
return (
<AppBar
position="static"
style={{
backgroundColor: "#FFFFFF",
zIndex: -1,
}}
>
<Container maxWidth="xl">
<Toolbar>
<Box sx={{ flexGrow: 1 }}>
<Link to={"/home"}>
<Logo className={classes.logo} />
</Link>
</Box>
<Box sx={{ flexGrow: 0 }}>
<IconButton onClick={handleOpenUserMenu}>
<MenuIcon />
</IconButton>
<Modal
open={menuOpen}
onClose={onClose}
BackdropProps={{ classes: { root: classes.backDrop } }}
>
<Card className={classes.card}>
<CardContent>
<MenuItems menuOptions={menuOptions} />
</CardContent>
</Card>
</Modal>
</Box>
</Toolbar>
</Container>
</AppBar>
);
};
As stated in MUIModal documentation the modal component is supposed to behave like that I would try to use menu component instead
Quoting documentation:
If you are creating a modal dialog, you probably want to use the Dialog component rather than directly using Modal. Modal is a lower-level construct that is leveraged by the following components:
Dialog
Drawer
Menu <--
Popover
I'm new react developer, here I have small problem my ClickAwayListener should close Popper when clicking 'x' or outside, which it does but problem is I have a component inside my Paper and that have Select when I click that select to see my options then options appear and fast disappear, I have tested and ClickAwayListener also affects this, how to disable the affect of ClickAwayListener to those inside Paper?
import ClickAwayListener from "#material-ui/core/ClickAwayListener";
const renderSettingsPopper = () => {
return (
<Popper
id={"graph"}
open={popupOpenState()}
placement="left-start"
anchorEl={anchorEl}
className={props.classes.components}
style={{ zIndex: 4 }}
modifiers={{
flip: {
enabled: true,
},
preventOverflow: {
enabled: true,
boundariesElement: "viewport",
},
}}
>
<ClickAwayListener onClickAway={closeSettingsPopup}>
<Paper className={props.classes.components}>
{renderSettingsForm()}
</Paper>
</ClickAwayListener>
</Popper>
);
};
This problem is harder than I anticipated but I finally found a solution. You need to track the Select open state inside the Popover.
<Select
label="Age"
defaultValue={10}
onOpen={() => setSelectOpen(true)}
MenuProps={{
TransitionProps: {
onExited: () => setSelectOpen(false),
},
}}
>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
And disable the close handler when one of the Select is currently opened:
<ClickAwayListener
onClickAway={() => {
if (selectOpen) return;
handleClose();
}}
>
I'm using this excellent example (Nested sidebar menu with material ui and Reactjs) to build a dynamic nested menu for my application. On top of that I'm trying to go one step further and put it into a Material UI appbar/temporary drawer. What I'd like to achieve is closing the drawer when the user clicks on one of the lowest level item (SingleLevel) however I'm having a tough time passing the toggleDrawer function down to the menu. When I handle the click at SingleLevel I consistently get a 'toggle is not a function' error.
I'm relatively new to this so I'm sure it's something easy and obvious. Many thanks for any answers/comments.
EDIT: Here's a sandbox link
https://codesandbox.io/s/temporarydrawer-material-demo-forked-v11ur
Code is as follows:
Appbar.js
export default function AppBar(props) {
const [drawerstate, setDrawerstate] = React.useState(false);
const toggleDrawer = (state, isopen) => (event) => {
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return;
}
setDrawerstate({ ...state, left: isopen });
};
return (
<Box sx={{ flexGrow: 1 }}>
<AppBar position="static" color="secondary">
<Toolbar>
<IconButton
size="large"
edge="start"
color="primary"
aria-label="menu"
onClick={toggleDrawer('left', true)}
>
<MenuIcon />
</IconButton>
<img src={logo} alt="logo" />
</Toolbar>
<Drawer
anchor='left'
open={drawerstate['left']}
onClose={toggleDrawer('left', false)}
>
<Box>
<AppMenu toggleDrawer={toggleDrawer} />
</Box>
</Drawer>
</AppBar>
</Box >
)
}
Menu.js
export default function AppMenu(props) {
return MenuItemsJSON.map((item, key) => <MenuItem key={key} item={item} toggleDrawer={props.toggleDrawer} />);
}
const MenuItem = ({ item, toggleDrawer }) => {
const MenuComponent = hasChildren(item) ? MultiLevel : SingleLevel;
return <MenuComponent item={item} toggleDrawer={toggleDrawer} />;
};
const SingleLevel = ({ item, toggleDrawer }) => {
const [toggle, setToggle] = React.useState(toggleDrawer);
return (
<ListItem button onClick={() => { toggle('left', false) }}>
<ListItemIcon>{item.icon}</ListItemIcon>
<ListItemText primary={item.title} />
</ListItem>
);
};
const MultiLevel = ({ item }) => {
const { items: children } = item;
const [open, setOpen] = useState(false);
const handleClick = () => {
setOpen((prev) => !prev);
};
return (
<React.Fragment>
<ListItem button onClick={handleClick}>
<ListItemIcon>{item.icon}</ListItemIcon>
<ListItemText primary={item.title} secondary={item.description} />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{children.map((child, key) => (
<MenuItem key={key} item={child} />
))}
</List>
</Collapse>
</React.Fragment>
);
};
You shouldn't call a react hook inside of any function that is not a react component. Please see React Rules of Hooks
What you could do instead is pass setToggle directly into the Drawer component as a prop and do something like this for it's onClick attribute:
onClick={() => setToggle(<value>)}
For some reason, when I change material-ui <SpeedDial> to remove the prop onMouseEnter={handleOpen} so that the speed dial only opens upon clicking the FAB instead of on hover, the onClick event in <SpeedDialAction> does not get triggered when I click a speed dial menu item. I feel like I'm missing something fundamental here.
return (
<SpeedDial
ariaLabel="Add"
className={classes.root}
icon={<SpeedDialIcon />}
onClick={handleClick}
onClose={handleClose}
onBlur={handleClose}
// onMouseEnter={handleOpen}
// onMouseLeave={handleClose}
open={open}
direction={mobile ? 'up' : 'down'}
>
{actions.map(action => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
tooltipOpen
classes={{ staticTooltipLabel: classes.staticTooltipLabel }}
onClick={e => {
e.preventDefault();
alert('x');
}}
/>
))}
</SpeedDial>
);
Using preventDefault will not cause the click event to not propagate to the parent element (which from what I understand is what you are trying to get).
You should use the stopPropagation instead:
<SpeedDial
ariaLabel="Add"
className={classes.SpeedDial}
icon={<SpeedDialIcon />}
onClick={handleClick}
open={open}
>
{actions.map(action => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
tooltipOpen
onClick={e => {
e.stopPropagation();
alert("x");
}}
/>
))}
</SpeedDial>
Check the following example: https://codesandbox.io/s/speeddial-open-on-click-rleg5?file=/demo.js