Test login component with react hooks, - reactjs

I am trying to test the Login.js component of my app.
I am actually trying to test 3 things:
When the values in the inputs field are not filled the disabled prop is true.
When the values in the inputs field are filled the disabled prop is false.
When the values in the inputs field are filled the login-button should call handleSubmit.
My test is failing in the second test and regarding the third test, I am not sure how to approach this test.
If someone has a good idea what I am doing wrong will be appreciated.
//Login.js
import React, { useState } from 'react';
import axios from 'axios';
import useStyles from '../styles/LoginStyle';
import { useStatefulFields } from '../../hooks/useStatefulFields';
function Login({ success }) {
const [values, handleChange] = useStatefulFields();
const [error, setError] = useState();
const handleSubmit = async () => {
await axios.post('www.example.com', {values}, {key})
.then((res) => {
if (res.data.success) {
success();
} else {
setError(res.data.error);
}
})
};
return (
<div>
<p className={classes.p}>Personalnummer</p>
<input
type="number"
className={classes.input}
onChange={handleChange}
name="personal_number"
title="personal_number"
/>
<p className={classes.p}>Arbeitsplatz</p>
<input
type="number"
onChange={(e) => handleChange(e)}
name="workplace"
title="workplace"
className={classes.input}
/>
<ColorButton
id="login-button"
className={
(values.password && values.personal_number && values.workplace)
? classes.button
: classes.buttonGray
}
disabled={!values.password && !values.personal_number && !values.workplace}
size="large"
onClick={
values.password && values.personal_number && values.workplace
? handleSubmit
: () => {}
}
>
</ColorButton>
</div>
)
}
//useStatefulFields.js
import { useState } from 'react';
export function useStatefulFields() {
const [values, setValues] = useState({});
const handleChange = (e) => {
setValues({
...values,
[e.target.name]: e.target.value,
});
};
return [values, handleChange];
}
//Login.test.js
import React from 'react';
import { shallow } from 'enzyme';
import Login from '../components/Workers/Login';
let wrapper;
beforeEach(() => {
wrapper = shallow(<Login success={() => {}} />);
});
test('check if login button is disabled', () => {
const loginButton = wrapper.find('#login-button');
expect(loginButton.prop('disabled')).toEqual(true);
});
test('check if login button is not disabled', () => {
const loginButton = wrapper.find('#login-button');
wrapper.find('input[name="workplace"]').simulate('change', { target: { value: 'test' } });
wrapper.find('input[name="password"]').simulate('change', { target: { value: 'test' } });
wrapper.find('input[name="personal_number"]').simulate('change', { target: { value: 'test' } });
expect(loginButton.prop('disabled')).toEqual(false);
});

Pass name and value once simulate the change.
test('check if login button is not disabled', () => {
let loginButton = wrapper.find('#login-button');
wrapper.find('input[name="workplace"]').simulate('change', { target: { name:'workplace', value: 'test' } });
wrapper.find('input[name="password"]').simulate('change', { target: { name:'password', value: 'test' } });
wrapper.find('input[name="personal_number"]').simulate('change', { target: {name: 'personal_number', value: 'test' } });
loginButton = wrapper.find('#login-button');
loginButton.props().onClick(); // for handleSubmit
expect(loginButton.prop('disabled')).toEqual(false);
});
Make sure that you have the input field for the password also that I am not seeing in your question. Otherwise, the test will fail again.

Related

Notification.requestPermission() is not a function

I am making a website in which I am able to send notifications to all the users but there is a problem, it says
Notification.requestPermission() is not a function
Here is my code:
import React, { useState } from "react";
const Notification = () => {
const [input, setInput] = useState("");
const handleInput = (e) => {
e.preventDefault();
setInput(e.target.value);
};
const sendNotification = () => {
Notification.requestPermission().then((perm) => {
if (perm === "granted") {
new Notification(input, {
body: "Go check it out!",
});
}
});
};
return (
<>
<input type="text" value={input} onChange={handleInput} />
<button onClick={sendNotification}>Send</button>
</>
);
};
export default Notification;
I am using react
Thank You in advance!

React Query: InvalidateQuery not working to update users list

