How can I submit form without button in unit test? - unit-testing

The fireEvent.submit(form) call doesn't seem to be submitting the form. How can I submit the form?
BasicInfo/index.test.tsx
import { fireEvent, render, screen, waitFor } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
import BasicInfo from '.';
describe('BasicInfo', () => {
it('can submit form', async () => {
const handleSubmit = jest.fn();
render(<BasicInfo onSubmit={handleSubmit} />);
const user = userEvent.setup();
await user.type(screen.getByLabelText('Event title'), 'Event title 1');
await user.type(screen.getByLabelText('Featured game'), 'Scrabble');
await user.type(screen.getByLabelText('Venue location'), '21 2nd St.');
await user.type(screen.getByLabelText('Event starts'), '2017-06-01T08:30');
await user.type(screen.getByLabelText('Event ends'), '2017-06-01T08:30');
const form = screen.getByTestId<HTMLFormElement>('basic-info');
fireEvent.submit(form);
await waitFor(() => {
expect(handleSubmit).toHaveBeenCalledWith({
title: 'Event title 1',
game: { name: 'Scrabble' },
address: '21 2nd St.',
starts_at: '2017-06-01T08:30',
ends_at: '2017-06-01T08:30',
});
});
});
});
BasicInfo/index.tsx
import { Form, Formik, FormikErrors, FormikHelpers, FormikProps } from 'formik';
import Image from 'next/image';
import Link from 'next/link';
import { useLocation } from '../../context/location';
import { Event } from '../../hooks/useEvents';
import useGames, { Game } from '../../hooks/useGames';
import useMapbox, { Feature, Coords } from '../../hooks/useMapbox';
import useThrottle from '../../hooks/useThrottle';
import { parseAddress } from '../../lib/helpers';
import AddressItem from '../AddressItem';
import AutoComplete from '../AutoComplete';
import Button from '../Button';
import FormSection from '../FormSection';
import InputGroup from '../InputGroup';
import GameItem from '../GameItem';
export interface BasicInfoValues {
title: string;
game: Game;
address: string;
starts_at: string;
ends_at: string;
coords: string;
}
interface Props {
event?: Event;
initialValues?: BasicInfoValues;
onSubmit: (values: BasicInfoValues) => Promise<void>;
}
function BasicInfo({ event, initialValues: initialVals, onSubmit }: Props) {
const { coords } = useLocation();
const { data: places, forward, getStaticMapUrl } = useMapbox();
const { data: games, search, leanGame } = useGames();
const throttle = useThrottle();
const initialValues: BasicInfoValues = initialVals || {
title: '',
game: { name: '' },
address: '',
starts_at: '',
ends_at: '',
coords: '',
};
const validate = (values: BasicInfoValues) => {
const errors: FormikErrors<BasicInfoValues> = {};
if (values.title.length < 3) {
errors.title = 'Must be at least 3 characters';
}
if (values.title.length > 50) {
errors.title = 'Must be less than 50 characters';
}
if (!values.game.name) {
errors.game = 'Must select a game' as FormikErrors<Game>;
}
if (!values.coords) {
errors.coords = 'Must select a location';
}
if (!values.starts_at) {
errors.starts_at = 'Required';
}
if (!values.ends_at) {
errors.ends_at = 'Required';
}
return errors;
};
const handleSubmit = async function (
values: BasicInfoValues,
formikHelpers: FormikHelpers<BasicInfoValues>
) {
try {
await onSubmit(values);
formikHelpers.setSubmitting(false);
} catch (error) {
console.error(error);
}
};
const renderLocation = function ({
setFieldValue,
values,
handleChange,
errors,
touched,
handleBlur,
}: FormikProps<BasicInfoValues>) {
const handleEditLocationClick = function () {
setFieldValue('address', '');
setFieldValue('coords', '');
};
const { coords: coordinates, address } = values;
if (coordinates && address) {
const staticMapUrl = getStaticMapUrl({
coords: JSON.parse(coordinates),
width: 600,
height: 165,
});
const { street, city } = parseAddress(address);
return (
<div className="basic-info__location">
<div className="basic-info__location__image-container">
<Image
src={staticMapUrl}
alt="static map"
layout="fill"
objectFit="fill"
/>
</div>
<div className="basic-info__location__address-container">
<div className="basic-info__location__address-container__address">
<span className="basic-info__location__street">{street}</span>
<span className="basic-info__location__city">{city}</span>
</div>
<Button
text="Edit location"
color="secondary"
size="small"
onClick={handleEditLocationClick}
/>
</div>
</div>
);
} else {
return (
<AutoComplete<Feature>
name="address"
value={values.address}
onChange={(e) => {
if (!handleChange) return;
handleChange(e);
throttle.wait(() => {
if (!e.target.value) return;
if (!coords) return;
forward({
coords,
q: e.target.value,
});
}, 500);
}}
label="Venue location"
placeholder="Search for a venue or address"
items={places}
Input={InputGroup}
itemRenderer={(item) => <AddressItem placeName={item.place_name} />}
onItemClick={(item) => {
if (!setFieldValue) return;
setFieldValue('address', item.place_name);
setFieldValue('coords', JSON.stringify(item.center));
}}
onBlur={handleBlur}
error={errors.coords && touched.address ? errors.coords : undefined}
/>
);
}
};
return (
<Formik
initialValues={initialValues}
onSubmit={handleSubmit}
validate={validate}
>
{(formikProps) => {
const {
values,
handleChange,
setFieldValue,
errors,
touched,
handleBlur,
} = formikProps;
return (
<Form id="basic-info" className="basic-info" data-testid="basic-info">
<FormSection
title="Basic Info"
description="Name your event and tell gamers what game will be played. Add
details that highlight what makes it unique."
icon="segment"
>
<InputGroup
name="title"
value={values.title}
onChange={handleChange}
onBlur={handleBlur}
label="Event title"
placeholder="Be clear and descriptive"
error={errors.title && touched.title ? errors.title : undefined}
/>
<AutoComplete<Game>
name="game.name"
value={values.game.name}
onBlur={handleBlur}
onChange={(e) => {
handleChange(e);
throttle.wait(() => {
if (!e.target.value) return;
search({ name: e.target.value, limit: 5 });
}, 500);
}}
label="Featured game"
placeholder="Search games"
Input={InputGroup}
items={games}
itemRenderer={(item) => <GameItem game={item} />}
onItemClick={(item) => {
const game = leanGame(item);
setFieldValue('game', game);
}}
error={
errors.game && touched.game
? (errors.game as string)
: undefined
}
/>
<span>
Need game ideas?{' '}
<Link href="/" passHref>
<a className="link">Browse games by category</a>
</Link>
</span>
</FormSection>
<FormSection
title="Location"
description="Help gamers in the area discover your event and let attendees know where to show up."
icon="map"
>
{renderLocation(formikProps)}
</FormSection>
<FormSection
title="Date and time"
description="Tell gamers when your event starts and ends so they can make plans to attend."
icon="date_range"
>
<InputGroup
name="starts_at"
value={values.starts_at}
onChange={handleChange}
onBlur={handleBlur}
label="Event starts"
placeholder="Search for a venue or address"
type="datetime-local"
// icon="calendar_today"
error={
errors.starts_at && touched.starts_at
? errors.starts_at
: undefined
}
/>
<InputGroup
name="ends_at"
value={values.ends_at}
onChange={handleChange}
onBlur={handleBlur}
label="Event ends"
placeholder="Search for a venue or address"
type="datetime-local"
// icon="calendar_today"
error={
errors.ends_at && touched.ends_at ? errors.ends_at : undefined
}
/>
</FormSection>
</Form>
);
}}
</Formik>
);
}
export default BasicInfo;

