Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

vue-form

Package Overview
Dependencies
Maintainers
1
Versions
46
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

vue-form - npm Package Compare versions

Comparing version 0.3.1 to 2.0.0

.babelrc

17

karma.conf.js

@@ -18,4 +18,6 @@ // Karma configuration

files: [
'node_modules/vue/dist/vue.min.js',
'vue-form.js',
'https://www.promisejs.org/polyfills/promise-6.1.0.js',
//'https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js',
'node_modules/vue/dist/vue.js',
'dist/vue-form.js',
'test/specs/*.js'

@@ -26,4 +28,3 @@ ],

// list of files to exclude
exclude: [
],
exclude: [],

@@ -48,3 +49,3 @@

},
// test results reporter to use

@@ -66,5 +67,9 @@ // possible values: 'dots', 'progress'

// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
logLevel: config.LOG_DEBUG,
client: {
captureConsole: true
},
// enable / disable watching file and executing tests whenever any file changes

@@ -71,0 +76,0 @@ autoWatch: true,

{
"name": "vue-form",
"version": "0.3.1",
"version": "2.0.0",
"description": "Form validation for Vue.js",
"main": "vue-form.js",
"main": "dist/vue-form.js",
"scripts": {
"dev": "rollup -w -c",
"test": "karma start",
"dist": "uglifyjs --compress -- vue-form.js > vue-form.min.js"
"dist": "rollup -c && uglifyjs --compress -- dist/vue-form.js > dist/vue-form.min.js"
},

@@ -13,12 +14,18 @@ "author": "Fergal Doyle",

"devDependencies": {
"jasmine-core": "^2.3.4",
"karma": "^0.13.11",
"karma-babel-preprocessor": "^5.2.2",
"babel-core": "^6.14.0",
"babel-plugin-external-helpers": "^6.8.0",
"babel-preset-es2015": "^6.14.0",
"jasmine-core": "^2.5.2",
"karma": "^1.5.0",
"karma-babel-preprocessor": "^6.0.1",
"karma-firefox-launcher": "^0.1.7",
"karma-jasmine": "^0.3.6",
"karma-phantomjs-launcher": "^0.2.1",
"phantomjs": "^1.9.18",
"uglify-js": "^2.5.0",
"vue": "^1.0.21"
"karma-jasmine": "^1.0.2",
"karma-phantomjs-launcher": "^1.0.2",
"phantomjs": "^2.1.7",
"rollup": "^0.41.4",
"rollup-plugin-babel": "^2.7.1",
"rollup-watch": "^3.2.2",
"uglify-js": "^2.7.3",
"vue": "2.2.1"
}
}

@@ -5,3 +5,3 @@ # vue-form

Form validation for Vue.js 1.0+. Works along side `v-model` but can also be used on your custom form control components (tinymce, select2, tag-editor etc).
Form validation for Vue.js 2.0+

@@ -13,38 +13,73 @@ ### Install

``` js
// es6: import * as vueForm from 'vue-form';
var vueForm = require('vue-form');
import vueForm from 'vue-form';
// install globally
Vue.use(vueForm);
// or use the mixin
...
mixins: [vueForm.mixin]
...
```
You can also directly include it with a `<script>` tag when you have Vue itself included globally. It will automatically install itself.
### Usage
This plugin registers two global directives, `v-form` and `v-form-ctrl`. Apply the `v-form` directive to a `form` element, and set the `name` attribute. This `name` will hold the overall form state object and is created on the current vm.
Once installed you have access to four components (`vue-form`, `validate`, `form-errors`, `form-error`) for managing form state, validating form fields and displaying validation error messages.
Apply the `v-form-ctrl` directive to each of the form inputs. `v-form-ctrl` will watch `v-model` and validate on change. Use static or binding attributes to specify validators (`required`, `maxlength`, `type="email"`, `type="url"`, etc)
Example
```html
<form v-form name="myform" @submit.prevent="onSubmit">
<div class="errors" v-if="myform.$submitted">
<p v-if="myform.name.$error.required">Name is required.</p>
<p v-if="myform.email.$error.email">Email is not valid.</p>
</div>
<label>
<span>Name *</span>
<input v-model="model.name" v-form-ctrl required name="name" />
</label>
<label>
<span>Email</span>
<input v-model="model.email" v-form-ctrl name="email" type="email" />
</label>
<button type="submit">Submit</button>
</form>
<pre>{{ myform | json }}</pre>
<div id="app">
<vue-form :state="formstate" @submit.prevent="onSubmit">
<validate tag="label">
<span>Name *</span>
<input v-model="model.name" required name="name" />
<form-error field="name" error="required">Name is a required field</form-error>
</validate>
<validate tag="label">
<span>Email</span>
<input v-model="model.email" name="email" type="email" required />
<form-errors field="email">
<div slot="required">Email is a required field</div>
<div slot="email">Email is not valid</div>
</form-errors>
</validate>
<button type="submit">Submit</button>
</vue-form>
<pre>{{ formstate }}</pre>
</div>
```
`myform` will be an object with the following properties:
```js
Vue.use(vueForm);
new Vue({
el: '#app',
data: {
formstate: {},
model: {
name: '',
email: 'invalid-email'
}
},
methods: {
onSubmit: function () {
if(this.formstate.$invalid) {
// alert user and exit early
return;
}
// otherwise submit form
}
}
});
```
The output of `formstate` will be:
```js
{
"$name": "myform",
"$dirty": false,

@@ -55,13 +90,7 @@ "$pristine": true,

"$submitted": false,
"$touched": false,
"$untouched": true,
"$pending": false,
"$error": {
"name": {
"$name": "name",
"$dirty": false,
"$pristine": true,
"$valid": false,
"$invalid": true,
"$error": {
"required": true
}
}
// fields with errors are copied into this object
},

@@ -74,2 +103,5 @@ "name": {

"$invalid": true,
"$touched": false,
"$untouched": true,
"$pending": false,
"$error": {

@@ -83,5 +115,10 @@ "required": true

"$pristine": true,
"$valid": true,
"$invalid": false,
"$error": {}
"$valid": false,
"$invalid": true,
"$touched": false,
"$untouched": true,
"$pending": false,
"$error": {
"email": true
}
}

@@ -91,7 +128,20 @@ }

### Validators
### Displaying errors
Display single errors with `form-error` or multiple errors with `form-errors`.
#### Built in validators:
The `show` prop supports simple expressions which specifiy when erros should be displayed based on the current state of the field, e.g: `$dirty`, `$dirty && $touched`, `$dirty || $touched`
```html
<form-error field="fieldName" error="errorKey" show="$dirty">Error message</form-error>
<form-errors field="fieldName" show="$dirty && $touched">
<div slot="errorKeyA">Error message A</div>
<div slot="errorKeyB">Error message B</div>
</form-errors>
```
### Validators
```
type="email"