I have a simple app that has a form and list. Currently, I am using query client.InvalidateQueries to update the users' list after submitting the form. As the documentation says, using InvalidateQuery will trigger refetching, but somehow I had not seen an update to the list after adding users. Am I missing something?
Add User
import React, { useState } from 'react';
import { useFormik } from 'formik';
import Input from '../../elements/Input/Input';
import * as Yup from 'yup';
import { QueryClient, useMutation } from 'react-query';
import axios from 'axios';
const queryClient = new QueryClient();
const CreateItemView = () => {
function gen4() {
return Math.random().toString(16).slice(-4);
}
function generateID(prefix) {
return (prefix || '').concat([gen4(), gen4(), gen4(), gen4(), gen4(), gen4(), gen4(), gen4()].join(''));
}
const mutation = useMutation(
(formData) => {
axios.post('http://localhost:8000/users', formData).then((response) => console.log(response));
},
{
onSuccess: () => {
queryClient.invalidateQueries('users');
},
},
);
const [data, setData] = useState([]);
const initialValues = {
id: '',
name: '',
email: '',
channel: '',
};
const onSubmit = (values, { resetForm }) => {
setData([...data, values]);
const ID = generateID().toString();
values.id = ID;
mutation.mutate(values);
resetForm();
};
const validationSchema = Yup.object({
name: Yup.string().required('Required!'),
email: Yup.string().email('Invalid format').required('Required!'),
channel: Yup.string().required('Required!'),
});
const formik = useFormik({
initialValues,
onSubmit,
validationSchema,
});
return (
<div>
<form onSubmit={formik.handleSubmit}>
<Input type={'text'} name={'name'} id={'name'} label={'Name'} formik={formik} />
<Input type={'email'} name={'email'} id={'email'} label={'Email'} formik={formik} />
<Input type={'text'} name={'channel'} id={'channel'} label={'channel'} formik={formik} />
<button type="submit">Submit</button>
</form>
</div>
);
};
export default CreateItemView;
User's list
import React from 'react';
import ListView from './ListView';
import { useQuery } from 'react-query';
import axios from 'axios';
const getUsers = async () => {
const response = await axios.get('http://localhost:8000/users');
return response.data;
};
const ListContainer = () => {
const { data, isLoading, isFetching } = useQuery('users', getUsers);
console.log('list', data);
return <div>{isFetching ? 'loading...' : <ListView dataSource={data} />}</div>;
};
export default ListContainer;
You have to return the fetch function in the mutation. The onSuccess handler will fire when the promise is resolved.
const mutation = useMutation(
formData => {
return axios.post('http://localhost:8000/users', formData)
.then((response) => console.log(response));
},
{
onSuccess: () => {
queryClient.invalidateQueries('users');
},
},
);
i think the solution for your problem is to replace this :
onSuccess: () => {
queryClient.invalidateQueries('users');
},
By this :
onSettled:() => {
queryClient.invalidateQueries('users');
},
you should use the same instance of queryClient from the root of your app which is accessible via the useQueryClient() hook.
Hence you should be doing const queryClient = useQueryClient() instead of generating new instance with const queryClient = new QueryClient().

handling multiple input in a array of objects

I am trying to handle multiple inputs in a array of objects and each object I am mapping to the input field in a form .Now I am getting empty array I want to know where I am doing wrong .I am also sending post axios request and the value I am getting in input text fields through urlParmas using hooks.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { useLocation } from "react-router-dom";
import axios from "axios";
import { Button, TextField } from "#material-ui/core";
const MyComponent = () => {
const [sale, setSale] = useState("");
const [district, setDistrict] = useState("");
const obj = [
{
key: 1,
label: "Sales office",
handleChange: (e) => {
setSale(e.target.value);
},
val: sale,
},
{
key: 2,
label: "Sales district",
handleChange: (e) => {
setDistrict(e.target.value);
},
val: sale,
},
];
const [object, setObject] = useState(obj);
const handleSubmit = () => {
axios
.post("localhots:8000", {
sale,
district,
})
.then(() => {});
setSale("");
setDistrict("");
};
const handleComment = (e, item) => {
e.preventDefault();
let result = object;
result = result.map((el) => {
if (el.name === item.name) el.name = e.target.value;
return el;
});
console.log(result);
setObject(result);
};
const { search } = useLocation();
const urlParams = Object.fromEntries([...new URLSearchParams(search)]);
useEffect(() => {
const { salesoff } = urlParams;
setSale(salesoff);
}, []);
object.map((item, index) => {
return (
<div>
<form
onSubmit={(e) => {
e.target.reset();
}}
>
<TextField
name={item.name}
value={item.sale}
label={item.label}
onChange={handleChange}
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
onClick={() => handleSubmit()}
>
Submit
</Button>
</form>
</div>
);
});
};
export default MyComponent;