My form validation was preventing me from submitting

Related

TypeError: null is not an object (evaluating 'RNFusedLocation.getCurrentPosition')

Please, am new to react native. Am trying to get the current location through google places autocomplete for android in expo but an getting the error 'TypeError: null is not an object (evaluating 'RNFusedLocation.getCurrentPosition')'
below is my code
Please, am new to react native. Am trying to get the current location through google places autocomplete for android in expo but an getting the error 'TypeError: null is not an object (evaluating 'RNFusedLocation.getCurrentPosition')'
below is my code
App.js
import { StyleSheet, Text, View, StatusBar, PermissionsAndroid, Platform,} from 'react-native';
import HomeScreen from './src/screens/HomeScreen/HomeScreen';
import DestinationSearch from './src/screens/DestinationSearch/DestinationSearch';
import SearchResults from './src/screens/SearchResults/SearchResults';
import { useEffect, useState } from 'react';
//import * as Location from 'expo-location';
import Geolocation, { getCurrentPosition } from 'react-native-geolocation-service';
navigator.geolocation = require('react-native-geolocation-service');
export default function App() {
const androidPermission = async () => {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{
title: "Uber App location Permission",
message:
"Uber App needs access to your location " +
"so you can take awesome rides.",
buttonNeutral: "Ask Me Later",
buttonNegative: "Cancel",
buttonPositive: "OK"
}
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
console.log("You can use the location");
} else {
console.log("Location permission denied");
}
} catch (err) {
console.warn(err);
}
};
useEffect(() => {
if (androidPermission) {
Geolocation.getCurrentPosition(
(position) => {
console.log(position);
},
(error) => {
// See error code charts below.
console.log(error.code, error.message);
},
{ enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 }
);
}
}, [])
useEffect(() => {
if(Platform.OS == 'android') {
androidPermission()
} else{
//IOS
Geolocation.requestAuthorization();
}
}, [])
return (
<View>
{/* <HomeScreen /> */}
<DestinationSearch />
{/* <SearchResults /> */}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
marginTop:StatusBar.currentHeight
},
});
`
DestinationSearch.jsx
import { View, Text, StyleSheet, SafeAreaView, StatusBar } from 'react-native'
import React, {useEffect, useState,} from 'react'
import { GooglePlacesAutocomplete } from 'react-native-google-places-autocomplete';
import {GOOGLE_MAPS_APIKEY} from '#env'
import PlaceRow from './PlaceRows';
const DestinationSearch = () => {
const [originPlace,setOriginPlace] = useState(null)
const [destinationPlace, setDestinationPlace] = useState(null)
useEffect(() => {
console.log('useEffect is called')
if (originPlace && destinationPlace) {
console.warn('Redirect to results')
}
}, [originPlace, destinationPlace])
return (
<SafeAreaView>
<View style={styles.container}>
<GooglePlacesAutocomplete
nearbyPlacesApi = 'GooglePlacesSearch'
placeholder = 'From...'
listViewDisplayed = 'auto'
debounce = {400}
currentLocation = {true}
currentLocationLabel='Current location'
minLenght = {2}
enabledPoweredByContainer = {true}
fetchDetails = {true}
autoFoccus = {true}
renderRow={(data)=> <PlaceRow data={data}/>}
query ={{
key: GOOGLE_MAPS_APIKEY ,
language :'en'
}}
styles={{
container: styles.autocompleteContainer,
textInput: styles.textInput,
listView: styles.listView,
seperator: styles.separator
}}
onPress = {(data, details = null)=> {
setOriginPlace({data, details})
console.log(currentLocation)
}}
/>
<GooglePlacesAutocomplete
nearbyPlacesApi = 'GooglePlacesSearch'
placeholder = 'To...'
listViewDisplayed = 'auto'
debounce = {400}
minLenght = {2}
enabledPoweredByContainer = {true}
fetchDetails = {true}
autoFoccus = {true}
query ={{
key: GOOGLE_MAPS_APIKEY ,
language :'en'
}}
renderRow={(data)=> <PlaceRow data={data}/>}
styles={{
container: {
...styles.autocompleteContainer,
top: 70
},
textInput: styles.textInput,
seperator: styles.separator
}}
onPress = {(data, details = null)=> {
setDestinationPlace({data, details})
}}
/>

how can i POST data from ant design form into Django Backed?

i trying to post data from ant design React.js into Python Django rest frame work.
so I am using method OnFinish to send data, but its not working.
MY big problem is , i don't know how can i Introduction Data i want to send them data from Form , by using React-redux or something else way , so please Help me .
#react.js Form:
import React, { Component } from "react";
import {
Form,
Input,
Button,
PageHeader,
Select,
DatePicker,
message,
} from "antd";
import "antd/dist/antd.css";
import { connect } from "react-redux";
import axios from "axios";
// defualt setting for django
axios.defaults.xsrfCookieName = "csrftoken";
axios.defaults.xsrfHeaderName = "X-CSRFToken";
// from layout setting
const formItemLayout = {
labelCol: {
xs: {
span: 24,
},
sm: {
span: 8,
},
},
wrapperCol: {
xs: {
span: 24,
},
sm: {
span: 16,
},
},
};
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0,
},
sm: {
span: 16,
offset: 8,
},
},
};
// end fform layout setting
// const onFinish = (values) => {
// console.log(values);
// axios.post("http://127.0.0.1:8000/api/create/", {
// title: values.title,
// manager: values.manager,
// });
// };
// const title = event.target.elements.title.value;
// const manager = event.target.elements.manager.value;
class ExtrashiftForm extends React.Component {
constructor(props) {
super(props);
this.state = {
Extrashifts: [],
};
}
// componentDidMount() {
// this.fetchExtrashift();
// }
handleSubmit = () => {
axios
.post("http://127.0.0.1:8000/api/create", {
data: {
title: this.target.elements.title.value,
manager: this.data.item.manager,
},
})
.then((res) => {
if (res.status == 200) message.success("data successfully updated!");
this.fetchExtrashift();
})
.catch((err) => {
message.error("data profile failed to update ...");
});
};
render() {
return (
<div>
<Form {...formItemLayout} name="update">
<Form.Item label="Title :">
<Input name="title" placeholder="Put a title here" />
</Form.Item>
<Form.Item label="Manager :">
<Input name="manager" placeholder="Enter manager name" />
</Form.Item>
<Form.Item {...tailFormItemLayout}>
<Button
type="primary"
htmlType="submit"
onFinish={this.handleSubmit}
>
create
</Button>
</Form.Item>
</Form>
</div>
);
}
}
export default ExtrashiftForm;
#back end api/urls.py :
from Extrashift.api.views import ExtrashiftViewSet
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'', ExtrashiftViewSet, basename='Extrashift')
urlpatterns = router.urls
#backend : api/views.py:
from rest_framework import viewsets
from Extrashift.models import Extrashift
from .Serializers import ExtrashiftSerializers
class ExtrashiftViewSet(viewsets.ModelViewSet):
serializer_class = ExtrashiftSerializers
queryset = Extrashift.objects.all()
from rest_framework import permissions
from rest_framework.generics import (
ListAPIView,
RetrieveAPIView,
CreateAPIView,
UpdateAPIView,
DestroyAPIView
)
from my back end everything is work but Please help me to i can send only one data from this form.
if is possible please ,change my Code to the Correct code
Nothing spectacular here, you can read the docs
Rather than giving the name as a prop to the Input field.
I've passed it as a prop to Form.Item component
You can check the example here
import React, { Component } from "react";
import {
Form,
Input,
Button,
PageHeader,
Select,
DatePicker,
message,
} from "antd";
import "antd/dist/antd.css";
import axios from "axios";
// defualt setting for django
axios.defaults.xsrfCookieName = "csrftoken";
axios.defaults.xsrfHeaderName = "X-CSRFToken";
// from layout setting
const formItemLayout = {
labelCol: {
xs: {
span: 24,
},
sm: {
span: 8,
},
},
wrapperCol: {
xs: {
span: 24,
},
sm: {
span: 16,
},
},
};
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0,
},
sm: {
span: 16,
offset: 8,
},
},
};
// end fform layout setting
// const onFinish = (values) => {
// console.log(values);
// axios.post("http://127.0.0.1:8000/api/create/", {
// title: values.title,
// manager: values.manager,
// });
// };
// const title = event.target.elements.title.value;
// const manager = event.target.elements.manager.value;
export default class ExtrashiftForm extends React.Component {
constructor(props) {
super(props);
this.state = {
Extrashifts: [],
};
}
// componentDidMount() {
// this.fetchExtrashift();
// }
handleSubmit = (values) => {
console.log(values)
// axios
// .post("http://127.0.0.1:8000/api/create", {
// data: {
// title: this.target.elements.title.value,
// manager: this.data.item.manager,
// },
// })
// .then((res) => {
// if (res.status == 200) message.success("data successfully updated!");
// this.fetchExtrashift();
// })
// .catch((err) => {
// message.error("data profile failed to update ...");
// });
};
render() {
return (
<div>
<Form {...formItemLayout} name="update" onFinish={this.handleSubmit}>
<Form.Item label="Title :" name="title">
<Input placeholder="Put a title here" />
</Form.Item>
<Form.Item label="Manager :" name="manager">
<Input placeholder="Enter manager name" />
</Form.Item>
<Form.Item {...tailFormItemLayout}>
<Button
type="primary"
htmlType="submit"
>
create
</Button>
</Form.Item>
</Form>
</div>
);
}
}

Unit Testing methods which dispatch actions in Vue.js

I am having trouble to unit test methods in a component, I want to test validations(), clear(), and registerUser() methods but I can't seem to increase the code coverage. Below is the Component :
<template>
<v-col class="pa-lg-10">
<v-card class=" mx-auto">
<form class="pa-10">
<p class="reg-text">Registration</p>
<v-text-field v-model="name" label="Name" required></v-text-field>
<v-text-field v-model="email" label="E-mail" required></v-text-field>
<v-text-field v-model="address" label="Address" required></v-text-field>
<v-text-field v-model="phoneNumber" label="Phone Number"></v-text-field>
<v-text-field v-model="password" label="Password" :type="'password'" required></v-text-field>
<v-btn class="mr-4" color="primary" #click="registerUser">Register</v-btn>
<v-btn #click="clear">clear</v-btn>
</form>
</v-card>
<SnackBar/>
</v-col>
</template>
<script>
import {mapActions, mapGetters} from "vuex";
import SnackBar from "./SnackBar";
export default {
name: "RegisterUsers",
components: {
SnackBar
},
data() {
return {
name: '',
email: '',
address: '',
phoneNumber: '',
password: '',
formHasErrors: false,
}
},
methods: {
registerUser() {
const formData = {
name: this.name,
email: this.email,
address: this.address,
number: this.phoneNumber,
password: this.password,
};
if (!this.validations()) {
this.register(formData);
}
},
clear() {
this.name = "";
this.email = "";
this.address = "";
this.phoneNumber = "";
this.password = "";
},
validations() {
// eslint-disable-next-line no-useless-escape
const mailFormat = /\S+#\S+\.\S+/;
const vm = this;
setTimeout(() => {
vm.reset_snackbar();
}, 2000);
if (this.email === '') {
this.toast_snackbar_on_error('Email is required');
return true;
}
if (mailFormat.test(this.email.toString()) !== true) {
this.toast_snackbar_on_error('Please enter a valid mail');
return true;
}
if (this.name === '') {
this.toast_snackbar_on_error('Name is required');
return true;
}
if (this.address === '') {
this.toast_snackbar_on_error('Address is required');
return true;
}
if (this.password === '') {
this.toast_snackbar_on_error('Password is required');
return true;
}
return this.formHasErrors;
},
...mapActions({
register: 'register/registerUsers',
reset_snackbar: 'register/reset_snackbar',
toast_snackbar_on_error: 'register/toast_snackbar_on_error',
}),
computed: {
...mapGetters('register', ['snackbar_status']),
},
},
};
</script>
<style scoped>
div{
color: inherit;
}
.reg-text {
color: black;
text-align: center;
font-size: 20px;
font-weight: bold;
}
</style>
I have tried to mock some data during the insertion however, I can't seem to get hit those methods. Secondly, I wanted also to expect the message from a SnackBar but I couldn't seem to target it as well.
How can I test this component methods.
Below are my tests :
it("should expect to have input fields", () => {
const wrapper = shallowMount(RegisterUsers);
wrapper.setData({ name: '',
email: 'ab#gmail.com',
address: 'Buziga, Kampala',
phoneNumber: '0704594180',
password: '9393939',
formHasErrors: false,});
const button = wrapper.find({name: 'v-btn'})
button.trigger('click');
expect(wrapper.classes('s-text')).toBe(false);
})
The css class s-text you see in the test it's the one wrapping the message of a SnackBar.
I kindly need guidance here.
You are using shallowMount. That will stub out all the components - there will be no button to find.
An alternative way to write this would
it("should expect to have input fields", () => {
const wrapper = mount(RegisterUsers, {
name: '',
email: 'ab#gmail.com',
address: 'Buziga, Kampala',
phoneNumber: '0704594180',
password: '9393939',
formHasErrors: false,
});
const button = wrapper.find({name: 'v-btn'})
button.trigger('click');
expect(wrapper.classes('s-text')).toBe(false);
})
In all likelyhood this will still lead to problems. You are using Vuetify and some other plugins, which are not installed. You need to install those using localVue.
Once you get it all running without errors, you can do something like wrapper.find('button').trigger('click') which should call registerUsers. If you do not want to use a real Vuex store, do something like this:
const store = {
dispatch: jest.fn()
}
mount(Component, {
mocks: {
$store
}
}

Error: Invalid hook call when using with redux

Sorry if I am asking a beginner's level question. I am new to React.js and recently I have been trying to grasps the concepts by following this tutorial:
JustDjango
What I am trying to accomplish is creating a login form which uses redux to store the states, my code is as follows :
import React from 'react';
import { Form, Icon, Input, Button, Spin } from 'antd/lib';
import { connect } from 'react-redux';
import { NavLink } from 'react-router-dom';
import * as actions from '../store/actions/auth';
const FormItem = Form.Item;
const antIcon = <Icon type="loading" style={{ fontSize: 24 }} spin />;
class NormalLoginForm extends React.Component {
handleSubmit = (e) => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
this.props.onAuth(values.userName, values.password);
this.props.history.push('/');
}
});
}
render() {
let errorMessage = null;
if (this.props.error) {
errorMessage = (
<p>{this.props.error.message}</p>
);
}
const { getFieldDecorator } = this.props.form;
return (
<div>
{errorMessage}
{
this.props.loading ?
<Spin indicator={antIcon} />
:
<Form onSubmit={this.handleSubmit} className="login-form">
<FormItem>
{getFieldDecorator('userName', {
rules: [{ required: true, message: 'Please input your username!' }],
})(
<Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="Username" />
)}
</FormItem>
<FormItem>
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Please input your Password!' }],
})(
<Input prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />} type="password" placeholder="Password" />
)}
</FormItem>
<FormItem>
<Button type="primary" htmlType="submit" style={{marginRight: '10px'}}>
Login
</Button>
Or
<NavLink
style={{marginRight: '10px'}}
to='/signup/'> signup
</NavLink>
</FormItem>
</Form>
}
</div>
);
}
}
const WrappedNormalLoginForm = Form.useForm()(NormalLoginForm);
const mapStateToProps = (state) => {
return {
loading: state.loading,
error: state.error
}
}
const mapDispatchToProps = dispatch => {
return {
onAuth: (username, password) => dispatch(actions.authLogin(username, password))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(WrappedNormalLoginForm);
The error traceback shows that the error is coming from :
76 | const WrappedNormalLoginForm = Form.useForm()(NormalLoginForm);
77 |
78 | const mapStateToProps = (state) => {
79 | return {
Some google search on this particular error shows that this error has something to do with hooks being defined in a classed based component , however i do not understand why :
const mapStateToProps = (state) => {......
is considered a hook
Will greatly appreciate anybody's help!
React hooks only used by functional components. You used class components.
Shortly, Form.useForm() the method is only used functional components, you can read it from this link below:
https://ant.design/components/form/

vue unit test - data not updated after triggering event ( form submit) as expected

I am testing that form data are sent after submit.
ContactForm.spec.js
import Vue from "vue";
import Vuex from "vuex";
import { mount, shallowMount } from "#vue/test-utils";
import VeeValidate from "vee-validate";
import i18n from "#/locales";
import Vuetify from "vuetify";
import ContactForm from "#/components/Home/ContactForm.vue";
Vue.use(VeeValidate, { errorBagName: "errors" });
Vue.use(Vuetify);
Vue.use(Vuex);
describe("ContactForm.vue", () => {
let store;
let wrapper;
let options;
let input;
const v = new VeeValidate.Validator();
beforeEach(() => {
const el = document.createElement("div");
el.setAttribute("data-app", true);
document.body.appendChild(el);
});
it("should sendMessage - valid form", async () => {
// given
store = new Vuex.Store({
state: {
language: "en",
loading: false
}
})
options = {
sync: false,
provide: {
$validator () {
return new VeeValidate.Validator();
}
},
i18n,
store
};
wrapper = mount(ContactForm, options);
// when
const radioInput = wrapper.findAll('input[type="radio"]');
radioInput.at(1).setChecked(); // input element value is changed, v-model is not
radioInput.at(1).trigger("change"); // v-model updated
input = wrapper.find('input[name="givenName"]');
input.element.value = "John"; // input element value is changed, v-model is not
input.trigger("input"); // v-model updated
input = wrapper.find('input[name="familyName"]');
input.element.value = "Doe"; // input element value is changed, v-model is not
input.trigger("input"); // v-model updated
input = wrapper.find('input[name="email"]');
input.element.value = "john.doe#example.com"; // input element value is changed, v-model is not
input.trigger("input"); // v-model updated
input = wrapper.find('textarea[name="messageContent"]');
input.element.value = "Hello World!"; // input element value is changed, v-model is not
input.trigger("input"); // v-model updated
const contactForm = wrapper.find("form");
contactForm.trigger("submit");
await wrapper.vm.$nextTick();
// then
console.log("DATA: ", wrapper.vm.$data.contactLang);
expect(wrapper.vm.validForm).toEqual(true);
});
});
Validation is successful ( so validForm is set to true in the component )
BUT the test does not pass
console.log
✕ should sendMessage - valid form (476ms)
● ContactForm.vue › should sendMessage - valid form
expect(received).toEqual(expected)
Expected value to equal:
true
Received:
false
The component vue is
ContactForm.vue
<template>
<form id="contactForm" #submit="sendMessage()">
<input v-model="contactLang" type='hidden' data-vv-name="contactLang" v-validate="'required'" name='contactLang'>
<v-layout row wrap align-center>
<v-flex xs12 sm3 md3 lg3>
<v-radio-group row :mandatory="false" v-model="gender" name="gender">
<v-radio :label='genderLabel("f")' value="f" name="female"></v-radio>
<v-radio :label='genderLabel("m")' value="m" name="male"></v-radio>
</v-radio-group>
</v-flex>
<v-flex xs12 sm4 md4 lg4>
<v-text-field
v-model="givenName"
browser-autocomplete="off"
:label="$t('lang.views.home.contactForm.givenName')"
data-vv-name="givenName"
:error-messages="errors.collect('givenName')"
v-validate="'required'"
name="givenName">
</v-text-field>
</v-flex>
<v-flex xs12 sm5 md5 lg5>
<v-text-field
v-model="familyName"
browser-autocomplete="off"
:label="$t('lang.views.home.contactForm.familyName')"
data-vv-name="familyName"
:error-messages="errors.collect('familyName')"
v-validate="'required'"
name="familyName">
</v-text-field>
</v-flex>
</v-layout>
<v-text-field
browser-autocomplete="off"
v-model="email"
:label="$t('lang.views.home.contactForm.email')"
data-vv-name="email"
:error-messages="errors.collect('email')"
v-validate="'required|email'"
name="email">
</v-text-field>
<v-textarea v-model="messageContent" :label="$t('lang.views.home.contactForm.message')" :error-messages="errors.collect('messageContent')" :rules="[(v) => v.length <= 200 || 'Max 200 characters']" :counter="200" v-validate="'required'" data-vv-name="messageContent" name="messageContent"></v-textarea>
<v-btn id="btnClear" round #click.native="clear">{{ $t('lang.views.global.clear') }}</v-btn>
<v-btn round large color="primary" type="submit">{{ $t('lang.views.global.send') }}
<v-icon right>email</v-icon><span slot="loader" class="custom-loader"><v-icon light>cached</v-icon></span>
</v-btn>
</form>
</template>
<script>
import swal from "sweetalert2";
import { mapState } from "vuex";
import appValidationDictionarySetup from "#/locales/appValidationDictionary";
export default {
name: "contactForm",
$_veeValidate: { validator: "new" },
data() {
return {
contactLang: "",
gender: "f",
givenName: "",
familyName: "",
email: "",
messageContent: "",
validForm: false
};
},
...
methods: {
...
sendMessage: function() {
console.log("sendMessage()...");
this.$validator
.validateAll()
.then(isValid => {
console.log("VALIDATION RESULT: ", isValid);
this.validForm = isValid;
if (!isValid) {
console.log("VALIDATION ERROR");
// console.log("Errors: ", this.$validator.errors.items.length);
const alertTitle = this.$validator.dictionary.container[
this.language
].custom.error;
const textMsg = this.$validator.dictionary.container[this.language]
.custom.correct_all;
swal({
title: alertTitle,
text: textMsg,
type: "error",
confirmButtonText: "OK"
});
return;
}
console.log("validation success, form submitted validForm: ", this.validForm);
return;
})
.catch(e => {
// catch error from validateAll() promise
console.log("error validation promise: ", e);
this.validForm = false;
return;
});
},
clear: function() {
this.contactLang = "";
this.gender = "f";
this.givenName = "";
this.familyName = "";
this.email = "";
this.messageContent = "";
this.validForm = false;
this.$validator.reset();
}
},
mounted() {
appValidationDictionarySetup(this.$validator);
this.$validator.localize(this.language);
this.contactLang = this.language;
}
};
</script>
</style>
and the console.log debugging is
console.log src/components/Home/ContactForm.vue:90
sendMessage()...
console.log tests/unit/ContactForm.spec.js:242
DATA: en
console.log src/components/Home/ContactForm.vue:94
VALIDATION RESULT: true
It's weird to see that the DATA ( contactLang ) value is false in the console.log from the spec ... displayed before the validation result
console.log src/components/Home/ContactForm.vue:90
sendMessage()...
console.log tests/unit/ContactForm.spec.js:242
DATA: en
console.log src/components/Home/ContactForm.vue:94
VALIDATION RESULT: true
console.log src/components/Home/ContactForm.vue:112
validation success, form submitted validForm: true
I guess there is async problem ... timout ?
thanks for feedback
SOLVED
It's actually a timeout issue
expect.assertions(1); // Otherwise jest will give a false positive
await contactForm.trigger("submit");
// then
setTimeout(() => {
expect(wrapper.vm.validForm).toEqual(true);
}, 2000);
I propose to use jest faketimers
jest.useFakeTimers()
contactForm.trigger("submit");
await wrapper.vm.$nextTick();
// then
jest.runTimersToTime(2000)
expect(wrapper.vm.validForm).toEqual(true);
I suggest to first make the test fail to avoid false positives
for more information about jest faketimers
https://jestjs.io/docs/en/timer-mocks.html
i did a simple test for my login form of component, in case it helps someone
it("submit form call method login", () => {
const login = jest.fn()
const wrapper = shallowMount(Login, {
methods: {
login
}
})
wrapper.findAll("v-form").at(0).trigger("submit")
expect(login).toBeCalled()
})