@@ -112,31 +162,40 @@ type="url"

<!-- static validators -->
<input type="email" name="email" v-model="model.email" v-form-ctrl required />
<input type="text" name="name" v-model="model.name" v-form-ctrl maxlength="25" minlength="5" />
<validate>
<input type="email" name="email" v-model="model.email" required />
</validate>
<validate>
<input type="text" name="name" v-model="model.name" maxlength="25" minlength="5" />
</validate>
<!-- bound validators -->
<input type="email" name="email" v-model="model.email" v-form-ctrl :required="isRequired" />
<input type="text" name="name" v-model="model.name" v-form-ctrl :maxlength="maxLen" :minlength="minLen" />
<validate>
<input type="email" name="email" v-model="model.email" :required="isRequired" />
</validate>
<validate>
<input type="text" name="name" v-model="model.name" :maxlength="maxLen" :minlength="minLen" />
</validate>
```
#### State classes
#### Custom validators
You can register global and local custom validators.
As form and input validation states change, state classes are added and removed
Possible form classes:
Global custom validator
```js
vueForm.addValidator('my-custom-validator', function (value, attrValue, vnode) {
// return true to set input as $valid, false to set as $invalid
return value === 'custom';
});
```
vf-dirty, vf-pristine, vf-valid, vf-invalid, vf-submitted
```
Possible input classes:
```html
<validate>
<input v-model="something" name="something" my-custom-validator />
</validate>
```
vf-dirty, vf-pristine, vf-valid, vf-invalid
// also for every validation error, a class will be added, e.g.
vf-invalid-required, vf-invalid-minlength, vf-invalid-max, etc
```
#### Custom validator:
Local custom validator
```html
<input v-model="something" v-form-ctrl name="something" custom-validator="customValidator" />
<validate :custom="{customValidator: customValidator}">
<input v-model="something" name="something" />
</validate>
```

@@ -155,16 +214,105 @@

#### Async validators:
### Custom form control component
Async validators are custom validators which return a Promise. `resolve()` `true` or `false` to set field vadility.
```js
// ...
methods: {
customValidator (value) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(value === 'ajax');
}, 100);
});
}
}
// ...
```
You can also use `vue-form` on your own form components. Simply wrap your component with an element with `v-form-ctrl`, `name` and any validation attributes. Set `v-form-ctrl` to the same property you will be updating via two-way binding in your component. You can also get a hook into the internals of `v-form-ctrl` to mange control state.
Async validator with debounce (example uses lodash debounce)
```js
methods: {
debounced: _.debounce(function (value, resolve, reject) {
fetch('https://httpbin.org/get').then(function(response){
resolve(response.isValid);
});
}, 500),
customValidator (value) {
return new Promise((resolve, reject) => {
this.debounced(value, resolve, reject);
});
}
}
```
[See custom tinymce component validation example ](https://github.com/fergaldoyle/vue-form/tree/master/example)
### State classes
As form and input validation states change, state classes are added and removed
```html
<div>
<span>Rich text *</span>
<span v-form-ctrl="model.html" name="html" required>
<tinymce id="inline-editor" :model.sync="model.html"></tinymce>
</span>
</div>
Possible form classes:
```
vf-form-dirty, vf-form-pristine, vf-form-valid, vf-form-invalid, vf-form-submitted
```
Possible input classes:
```
vf-dirty, vf-pristine, vf-valid, vf-invalid
// also for every validation error, a class will be added, e.g.
vf-invalid-required, vf-invalid-minlength, vf-invalid-max, etc
```
Input wrappers (e.g. the tag the `validate` component renders) will also get state classes, but with the `container` prefix, e.g.
```
vf-container-dirty, vf-container-pristine, vf-container-valid, vf-container-invalid
```
### Custom components
When writing custom form field components, e.g. `<my-checkbox v-model="foo"></my-checkbox>` you should trigger the `focus` and `blur` events after user interaction either by triggering native dom events on the root node of your component, or emitting Vue events (`this.$emit('focus)`) so the `validate` component can detect and set the `$dirty` and `$touched` states on the field.
### Component props
#### vue-form
* `state` Object on which form state is set
#### validate
* `state` Optional way of passing in the form state. If omitted form state will be found in the $parent
* `custom` Object containing one or many custom validators. `{validatorName: validatorFunction}`
* `tag` String which specifies what element tag should be rendered by the `validate` component, defaults to `span`
#### form-error
* `state` Optional way of passing in the form state. If omitted form state will be found in the $parent
* `field` String which specifies the related field name
* `error` String which specifies the error key which the error should be shown for
* `tag` String, defaults to `span`
* `show`: String, show error dependant on form field state e.g. `$dirty`, `$dirty && $touched`
#### form-errors
* `state` Optional way of passing in the form state. If omitted form state will be found in the $parent
* `field` String which specifies the related field name
* `tag` String, defaults to `div`
* `show`: String, show error dependant on form field state e.g. `$touched`, `$dirty || $touched`
### Config
Set config options using `vueForm.config`, defaults:
```js
{
formComponent: 'vueForm',
errorComponent: 'formError',
errorsComponent: 'formErrors',
validateComponent: 'validate',
errorTag: 'span',
errorsTag: 'div',
classPrefix: 'vf-',
dirtyClass: 'dirty',
pristineClass: 'pristine',
validClass: 'valid',
invalidClass: 'invalid',
submittedClass: 'submitted',
touchedClass: 'touched',
untouchedClass: 'untouched',
pendingClass: 'pending',
Promise: window.Promise
}
```