How to mock out a response triggered by a function on the same component in a wrapper?

My component has a function which is triggered when a save button is clicked. Then based on that a fetch is done in the wrapper and the fetch response is then again passed down as a prop. So the putFn property accepts a function, the putResponse accepts a Promise.
I would like to mock the wrapper and focus in this test just on the component, in this example "myComponent".
Given the following test setup:
./MyComponent.test.js
function setup() {
let mockPutResponse;
const putMockFn = jest.fn(() => {
mockPutResponse = Promise.resolve(
JSON.stringify({ success: true, loading: false })
);
});
render(<MyComponent putFn={putMockFn} putResponse={mockPutResponse} />);
return { putMockFn };
}
test("MyComponent saves the stuff", async () => {
const { putMockFn } = setup();
const button = screen.getByRole("button", { name: /save changes/i });
userEvent.click(button);
// this passes
expect(putMockFn).toHaveBeenCalled();
// this does not pass since the component shows this message
// based on the putResponse property
expect(await screen.findByText(/saved successfully/i)).toBeInTheDocument();
});
How can I mock the return value passed into the putResponse property?
The component I want to test is something in the line of this:
./MyComponent.js
import React from "react";
const MyComponent = ({ putFn, putResponse }) => {
return (
<form onSubmit={putFn}>
{putResponse?.loading && <p>Loading...</p>}
{putResponse?.success && <p>saved succesfully</p>}
<label htmlFor="myInput">My input</label>
<input name="myInput" id="myInput" type="text" />
<button>Save changes</button>
</form>
);
};
export default MyComponent;
Which is used by a kind of wrapper, something similar to:
./App.js (arbitrary code)
import React, { useState } from "react";
import MyComponent from "./MyComponent";
export default function App() {
const [wrapperPutResponse, setWrapperPutResponse] = useState();
const handlePut = e => {
e.preventDefault();
setWrapperPutResponse({ loading: true });
// timeout, in the actual app this is a fetch
setTimeout(function() {
setWrapperPutResponse({ success: true, loading: false });
}, 3000);
};
return <MyComponent putFn={handlePut} putResponse={wrapperPutResponse} />;
}
Created a sandbox: codesandbox.io/s/bold-cloud-2ule8?file=/src/MyComponent.test.js
You can create a Wrapper component to render and control MyComponent
import React, { useState, useEffect } from "react";
import { screen, render } from "#testing-library/react";
import userEvent from "#testing-library/user-event";
import MyComponent from "./MyComponent";
const mockPutResponse = jest.fn()
function setup() {
const Wrapper = () => {
const [clicked, setClicked] = useState(false)
const response = clicked ? { success: true, loading: false} : null
useEffect(() => {
mockPutResponse.mockImplementation(() => {
setClicked(true)
})
}, [])
return <MyComponent putFn={mockPutResponse} putResponse={response} />
}
render(<Wrapper />);
}
test("MyComponent saves the stuff", async () => {
setup()
// expect(await screen.findByText(/loading.../i)).toBeInTheDocument();
const button = screen.getByRole("button", { name: /save changes/i });
userEvent.click(button);
// this passes
expect(mockPutResponse).toHaveBeenCalled();
// had some issue with expect acting up when using the toBeInDocument assertion
// so I used this one instead
const text = await screen.findByText(/saved succesfully/i)
expect(text).toBeTruthy()
});
Codesandbox

Dispatching action on Redux during asynchronus API request using redux thunk

