I'm developing an app with React, and I'm new to its testing framework. I'm trying to develop a testing method to check if an onClick button is clicked. I've read that I need a "spy" function to get the information about whether or not it's been called.
My testing method:
test("Btn1 click", () => {
const wrapper = mount(<Register />);
const spyOn = jest.spyOn(wrapper.instance(), "openWindow")
const element = wrapper.find({id: "btn-how-to-choose-provider"}).first();
element.simulate("click");
expect(spyOn).toHaveBeenCalled();
});
The Register.js component that I want to test:
export default function Register() {
const classes = useStyles();
function openWindow(url) {
window.open(url);
}
return (
<div>
<NavBar />
<Container component="main" maxWidth="sm">
<Card className={classes.root} elevation={4}>
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<AccountCircleIcon />
</Avatar>
<Typography component="h1" variant="h5">Hi! Welcome to Solid.</Typography>
<div className={classes.form}>
<Button
id="btn-how-to-choose-provider"
fullWidth
color="primary"
className={classes.link}
startIcon={<EmojiPeopleIcon/>}
onClick={(e) => openWindow('https://solid.inrupt.com/how-it-works')}
>How to choose a Provider?</Button>
<Button
id="btn-inrupt-provider"
fullWidth
variant="outlined"
color="primary"
className={classes.submit}
startIcon={<ContactsOutlinedIcon/>}
onClick={(e) => window.open('https://inrupt.net/register')}
>Inrupt</Button>
<Button
id="btn-solid-community-provider"
fullWidth
variant="outlined"
color="primary"
className={classes.submit}
startIcon={<ContactsIcon/>}
onClick={() => window.open('https://solid.community/register')}
>Solid Community</Button>
</div>
</div>
</Card>
</Container>
<Grid
item
xs={12}
sm={12}
md={12}
style={{marginTop: '36rem'}}
>
<Footer />
</Grid>
</div>
);
}
With this configuration, I obtain the following error:
TypeError: Cannot read property 'openWindow' of null
34 | test("Btn1 click", () => {
35 | const wrapper = mount(<Register />);
> 36 | const spyOn = jest.spyOn(wrapper.instance(), "openWindow")
| ^
37 | const element = wrapper.find({id: "btn-how-to-choose-provider"}).first();
38 | element.simulate("click");
39 | expect(spyOn).toHaveBeenCalled();
at ModuleMockerClass.spyOn (node_modules/jest-mock/build/index.js:837:28)
at Object.<anonymous> (src/components/containers/register/Register.test.js:36:24)
My question is: is there any way to test the onClick function of a button with Jest or any other testing framework?
Okay, so after more research, I've found that the React Testing Library provides some tools to test the onClick event of some buttons (at least those that are like the ones I've shown in my example above). One of those tools is the fireEvent, which helps to accomplish what I wanted:
test("Click", () => {
const {container} = render(<Register />);
const button = getByTestId(container, 'btn-how-to-choose-provider');
fireEvent.click(button);
});
Related
getting this error:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
const MapBox = () => {
const mapContainer = useRef(null);
const map = useRef(null);
const [visible, setVisible] = useState(false)
const onClick = () => setVisible(true);
const closeHandler = () => {
setVisible(false);
}
useEffect(() => {
}, [visible])
return (
<>
<MapContainer ref={mapContainer} className="map-container" onClick={onClick}></MapContainer>
<ModalContainer visible={visible} closeHandler={closeHandler} />
</>
)
}
The modal container is just a simple modal pop, only returning a jsx component. That only shows if visible is true, and if runs closeHandler is a button is pressed.
const ModalContainer = ({ visible, closeHandler }) => {
useEffect(() => {}, [visible])
return (
<>
{visible && (
<div>
<Modal
closeButton
aria-labelledby="modal-title"
open={visible}
onClose={closeHandler}
>
<Modal.Header>
<Text id="modal-title" size={18}>
Welcome to
<Text b size={18}>
NextUI
</Text>
</Text>
</Modal.Header>
<Modal.Body>
<Input
clearable
bordered
fullWidth
color="primary"
size="large"
placeholder="Email"
/>
<Input
clearable
bordered
fullWidth
color="primary"
size="large"
placeholder="Password"
/>
<Row justify="space-between">
<Checkbox>
<Text size={14}>
Remember me
</Text>
</Checkbox>
<Text size={14}>
Forgot password?
</Text>
</Row>
</Modal.Body>
<Modal.Footer>
<Button auto flat color="error" onClick={closeHandler}>
Close
</Button>
<Button auto onClick={closeHandler}>
Sign in
</Button>
</Modal.Footer>
</Modal>
</div>)
}
</>
);
}
Any help would be much appreciated, all other people on here with this error message are running async code etc. This one is a lot simpler and the error only comes up the first time the click to close event happens.
Describe the bug
Hi everyone,
I'm trying to generate a PDF which includes some user-provided info. In particular, I want my user to choose the filename of the pdf which will be also the title of it.
In order to do this I'm using:
"#react-pdf/renderer": "^1.6.12" for the pdf rendering
"react-bootstrap": "^1.3.0" for the page layout
"formik": "^2.2.0" for form management
The problem I have consists in the fact that whenever the user starts typing the desired filename/title in the apposite input the page crashes with the following error:
Unhandled Rejection (Error): write after end
3 stack frames were expanded.
writeAfterEnd
node_modules/stream-browserify/node_modules/readable-stream/lib/_stream_writable.js:288
(anonymous function)
node_modules/stream-browserify/node_modules/readable-stream/lib/_stream_writable.js:332
addContent
node_modules/#react-pdf/pdfkit/dist/pdfkit.browser.es.js:4184
After deeper investigations, I noticed that the problem resides in the "dynamicity" of the generated pdf content.
The code is the following:
const DownloadModal = ({configuration}) => {
const [show, setShow] = useState(false);
const formik = useFormik({
initialValues: {
name: "",
}
});
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<>
<Button variant="primary"
onClick={() => {
handleShow()
}}
>
Download PDF <DownloadIcon/>
</Button>
<Modal size={"lg"} show={show} onHide={handleClose} animation={true}>
<Modal.Header closeButton>
<Modal.Title>Choose filename</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form onSubmit={(event) => {
event.preventDefault();
}}>
<Form.Group as={Row}>
<Form.Label column sm={2}>
Filename
</Form.Label>
<Col sm={10}>
<InputGroup className="mb-3">
<FormControl
type="text"
id={"name"}
name={"name"}
placeholder="filename"
onChange={formik.handleChange}
value={formik.values.name}
autoComplete={"off"}
/>
<InputGroup.Append>
<InputGroup.Text id="basic-addon2">.pdf</InputGroup.Text>
</InputGroup.Append>
</InputGroup>
</Col>
</Form.Group>
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
<DownloadPDFButton
data={configuration}
title={formik.values.name}
/>
</Modal.Footer>
</Modal>
</>
);
};
const DownloadPDFButton = ({data, title}) => {
return (
<PDFDownloadLink
document={
<PdfDocument
data={data}
title={{title}}
/>}
fileName={`${title}.pdf`}
className={"btn btn-link"}
>
{({blob, url, loading, error}) =>
loading ? <Spinner animation="border" variant="primary"/> : "Download"
}
</PDFDownloadLink>
)
}
I think that the problem resides here, in the way I display title.title. But I really cannot find a way to make it works.
const Title = ({title}) => {
return (
<View style={styles.titleContainer}>
<Text>{title.title}</Text>
</View>
)
}
;
const PdfDocument = (props) => {
const configurationData = {
plant: props.data.plant,
flow: props.data.flow,
optionals: props.data.optionals
};
const utilitiesData = {
total_installed_power: props.data.total_installed_power,
total_absorbed_power: props.data.total_absorbed_power,
makeup_water: props.data.makeup_water,
compressed_air: props.data.compressed_air
};
return (
<Document>
<Page key={"page-0"} style={styles.page}>
<Image style={styles.logo} src={logo}/>
<Title title={props.title}/>
<ConfigurationPDF configuration={configurationData}/>
<ResultsPDF results={utilitiesData}/>
</Page>
</Document>
);
};
Thanks to everyone in advance!!
Running environment:
OS: [MacOS]
Browser [chrome, safari]
I think your problem is solved now
I only convert formik state to react state
Codesandbox: https://codesandbox.io/s/reverent-browser-1bn4n
So, I'm trying to create a signup form for a web-app - but are running into a few issues.
I'm using hooks with a function to render signup page, which I'm routing to from the login page.
It works fine assuming I return the html directly from the return in the function (signup), but once the signup has been engaged, I wish swap the form for an acknowledge of it being send.
From what I can tell, people simply wrap each html in an arrow function and then toggles between using a bool or similar. But that's where the issues arrive.
TLDR;
One of the signup textfields autocompletes, fetching from an API. The API then saves the content in a hook variable (address). The second I update the address variable, the form seem to reset - cursor going to the first inputfield.
This only happens when I wrap the html in components, not if I insert all the html in the (signup) return.
I tried to clean it up a bit, but the code more or less look like this.
Any help or pointers would be great :)
export default function SignUp(props)
{
const [activeStep, setActiveStep] = React.useState(0);
const [addresses, setAddresses] = React.useState([{ tekst: '' }]);
const APICall = async (e) =>
{
e.preventDefault();
// Fetchs JSON and set Addresses hook
}
const handleSubmit = props => form =>
{
form.preventDefault()
setActiveStep(activeStep + 1);
}
const CreateAccount = (e) =>
{
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Opret konto
</Typography>
<form className={classes.form} noValidate
onSubmit={handleSubmit(props)}>
<Autocomplete
id="address"
options={addresses}
getOptionLabel={(option) => option.tekst}
style={{ width: 300 }}
renderInput={(params) =>
<TextField {...params} label="Combo box" variant="outlined" onChange={userTest} />
}
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Sign Up
</Button>
</form>
</div>
<Box mt={5}>
<Copyright />
</Box>
</Container>
);
}
const CreateAccountACK = () =>
{
return (
<React.Fragment>
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Nyt konto oprettet!
</Typography>
<Button
type="button"
variant="contained"
color="primary"
className={classes.submit}
onClick={() => { props.history.push('/') }}
>
Ok
</Button>
</div>
<Box mt={8}>
<Copyright />
</Box>
</Container>
</React.Fragment>
);
}
return (
<div>
{activeStep == 0 ? <CreateAccount /> : <CreateAccountACK />}
</div>
)
}
Got it working by breaking each function into its own functional component, then render these from the main class (signup) using conditionals. Swapping between pages are handled by callback to "handlesubmit" in this function. Pass history as the final page routes back to main. Feel like this isn't the best way of doing this tho =D, but it avoids the issues with re-renders while typing.
So now the Signup just return ...
export default function SignUp(props)
{
const [activeStep, setActiveStep] = React.useState(0);
const handleSubmit = props => form =>
{
form.preventDefault()
console.log(form)
setActiveStep(activeStep + 1);
}
return (
<div>
{activeStep == 0 ? <CreateAccount handleSubmit={handleSubmit} /> : <CreateAccountACK handleSubmit={handleSubmit} history={props.history}/>}
</div>
)
}
And each function, hooks/variables exist in their own file/function.
I am opening Model (child Component) on Button Click from Parent Component, it opens very well but its not closing and it shows some error:
Uncaught TypeError: setOpen is not a function from Child Component
Here is My Parent Component
<TableCell>
<Button
variant="contained"
size="small"
color="primary"
onClick={() => deleteHandler(index)}
>
Delete Me
</Button>
</TableCell>
{console.log(open)}
{open && <AddList open={open} setOpen={open} />}
My Child Component
export default function TransitionsModal(open, setOpen) {
const classes = useStyles();
// const [openL, setOpenL] = React.useState(null);
// const handleOpen = () => {
// setOpen(true);
// };
const handleClose = () => {
setOpen(!open);
};
return (
<div>
<Modal
aria-labelledby="transition-modal-title"
aria-describedby="transition-modal-description"
className={classes.modal}
open={open}
onClose={handleClose}
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500
}}
>
<Fade in={open}>
<div className={classes.paper}>
<h2 id="transition-modal-title">Transition modal</h2>
<p id="transition-modal-description">
react-transition-group animates me.
</p>
</div>
</Fade>
</Modal>
</div>
);
}
Your first issue is that you are passing a Boolean for the setOpen prop rather than the setOpen function itself, so change it to setOpen={setOpen}.
// RenderList.js
const RenderList = props => {
// ...
return (
...
{open && <AddList open={open} setOpen={setOpen} />}
)
}
Your second issue is that you're not destructing props properly in the TransitionsModal component. Use {} to destruct the props object and grab what you need.
// AddList.js
export default function TransitionsModal({ open, setOpen }) {
// ...
}
Here's the fixed example:
CodeSandbox
Hope this helps.
Hi take a look at this
https://codesandbox.io/s/frosty-bird-5yh5g
in RenderList.js you didn't pass setOpen
{open && <AddList open={open} setOpen={setOpen} />}
also export default function TransitionsModal({ open, setOpen }) {
I have button in a Material Table. I am using react routers to route pages to different URLs.
This page is supposed to set up functions and call the Material Table <MuiTable> and then render a button below the material table. It is set up this way due to the reusability of the MuiTable element.
export default function ListJobs(props) {
const url = 'http://localhost:8000/api/Jobs/'
const [data, loading] = DataLoader(url);
const handleEdit = (e,rowData) => {
<EditJob id={rowData.id} />
}
const handleDelete = (e,rowData) => {
//edit operation
<ListJobs />
DataDelete(url, rowData.id)
}
const createButton =
<div style={{display: 'flex', justifyContent:'center', alignItems:'center'}}>
<Button
component={Link} to='/Job/Create'
variant="contained"
color="primary">
Create New Job
</Button>
</div>
return (
<> {loading ? (
<Grid
container
spacing={0}
alignItems="center"
justify="center"
style={{ minHeight: '90vh' }}
>
<CircularProgress size="10vh" />
</Grid>
) : (
<MuiTable
model="Job"
data={data}
url={url}
handleEdit={handleEdit}
handleDelete={handleDelete}
createButton={createButton}
/>
)}
</>
);
}
This currently throws and error "Expected an assignment or function call and instead saw an expression" on the lines that call <EditJob...> and <ListJobs>. I know this is not the correct way to write this but, I want to change it to using react routers. I have my routers set up already but don't know how to use them in this instance. I want it to work something like this.
const handleEdit = (e,rowData) => {
<component ={Link} to='Jobs' />
}
I know this isn't correct eit,her because the react router link must be inside of a component like a<button>or <MenuItem>.
Try to return EditJob and ListJobs
const handleEdit = (e,rowData) => {
return <EditJob id={rowData.id} /> // return the function <EditJob />
}
const handleDelete = (e,rowData) => {
//edit operation
DataDelete(url, rowData.id) // Any operation before the return
return <ListJobs /> // return the function <ListJobs />
}