@@ -1,34 +0,95 @@

describe('vue-form', function () {
var vm;
describe('vue-form', function() {
let vm;
beforeEach(function (done) {
Vue.use(vueForm);
console.log('Vue version', Vue.version);
function setValid() {
vm.model.b = '123456';
vm.model.c = '12346';
vm.model.multicheck = ['Jack'];
}
beforeEach(function(done) {
const div = document.createElement('div');
document.body.appendChild(div);
vm = new Vue({
el: 'body',
replace: false,
el: div,
template: `
<form v-form name="myform">
<input v-model="model.a" v-form-ctrl name="a" required type="text" />
<input v-model="model.b" v-form-ctrl name="b" required type="text" />
<input v-model="model.c" v-form-ctrl name="c" type="text" />
<input v-model="model.d" v-form-ctrl name="d" :required="isRequired" type="text" />
<input v-model="model.e" v-form-ctrl name="e" :required="isRequired" type="text" />
<input v-model="model.f" v-form-ctrl name="f" type="email" />
<input v-model="model.g" v-form-ctrl name="g" type="number" />
<input v-model="model.h" v-form-ctrl name="h" type="text" minlength="6" />
<input v-model="model.i" v-form-ctrl name="i" type="text" maxlength="10" />
<input v-model="model.j" v-form-ctrl name="j" type="number" min="10" />
<input v-model="model.k" v-form-ctrl name="k" type="number" max="10" />
<input v-model="model.l" v-form-ctrl name="l" type="url" />
<input v-model="model.m" v-form-ctrl name="m" type="text" :pattern="'[A-Za-z]{3}'" />
<input v-model="model.n" v-form-ctrl name="n" type="email" required minlength="8" />
<input v-model="model.o" v-form-ctrl name="o" type="text" custom-validator="customValidator" />
<input type="checkbox" value="Jack" v-model="multicheck" v-form-ctrl required name="multicheck"/>
<input type="checkbox" value="John" v-model="multicheck" v-form-ctrl required name="multicheck"/>
<input type="checkbox" value="Mike" v-model="multicheck" v-form-ctrl required name="multicheck"/>
<vue-form :state="formstate" @submit.prevent="onSubmit">
</form>
<validate>
<input v-model="model.a" name="a" required type="text" />
</validate>
<validate :state="formstate">
<input v-model="model.b" name="b" required type="text" minlength="6" />
<form-errors field="b" show="$dirty && $touched">
<span id="error-message-b" slot="required">required error</span>
</form-errors>
<form-error field="b" error="required" show="$touched"><span id="error-message-b-2"></span></form-error>
</validate>
<div v-if="isCEnabled">
<validate>
<input v-model="model.c" name="c" :required="isRequired" :minlength="minlength" type="text" />
</validate>
<form-errors field="c">
<span id="error-message" slot="required">required error</span>
<span id="minlength-message" slot="minlength">minlength error</span>
</form-errors>
<form-error field="c" error="required"><span id="error-message2"></span></form-error>
<form-error field="c" error="minlength"><span id="minlength-message2"></span></form-error>
</div>
<validate>
<input v-model="model.email" name="email" type="email" />
</validate>
<validate>
<input v-model="model.number" name="number" type="number" required />
</validate>
<validate>
<input v-model="model.url" name="url" type="url" />
</validate>
<validate>
<input v-model="model.length" name="length" type="text" minlength="4" maxlength="8" />
</validate>
<validate>
<input v-model="model.minmax" name="minmax" type="number" min="4" max="8" />
</validate>
<validate>
<input v-model="model.pattern" name="pattern" type="text" pattern="\\d\\d\\d\\d" />
</validate>
<validate :custom="{ 'custom-key': customValidator }">
<input v-model="model.custom" name="custom" type="text" />
</validate>
<validate v-if="asyncEnabled" :custom="{ customAsync: customValidatorAsync }">
<input v-model="model.custom2" name="custom2" type="text" />
</validate>
<validate>
<input type="checkbox" value="Jack" v-model="model.multicheck" required name="multicheck"/>
<input type="checkbox" value="John" v-model="model.multicheck" required name="multicheck"/>
<input type="checkbox" value="Mike" v-model="model.multicheck" required name="multicheck"/>
</validate>
<button id="submit" type="submit"></button>
</vue-form>
`,
data: {
hasSubmitted: false,
formstate: {},
isRequired: true,
minlength: 5,
isCEnabled: true,
asyncEnabled: false,
model: {

@@ -38,14 +99,10 @@ a: 'aaa',

c: null,
d: '',
e: 'eee',
f: 'foo.bar@com.com',
g: '3',
h: '12',
i: '',
j: 11,
k: 5,
l: 'non url',
m: 'x',
n: '',
o: 'abc',
email: 'joe.doe@foo.com',
number: 1,
url: 'https://foo.bar.com',
length: '12345',
minmax: 5,
pattern: '1234',
custom: 'custom',
custom2: 'custom2',
multicheck: []

@@ -55,4 +112,14 @@ }

methods: {
customValidator: function (value) {
onSubmit() {
this.hasSubmitted = true;
},
customValidator(value) {
return value === 'custom';
},
customValidatorAsync(value) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(value === 'custom2');
}, 100);
});
}

@@ -64,3 +131,3 @@ }

afterEach(function (done) {
afterEach(function(done) {
vm.$destroy();

@@ -70,291 +137,458 @@ Vue.nextTick(done);

it('should create an object in the current vm', function () {
expect(vm.myform).toBeDefined();
it('should create a form tag and listen to submit event', () => {
expect(vm.$el.tagName).toBe('FORM');
vm.$el.querySelector('button[type=submit]').click();
expect(vm.hasSubmitted).toBe(true);
});
it('should work on an element with no validation attributes', function () {
expect(vm.myform.c).toBeDefined();
it('should populate formstate', () => {
expect(vm.formstate.$valid).toBeDefined();
});
it('should validate against static attributes', function () {
expect(vm.myform.a.$valid).toBe(true);
expect(vm.myform.b.$invalid).toBe(true);
it('should automatically find parent formstate and also work by passing state as a prop', () => {
expect(vm.formstate.a).toBeDefined(true);
expect(vm.formstate.b).toBeDefined(true);
});
it('should validate against binding attributes', function () {
expect(vm.myform.d.$invalid).toBe(true);
expect(vm.myform.e.$valid).toBe(true);
it('should validate required fields', (done) => {
expect(vm.formstate.a.$valid).toBe(true);
expect(vm.formstate.a.$error.required).toBeUndefined();
vm.model.a = '';
vm.$nextTick(() => {
expect(vm.formstate.a.$valid).toBe(false);
expect(vm.formstate.a.$error.required).toBe(true);
done();
});
});
it('should react to model changes', function (done) {
expect(vm.myform.a.$valid).toBe(true);
vm.model.a = '';
Vue.nextTick(function () {
expect(vm.myform.a.$valid).toBe(false);
Vue.nextTick(done);
it('should not show other validators if required validator fails', (done) => {
expect(vm.formstate.b.$error.required).toBe(true);
expect(vm.formstate.b.$error.minlength).toBeUndefined();
vm.model.b = 'acb';
vm.$nextTick(() => {
expect(vm.formstate.b.$error.required).toBeUndefined();
expect(vm.formstate.b.$error.minlength).toBe(true);
done();
});
});
it('should react to attribue binding changes', function (done) {
expect(vm.myform.d.$valid).toBe(false);
it('should react to bound validators', (done) => {
expect(vm.formstate.c.$error.required).toBe(true);
vm.isRequired = false;
Vue.nextTick(function () {
expect(vm.myform.d.$valid).toBe(true);
Vue.nextTick(done);
vm.$nextTick(() => {
expect(vm.formstate.c.$error.required).toBeUndefined();
vm.model.c = '1234';
vm.$nextTick(() => {
expect(vm.formstate.c.$error.minlength).toBe(true);
vm.minlength = 2;
vm.$nextTick(() => {
expect(vm.formstate.c.$error.minlength).toBeUndefined();
done();
});
});
});
});
it('should validate [type=email]', function (done) {
expect(vm.myform.f.$valid).toBe(true);
vm.model.f = 'not a real email';
Vue.nextTick(function () {
expect(vm.myform.f.$valid).toBe(false);
Vue.nextTick(done);
it('should validate [type=email]', (done) => {
expect(vm.formstate.email.$valid).toBe(true);
expect(vm.formstate.email.$error.email).toBeUndefined();
vm.model.email = 'not a real email';
vm.$nextTick(() => {
expect(vm.formstate.email.$valid).toBe(false);
expect(vm.formstate.email.$error.email).toBe(true);
done();
});
});
it('should validate [type=number]', function (done) {
expect(vm.myform.g.$valid).toBe(true);
vm.model.g = 'not a real email';
Vue.nextTick(function () {
expect(vm.myform.g.$valid).toBe(false);
Vue.nextTick(done);
it('should validate [type=number]', (done) => {
expect(vm.formstate.number.$valid).toBe(true);
expect(vm.formstate.number.$error.number).toBeUndefined();
vm.model.number = 'a string';
vm.$nextTick(() => {
expect(vm.formstate.number.$valid).toBe(false);
expect(vm.formstate.number.$error.number).toBe(true);
done();
});
});
it('should validate [type=url]', function (done) {
expect(vm.myform.l.$valid).toBe(false);
vm.model.l = 'http://foo.bar/baz';
Vue.nextTick(function () {
expect(vm.myform.l.$valid).toBe(true);
Vue.nextTick(done);
it('should validate required [type=number] === 0', (done) => {
vm.model.number = 0;
vm.$nextTick(() => {
expect(vm.formstate.number.$valid).toBe(true);
expect(vm.formstate.number.$error.number).toBeUndefined();
expect(vm.formstate.number.$error.required).toBeUndefined();
done();
});
});
it('should validate [required]', function (done) {
expect(vm.myform.a.$valid).toBe(true);
vm.model.a = '';
Vue.nextTick(function () {
expect(vm.myform.a.$valid).toBe(false);
Vue.nextTick(done);
it('should validate [type=url]', (done) => {
expect(vm.formstate.url.$valid).toBe(true);
expect(vm.formstate.url.$error.url).toBeUndefined();
vm.model.url = 'not a real url';
vm.$nextTick(() => {
expect(vm.formstate.url.$valid).toBe(false);
expect(vm.formstate.url.$error.url).toBe(true);
done();
});
});
it('should validate [minlength]', function (done) {
expect(vm.myform.h.$valid).toBe(false);
vm.model.h = '123456';
Vue.nextTick(function () {
expect(vm.myform.h.$valid).toBe(true);
Vue.nextTick(done);
it('should validate [minlength]', (done) => {
expect(vm.formstate.length.$valid).toBe(true);
expect(vm.formstate.length.$error.minlength).toBeUndefined();
vm.model.length = '1';
vm.$nextTick(() => {
expect(vm.formstate.length.$valid).toBe(false);
expect(vm.formstate.length.$error.minlength).toBe(true);
done();
});
});
it('should validate [maxlength]', function (done) {
expect(vm.myform.i.$valid).toBe(true);
vm.model.i = '123456789100';
Vue.nextTick(function () {
expect(vm.myform.i.$valid).toBe(false);
Vue.nextTick(done);
it('should validate [maxlength]', (done) => {
expect(vm.formstate.length.$valid).toBe(true);
expect(vm.formstate.length.$error.maxlength).toBeUndefined();
vm.model.length = '1234567890';
vm.$nextTick(() => {
expect(vm.formstate.length.$valid).toBe(false);
expect(vm.formstate.length.$error.maxlength).toBe(true);
done();
});
});
it('should validate [number][min]', function (done) {
expect(vm.myform.j.$valid).toBe(true);
vm.model.j = 9;
Vue.nextTick(function () {
expect(vm.myform.j.$valid).toBe(false);
Vue.nextTick(done);
it('should validate [number][min]', (done) => {
expect(vm.formstate.minmax.$valid).toBe(true);
expect(vm.formstate.minmax.$error.min).toBeUndefined();
vm.model.minmax = 1;
vm.$nextTick(() => {
expect(vm.formstate.minmax.$valid).toBe(false);
expect(vm.formstate.minmax.$error.min).toBe(true);
done();
});
});
it('should validate [number][max]', function (done) {
expect(vm.myform.k.$valid).toBe(true);
vm.model.k = 15;
Vue.nextTick(function () {
expect(vm.myform.k.$valid).toBe(false);
Vue.nextTick(done);
it('should validate [number][max]', (done) => {
expect(vm.formstate.minmax.$valid).toBe(true);
expect(vm.formstate.minmax.$error.max).toBeUndefined();
vm.model.minmax = 100;
vm.$nextTick(() => {
expect(vm.formstate.minmax.$valid).toBe(false);
expect(vm.formstate.minmax.$error.max).toBe(true);
done();
});
});
it('should validate [pattern]', function (done) {
expect(vm.myform.m.$valid).toBe(false);
vm.model.m = 'abc';
Vue.nextTick(function () {
expect(vm.myform.m.$valid).toBe(true);
Vue.nextTick(done);
it('should validate [pattern]', (done) => {
expect(vm.formstate.pattern.$valid).toBe(true);
expect(vm.formstate.pattern.$error.pattern).toBeUndefined();
vm.model.pattern = 'not four numbers';
vm.$nextTick(() => {
expect(vm.formstate.pattern.$valid).toBe(false);
expect(vm.formstate.pattern.$error.pattern).toBe(true);
done();
});
});
it('should validate multiple validators', function (done) {
expect(vm.myform.n.$valid).toBe(false);
// pass required
vm.model.n = 'abc';
Vue.nextTick(function () {
// email will be invalid
expect(vm.myform.n.$valid).toBe(false);
// pass email
vm.model.n = 'a@b.c';
Vue.nextTick(function () {
// minlength will be invalid
expect(vm.myform.n.$valid).toBe(false);
// pass minlength
vm.model.n = 'aa@bb.xxxx';
Vue.nextTick(function () {
expect(vm.myform.n.$valid).toBe(true);
Vue.nextTick(done);
});
});
it('should validate custom validators', function(done) {
expect(vm.formstate.custom.$valid).toBe(true);
expect(vm.formstate.custom.$error['custom-key']).toBeUndefined();
vm.model.custom = 'custom invalid value';
vm.$nextTick(function() {
expect(vm.formstate.custom.$valid).toBe(false);
expect(vm.formstate.custom.$error['custom-key']).toBe(true);
done();
});
});
});
it('should validate custom-validator', function (done) {
expect(vm.myform.o.$valid).toBe(false);
vm.model.o = 'custom';
Vue.nextTick(function () {
expect(vm.myform.o.$valid).toBe(true);
Vue.nextTick(done);
it('should validate async validators', function(done) {
vm.asyncEnabled = true;
vm.$nextTick(() => {
expect(vm.formstate.custom2.$pending).toBe(true);
expect(vm.formstate.custom2.$valid).toBe(true);
setTimeout(() => {
expect(vm.formstate.custom2.$pending).toBe(false);
expect(vm.formstate.custom2.$valid).toBe(true);
vm.model.custom2 = 'foo';
setTimeout(() => {
expect(vm.formstate.custom2.$pending).toBe(false);
expect(vm.formstate.custom2.$valid).toBe(false);
done();
}, 150);
}, 150);
});
});
it('should validate checkbox array', function (done) {
expect(vm.myform.multicheck.$valid).toBe(false);
it('should validate checkbox array', (done) => {
expect(vm.formstate.multicheck.$valid).toBe(false);
expect(vm.formstate.multicheck.$error.required).toBe(true);
vm.$el.querySelector('[name=multicheck]').click();
Vue.nextTick(function () {
expect(vm.myform.multicheck.$valid).toBe(true);
Vue.nextTick(done);
vm.$nextTick(() => {
expect(vm.formstate.multicheck.$valid).toBe(true);
expect(vm.formstate.multicheck.$error.required).toBeUndefined();
done();
});
});
it('should set input dirty when changed', function (done) {
expect(vm.myform.d.$dirty).toBe(false);
vm.model.d = 'abc';
Vue.nextTick(function () {
expect(vm.myform.d.$dirty).toBe(true);
Vue.nextTick(done);
it('should set $dirty when model changed by user', (done) => {
expect(vm.formstate.a.$dirty).toBe(false);
// non user change
vm.model.a = 'abc';
vm.$nextTick(() => {
expect(vm.formstate.a.$dirty).toBe(false);
// user interacted with field then changed text
vm.$el.querySelector('[name=a]').focus();
vm.model.a = 'abcc';
vm.$nextTick(() => {
expect(vm.formstate.a._hasFocused).toBe(true);
expect(vm.formstate.a.$dirty).toBe(true);
done();
});
});
});
it('should set form dirty when child changed', function (done) {
expect(vm.myform.$dirty).toBe(false);
vm.model.d = 'abc';
Vue.nextTick(function () {
expect(vm.myform.$dirty).toBe(true);
Vue.nextTick(done);
it('should set $touched on blur', (done) => {
expect(vm.formstate.a.$touched).toBe(false);
vm.$el.querySelector('[name=a]').focus();
vm.$el.querySelector('[name=a]').blur();
vm.$nextTick(() => {
expect(vm.formstate.a.$touched).toBe(true);
done();
});
});
it('should set form invalid when child is invald', function () {
expect(vm.myform.$invalid).toBe(true);
it('should set form properties when child properties change', (done) => {
// starts off invalid
expect(vm.formstate.$valid).toBe(false);
expect(vm.formstate.$invalid).toBe(true);
expect(vm.formstate.$dirty).toBe(false);
expect(vm.formstate.$pristine).toBe(true);
expect(vm.formstate.$touched).toBe(false);
expect(vm.formstate.$untouched).toBe(true);
expect(Object.keys(vm.formstate.$error).length).toBe(3);
// emulate user interaction
vm.$el.querySelector('[name=b]').focus();
vm.$el.querySelector('[name=b]').blur();
setValid();
vm.$nextTick(() => {
expect(vm.formstate.$valid).toBe(true);
expect(vm.formstate.$invalid).toBe(false);
expect(vm.formstate.$dirty).toBe(true);
expect(vm.formstate.$pristine).toBe(false);
expect(vm.formstate.$touched).toBe(true);
expect(vm.formstate.$untouched).toBe(false);
expect(Object.keys(vm.formstate.$error).length).toBe(0);
done();
});
});
it('should add and remove state classes from inputs', function (done) {
var classes = vm.$el.querySelector('[name=b]').className;
expect(classes.indexOf('vf-pristine')).not.toBe(-1);
expect(classes.indexOf('vf-invalid')).not.toBe(-1);
expect(classes.indexOf('vf-invalid-required')).not.toBe(-1);
vm.model.b = 'abc';
Vue.nextTick(function () {
classes = vm.$el.querySelector('[name=b]').className;
expect(classes.indexOf('vf-pristine')).toBe(-1);
expect(classes.indexOf('vf-invalid')).toBe(-1);
expect(classes.indexOf('vf-invalid-required')).toBe(-1);
expect(classes.indexOf('vf-valid')).not.toBe(-1);
Vue.nextTick(done);
it('should add and remove state classes', (done) => {
// starts off invalid, pristine and untouched
expect(vm.$el.classList.contains('vf-form-pristine')).toBe(true);
expect(vm.$el.classList.contains('vf-form-invalid')).toBe(true);
expect(vm.$el.classList.contains('vf-form-untouched')).toBe(true);
const input = vm.$el.querySelector('[name=b]');
expect(input.classList.contains('vf-pristine')).toBe(true);
expect(input.classList.contains('vf-invalid')).toBe(true);
expect(input.classList.contains('vf-untouched')).toBe(true);
expect(input.classList.contains('vf-invalid-required')).toBe(true);
// set valid and interacted
input.focus();
input.blur();
setValid();
vm.$nextTick(() => {
expect(vm.$el.classList.contains('vf-form-dirty')).toBe(true);
expect(vm.$el.classList.contains('vf-form-valid')).toBe(true);
expect(vm.$el.classList.contains('vf-form-touched')).toBe(true);
expect(input.classList.contains('vf-dirty')).toBe(true);
expect(input.classList.contains('vf-valid')).toBe(true);
expect(input.classList.contains('vf-touched')).toBe(true);
expect(input.classList.contains('vf-invalid-required')).toBe(false);
done();
});
});
it('should add and remove state classes from form', function (done) {
var classes = vm.$el.querySelector('[name="myform"]').className;
expect(classes.indexOf('vf-invalid')).not.toBe(-1);
expect(classes.indexOf('vf-pristine')).not.toBe(-1);
vm.model.b = 'abc';
Vue.nextTick(function () {
classes = vm.$el.querySelector('[name="myform"]').className;
//expect(classes.indexOf('vf-pristine')).toBe(-1);
//expect(classes.indexOf('vf-invalid')).toBe(-1);
//expect(classes.indexOf('vf-valid')).not.toBe(-1);
Vue.nextTick(done);
});
it('should add and remove a field from overall state when inside v-if', (done) => {
setValid();
vm.model.c = '';
vm.$nextTick(() => {
expect(vm.formstate.c).toBeDefined();
expect(vm.formstate.c.$invalid).toBe(true);
expect(vm.formstate.$invalid).toBe(true);
vm.isCEnabled = false;
vm.$nextTick(() => {
expect(vm.formstate.c).toBeUndefined();
expect(vm.formstate.$valid).toBe(true);
done();
});
});
});
});
it('should work with v-for scope', function (done) {
vm.$destroy();
it('should show the correct form errors', (done) => {
vm.$nextTick(() => {
var vmx = new Vue({
el: 'body',
replace: false,
// field b
expect(vm.$el.querySelector('#error-message-b')).toBe(null);
expect(vm.$el.querySelector('#error-message-b-2')).toBe(null);
vm.model.b = '123';
// field c
expect(vm.$el.querySelector('#error-message')).not.toBeNull(null);
expect(vm.$el.querySelector('#minlength-message')).toBe(null);
expect(vm.$el.querySelector('#error-message2')).not.toBeNull(null);
expect(vm.$el.querySelector('#minlength-message2')).toBe(null);
vm.model.c = '123';
vm.$nextTick(() => {
// field b could still be null
expect(vm.$el.querySelector('#error-message-b')).toBe(null);
expect(vm.$el.querySelector('#error-message-b-2')).toBe(null);
vm.$el.querySelector('[name=b]').focus();
vm.$el.querySelector('[name=b]').blur();
vm.model.b = '';
// field c
expect(vm.$el.querySelector('#error-message')).toBe(null);
expect(vm.$el.querySelector('#minlength-message')).not.toBeNull(null);
expect(vm.$el.querySelector('#error-message2')).toBe(null);
expect(vm.$el.querySelector('#minlength-message2')).not.toBeNull(null);
vm.model.c = '123456';
vm.$nextTick(() => {
// field b
expect(vm.$el.querySelector('#error-message-b')).not.toBeNull(null);
expect(vm.$el.querySelector('#error-message-b-2')).not.toBeNull(null);
// field c
expect(vm.$el.querySelector('#error-message')).toBe(null);
expect(vm.$el.querySelector('#minlength-message')).toBe(null);
expect(vm.$el.querySelector('#error-message2')).toBe(null);
expect(vm.$el.querySelector('#minlength-message2')).toBe(null);
done();
});
});
});
});
it('should work with v-for', function(done) {
vm.$destroy();
const div = document.createElement('div');
document.body.appendChild(div);
new Vue({
el: div,
template: `
<form v-form name="myform">
<label v-for="input in inputs">
<label> {{input.label}} <br>
<input v-form-ctrl type="text" :name="input.name" v-model="input.model" :required="input.required" />
</label>
</label>
</form>
<vue-form :state="formstate">
<validate tag="label" v-for="input in inputs" :key="input.name">
{{input.label}} <br>
<input type="text" :name="input.name" v-model="input.model" :required="input.required" />
</validate>
</vue-form>
`,
data: {
data: {
inputs: [{
label: 'Input A',
name: 'a',
model: '',
required: true
label: 'Input A',
name: 'a',
model: '',
required: true
}, {
label: 'Input B',
name: 'b',
model: '',
required: false
label: 'Input B',
name: 'b',
model: '',
required: false
}, {
label: 'Input C',
name: 'c',
model: 'abc',
required: true
label: 'Input C',
name: 'c',
model: 'abc',
required: true
}],
myform: {}
formstate: {}
},
ready: function () {
setTimeout(function () {
expect(vmx.myform.a.$valid).toBe(false);
expect(vmx.myform.b.$valid).toBe(true);
expect(vmx.myform.c.$valid).toBe(true);
mounted: function() {
this.$nextTick(() => {
expect(this.formstate.a.$valid).toBe(false);
expect(this.formstate.b.$valid).toBe(true);
expect(this.formstate.c.$valid).toBe(true);
done();
}, 100);
});
}
});
});
});
it('should work with v-bind object syntax', function (done) {
vm.$destroy();
var vmx = new Vue({
el: 'body',
replace: false,
it('should work with components, even if some validation attributes are also component props', function(done) {
vm.$destroy();
const div = document.createElement('div');
document.body.appendChild(div);
new Vue({
el: div,
components: {
test: {
props: ['value'],
template: '<span></span>'
},
test2: {
props: ['value', 'required', 'name'],
template: '<span><input type="text" id="input" /></span>',
mounted () {
this.$el.querySelector('input').addEventListener('focus', this.$emit('focus'));
this.$el.querySelector('input').addEventListener('blur', this.$emit('blur'));
}
}
},
template: `
<form v-form name="myform">
<input v-model="model.a" v-form-ctrl v-bind="{'name': 'a', required: true}" />
<input v-model="model.b" v-form-ctrl :="{'name': 'b', required: false}" />
</form>
<vue-form :state="formstate">
<validate>
<test name="test" v-model="model.test" required ></test>
</validate>
<validate>
<test2 name="test2" v-model="model.test2" required ></test2>
</validate>
</vue-form>
`,
data: {
data: {
model: {
b: 'xxx'
test: '',
test2: ''
},
myform: {}
formstate: {}
},
ready: function () {
this.$nextTick(function () {
expect(vmx.myform.a.$valid).toBe(false);
expect(vmx.myform.b.$valid).toBe(true);
/*vmx.model.a = 'aaa';
vmx.model.b = 'aa';
this.$nextTick(function () {
console.log(vmx.model);
expect(vmx.myform.a.$valid).toBe(true);
expect(vmx.myform.b.$valid).toBe(false);
done();
});*/
mounted: function() {
expect(this.formstate.test).toBeDefined();
expect(this.formstate.test.$error.required).toBe(true);
expect(this.formstate.test2).toBeDefined();
expect(this.formstate.test2.$error.required).toBe(true);
expect(this.formstate.test2.$dirty).toBe(false);
this.$el.querySelector('#input').focus();
this.$el.querySelector('#input').blur();
this.model.test = 'xxx';
this.model.test2 = 'xxx';
this.$nextTick(() => {
expect(this.formstate.test.$error.required).toBeUndefined();
expect(this.formstate.test.$dirty).toBe(false);
expect(this.formstate.test2.$dirty).toBe(true);
expect(this.formstate.test2.$touched).toBe(true);
done();

@@ -365,4 +599,4 @@ });

});
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc