Merge pull request #610 from gradido/test-validation-rules

feat: Test Validation Rules
This commit is contained in:
Moriz Wahl 2021-07-07 17:11:30 +02:00 committed by GitHub
commit fd228a3942
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 262 additions and 185 deletions

View File

@ -206,7 +206,7 @@ jobs:
report_name: Coverage Frontend
type: lcov
result_path: ./coverage/lcov.info
min_coverage: 37
min_coverage: 44
token: ${{ github.token }}
##############################################################################

View File

@ -1,26 +1,11 @@
import { mount } from '@vue/test-utils'
import { extend } from 'vee-validate'
import InputPasswordConfirmation from './InputPasswordConfirmation'
const rules = [
'containsLowercaseCharacter',
'containsUppercaseCharacter',
'containsNumericCharacter',
'atLeastEightCharactera',
'samePassword',
]
rules.forEach((rule) => {
extend(rule, {
validate(value) {
return true
},
})
})
const localVue = global.localVue
// validation is tested in src/views/Pages/UserProfile/UserCard_FormUserPasswort.spec.js
describe('InputPasswordConfirmation', () => {
let wrapper

View File

@ -2,7 +2,7 @@ import Vue from 'vue'
import DashboardPlugin from './plugins/dashboard-plugin'
import App from './App.vue'
import i18n from './i18n.js'
import './validation-rules'
import { loadAllRules } from './validation-rules'
import { store } from './store/store'
@ -12,6 +12,8 @@ import router from './routes/router'
Vue.use(DashboardPlugin)
Vue.config.productionTip = false
loadAllRules(i18n)
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !store.state.sessionId) {
next({ path: '/login' })

View File

@ -1,5 +1,4 @@
import '@/polyfills'
import { configure, extend } from 'vee-validate'
import GlobalComponents from './globalComponents'
import GlobalDirectives from './globalDirectives'
import SideBar from '@/components/SidebarPlugin'
@ -14,8 +13,6 @@ import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
// asset imports
import '@/assets/scss/argon.scss'
import '@/assets/vendor/nucleo/css/nucleo.css'
import * as rules from 'vee-validate/dist/rules'
import { messages } from 'vee-validate/dist/locale/en.json'
import VueQrcodeReader from 'vue-qrcode-reader'
import VueQrcode from 'vue-qrcode'
@ -28,13 +25,6 @@ import VueMoment from 'vue-moment'
import Loading from 'vue-loading-overlay'
import 'vue-loading-overlay/dist/vue-loading.css'
Object.keys(rules).forEach((rule) => {
extend(rule, {
...rules[rule], // copies rule configuration
message: messages[rule], // assign message
})
})
export default {
install(Vue) {
Vue.use(GlobalComponents)
@ -49,12 +39,5 @@ export default {
Vue.use(VueQrcode)
Vue.use(FlatPickr)
Vue.use(Loading)
configure({
classes: {
valid: 'is-valid',
invalid: 'is-invalid',
dirty: ['is-dirty', 'is-dirty'], // multiple classes per flag!
},
})
},
}

View File

@ -1,104 +1,110 @@
import i18n from './i18n.js'
import { configure, extend } from 'vee-validate'
// eslint-disable-next-line camelcase
import { required, email, min, max, is_not } from 'vee-validate/dist/rules'
import loginAPI from './apis/loginAPI'
configure({
defaultMessage: (field, values) => {
values._field_ = i18n.t(`fields.${field}`)
return i18n.t(`validations.messages.${values._rule_}`, values)
},
})
export const loadAllRules = (i18nCallback) => {
configure({
defaultMessage: (field, values) => {
values._field_ = i18nCallback.t(`fields.${field}`)
return i18nCallback.t(`validations.messages.${values._rule_}`, values)
},
classes: {
valid: 'is-valid',
invalid: 'is-invalid',
dirty: ['is-dirty', 'is-dirty'], // multiple classes per flag!
},
})
extend('email', {
...email,
message: (_, values) => i18n.t('validations.messages.email', values),
})
extend('email', {
...email,
message: (_, values) => i18nCallback.t('validations.messages.email', values),
})
extend('required', {
...required,
message: (_, values) => i18n.t('validations.messages.required', values),
})
extend('required', {
...required,
message: (_, values) => i18nCallback.t('validations.messages.required', values),
})
extend('min', {
...min,
message: (_, values) => i18n.t('validations.messages.min', values),
})
extend('min', {
...min,
message: (_, values) => i18nCallback.t('validations.messages.min', values),
})
extend('max', {
...max,
message: (_, values) => i18n.t('validations.messages.max', values),
})
extend('max', {
...max,
message: (_, values) => i18nCallback.t('validations.messages.max', values),
})
extend('gddSendAmount', {
validate(value, { min, max }) {
value = value.replace(',', '.')
return value.match(/^[0-9]+(\.[0-9]{0,2})?$/) && Number(value) >= min && Number(value) <= max
},
params: ['min', 'max'],
message: (_, values) => {
values.min = i18n.n(values.min, 'ungroupedDecimal')
values.max = i18n.n(values.max, 'ungroupedDecimal')
return i18n.t('form.validation.gddSendAmount', values)
},
})
extend('gddSendAmount', {
validate(value, { min, max }) {
value = value.replace(',', '.')
return value.match(/^[0-9]+(\.[0-9]{0,2})?$/) && Number(value) >= min && Number(value) <= max
},
params: ['min', 'max'],
message: (_, values) => {
values.min = i18nCallback.n(values.min, 'ungroupedDecimal')
values.max = i18nCallback.n(values.max, 'ungroupedDecimal')
return i18nCallback.t('form.validation.gddSendAmount', values)
},
})
extend('gddUsernameUnique', {
async validate(value) {
const result = await loginAPI.checkUsername(value)
return result.result.data.state === 'success'
},
message: (_, values) => i18n.t('form.validation.usernmae-unique', values),
})
extend('gddUsernameUnique', {
async validate(value) {
const result = await loginAPI.checkUsername(value)
return result.result.data.state === 'success'
},
message: (_, values) => i18nCallback.t('form.validation.usernmae-unique', values),
})
extend('gddUsernameRgex', {
validate(value) {
return !!value.match(/^[a-zA-Z][-_a-zA-Z0-9]{2,}$/)
},
message: (_, values) => i18n.t('form.validation.usernmae-regex', values),
})
extend('gddUsernameRgex', {
validate(value) {
return !!value.match(/^[a-zA-Z][-_a-zA-Z0-9]{2,}$/)
},
message: (_, values) => i18nCallback.t('form.validation.usernmae-regex', values),
})
// eslint-disable-next-line camelcase
extend('is_not', {
// eslint-disable-next-line camelcase
...is_not,
message: (_, values) => i18n.t('form.validation.is-not', values),
})
extend('is_not', {
// eslint-disable-next-line camelcase
...is_not,
message: (_, values) => i18nCallback.t('form.validation.is-not', values),
})
// Password validation
// Password validation
extend('containsLowercaseCharacter', {
validate(value) {
return !!value.match(/[a-z]+/)
},
message: (_, values) => i18n.t('site.signup.lowercase', values),
})
extend('containsLowercaseCharacter', {
validate(value) {
return !!value.match(/[a-z]+/)
},
message: (_, values) => i18nCallback.t('site.signup.lowercase', values),
})
extend('containsUppercaseCharacter', {
validate(value) {
return !!value.match(/[A-Z]+/)
},
message: (_, values) => i18n.t('site.signup.uppercase', values),
})
extend('containsUppercaseCharacter', {
validate(value) {
return !!value.match(/[A-Z]+/)
},
message: (_, values) => i18nCallback.t('site.signup.uppercase', values),
})
extend('containsNumericCharacter', {
validate(value) {
return !!value.match(/[0-9]+/)
},
message: (_, values) => i18n.t('site.signup.one_number', values),
})
extend('containsNumericCharacter', {
validate(value) {
return !!value.match(/[0-9]+/)
},
message: (_, values) => i18nCallback.t('site.signup.one_number', values),
})
extend('atLeastEightCharactera', {
validate(value) {
return !!value.match(/.{8,}/)
},
message: (_, values) => i18n.t('site.signup.minimum', values),
})
extend('atLeastEightCharactera', {
validate(value) {
return !!value.match(/.{8,}/)
},
message: (_, values) => i18nCallback.t('site.signup.minimum', values),
})
extend('samePassword', {
validate(value, [pwd]) {
return value === pwd
},
message: (_, values) => i18n.t('site.signup.dont_match', values),
})
extend('samePassword', {
validate(value, [pwd]) {
return value === pwd
},
message: (_, values) => i18nCallback.t('site.signup.dont_match', values),
})
}

View File

@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils'
import TransactionForm from './TransactionForm'
import flushPromises from 'flush-promises'
const localVue = global.localVue
@ -19,8 +20,12 @@ describe('GddSend', () => {
},
}
const propsData = {
balance: 100.0,
}
const Wrapper = () => {
return mount(TransactionForm, { localVue, mocks })
return mount(TransactionForm, { localVue, mocks, propsData })
}
describe('mount', () => {
@ -53,6 +58,18 @@ describe('GddSend', () => {
'E-Mail',
)
})
it('flushes an error message when no valid email is given', async () => {
await wrapper.find('#input-group-1').find('input').setValue('a')
await flushPromises()
expect(wrapper.find('span.errors').text()).toBe('validations.messages.email')
})
it('trims the email after blur', async () => {
await wrapper.find('#input-group-1').find('input').setValue(' valid@email.com ')
await flushPromises()
expect(wrapper.vm.form.email).toBe('valid@email.com')
})
})
describe('ammount field', () => {
@ -73,6 +90,24 @@ describe('GddSend', () => {
'0.01',
)
})
it('flushes an error message when no valid amount is given', async () => {
await wrapper.find('#input-group-2').find('input').setValue('a')
await flushPromises()
expect(wrapper.find('span.errors').text()).toBe('form.validation.gddSendAmount')
})
it('flushes an error message when amount is too high', async () => {
await wrapper.find('#input-group-2').find('input').setValue('123.34')
await flushPromises()
expect(wrapper.find('span.errors').text()).toBe('form.validation.gddSendAmount')
})
it('flushes no errors when amount is valid', async () => {
await wrapper.find('#input-group-2').find('input').setValue('87.34')
await flushPromises()
expect(wrapper.find('span.errors').exists()).toBeFalsy()
})
})
describe('message text box', () => {
@ -89,6 +124,18 @@ describe('GddSend', () => {
it('has a label form.memo', () => {
expect(wrapper.find('label.input-3').text()).toBe('form.memo')
})
it('flushes an error message when memo is less than 5 characters', async () => {
await wrapper.find('#input-group-3').find('textarea').setValue('a')
await flushPromises()
expect(wrapper.find('span.errors').text()).toBe('validations.messages.min')
})
it('flushes no error message when memo is valid', async () => {
await wrapper.find('#input-group-3').find('textarea').setValue('Long enough')
await flushPromises()
expect(wrapper.find('span.errors').exists()).toBeFalsy()
})
})
describe('cancel button', () => {
@ -100,11 +147,42 @@ describe('GddSend', () => {
expect(wrapper.find('button[type="reset"]').text()).toBe('form.reset')
})
it.skip('clears the email field on click', async () => {
wrapper.find('#input-group-1').find('input').setValue('someone@watches.tv')
wrapper.find('button[type="reset"]').trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.vm.form.email).toBeNull()
it('clears all fields on click', async () => {
await wrapper.find('#input-group-1').find('input').setValue('someone@watches.tv')
await wrapper.find('#input-group-2').find('input').setValue('87.23')
await wrapper.find('#input-group-3').find('textarea').setValue('Long enugh')
await flushPromises()
expect(wrapper.vm.form.email).toBe('someone@watches.tv')
expect(wrapper.vm.form.amount).toBe('87.23')
expect(wrapper.vm.form.memo).toBe('Long enugh')
await wrapper.find('button[type="reset"]').trigger('click')
await flushPromises()
expect(wrapper.vm.form.email).toBe('')
expect(wrapper.vm.form.amount).toBe('')
expect(wrapper.vm.form.memo).toBe('')
})
})
describe('submit', () => {
beforeEach(async () => {
await wrapper.find('#input-group-1').find('input').setValue('someone@watches.tv')
await wrapper.find('#input-group-2').find('input').setValue('87.23')
await wrapper.find('#input-group-3').find('textarea').setValue('Long enugh')
await wrapper.find('form').trigger('submit')
await flushPromises()
})
it('emits set-transaction', async () => {
expect(wrapper.emitted('set-transaction')).toBeTruthy()
expect(wrapper.emitted('set-transaction')).toEqual([
[
{
email: 'someone@watches.tv',
amount: 87.23,
memo: 'Long enugh',
},
],
])
})
})
})

View File

@ -168,7 +168,7 @@ export default {
},
methods: {
onSubmit() {
this.normalizeAmount()
this.normalizeAmount(true)
this.$emit('set-transaction', {
email: this.form.email,
amount: this.form.amountValue,
@ -181,10 +181,11 @@ export default {
this.form.amount = ''
this.form.memo = ''
},
setTransaction(data) {
this.form.email = data.email
this.form.amount = data.amount
},
/*
setTransaction(data) {
this.form.email = data.email
this.form.amount = data.amount
}, */
normalizeAmount(isValid) {
this.amountFocused = false
if (!isValid) return

View File

@ -83,9 +83,7 @@ describe('ForgotPassword', () => {
})
it('displays an error', () => {
expect(form.find('div.invalid-feedback').text()).toEqual(
'The Email field must be a valid email',
)
expect(form.find('div.invalid-feedback').text()).toEqual('validations.messages.email')
})
it('does not call the API', () => {

View File

@ -123,7 +123,7 @@ describe('Login', () => {
await wrapper.find('form').trigger('submit')
await flushPromises()
expect(wrapper.findAll('div.invalid-feedback').at(0).text()).toBe(
'The Email field is required',
'validations.messages.required',
)
})
@ -131,7 +131,7 @@ describe('Login', () => {
await wrapper.find('form').trigger('submit')
await flushPromises()
expect(wrapper.findAll('div.invalid-feedback').at(1).text()).toBe(
'The form.password field is required',
'validations.messages.required',
)
})
})

View File

@ -84,7 +84,7 @@ describe('Register', () => {
wrapper.find('#registerEmail').setValue('no_valid@Email')
await flushPromises()
await expect(wrapper.find('#registerEmailLiveFeedback').text()).toEqual(
'The Email field must be a valid email',
'validations.messages.email',
)
})

View File

@ -2,23 +2,8 @@ import { mount, RouterLinkStub } from '@vue/test-utils'
import loginAPI from '../../apis/loginAPI'
import ResetPassword from './ResetPassword'
import flushPromises from 'flush-promises'
import { extend } from 'vee-validate'
const rules = [
'containsLowercaseCharacter',
'containsUppercaseCharacter',
'containsNumericCharacter',
'atLeastEightCharactera',
'samePassword',
]
rules.forEach((rule) => {
extend(rule, {
validate(value) {
return true
},
})
})
// validation is tested in src/views/Pages/UserProfile/UserCard_FormUserPasswort.spec.js
jest.mock('../../apis/loginAPI')

View File

@ -2,23 +2,6 @@ import { mount } from '@vue/test-utils'
import UserCardFormPasswort from './UserCard_FormUserPasswort'
import loginAPI from '../../../apis/loginAPI'
import flushPromises from 'flush-promises'
import { extend } from 'vee-validate'
const rules = [
'containsLowercaseCharacter',
'containsUppercaseCharacter',
'containsNumericCharacter',
'atLeastEightCharactera',
'samePassword',
]
rules.forEach((rule) => {
extend(rule, {
validate(value) {
return true
},
})
})
jest.mock('../../../apis/loginAPI')
@ -122,6 +105,57 @@ describe('UserCardFormUserPasswort', () => {
expect(form.find('button[type="submit"]').exists()).toBeTruthy()
})
describe('validation', () => {
it('displays all password requirements', () => {
const feedbackArray = wrapper.findAll('div.invalid-feedback').at(1).findAll('span')
expect(feedbackArray).toHaveLength(5)
expect(feedbackArray.at(0).text()).toBe('validations.messages.required')
expect(feedbackArray.at(1).text()).toBe('site.signup.lowercase')
expect(feedbackArray.at(2).text()).toBe('site.signup.uppercase')
expect(feedbackArray.at(3).text()).toBe('site.signup.one_number')
expect(feedbackArray.at(4).text()).toBe('site.signup.minimum')
})
it('removes first message when a character is given', async () => {
await wrapper.findAll('input').at(1).setValue('@')
await flushPromises()
const feedbackArray = wrapper.findAll('div.invalid-feedback').at(1).findAll('span')
expect(feedbackArray).toHaveLength(4)
expect(feedbackArray.at(0).text()).toBe('site.signup.lowercase')
})
it('removes first and second message when a lowercase character is given', async () => {
await wrapper.findAll('input').at(1).setValue('a')
await flushPromises()
const feedbackArray = wrapper.findAll('div.invalid-feedback').at(1).findAll('span')
expect(feedbackArray).toHaveLength(3)
expect(feedbackArray.at(0).text()).toBe('site.signup.uppercase')
})
it('removes the first three messages when a lowercase and uppercase characters are given', async () => {
await wrapper.findAll('input').at(1).setValue('Aa')
await flushPromises()
const feedbackArray = wrapper.findAll('div.invalid-feedback').at(1).findAll('span')
expect(feedbackArray).toHaveLength(2)
expect(feedbackArray.at(0).text()).toBe('site.signup.one_number')
})
it('removes the first four messages when a lowercase, uppercase and numeric characters are given', async () => {
await wrapper.findAll('input').at(1).setValue('Aa1')
await flushPromises()
const feedbackArray = wrapper.findAll('div.invalid-feedback').at(1).findAll('span')
expect(feedbackArray).toHaveLength(1)
expect(feedbackArray.at(0).text()).toBe('site.signup.minimum')
})
it('removes all messages when all rules are fulfilled', async () => {
await wrapper.findAll('input').at(1).setValue('Aa123456')
await flushPromises()
const feedbackArray = wrapper.findAll('div.invalid-feedback').at(1).findAll('span')
expect(feedbackArray).toHaveLength(0)
})
})
describe('submit', () => {
describe('valid data', () => {
beforeEach(async () => {

View File

@ -1,29 +1,24 @@
import { mount } from '@vue/test-utils'
import { extend } from 'vee-validate'
import UserCardFormUsername from './UserCard_FormUsername'
import loginAPI from '../../../apis/loginAPI'
import flushPromises from 'flush-promises'
import { extend } from 'vee-validate'
jest.mock('../../../apis/loginAPI')
extend('gddUsernameRgex', {
validate(value) {
return true
},
})
extend('gddUsernameUnique', {
validate(value) {
return true
},
})
const localVue = global.localVue
const mockAPIcall = jest.fn((args) => {
return { success: true }
})
// override this rule to avoid API call
extend('gddUsernameUnique', {
validate(value) {
return true
},
})
const toastErrorMock = jest.fn()
const toastSuccessMock = jest.fn()
const storeCommitMock = jest.fn()

View File

@ -1,10 +1,11 @@
import { createLocalVue } from '@vue/test-utils'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
import Vuex from 'vuex'
import { ValidationProvider, ValidationObserver, extend } from 'vee-validate'
import * as rules from 'vee-validate/dist/rules'
import { messages } from 'vee-validate/dist/locale/en.json'
import RegeneratorRuntime from 'regenerator-runtime'
import SideBar from '@/components/SidebarPlugin'
import VueQrcode from 'vue-qrcode'
@ -14,7 +15,7 @@ import VueMoment from 'vue-moment'
import clickOutside from '@/directives/click-ouside.js'
import { focus } from 'vue-focus'
global.localVue = createLocalVue()
import { loadAllRules } from '../src/validation-rules'
Object.keys(rules).forEach((rule) => {
extend(rule, {
@ -23,6 +24,15 @@ Object.keys(rules).forEach((rule) => {
})
})
const i18nMock = {
t: (identifier, values) => identifier,
n: (value, format) => value,
}
loadAllRules(i18nMock)
global.localVue = createLocalVue()
global.localVue.use(BootstrapVue)
global.localVue.use(Vuex)
global.localVue.use(IconsPlugin)