I am quite new to redux and react. I have also checked out a number of ways here to solve my problem but it appears I am not making any headway.
I intend performing an asynchronous operation using redux-thung following the tutorial https://github.com/reduxjs/redux-thunk, but the challenge I have is that the the function sendApplication() does not dispatch the action nextPage() neither does the function hitUrl() works. I have been on this issues for days. Someone should help me out please.
import React from 'react';
import { withStyles} from '#material-ui/styles';
import { FormLabel, TextField, Button } from '#material-ui/core';
import {connect} from 'react-redux';
import { nextPage, previousPage, enableBtnAvailability} from '../../actions/formPageController';
import { updateTextValueAvailability, clearField } from '../../actions/formInputController';
import { useStyles } from '../Styles/formStyles';
import { ValidatorForm, TextValidator} from 'react-material-ui-form-validator';
import sendApplication from '../../utility/sendRequest';
import { bindActionCreators } from 'redux';
const axios = require('axios');
const AvailablityTab= withStyles({
})((props) => {
console.log(props);
const handleChange=(e)=>{
const name= e.target.name;
const value = e.target.value;
const {updateTextValueAvailability} = props;
updateTextValueAvailability(name,value);
let unfilledFormFieldArray = props.text.filter((item)=> {
console.log(item);
return item.value=== "";
})
console.log(unfilledFormFieldArray);
console.log(unfilledFormFieldArray.length);
if(unfilledFormFieldArray.length ===0){
const {enableBtnAvailability} = props;
enableBtnAvailability();
}
}
const handleSubmit=()=>{
//const {postApplication} = props;
sendApplication();
console.log(props);
console.log('he submit');
}
const hitUrl = async function () {
//alert('hi')
try {
console.log(3);
const response = await axios.get('http://localhost:1337/api/v1/application/fetch-all');
console.log(response);
return response;
} catch (error) {
console.error(error);
}
};
const sendApplication = () => {
console.log(4);
console.log(props);
return function(props) {
console.log('xyz');
console.log(props);
const {nextPage} = props;
// dispatch(nextPage());
nextPage();
console.log(5);
alert('hi2')
return hitUrl().then(
() => {
console.log('thunk success');
nextPage();
},
() => {
console.log('thunk error');
//props.dispatch(previousPage())
},
);
};
}
const handlePrevious=()=>{
const {previousPage} = props;
previousPage();
}
console.log(props);
const classes = useStyles();
let validationRule = ['required'];
let errorMessages = ['This field is required'];
return (
<div className="formtab">
<ValidatorForm //ref="form"
onSubmit={handleSubmit}
onError={errors => console.log(errors)}
>
{props.text.map((each)=>{
let onFocus = false;
if(each.id === 1){
onFocus = true;
}
return(<div className={classes.question} key={each.id}>
<FormLabel className={classes.questionLabel} component="legend">{each.label}</FormLabel>
<TextValidator
id={"filled-hidden-label"}
className={classes.textField}
hiddenLabel
variant="outlined"
fullWidth
name={each.name}
onChange={handleChange}
value={each.value}
margin= "none"
placeholder={each.placeholder}
validators={validationRule}
errorMessages={errorMessages}
autoFocus= {onFocus}
/>
</div>)
})}
<div className={classes.buttondiv} >
<Button className={classes.prev} variant="contained" onClick={handlePrevious}>Previous</Button>
<Button className={classes.next} variant="contained" type="submit" disabled={!props.btnActivator} >Submit</Button>
</div>
</ValidatorForm>
</div>
)});
const mapStateToProps= (state)=>{
const availablity = state.availabilityQuestion;
return {
text: availablity.text,
radio: availablity.radio,
btnActivator: state.btnActivator.availability
}
}
const mapDispatchToProps = dispatch => bindActionCreators({
postApplication: sendApplication,
previousPage,
enableBtnAvailability,
updateTextValueAvailability,
nextPage,
clearField
}, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(AvailablityTab);
Since sendApplication returns a function, but does not execute it, you can call it like this:
sendApplication()(props); // it looks like you expect props to be passed to your function
This should successfully execute your nextPage function and return the value returned by hitUrl.
The alternative would be to execute the function instead of returning it
sendApplication(props);
...
const sendApplication = (props) => {
console.log('xyz');
console.log(props);
const {nextPage} = props;
// dispatch(nextPage());
nextPage();
console.log(5);
alert('hi2')
return hitUrl().then(
() => {
console.log('thunk success');
nextPage();
},
() => {
console.log('thunk error');
//props.dispatch(previousPage())
},
);
};
Now we've eliminated the internal function and just called it directly instead. Now calling sendApplication will return the return value of hitUrl.

Resources