How to test formik yup validation error on blur? - reactjs

I've written the following test:
it('validates the first name cannot be blank', () => {
const { findByLabelText, getByText } = render(<Profile />);
const firstName = findByLabelText('first name');
firstName.value = '';
fireEvent.blur(firstName);
const error = getByText('First name is required');
expect(error).not.toBeNull();
});
After the test runs I get the error:
Unable to find the "window" object for the given node.
How do I get this test to pass?

So it turns out I was setting the value of first name the wrong way. In fact, in this case there is no need to set the first name, since it defaults to ''. The correct test implementation would be this:
it('validates the first name cannot be blank', async () => {
const { getByLabelText, getByText } = render(<Profile />);
const firstName = getByLabelText(/first name/i);
fireEvent.blur(firstName);
let error;
await waitFor(() => {
error = getByText('First name is required');
});
expect(error).not.toBeNull();
});

Related

React Custom Hook always behind with one value

Code below is a simplified version of the reality.
It almost works. The issue is in my AddSomething component the value of the snackbar is always one value behind. I've used console logs as you can see in the code.
It goes like this:
"inside validateName"
"error is set to .... " (inside validateName)
"ELSE" (inside addSomething)
So it does everything in correct order. But the error in AddSomething is always one value behind.
So first time I input an empty string and the error message displayed is the default ''
(It should display 'Name can not be below 1')
Second time I try with a string above > 50 characters and it displays the old value of 'Name can not be below 1' but it should be: 'Name can not be above 50'
And it keeps going like this. How can I change the code to actually get the current value?
const AddSomething = () => {
const [error, validate] = useValidation()
const handleSave = async () => {
if(await validate(valuesToBeSaved){
saveItems()
}else {
console.log("ELSE")
setSnackBarError({
message: error
})
}
}
}
function useValidation() {
const [error, setError] = useState('')
let valid = true
async function validate(valuesToBeSaved: string[] | number[]) {
for(const value in valuesToBeSaved) {
if(typeof value === 'string'){
valid = await validateName(value)
}
if(valid === false) return false
}
}
async function validateName(name: string): Promise<boolean> {
console.log("inside validateName")
let valid = true
await nameSchema
.validate({ name })
.then((err) => {
valid = true
})
.catch((err) => {
console.log("error is set to: " + err.mesage)
setError(err.message)
valid = false
})
return valid
}
return [error, validate]
}
const nameSchema = yup.object({
name: yup
.string()
.min(1, 'Name can not be below 1')
.max(50, 'Name can not be above 50')
.required('Name is required'),
})

How to invoke Axios synchronously in React?

I'm using the Axios in React to register a user into MongoDb database.
But before I register a user, I check if that user already exists in the database, but since axios.post() is asynchronous, the rest of the code precending this response executes and user with same Id is regsitered again.
How do I solve this. PFB my code:
const validateRegister = (values) => {
let errors={};
const patternName = new RegExp('^[a-zA-Z ]{3,20}$')
const patternPhone = new RegExp('^[0-9]{9,10}$')
const patternEmail = new RegExp('^[a-zA-Z0-9._:$!%-]+#[a-zA-Z0-9.-]+.[a-zA-Z]$')
const patternPassword = new RegExp('(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^A-Za-z0-9])(?=.{8,})')
if(!values.name || !patternName.test(values.name)){
errors.name="Please enter a valid name"
}
if(!values.phone || !patternPhone.test(values.phone)){
errors.phone="Please enter a valid Phone number of 9-10 digits"
}
if(!values.email || !patternEmail.test(values.email)){
errors.email="Please enter a valid email address"
}
if(!values.password || !patternPassword.test(values.password)){
errors.password="Please enter a strong password to continue. A strong password has: Atleast 8 characters in length, 2 letters in upper case, 1 special character (!##$&*), 2 number (0-9), 3 letters in lower case"
}
if(!values.isTermsAndConditionsAccepted){
errors.isTermsAndConditionsAccepted = "Please Accept the Terms and conditions"
}
//Check if the user already exist
if(values.phone){
let formData = new FormData();
formData.append('phone', values.phone);
console.log('inside user check')
axios.post('http://localhost:3001/doesUserExistByPhone', formData).then(response => {
//Success - then create account
}).catch(errorResponse=>{
console.log(errorResponse)
if(errorResponse.response.status===409){
console.log('User already exist');
errors.phone="User Already exist. If you've already registered. Please try to Login.";
return errors;
}
else if(errorResponse.response.status===500){
errors.phone = "Unable to register user, contact SwapiFi Support";
return errors;
}
})
}
console.log('Errors found before creating user: ', errors);
return errors;
}
export default validateRegister
I invoke this Validator from another js file:
const useFormRegister = (submitForm) => {
const [errors, setErrors] = useState({});
const [dataIsCorrect, setDataIsCorrect] = useState(false);
const [values, setValues] = useState({
name: "",
phone: "",
email: "",
password: "",
isTermsAndConditionsAccepted: false
})
const handleValueChangeEvent = (event) => {
setValues({
...values,
[event.target.name] : event.target.value,
})
}
const handleRegisterEvent = (event) => {
console.log('Register button clicked');
event.preventDefault();
setErrors(validation(values));
console.log('Errors-Phone:', errors)
setDataIsCorrect(true);
}
useEffect(() => {
console.log('No. of errors:', Object.keys(errors).length)
{Object.entries(errors).map(([key, value]) => (
console.log("Error, ", key, ':', value)
))}
if(Object.keys(errors).length === 0 && dataIsCorrect){
submitForm(true);
let formData = new FormData();
{Object.entries(values).map(([key, value]) => (
formData.append(key, value)
))}
console.log(formData)
axios.post('http://localhost:3001/registerUser', formData).then(response => {console.log(response)}).catch(error=>{console.log(error)})
}
}, [errors])
return {handleValueChangeEvent, handleRegisterEvent, values, errors};
}
export default useFormRegister
You probably don't want to fire off a check synchronously. Look into async/await syntax. You can write code that "looks" synchronous but will actually execute asynchronously. This will allow you to do something like:
const checkUserExists = async (user) => {
const repsonse = await axios('/check/user/endpoint');
const user = await response.json();
return !!user;
}
const registerUser = async (user) => {
const repsonse = await axios('/register/user/endpoint');
const data = await response.json();
// do stuff here
}
and now you can implement whatever logic you need around these functions
useEffect(()=>{
async function deal(){
let data = await axios.get("http://localhost:8000")
setDeal(..)
}
deal()
},[])

React Testing - get text inside an element

I am coding a couple of tests for my app. I want to check that after saving time, it goes to the list.
Now I got this test working by finding a text from the screen, but I want to specify it by id. I mean to check that this 00:01:00 is really in the right place, not just somewhere on the page. I hope guys understand what I mean.
How can I do this?
This is working by finding from screen:
test("Time saved", async () => {
render(<SecondTimer />);
const start = screen.getByText("START");
fireEvent.click(start);
await waitFor(() => screen.getByText(/00:01:00/i), {
timeout: 2000
});
const pause = screen.getByText("PAUSE");
fireEvent.click(pause);
const saveTime = screen.getByText("SAVE TIME");
fireEvent.click(saveTime);
const history = screen.queryByText(/00:01:00/i);
screen.debug(); // printtaa renderinnin consoliin
});
I have DIV like this:
<div data-testid={'aikalista'} className={'tulostaulu'}>
<ul>
{this.state.aikoja === false && ('No times my friend!')}
{this.state.aikoja === true && (<div>{this.state.history.map((item, index) => <li key={index}>{item}</li>)}</div>)}
</ul>
This is what I've tried with different variations, but no luck.
test("Time saved v2", async () => {
render(<SecondTimer />);
const start = screen.getByText("START");
fireEvent.click(start);
await waitFor(() => screen.getByText(/00:01:00/i), {
timeout: 2000
});
const pause = screen.getByText("PAUSE");
fireEvent.click(pause);
const saveTime = screen.getByText("SAVE TIME");
fireEvent.click(saveTime);
const aikalista = screen.getByTestId('aikalista');
expect(getByTestId('aikalista').textContent).toEqual(/00:01:00/i)
screen.debug(); // printtaa renderinnin consoliin
});
getByText has this signature.
getByText(
container: HTMLElement, // if you're using `screen`, then skip this argument
text: TextMatch,
options?: {
selector?: string = '*',
exact?: boolean = true,
ignore?: string|boolean = 'script, style',
normalizer?: NormalizerFn,
}): HTMLElement
You can you use this selector property inside options object to select the element. So you can write something like this.
screen.getByText(/00:01:00/i, {selector: "#someId"})
You can check if the element has an id you want after you get it by text also.
const element = screen.getByText(/00:01:00/i);
expect(element).toHaveAttribute('id', "someId");

simulate change not working with trim() enzyme

I was using this test when I had a bug, so I used the trim function for resolve it, and the these test fail, tried in different ways but didn't found the solution
const generalWrapper = shallow(<AddVehiclesTable {...generalProps} />)
const generalInstance = generalWrapper.instance()
describe('onSearchChange', () => {
test('should change the "search" state', () => {
const theFilterValue = 'a new filter value'
generalWrapper.find('.filter-input').simulate('change', { target: { value: theFilterValue } })
const expectedState = Object.assign({}, generalInstance.state)
expectedState.searchValue = { 'target': { 'value': theFilterValue } }
expect(generalInstance.state).toEqual(expectedState)
expect(generalInstance.state.userInteractedWithComponent).toBe(true)
})
})
onSearchChange (searchValue) {
const value = searchValue.trim()
this.setState({ searchValue: value, userInteractedWithComponent: true })
}
Error message
TypeError: searchValue.trim is not a function
Any suggestions
Your function gets the Object as a parameter.
Expose field that you needed
I don't see the whole picture, but can guess that you need something like
onSearchChange ({ target: { value: incomeValue } }) {
const value = incomeValue.trim()
this.setState({ searchValue: value, userInteractedWithComponent: true })
}

React testing: Change value of input field

I can not set the value of input field. What am I doing wrong?
If any more information is needed, please tell me so. Thank you.
describe('SignUpComp', () => {
let signUpComp, node;
beforeEach(() => {
signUpComp = TestUtils.renderIntoDocument(<SignUp />);
node = ReactDOM.findDOMNode(signUpComp);
});
// First name
it('it should trigger error `min chars` if input firstName is too short', () => {
let elements = selectElements('firstName');
TestUtils.Simulate.change(elements.input, { target: { value: 'abc' } });
console.log(elements.input); // I can not see the change
console.log(node); // I can not see the change
expect(elements.error.textContent).to.equal(errorMsg.tooShort('First name', 2));
});
function selectElements(element) {
let input = node.querySelector('#' + element);
let error = node.querySelector('#' + element + '+ p');
return { input, error };
}
I recommend you to take a look at enzyme, it significantly simplifies testing react components.
With enzyme you can do simply:
const form = mount(<MyComponent />);
const input = form.find('input').get(0);
input.value = 'Blah blah';

Resources