1
0
mirror of synced 2024-11-22 05:16:05 +03:00

Adds support for validation rules that are aware of other form elements

This commit is contained in:
Justin Schroeder 2020-02-25 17:32:40 -05:00
parent f30dc39927
commit d7e3859951
32 changed files with 3710 additions and 9457 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -10,10 +10,11 @@ export default {
globals: {
'is-plain-object': 'isPlainObject',
'nanoid': 'nanoid',
'is-url': 'isUrl'
'is-url': 'isUrl',
'clone-deep': 'cloneDeep'
}
},
external: ['is-plain-object', 'nanoid', 'is-url'],
external: ['is-plain-object', 'nanoid', 'is-url', 'clone-deep'],
plugins: [
commonjs(),
vue({

240
dist/formulate.esm.js vendored
View File

@ -256,7 +256,6 @@ FileUpload.prototype.removeFile = function removeFile (uuid) {
*/
FileUpload.prototype.loadPreviews = function loadPreviews () {
this.files.map(function (file) {
console.log(file.type);
if (!file.previewData && window && window.FileReader && /^image\//.test(file.file.type)) {
var reader = new FileReader();
reader.onload = function (e) { return Object.assign(file, { previewData: e.target.result }); };
@ -279,6 +278,11 @@ FileUpload.prototype.getFiles = function getFiles () {
return this.files
};
FileUpload.prototype.toString = function toString () {
var descriptor = this.files.length ? this.files.length + ' files' : 'empty';
return ("FileUpload(" + descriptor + ")")
};
/**
* Function to map over an object.
* @param {Object} obj An object to map over
@ -426,6 +430,42 @@ function regexForFormat (format) {
}, escaped))
}
/**
* Check if
* @param {mixed} data
*/
function isValueType (data) {
switch (typeof data) {
case 'symbol':
case 'number':
case 'string':
case 'boolean':
case 'undefined':
return true
default:
if (data === null) {
return true
}
return false
}
}
/**
* A simple (somewhat non-comprehensive) cloneDeep function, valid for our use
* case of needing to unbind reactive watchers.
*/
function cloneDeep (obj) {
var newObj = {};
for (var key in obj) {
if (obj[key] instanceof FileUpload || isValueType(obj[key])) {
newObj[key] = obj;
} else {
newObj[key] = cloneDeep(obj[key]);
}
}
return newObj
}
/**
* Library of rules
*/
@ -433,14 +473,17 @@ var rules = {
/**
* Rule: the value must be "yes", "on", "1", or true
*/
accepted: function (value) {
accepted: function (ref) {
var value = ref.value;
return Promise.resolve(['yes', 'on', '1', 1, true, 'true'].includes(value))
},
/**
* Rule: checks if a value is after a given date. Defaults to current time
*/
after: function (value, compare) {
after: function (ref, compare) {
var value = ref.value;
if ( compare === void 0 ) compare = false;
var timestamp = Date.parse(compare || new Date());
@ -451,7 +494,8 @@ var rules = {
/**
* Rule: checks if the value is only alpha
*/
alpha: function (value, set) {
alpha: function (ref, set) {
var value = ref.value;
if ( set === void 0 ) set = 'default';
var sets = {
@ -465,7 +509,8 @@ var rules = {
/**
* Rule: checks if the value is alpha numeric
*/
alphanumeric: function (value, set) {
alphanumeric: function (ref, set) {
var value = ref.value;
if ( set === void 0 ) set = 'default';
var sets = {
@ -479,7 +524,8 @@ var rules = {
/**
* Rule: checks if a value is after a given date. Defaults to current time
*/
before: function (value, compare) {
before: function (ref, compare) {
var value = ref.value;
if ( compare === void 0 ) compare = false;
var timestamp = Date.parse(compare || new Date());
@ -490,7 +536,8 @@ var rules = {
/**
* Rule: checks if the value is between two other values
*/
between: function (value, from, to) {
between: function (ref, from, to) {
var value = ref.value;
if ( from === void 0 ) from = 0;
if ( to === void 0 ) to = 10;
@ -511,11 +558,31 @@ var rules = {
})())
},
/**
* Confirm that the value of one field is the same as another, mostly used
* for password confirmations.
*/
confirm: function (ref, field) {
var value = ref.value;
var getFormValues = ref.getFormValues;
var name = ref.name;
return Promise.resolve((function () {
var formValues = getFormValues();
var confirmationFieldName = field;
if (!confirmationFieldName) {
confirmationFieldName = /_confirm$/.test(name) ? name.substr(0, name.length - 8) : (name + "_confirm");
}
return formValues[confirmationFieldName] === value
})())
},
/**
* Rule: ensures the value is a date according to Date.parse(), or a format
* regex.
*/
date: function (value, format) {
date: function (ref, format) {
var value = ref.value;
if ( format === void 0 ) format = false;
return Promise.resolve((function () {
@ -529,7 +596,9 @@ var rules = {
/**
* Rule: tests
*/
email: function (value) {
email: function (ref) {
var value = ref.value;
// eslint-disable-next-line
var isEmail = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
return Promise.resolve(isEmail.test(value))
@ -538,7 +607,8 @@ var rules = {
/**
* Rule: Value is in an array (stack).
*/
in: function (value) {
in: function (ref) {
var value = ref.value;
var stack = [], len = arguments.length - 1;
while ( len-- > 0 ) stack[ len ] = arguments[ len + 1 ];
@ -553,7 +623,8 @@ var rules = {
/**
* Rule: Match the value against a (stack) of patterns or strings
*/
matches: function (value) {
matches: function (ref) {
var value = ref.value;
var stack = [], len = arguments.length - 1;
while ( len-- > 0 ) stack[ len ] = arguments[ len + 1 ];
@ -568,7 +639,8 @@ var rules = {
/**
* Check the maximum value of a particular.
*/
max: function (value, minimum, force) {
max: function (ref, minimum, force) {
var value = ref.value;
if ( minimum === void 0 ) minimum = 10;
return Promise.resolve((function () {
@ -591,13 +663,14 @@ var rules = {
/**
* Check the file type is correct.
*/
mime: function (files) {
mime: function (ref) {
var value = ref.value;
var types = [], len = arguments.length - 1;
while ( len-- > 0 ) types[ len ] = arguments[ len + 1 ];
return Promise.resolve((function () {
if (files instanceof FileUpload) {
var fileList = files.getFileList();
if (value instanceof FileUpload) {
var fileList = value.getFileList();
for (var i = 0; i < fileList.length; i++) {
var file = fileList[i];
if (!types.includes(file.type)) {
@ -612,7 +685,8 @@ var rules = {
/**
* Check the minimum value of a particular.
*/
min: function (value, minimum, force) {
min: function (ref, minimum, force) {
var value = ref.value;
if ( minimum === void 0 ) minimum = 1;
return Promise.resolve((function () {
@ -635,7 +709,8 @@ var rules = {
/**
* Rule: Value is not in stack.
*/
not: function (value) {
not: function (ref) {
var value = ref.value;
var stack = [], len = arguments.length - 1;
while ( len-- > 0 ) stack[ len ] = arguments[ len + 1 ];
@ -650,14 +725,17 @@ var rules = {
/**
* Rule: checks if the value is only alpha numeric
*/
number: function (value) {
number: function (ref) {
var value = ref.value;
return Promise.resolve(!isNaN(value))
},
/**
* Rule: must be a value
*/
required: function (value, isRequired) {
required: function (ref, isRequired) {
var value = ref.value;
if ( isRequired === void 0 ) isRequired = true;
return Promise.resolve((function () {
@ -680,7 +758,9 @@ var rules = {
/**
* Rule: checks if a string is a valid url
*/
url: function (value) {
url: function (ref) {
var value = ref.value;
return Promise.resolve(isUrl(value))
}
};
@ -757,6 +837,16 @@ var en = {
return ((sentence(name)) + " must be between " + (args[0]) + " and " + (args[1]) + " characters long.")
},
/**
* The confirmation field does not match
*/
confirm: function (ref) {
var name = ref.name;
var args = ref.args;
return ((sentence(name)) + " does not match.")
},
/**
* Is not a valid date.
*/
@ -1328,7 +1418,11 @@ var script = {
var rule = ref[0];
var args = ref[1];
return rule.apply(void 0, [ this$1.context.model ].concat( args ))
return rule.apply(void 0, [ {
value: this$1.context.model,
getFormValues: this$1.getFormValues.bind(this$1),
name: this$1.context.name
} ].concat( args ))
.then(function (res) { return res ? false : this$1.$formulate.validationMessage(rule.name, {
args: args,
name: this$1.mergedValidationName,
@ -1535,7 +1629,7 @@ __vue_render__._withStripped = true;
var FormulateInput = normalizeComponent(
var __vue_component__ = normalizeComponent(
{ render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },
__vue_inject_styles__,
__vue_script__,
@ -1548,6 +1642,44 @@ __vue_render__._withStripped = true;
undefined
);
var FormSubmission = function FormSubmission (form) {
this.form = form;
};
/**
* Determine if the form has any validation errors.
*
* @return {Promise} resolves a boolean
*/
FormSubmission.prototype.hasValidationErrors = function hasValidationErrors () {
return this.form.hasValidationErrors()
};
/**
* Asynchronously generate the values payload of this form.
* @return {Promise} resolves to json
*/
FormSubmission.prototype.values = function values () {
var this$1 = this;
return new Promise(function (resolve, reject) {
var pending = [];
var values = cloneDeep(this$1.form.internalModelProxy);
for (var key in values) {
if (typeof this$1.form.internalModelProxy[key] === 'object' && this$1.form.internalModelProxy[key] instanceof FileUpload) {
pending.push(this$1.form.internalModelProxy[key].upload());
}
}
/**
* @todo - how do we get these uploaded path values back into our data?
*/
Promise.all(pending)
// .then(file => file.path)
.then(function () { return resolve(values); })
.catch(function (err) { return reject(err); });
})
};
//
var script$1 = {
@ -1636,9 +1768,15 @@ var script$1 = {
}
},
formSubmitted: function formSubmitted () {
var this$1 = this;
// perform validation here
this.showErrors();
this.$emit('submit', this.internalFormModelProxy);
var submission = new FormSubmission(this);
this.$emit('submit-raw', submission);
submission.hasValidationErrors()
.then(function (hasErrors) { return hasErrors ? false : submission.values(); })
.then(function (json) { return this$1.$emit('submit', json); });
},
showErrors: function showErrors () {
for (var fieldName in this.registry) {
@ -1647,6 +1785,15 @@ var script$1 = {
},
getFormValues: function getFormValues () {
return this.internalFormModelProxy
},
hasValidationErrors: function hasValidationErrors () {
var resolvers = [];
for (var fieldName in this.registry) {
if (typeof this.registry[fieldName].hasValidationErrors === 'function') {
resolvers.push(this.registry[fieldName].hasValidationErrors());
}
}
return Promise.all(resolvers).then(function (fields) { return !!fields.find(function (hasErrors) { return hasErrors; }); })
}
}
};
@ -1692,7 +1839,7 @@ __vue_render__$1._withStripped = true;
var FormulateForm = normalizeComponent(
var __vue_component__$1 = normalizeComponent(
{ render: __vue_render__$1, staticRenderFns: __vue_staticRenderFns__$1 },
__vue_inject_styles__$1,
__vue_script__$1,
@ -1773,7 +1920,7 @@ __vue_render__$2._withStripped = true;
var FormulateInputErrors = normalizeComponent(
var __vue_component__$2 = normalizeComponent(
{ render: __vue_render__$2, staticRenderFns: __vue_staticRenderFns__$2 },
__vue_inject_styles__$2,
__vue_script__$2,
@ -1893,7 +2040,7 @@ __vue_render__$3._withStripped = true;
var FormulateInputGroup = normalizeComponent(
var __vue_component__$3 = normalizeComponent(
{ render: __vue_render__$3, staticRenderFns: __vue_staticRenderFns__$3 },
__vue_inject_styles__$3,
__vue_script__$3,
@ -2095,7 +2242,7 @@ __vue_render__$4._withStripped = true;
var FormulateInputBox = normalizeComponent(
var __vue_component__$4 = normalizeComponent(
{ render: __vue_render__$4, staticRenderFns: __vue_staticRenderFns__$4 },
__vue_inject_styles__$4,
__vue_script__$4,
@ -2259,7 +2406,7 @@ __vue_render__$5._withStripped = true;
var FormulateInputText = normalizeComponent(
var __vue_component__$5 = normalizeComponent(
{ render: __vue_render__$5, staticRenderFns: __vue_staticRenderFns__$5 },
__vue_inject_styles__$5,
__vue_script__$5,
@ -2400,7 +2547,7 @@ __vue_render__$6._withStripped = true;
var FormulateFiles = normalizeComponent(
var __vue_component__$6 = normalizeComponent(
{ render: __vue_render__$6, staticRenderFns: __vue_staticRenderFns__$6 },
__vue_inject_styles__$6,
__vue_script__$6,
@ -2418,7 +2565,7 @@ __vue_render__$6._withStripped = true;
var script$7 = {
name: 'FormulateInputFile',
components: {
FormulateFiles: FormulateFiles
FormulateFiles: __vue_component__$6
},
mixins: [FormulateInputMixin],
data: function data () {
@ -2572,7 +2719,7 @@ __vue_render__$7._withStripped = true;
var FormulateInputFile = normalizeComponent(
var __vue_component__$7 = normalizeComponent(
{ render: __vue_render__$7, staticRenderFns: __vue_staticRenderFns__$7 },
__vue_inject_styles__$7,
__vue_script__$7,
@ -2650,7 +2797,7 @@ __vue_render__$8._withStripped = true;
var FormulateInputButton = normalizeComponent(
var __vue_component__$8 = normalizeComponent(
{ render: __vue_render__$8, staticRenderFns: __vue_staticRenderFns__$8 },
__vue_inject_styles__$8,
__vue_script__$8,
@ -2816,7 +2963,7 @@ __vue_render__$9._withStripped = true;
var FormulateInputSelect = normalizeComponent(
var __vue_component__$9 = normalizeComponent(
{ render: __vue_render__$9, staticRenderFns: __vue_staticRenderFns__$9 },
__vue_inject_styles__$9,
__vue_script__$9,
@ -2980,7 +3127,7 @@ __vue_render__$a._withStripped = true;
var FormulateInputSlider = normalizeComponent(
var __vue_component__$a = normalizeComponent(
{ render: __vue_render__$a, staticRenderFns: __vue_staticRenderFns__$a },
__vue_inject_styles__$a,
__vue_script__$a,
@ -3065,7 +3212,7 @@ __vue_render__$b._withStripped = true;
var FormulateInputTextArea = normalizeComponent(
var __vue_component__$b = normalizeComponent(
{ render: __vue_render__$b, staticRenderFns: __vue_staticRenderFns__$b },
__vue_inject_styles__$b,
__vue_script__$b,
@ -3084,17 +3231,17 @@ __vue_render__$b._withStripped = true;
var Formulate = function Formulate () {
this.defaults = {
components: {
FormulateForm: FormulateForm,
FormulateInput: FormulateInput,
FormulateInputErrors: FormulateInputErrors,
FormulateInputBox: FormulateInputBox,
FormulateInputText: FormulateInputText,
FormulateInputFile: FormulateInputFile,
FormulateInputGroup: FormulateInputGroup,
FormulateInputButton: FormulateInputButton,
FormulateInputSelect: FormulateInputSelect,
FormulateInputSlider: FormulateInputSlider,
FormulateInputTextArea: FormulateInputTextArea
FormulateForm: __vue_component__$1,
FormulateInput: __vue_component__,
FormulateInputErrors: __vue_component__$2,
FormulateInputBox: __vue_component__$4,
FormulateInputText: __vue_component__$5,
FormulateInputFile: __vue_component__$7,
FormulateInputGroup: __vue_component__$3,
FormulateInputButton: __vue_component__$8,
FormulateInputSelect: __vue_component__$9,
FormulateInputSlider: __vue_component__$a,
FormulateInputTextArea: __vue_component__$b
},
library: library,
rules: rules,
@ -3206,3 +3353,4 @@ Formulate.prototype.createUpload = function createUpload (fileList, context) {
var Formulate$1 = new Formulate();
export default Formulate$1;
export { FileUpload };

240
dist/formulate.min.js vendored
View File

@ -259,7 +259,6 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
*/
FileUpload.prototype.loadPreviews = function loadPreviews () {
this.files.map(function (file) {
console.log(file.type);
if (!file.previewData && window && window.FileReader && /^image\//.test(file.file.type)) {
var reader = new FileReader();
reader.onload = function (e) { return Object.assign(file, { previewData: e.target.result }); };
@ -282,6 +281,11 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
return this.files
};
FileUpload.prototype.toString = function toString () {
var descriptor = this.files.length ? this.files.length + ' files' : 'empty';
return ("FileUpload(" + descriptor + ")")
};
/**
* Function to map over an object.
* @param {Object} obj An object to map over
@ -429,6 +433,42 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
}, escaped))
}
/**
* Check if
* @param {mixed} data
*/
function isValueType (data) {
switch (typeof data) {
case 'symbol':
case 'number':
case 'string':
case 'boolean':
case 'undefined':
return true
default:
if (data === null) {
return true
}
return false
}
}
/**
* A simple (somewhat non-comprehensive) cloneDeep function, valid for our use
* case of needing to unbind reactive watchers.
*/
function cloneDeep (obj) {
var newObj = {};
for (var key in obj) {
if (obj[key] instanceof FileUpload || isValueType(obj[key])) {
newObj[key] = obj;
} else {
newObj[key] = cloneDeep(obj[key]);
}
}
return newObj
}
/**
* Library of rules
*/
@ -436,14 +476,17 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
/**
* Rule: the value must be "yes", "on", "1", or true
*/
accepted: function (value) {
accepted: function (ref) {
var value = ref.value;
return Promise.resolve(['yes', 'on', '1', 1, true, 'true'].includes(value))
},
/**
* Rule: checks if a value is after a given date. Defaults to current time
*/
after: function (value, compare) {
after: function (ref, compare) {
var value = ref.value;
if ( compare === void 0 ) compare = false;
var timestamp = Date.parse(compare || new Date());
@ -454,7 +497,8 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
/**
* Rule: checks if the value is only alpha
*/
alpha: function (value, set) {
alpha: function (ref, set) {
var value = ref.value;
if ( set === void 0 ) set = 'default';
var sets = {
@ -468,7 +512,8 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
/**
* Rule: checks if the value is alpha numeric
*/
alphanumeric: function (value, set) {
alphanumeric: function (ref, set) {
var value = ref.value;
if ( set === void 0 ) set = 'default';
var sets = {
@ -482,7 +527,8 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
/**
* Rule: checks if a value is after a given date. Defaults to current time
*/
before: function (value, compare) {
before: function (ref, compare) {
var value = ref.value;
if ( compare === void 0 ) compare = false;
var timestamp = Date.parse(compare || new Date());
@ -493,7 +539,8 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
/**
* Rule: checks if the value is between two other values
*/
between: function (value, from, to) {
between: function (ref, from, to) {
var value = ref.value;
if ( from === void 0 ) from = 0;
if ( to === void 0 ) to = 10;
@ -514,11 +561,31 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
})())
},
/**
* Confirm that the value of one field is the same as another, mostly used
* for password confirmations.
*/
confirm: function (ref, field) {
var value = ref.value;
var getFormValues = ref.getFormValues;
var name = ref.name;
return Promise.resolve((function () {
var formValues = getFormValues();
var confirmationFieldName = field;
if (!confirmationFieldName) {
confirmationFieldName = /_confirm$/.test(name) ? name.substr(0, name.length - 8) : (name + "_confirm");
}
return formValues[confirmationFieldName] === value
})())
},
/**
* Rule: ensures the value is a date according to Date.parse(), or a format
* regex.
*/
date: function (value, format) {
date: function (ref, format) {
var value = ref.value;
if ( format === void 0 ) format = false;
return Promise.resolve((function () {
@ -532,7 +599,9 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
/**
* Rule: tests
*/
email: function (value) {
email: function (ref) {
var value = ref.value;
// eslint-disable-next-line
var isEmail = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
return Promise.resolve(isEmail.test(value))
@ -541,7 +610,8 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
/**
* Rule: Value is in an array (stack).
*/
in: function (value) {
in: function (ref) {
var value = ref.value;
var stack = [], len = arguments.length - 1;
while ( len-- > 0 ) stack[ len ] = arguments[ len + 1 ];
@ -556,7 +626,8 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
/**
* Rule: Match the value against a (stack) of patterns or strings
*/
matches: function (value) {
matches: function (ref) {
var value = ref.value;
var stack = [], len = arguments.length - 1;
while ( len-- > 0 ) stack[ len ] = arguments[ len + 1 ];
@ -571,7 +642,8 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
/**
* Check the maximum value of a particular.
*/
max: function (value, minimum, force) {
max: function (ref, minimum, force) {
var value = ref.value;
if ( minimum === void 0 ) minimum = 10;
return Promise.resolve((function () {
@ -594,13 +666,14 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
/**
* Check the file type is correct.
*/
mime: function (files) {
mime: function (ref) {
var value = ref.value;
var types = [], len = arguments.length - 1;
while ( len-- > 0 ) types[ len ] = arguments[ len + 1 ];
return Promise.resolve((function () {
if (files instanceof FileUpload) {
var fileList = files.getFileList();
if (value instanceof FileUpload) {
var fileList = value.getFileList();
for (var i = 0; i < fileList.length; i++) {
var file = fileList[i];
if (!types.includes(file.type)) {
@ -615,7 +688,8 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
/**
* Check the minimum value of a particular.
*/
min: function (value, minimum, force) {
min: function (ref, minimum, force) {
var value = ref.value;
if ( minimum === void 0 ) minimum = 1;
return Promise.resolve((function () {
@ -638,7 +712,8 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
/**
* Rule: Value is not in stack.
*/
not: function (value) {
not: function (ref) {
var value = ref.value;
var stack = [], len = arguments.length - 1;
while ( len-- > 0 ) stack[ len ] = arguments[ len + 1 ];
@ -653,14 +728,17 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
/**
* Rule: checks if the value is only alpha numeric
*/
number: function (value) {
number: function (ref) {
var value = ref.value;
return Promise.resolve(!isNaN(value))
},
/**
* Rule: must be a value
*/
required: function (value, isRequired) {
required: function (ref, isRequired) {
var value = ref.value;
if ( isRequired === void 0 ) isRequired = true;
return Promise.resolve((function () {
@ -683,7 +761,9 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
/**
* Rule: checks if a string is a valid url
*/
url: function (value) {
url: function (ref) {
var value = ref.value;
return Promise.resolve(isUrl(value))
}
};
@ -760,6 +840,16 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
return ((sentence(name)) + " must be between " + (args[0]) + " and " + (args[1]) + " characters long.")
},
/**
* The confirmation field does not match
*/
confirm: function (ref) {
var name = ref.name;
var args = ref.args;
return ((sentence(name)) + " does not match.")
},
/**
* Is not a valid date.
*/
@ -1331,7 +1421,11 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
var rule = ref[0];
var args = ref[1];
return rule.apply(void 0, [ this$1.context.model ].concat( args ))
return rule.apply(void 0, [ {
value: this$1.context.model,
getFormValues: this$1.getFormValues.bind(this$1),
name: this$1.context.name
} ].concat( args ))
.then(function (res) { return res ? false : this$1.$formulate.validationMessage(rule.name, {
args: args,
name: this$1.mergedValidationName,
@ -1538,7 +1632,7 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
var FormulateInput = normalizeComponent(
var __vue_component__ = normalizeComponent(
{ render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },
__vue_inject_styles__,
__vue_script__,
@ -1551,6 +1645,44 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
undefined
);
var FormSubmission = function FormSubmission (form) {
this.form = form;
};
/**
* Determine if the form has any validation errors.
*
* @return {Promise} resolves a boolean
*/
FormSubmission.prototype.hasValidationErrors = function hasValidationErrors () {
return this.form.hasValidationErrors()
};
/**
* Asynchronously generate the values payload of this form.
* @return {Promise} resolves to json
*/
FormSubmission.prototype.values = function values () {
var this$1 = this;
return new Promise(function (resolve, reject) {
var pending = [];
var values = cloneDeep(this$1.form.internalModelProxy);
for (var key in values) {
if (typeof this$1.form.internalModelProxy[key] === 'object' && this$1.form.internalModelProxy[key] instanceof FileUpload) {
pending.push(this$1.form.internalModelProxy[key].upload());
}
}
/**
* @todo - how do we get these uploaded path values back into our data?
*/
Promise.all(pending)
// .then(file => file.path)
.then(function () { return resolve(values); })
.catch(function (err) { return reject(err); });
})
};
//
var script$1 = {
@ -1639,9 +1771,15 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
}
},
formSubmitted: function formSubmitted () {
var this$1 = this;
// perform validation here
this.showErrors();
this.$emit('submit', this.internalFormModelProxy);
var submission = new FormSubmission(this);
this.$emit('submit-raw', submission);
submission.hasValidationErrors()
.then(function (hasErrors) { return hasErrors ? false : submission.values(); })
.then(function (json) { return this$1.$emit('submit', json); });
},
showErrors: function showErrors () {
for (var fieldName in this.registry) {
@ -1650,6 +1788,15 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
},
getFormValues: function getFormValues () {
return this.internalFormModelProxy
},
hasValidationErrors: function hasValidationErrors () {
var resolvers = [];
for (var fieldName in this.registry) {
if (typeof this.registry[fieldName].hasValidationErrors === 'function') {
resolvers.push(this.registry[fieldName].hasValidationErrors());
}
}
return Promise.all(resolvers).then(function (fields) { return !!fields.find(function (hasErrors) { return hasErrors; }); })
}
}
};
@ -1695,7 +1842,7 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
var FormulateForm = normalizeComponent(
var __vue_component__$1 = normalizeComponent(
{ render: __vue_render__$1, staticRenderFns: __vue_staticRenderFns__$1 },
__vue_inject_styles__$1,
__vue_script__$1,
@ -1776,7 +1923,7 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
var FormulateInputErrors = normalizeComponent(
var __vue_component__$2 = normalizeComponent(
{ render: __vue_render__$2, staticRenderFns: __vue_staticRenderFns__$2 },
__vue_inject_styles__$2,
__vue_script__$2,
@ -1896,7 +2043,7 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
var FormulateInputGroup = normalizeComponent(
var __vue_component__$3 = normalizeComponent(
{ render: __vue_render__$3, staticRenderFns: __vue_staticRenderFns__$3 },
__vue_inject_styles__$3,
__vue_script__$3,
@ -2098,7 +2245,7 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
var FormulateInputBox = normalizeComponent(
var __vue_component__$4 = normalizeComponent(
{ render: __vue_render__$4, staticRenderFns: __vue_staticRenderFns__$4 },
__vue_inject_styles__$4,
__vue_script__$4,
@ -2262,7 +2409,7 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
var FormulateInputText = normalizeComponent(
var __vue_component__$5 = normalizeComponent(
{ render: __vue_render__$5, staticRenderFns: __vue_staticRenderFns__$5 },
__vue_inject_styles__$5,
__vue_script__$5,
@ -2403,7 +2550,7 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
var FormulateFiles = normalizeComponent(
var __vue_component__$6 = normalizeComponent(
{ render: __vue_render__$6, staticRenderFns: __vue_staticRenderFns__$6 },
__vue_inject_styles__$6,
__vue_script__$6,
@ -2421,7 +2568,7 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
var script$7 = {
name: 'FormulateInputFile',
components: {
FormulateFiles: FormulateFiles
FormulateFiles: __vue_component__$6
},
mixins: [FormulateInputMixin],
data: function data () {
@ -2575,7 +2722,7 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
var FormulateInputFile = normalizeComponent(
var __vue_component__$7 = normalizeComponent(
{ render: __vue_render__$7, staticRenderFns: __vue_staticRenderFns__$7 },
__vue_inject_styles__$7,
__vue_script__$7,
@ -2653,7 +2800,7 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
var FormulateInputButton = normalizeComponent(
var __vue_component__$8 = normalizeComponent(
{ render: __vue_render__$8, staticRenderFns: __vue_staticRenderFns__$8 },
__vue_inject_styles__$8,
__vue_script__$8,
@ -2819,7 +2966,7 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
var FormulateInputSelect = normalizeComponent(
var __vue_component__$9 = normalizeComponent(
{ render: __vue_render__$9, staticRenderFns: __vue_staticRenderFns__$9 },
__vue_inject_styles__$9,
__vue_script__$9,
@ -2983,7 +3130,7 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
var FormulateInputSlider = normalizeComponent(
var __vue_component__$a = normalizeComponent(
{ render: __vue_render__$a, staticRenderFns: __vue_staticRenderFns__$a },
__vue_inject_styles__$a,
__vue_script__$a,
@ -3068,7 +3215,7 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
var FormulateInputTextArea = normalizeComponent(
var __vue_component__$b = normalizeComponent(
{ render: __vue_render__$b, staticRenderFns: __vue_staticRenderFns__$b },
__vue_inject_styles__$b,
__vue_script__$b,
@ -3087,17 +3234,17 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
var Formulate = function Formulate () {
this.defaults = {
components: {
FormulateForm: FormulateForm,
FormulateInput: FormulateInput,
FormulateInputErrors: FormulateInputErrors,
FormulateInputBox: FormulateInputBox,
FormulateInputText: FormulateInputText,
FormulateInputFile: FormulateInputFile,
FormulateInputGroup: FormulateInputGroup,
FormulateInputButton: FormulateInputButton,
FormulateInputSelect: FormulateInputSelect,
FormulateInputSlider: FormulateInputSlider,
FormulateInputTextArea: FormulateInputTextArea
FormulateForm: __vue_component__$1,
FormulateInput: __vue_component__,
FormulateInputErrors: __vue_component__$2,
FormulateInputBox: __vue_component__$4,
FormulateInputText: __vue_component__$5,
FormulateInputFile: __vue_component__$7,
FormulateInputGroup: __vue_component__$3,
FormulateInputButton: __vue_component__$8,
FormulateInputSelect: __vue_component__$9,
FormulateInputSlider: __vue_component__$a,
FormulateInputTextArea: __vue_component__$b
},
library: library,
rules: rules,
@ -3208,6 +3355,7 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
var Formulate$1 = new Formulate();
exports.FileUpload = FileUpload;
exports.default = Formulate$1;
return exports;

240
dist/formulate.umd.js vendored
View File

@ -262,7 +262,6 @@
*/
FileUpload.prototype.loadPreviews = function loadPreviews () {
this.files.map(function (file) {
console.log(file.type);
if (!file.previewData && window && window.FileReader && /^image\//.test(file.file.type)) {
var reader = new FileReader();
reader.onload = function (e) { return Object.assign(file, { previewData: e.target.result }); };
@ -285,6 +284,11 @@
return this.files
};
FileUpload.prototype.toString = function toString () {
var descriptor = this.files.length ? this.files.length + ' files' : 'empty';
return ("FileUpload(" + descriptor + ")")
};
/**
* Function to map over an object.
* @param {Object} obj An object to map over
@ -432,6 +436,42 @@
}, escaped))
}
/**
* Check if
* @param {mixed} data
*/
function isValueType (data) {
switch (typeof data) {
case 'symbol':
case 'number':
case 'string':
case 'boolean':
case 'undefined':
return true
default:
if (data === null) {
return true
}
return false
}
}
/**
* A simple (somewhat non-comprehensive) cloneDeep function, valid for our use
* case of needing to unbind reactive watchers.
*/
function cloneDeep (obj) {
var newObj = {};
for (var key in obj) {
if (obj[key] instanceof FileUpload || isValueType(obj[key])) {
newObj[key] = obj;
} else {
newObj[key] = cloneDeep(obj[key]);
}
}
return newObj
}
/**
* Library of rules
*/
@ -439,14 +479,17 @@
/**
* Rule: the value must be "yes", "on", "1", or true
*/
accepted: function (value) {
accepted: function (ref) {
var value = ref.value;
return Promise.resolve(['yes', 'on', '1', 1, true, 'true'].includes(value))
},
/**
* Rule: checks if a value is after a given date. Defaults to current time
*/
after: function (value, compare) {
after: function (ref, compare) {
var value = ref.value;
if ( compare === void 0 ) compare = false;
var timestamp = Date.parse(compare || new Date());
@ -457,7 +500,8 @@
/**
* Rule: checks if the value is only alpha
*/
alpha: function (value, set) {
alpha: function (ref, set) {
var value = ref.value;
if ( set === void 0 ) set = 'default';
var sets = {
@ -471,7 +515,8 @@
/**
* Rule: checks if the value is alpha numeric
*/
alphanumeric: function (value, set) {
alphanumeric: function (ref, set) {
var value = ref.value;
if ( set === void 0 ) set = 'default';
var sets = {
@ -485,7 +530,8 @@
/**
* Rule: checks if a value is after a given date. Defaults to current time
*/
before: function (value, compare) {
before: function (ref, compare) {
var value = ref.value;
if ( compare === void 0 ) compare = false;
var timestamp = Date.parse(compare || new Date());
@ -496,7 +542,8 @@
/**
* Rule: checks if the value is between two other values
*/
between: function (value, from, to) {
between: function (ref, from, to) {
var value = ref.value;
if ( from === void 0 ) from = 0;
if ( to === void 0 ) to = 10;
@ -517,11 +564,31 @@
})())
},
/**
* Confirm that the value of one field is the same as another, mostly used
* for password confirmations.
*/
confirm: function (ref, field) {
var value = ref.value;
var getFormValues = ref.getFormValues;
var name = ref.name;
return Promise.resolve((function () {
var formValues = getFormValues();
var confirmationFieldName = field;
if (!confirmationFieldName) {
confirmationFieldName = /_confirm$/.test(name) ? name.substr(0, name.length - 8) : (name + "_confirm");
}
return formValues[confirmationFieldName] === value
})())
},
/**
* Rule: ensures the value is a date according to Date.parse(), or a format
* regex.
*/
date: function (value, format) {
date: function (ref, format) {
var value = ref.value;
if ( format === void 0 ) format = false;
return Promise.resolve((function () {
@ -535,7 +602,9 @@
/**
* Rule: tests
*/
email: function (value) {
email: function (ref) {
var value = ref.value;
// eslint-disable-next-line
var isEmail = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
return Promise.resolve(isEmail.test(value))
@ -544,7 +613,8 @@
/**
* Rule: Value is in an array (stack).
*/
in: function (value) {
in: function (ref) {
var value = ref.value;
var stack = [], len = arguments.length - 1;
while ( len-- > 0 ) stack[ len ] = arguments[ len + 1 ];
@ -559,7 +629,8 @@
/**
* Rule: Match the value against a (stack) of patterns or strings
*/
matches: function (value) {
matches: function (ref) {
var value = ref.value;
var stack = [], len = arguments.length - 1;
while ( len-- > 0 ) stack[ len ] = arguments[ len + 1 ];
@ -574,7 +645,8 @@
/**
* Check the maximum value of a particular.
*/
max: function (value, minimum, force) {
max: function (ref, minimum, force) {
var value = ref.value;
if ( minimum === void 0 ) minimum = 10;
return Promise.resolve((function () {
@ -597,13 +669,14 @@
/**
* Check the file type is correct.
*/
mime: function (files) {
mime: function (ref) {
var value = ref.value;
var types = [], len = arguments.length - 1;
while ( len-- > 0 ) types[ len ] = arguments[ len + 1 ];
return Promise.resolve((function () {
if (files instanceof FileUpload) {
var fileList = files.getFileList();
if (value instanceof FileUpload) {
var fileList = value.getFileList();
for (var i = 0; i < fileList.length; i++) {
var file = fileList[i];
if (!types.includes(file.type)) {
@ -618,7 +691,8 @@
/**
* Check the minimum value of a particular.
*/
min: function (value, minimum, force) {
min: function (ref, minimum, force) {
var value = ref.value;
if ( minimum === void 0 ) minimum = 1;
return Promise.resolve((function () {
@ -641,7 +715,8 @@
/**
* Rule: Value is not in stack.
*/
not: function (value) {
not: function (ref) {
var value = ref.value;
var stack = [], len = arguments.length - 1;
while ( len-- > 0 ) stack[ len ] = arguments[ len + 1 ];
@ -656,14 +731,17 @@
/**
* Rule: checks if the value is only alpha numeric
*/
number: function (value) {
number: function (ref) {
var value = ref.value;
return Promise.resolve(!isNaN(value))
},
/**
* Rule: must be a value
*/
required: function (value, isRequired) {
required: function (ref, isRequired) {
var value = ref.value;
if ( isRequired === void 0 ) isRequired = true;
return Promise.resolve((function () {
@ -686,7 +764,9 @@
/**
* Rule: checks if a string is a valid url
*/
url: function (value) {
url: function (ref) {
var value = ref.value;
return Promise.resolve(isUrl(value))
}
};
@ -763,6 +843,16 @@
return ((sentence(name)) + " must be between " + (args[0]) + " and " + (args[1]) + " characters long.")
},
/**
* The confirmation field does not match
*/
confirm: function (ref) {
var name = ref.name;
var args = ref.args;
return ((sentence(name)) + " does not match.")
},
/**
* Is not a valid date.
*/
@ -1334,7 +1424,11 @@
var rule = ref[0];
var args = ref[1];
return rule.apply(void 0, [ this$1.context.model ].concat( args ))
return rule.apply(void 0, [ {
value: this$1.context.model,
getFormValues: this$1.getFormValues.bind(this$1),
name: this$1.context.name
} ].concat( args ))
.then(function (res) { return res ? false : this$1.$formulate.validationMessage(rule.name, {
args: args,
name: this$1.mergedValidationName,
@ -1541,7 +1635,7 @@
var FormulateInput = normalizeComponent(
var __vue_component__ = normalizeComponent(
{ render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },
__vue_inject_styles__,
__vue_script__,
@ -1554,6 +1648,44 @@
undefined
);
var FormSubmission = function FormSubmission (form) {
this.form = form;
};
/**
* Determine if the form has any validation errors.
*
* @return {Promise} resolves a boolean
*/
FormSubmission.prototype.hasValidationErrors = function hasValidationErrors () {
return this.form.hasValidationErrors()
};
/**
* Asynchronously generate the values payload of this form.
* @return {Promise} resolves to json
*/
FormSubmission.prototype.values = function values () {
var this$1 = this;
return new Promise(function (resolve, reject) {
var pending = [];
var values = cloneDeep(this$1.form.internalModelProxy);
for (var key in values) {
if (typeof this$1.form.internalModelProxy[key] === 'object' && this$1.form.internalModelProxy[key] instanceof FileUpload) {
pending.push(this$1.form.internalModelProxy[key].upload());
}
}
/**
* @todo - how do we get these uploaded path values back into our data?
*/
Promise.all(pending)
// .then(file => file.path)
.then(function () { return resolve(values); })
.catch(function (err) { return reject(err); });
})
};
//
var script$1 = {
@ -1642,9 +1774,15 @@
}
},
formSubmitted: function formSubmitted () {
var this$1 = this;
// perform validation here
this.showErrors();
this.$emit('submit', this.internalFormModelProxy);
var submission = new FormSubmission(this);
this.$emit('submit-raw', submission);
submission.hasValidationErrors()
.then(function (hasErrors) { return hasErrors ? false : submission.values(); })
.then(function (json) { return this$1.$emit('submit', json); });
},
showErrors: function showErrors () {
for (var fieldName in this.registry) {
@ -1653,6 +1791,15 @@
},
getFormValues: function getFormValues () {
return this.internalFormModelProxy
},
hasValidationErrors: function hasValidationErrors () {
var resolvers = [];
for (var fieldName in this.registry) {
if (typeof this.registry[fieldName].hasValidationErrors === 'function') {
resolvers.push(this.registry[fieldName].hasValidationErrors());
}
}
return Promise.all(resolvers).then(function (fields) { return !!fields.find(function (hasErrors) { return hasErrors; }); })
}
}
};
@ -1698,7 +1845,7 @@
var FormulateForm = normalizeComponent(
var __vue_component__$1 = normalizeComponent(
{ render: __vue_render__$1, staticRenderFns: __vue_staticRenderFns__$1 },
__vue_inject_styles__$1,
__vue_script__$1,
@ -1779,7 +1926,7 @@
var FormulateInputErrors = normalizeComponent(
var __vue_component__$2 = normalizeComponent(
{ render: __vue_render__$2, staticRenderFns: __vue_staticRenderFns__$2 },
__vue_inject_styles__$2,
__vue_script__$2,
@ -1899,7 +2046,7 @@
var FormulateInputGroup = normalizeComponent(
var __vue_component__$3 = normalizeComponent(
{ render: __vue_render__$3, staticRenderFns: __vue_staticRenderFns__$3 },
__vue_inject_styles__$3,
__vue_script__$3,
@ -2101,7 +2248,7 @@
var FormulateInputBox = normalizeComponent(
var __vue_component__$4 = normalizeComponent(
{ render: __vue_render__$4, staticRenderFns: __vue_staticRenderFns__$4 },
__vue_inject_styles__$4,
__vue_script__$4,
@ -2265,7 +2412,7 @@
var FormulateInputText = normalizeComponent(
var __vue_component__$5 = normalizeComponent(
{ render: __vue_render__$5, staticRenderFns: __vue_staticRenderFns__$5 },
__vue_inject_styles__$5,
__vue_script__$5,
@ -2406,7 +2553,7 @@
var FormulateFiles = normalizeComponent(
var __vue_component__$6 = normalizeComponent(
{ render: __vue_render__$6, staticRenderFns: __vue_staticRenderFns__$6 },
__vue_inject_styles__$6,
__vue_script__$6,
@ -2424,7 +2571,7 @@
var script$7 = {
name: 'FormulateInputFile',
components: {
FormulateFiles: FormulateFiles
FormulateFiles: __vue_component__$6
},
mixins: [FormulateInputMixin],
data: function data () {
@ -2578,7 +2725,7 @@
var FormulateInputFile = normalizeComponent(
var __vue_component__$7 = normalizeComponent(
{ render: __vue_render__$7, staticRenderFns: __vue_staticRenderFns__$7 },
__vue_inject_styles__$7,
__vue_script__$7,
@ -2656,7 +2803,7 @@
var FormulateInputButton = normalizeComponent(
var __vue_component__$8 = normalizeComponent(
{ render: __vue_render__$8, staticRenderFns: __vue_staticRenderFns__$8 },
__vue_inject_styles__$8,
__vue_script__$8,
@ -2822,7 +2969,7 @@
var FormulateInputSelect = normalizeComponent(
var __vue_component__$9 = normalizeComponent(
{ render: __vue_render__$9, staticRenderFns: __vue_staticRenderFns__$9 },
__vue_inject_styles__$9,
__vue_script__$9,
@ -2986,7 +3133,7 @@
var FormulateInputSlider = normalizeComponent(
var __vue_component__$a = normalizeComponent(
{ render: __vue_render__$a, staticRenderFns: __vue_staticRenderFns__$a },
__vue_inject_styles__$a,
__vue_script__$a,
@ -3071,7 +3218,7 @@
var FormulateInputTextArea = normalizeComponent(
var __vue_component__$b = normalizeComponent(
{ render: __vue_render__$b, staticRenderFns: __vue_staticRenderFns__$b },
__vue_inject_styles__$b,
__vue_script__$b,
@ -3090,17 +3237,17 @@
var Formulate = function Formulate () {
this.defaults = {
components: {
FormulateForm: FormulateForm,
FormulateInput: FormulateInput,
FormulateInputErrors: FormulateInputErrors,
FormulateInputBox: FormulateInputBox,
FormulateInputText: FormulateInputText,
FormulateInputFile: FormulateInputFile,
FormulateInputGroup: FormulateInputGroup,
FormulateInputButton: FormulateInputButton,
FormulateInputSelect: FormulateInputSelect,
FormulateInputSlider: FormulateInputSlider,
FormulateInputTextArea: FormulateInputTextArea
FormulateForm: __vue_component__$1,
FormulateInput: __vue_component__,
FormulateInputErrors: __vue_component__$2,
FormulateInputBox: __vue_component__$4,
FormulateInputText: __vue_component__$5,
FormulateInputFile: __vue_component__$7,
FormulateInputGroup: __vue_component__$3,
FormulateInputButton: __vue_component__$8,
FormulateInputSelect: __vue_component__$9,
FormulateInputSlider: __vue_component__$a,
FormulateInputTextArea: __vue_component__$b
},
library: library,
rules: rules,
@ -3211,6 +3358,7 @@
var Formulate$1 = new Formulate();
exports.FileUpload = FileUpload;
exports.default = Formulate$1;
Object.defineProperty(exports, '__esModule', { value: true });

4
dist/snow.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -1,6 +0,0 @@
{
"presets": [
["env", { "modules": false }],
"stage-2"
]
}

4
example/.gitignore vendored
View File

@ -1,4 +0,0 @@
.DS_Store
node_modules/
dist/
npm-debug.log

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2018 Braid LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,13 +0,0 @@
# Vue Formulate Example
A simple example of a registration form using `vue-formulate`. The store is
set up using a *Root store* configuration (as opposed to using a vuex module).
### Installation
To install and run the example:
```sh
npm install
npm run dev
```

View File

@ -1,11 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Vue Formulate Example</title>
</head>
<body>
<div id="app"></div>
<script src="/dist/build.js"></script>
</body>
</html>

8135
example/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +0,0 @@
{
"version": "1.0.0",
"author": "Justin Schroeder <justin@wearebraid.com>",
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --hot",
"build": "cross-env NODE_ENV=production webpack -p"
},
"dependencies": {
"vue": "^2.6.9",
"vue-formulate": "^0.10.0",
"vuex": "^2.4.0"
},
"devDependencies": {
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"babel-preset-env": "^1.7.0",
"babel-preset-stage-2": "^6.24.1",
"cross-env": "^3.0.0",
"css-loader": "^0.25.0",
"file-loader": "^0.9.0",
"vue-loader": "^13.7.3",
"vue-template-compiler": "^2.6.9",
"webpack": "^3.12.0",
"webpack-dev-server": "^2.11.3"
}
}

View File

@ -1,196 +0,0 @@
<template>
<div id="app">
<formulate
name="registration"
class="my-form"
v-if="!values"
@submit="useFormData"
>
<formulate-element
name="email"
type="email"
label="Email address"
placeholder="you@example.com"
validation="required|email"
/>
<formulate-element
name="password_confirmation"
type="password"
label="Password"
placeholder="Choose a password"
validation="required"
/>
<formulate-element
name="password"
type="password"
label="Confirm Password"
placeholder="Confirm your password"
validation-label="Password"
validation="required|confirmed"
/>
<formulate-element
name="animal"
label="Spirit Animal"
>
<custom-input
v-model="animal"
:options="{eagle: 'Eagle', lion: 'Lion', sloth: 'Sloth'}"
/>
<small>Example of a custom field using vue-formulate.</small>
</formulate-element>
<formulate-element
type="submit"
name="Save"
/>
</formulate>
<code
v-else
class="my-form my-form--code"
v-text="values"
/>
</div>
</template>
<script>
import CustomInput from './CustomInput.vue'
import {mapModels} from 'vue-formulate'
export default {
components: {
'custom-input': CustomInput
},
data () {
return {
values: false
}
},
computed: {
...mapModels({
animal: 'registration/animal'
})
},
methods: {
useFormData (vals) {
this.values = JSON.stringify(vals, null, 2)
}
}
}
</script>
<style>
html,
body {
min-height: 100%;
}
body {
background: linear-gradient(to top left, #35495e, #41b883);
background-repeat: no-repeat;
padding: 0;
border: 0;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
-webkit-font-smoothing: antialiased;
}
code {
white-space: pre;
}
::-webkit-input-placeholder {
color: lightgray;
}
::-moz-placeholder {
color: lightgray;
}
:-ms-input-placeholder {
color: lightgray;
}
:-moz-placeholder {
color: lightgray;
}
.my-form {
box-sizing: border-box;
box-shadow: 0 0 5px 0 rgba(0, 0, 0, .2);
max-width: 500px;
background-color: white;
border-radius: .25em;
padding: 2em;
position: absolute;
min-width: 500px;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.formulate-element {
font-size: 1em;
margin-bottom: 1em;
}
.formulate-element:last-child {
margin-bottom: 0;
}
.formulate-errors {
list-style-type: none;
padding: 0;
margin: 0;
}
.formulate-errors li {
margin: .25em 0;
font-size: 14px;
color: red;
font-style: italic;
}
label {
display: block;
font-weight: normal;
font-weight: bold;
margin-bottom: .5em;
}
input[type="text"],
input[type="password"],
input[type="email"] {
border: 1px solid gainsboro;
box-sizing: border-box;
font-size: 1em;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
background-color: transparent;
padding: .5em;
width: 100%;
}
input[type="text"]:focus,
input[type="password"]:focus,
input[type="email"]:focus {
outline: 0;
border-color: #41b883;
}
button {
box-sizing: border-box;
border: 1px solid #41b883;
border-radius: 2em;
background-color: #41b883;
color: white;
font-size: 1em;
width: 100%;
padding: .75em;
text-transform: uppercase;
text-align: center;
min-width: 100%;
}
button:focus {
outline: 0;
border-color: #35495e;
}
small {
color: grey;
display: block;
font-size: .8em;
margin: .5em 0;
}
</style>

View File

@ -1,63 +0,0 @@
<template>
<ul class="custom-input pill">
<li
v-for="(option, val) in options"
:key="val"
v-text="option"
:data-selected="val === value"
@click="$emit('input', val)"
/>
</ul>
</template>
<script>
export default {
props: {
options: {
type: Object,
required: true
},
value: {
type: [String, Number, Boolean],
default: false
}
}
}
</script>
<style>
.pill {
display: flex;
list-style-type: none;
padding: 0;
margin: 0;
justify-content: space-between;
}
.pill li {
flex-grow: 1;
padding: .75em 1em;
text-align: center;
border: 1px solid #41b883;
cursor: pointer;
}
.pill li:hover {
background-color: rgba(65, 184, 131, .15);
}
.pill li[data-selected] {
color: white;
background-color: #41b883;
}
.pill li:first-child {
border-radius: .5em 0 0 .5em;
border-right: 0;
}
.pill li:last-child {
border-radius: 0 .5em .5em 0;
border-left: 0;
}
</style>

View File

@ -1,12 +0,0 @@
import Vue from 'vue'
import App from './App.vue'
import store from '../store'
import formulate from 'vue-formulate'
Vue.use(formulate)
window.example = new Vue({
el: '#app',
store,
render: h => h(App)
})

View File

@ -1,26 +0,0 @@
import Vue from 'vue'
import Vuex from 'vuex'
import {formulateState, formulateGetters, formulateMutations} from 'vue-formulate'
Vue.use(Vuex)
const state = {
...formulateState()()
}
const getters = {
...formulateGetters()
}
const mutations = {
...formulateMutations()
}
const actions = {}
export default new Vuex.Store({
state,
getters,
mutations,
actions
})

View File

@ -1,67 +0,0 @@
var path = require('path')
var webpack = require('webpack')
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
}
}
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
]
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
},
symlinks: false
},
devServer: {
historyApiFallback: true,
noInfo: true
},
performance: {
hints: false
}
}
if (process.env.NODE_ENV === 'production') {
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
])
}

3296
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -43,44 +43,45 @@
},
"homepage": "https://www.vueformulate.com",
"devDependencies": {
"@babel/core": "^7.6.4",
"@babel/plugin-transform-modules-commonjs": "^7.6.0",
"@babel/preset-env": "^7.6.3",
"@vue/component-compiler-utils": "^3.0.0",
"@vue/test-utils": "1.0.0-beta.29",
"autoprefixer": "^9.7.0",
"@babel/core": "^7.8.4",
"@babel/plugin-transform-modules-commonjs": "^7.8.3",
"@babel/preset-env": "^7.8.4",
"@vue/component-compiler-utils": "^3.1.1",
"@vue/test-utils": "^1.0.0-beta.31",
"autoprefixer": "^9.7.4",
"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^10.0.1",
"babel-jest": "^24.9.0",
"cssnano": "^4.1.10",
"eslint": "^5.16.0",
"eslint-config-standard": "^12.0.0",
"eslint-plugin-import": "^2.16.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-node": "^8.0.1",
"eslint-plugin-promise": "^4.1.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^5.2.3",
"flush-promises": "^1.0.2",
"jest": "^24.9.0",
"jest-vue-preprocessor": "^1.7.0",
"node-sass": "^4.13.0",
"postcss": "^7.0.21",
"jest-vue-preprocessor": "^1.7.1",
"node-sass": "^4.13.1",
"postcss": "^7.0.27",
"postcss-cli": "^6.1.3",
"rollup": "^1.26.0",
"rollup": "^1.31.1",
"rollup-plugin-buble": "^0.19.8",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-vue": "^5.1.1",
"typescript": "^3.6.4",
"vue": "^2.6.10",
"rollup-plugin-vue": "^5.1.6",
"typescript": "^3.8.2",
"vue": "^2.6.11",
"vue-jest": "^3.0.5",
"vue-runtime-helpers": "^1.1.2",
"vue-template-compiler": "^2.6.10",
"vue-template-compiler": "^2.6.11",
"vue-template-es2015-compiler": "^1.9.1",
"watch": "^1.0.2"
},
"dependencies": {
"clone-deep": "^4.0.1",
"is-plain-object": "^3.0.0",
"is-url": "^1.2.4",
"nanoid": "^2.1.6"
"nanoid": "^2.1.11"
}
}

View File

@ -141,7 +141,6 @@ class FileUpload {
*/
loadPreviews () {
this.files.map(file => {
console.log(file.type)
if (!file.previewData && window && window.FileReader && /^image\//.test(file.file.type)) {
const reader = new FileReader()
reader.onload = e => Object.assign(file, { previewData: e.target.result })
@ -163,6 +162,11 @@ class FileUpload {
getFiles () {
return this.files
}
toString () {
const descriptor = this.files.length ? this.files.length + ' files' : 'empty'
return `FileUpload(${descriptor})`
}
}
export default FileUpload

44
src/FormSubmission.js Normal file
View File

@ -0,0 +1,44 @@
import { cloneDeep } from './libs/utils'
import FileUpload from './FileUpload'
export default class FormSubmission {
/**
* Initialize a formulate form.
* @param {vm} form an instance of FormulateForm
*/
constructor (form) {
this.form = form
}
/**
* Determine if the form has any validation errors.
*
* @return {Promise} resolves a boolean
*/
hasValidationErrors () {
return this.form.hasValidationErrors()
}
/**
* Asynchronously generate the values payload of this form.
* @return {Promise} resolves to json
*/
values () {
return new Promise((resolve, reject) => {
const pending = []
const values = cloneDeep(this.form.internalModelProxy)
for (const key in values) {
if (typeof this.form.internalModelProxy[key] === 'object' && this.form.internalModelProxy[key] instanceof FileUpload) {
pending.push(this.form.internalModelProxy[key].upload())
}
}
/**
* @todo - how do we get these uploaded path values back into our data?
*/
Promise.all(pending)
// .then(file => file.path)
.then(() => resolve(values))
.catch(err => reject(err))
})
}
}

View File

@ -147,3 +147,6 @@ class Formulate {
}
export default new Formulate()
export {
FileUpload
}

View File

@ -8,6 +8,7 @@
<script>
import { shallowEqualObjects } from './libs/utils'
import FormSubmission from './FormSubmission'
export default {
provide () {
@ -95,7 +96,11 @@ export default {
formSubmitted () {
// perform validation here
this.showErrors()
this.$emit('submit', this.internalFormModelProxy)
const submission = new FormSubmission(this)
this.$emit('submit-raw', submission)
submission.hasValidationErrors()
.then(hasErrors => hasErrors ? false : submission.values())
.then(json => this.$emit('submit', json))
},
showErrors () {
for (const fieldName in this.registry) {
@ -104,6 +109,15 @@ export default {
},
getFormValues () {
return this.internalFormModelProxy
},
hasValidationErrors () {
const resolvers = []
for (const fieldName in this.registry) {
if (typeof this.registry[fieldName].hasValidationErrors === 'function') {
resolvers.push(this.registry[fieldName].hasValidationErrors())
}
}
return Promise.all(resolvers).then(fields => !!fields.find(hasErrors => hasErrors))
}
}
}

View File

@ -223,7 +223,11 @@ export default {
const rules = parseRules(this.validation, this.$formulate.rules())
this.pendingValidation = Promise.all(
rules.map(([rule, args]) => {
return rule(this.context.model, ...args)
return rule({
value: this.context.model,
getFormValues: this.getFormValues.bind(this),
name: this.context.name
}, ...args)
.then(res => res ? false : this.$formulate.validationMessage(rule.name, {
args,
name: this.mergedValidationName,

View File

@ -9,14 +9,14 @@ export default {
/**
* Rule: the value must be "yes", "on", "1", or true
*/
accepted: function (value) {
accepted: function ({ value }) {
return Promise.resolve(['yes', 'on', '1', 1, true, 'true'].includes(value))
},
/**
* Rule: checks if a value is after a given date. Defaults to current time
*/
after: function (value, compare = false) {
after: function ({ value }, compare = false) {
const timestamp = Date.parse(compare || new Date())
const fieldValue = Date.parse(value)
return Promise.resolve(isNaN(fieldValue) ? false : (fieldValue > timestamp))
@ -25,7 +25,7 @@ export default {
/**
* Rule: checks if the value is only alpha
*/
alpha: function (value, set = 'default') {
alpha: function ({ value }, set = 'default') {
const sets = {
default: /^[a-zA-ZÀ-ÖØ-öø-ÿ]+$/,
latin: /^[a-zA-Z]+$/
@ -37,7 +37,7 @@ export default {
/**
* Rule: checks if the value is alpha numeric
*/
alphanumeric: function (value, set = 'default') {
alphanumeric: function ({ value }, set = 'default') {
const sets = {
default: /^[a-zA-Z0-9À-ÖØ-öø-ÿ]+$/,
latin: /^[a-zA-Z0-9]+$/
@ -49,7 +49,7 @@ export default {
/**
* Rule: checks if a value is after a given date. Defaults to current time
*/
before: function (value, compare = false) {
before: function ({ value }, compare = false) {
const timestamp = Date.parse(compare || new Date())
const fieldValue = Date.parse(value)
return Promise.resolve(isNaN(fieldValue) ? false : (fieldValue < timestamp))
@ -58,7 +58,7 @@ export default {
/**
* Rule: checks if the value is between two other values
*/
between: function (value, from = 0, to = 10) {
between: function ({ value }, from = 0, to = 10) {
return Promise.resolve((() => {
if (from === null || to === null || isNaN(from) || isNaN(to)) {
return false
@ -76,11 +76,26 @@ export default {
})())
},
/**
* Confirm that the value of one field is the same as another, mostly used
* for password confirmations.
*/
confirm: function ({ value, getFormValues, name }, field) {
return Promise.resolve((() => {
const formValues = getFormValues()
var confirmationFieldName = field
if (!confirmationFieldName) {
confirmationFieldName = /_confirm$/.test(name) ? name.substr(0, name.length - 8) : `${name}_confirm`
}
return formValues[confirmationFieldName] === value
})())
},
/**
* Rule: ensures the value is a date according to Date.parse(), or a format
* regex.
*/
date: function (value, format = false) {
date: function ({ value }, format = false) {
return Promise.resolve((() => {
if (format && typeof format === 'string') {
return regexForFormat(format).test(value)
@ -92,7 +107,7 @@ export default {
/**
* Rule: tests
*/
email: function (value) {
email: function ({ value }) {
// eslint-disable-next-line
const isEmail = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i
return Promise.resolve(isEmail.test(value))
@ -101,7 +116,7 @@ export default {
/**
* Rule: Value is in an array (stack).
*/
in: function (value, ...stack) {
in: function ({ value }, ...stack) {
return Promise.resolve(stack.find(item => {
if (typeof item === 'object') {
return shallowEqualObjects(item, value)
@ -113,7 +128,7 @@ export default {
/**
* Rule: Match the value against a (stack) of patterns or strings
*/
matches: function (value, ...stack) {
matches: function ({ value }, ...stack) {
return Promise.resolve(!!stack.find(pattern => {
if (pattern instanceof RegExp) {
return pattern.test(value)
@ -125,7 +140,7 @@ export default {
/**
* Check the maximum value of a particular.
*/
max: function (value, minimum = 10, force) {
max: function ({ value }, minimum = 10, force) {
return Promise.resolve((() => {
if (Array.isArray(value)) {
minimum = !isNaN(minimum) ? Number(minimum) : minimum
@ -146,10 +161,10 @@ export default {
/**
* Check the file type is correct.
*/
mime: function (files, ...types) {
mime: function ({ value }, ...types) {
return Promise.resolve((() => {
if (files instanceof FileUpload) {
const fileList = files.getFileList()
if (value instanceof FileUpload) {
const fileList = value.getFileList()
for (let i = 0; i < fileList.length; i++) {
const file = fileList[i]
if (!types.includes(file.type)) {
@ -164,7 +179,7 @@ export default {
/**
* Check the minimum value of a particular.
*/
min: function (value, minimum = 1, force) {
min: function ({ value }, minimum = 1, force) {
return Promise.resolve((() => {
if (Array.isArray(value)) {
minimum = !isNaN(minimum) ? Number(minimum) : minimum
@ -185,7 +200,7 @@ export default {
/**
* Rule: Value is not in stack.
*/
not: function (value, ...stack) {
not: function ({ value }, ...stack) {
return Promise.resolve(stack.find(item => {
if (typeof item === 'object') {
return shallowEqualObjects(item, value)
@ -197,14 +212,14 @@ export default {
/**
* Rule: checks if the value is only alpha numeric
*/
number: function (value) {
number: function ({ value }) {
return Promise.resolve(!isNaN(value))
},
/**
* Rule: must be a value
*/
required: function (value, isRequired = true) {
required: function ({ value }, isRequired = true) {
return Promise.resolve((() => {
if (!isRequired || ['no', 'false'].includes(isRequired)) {
return true
@ -225,7 +240,7 @@ export default {
/**
* Rule: checks if a string is a valid url
*/
url: function (value) {
url: function ({ value }) {
return Promise.resolve(isUrl(value))
}
}

View File

@ -1,3 +1,5 @@
import FileUpload from '../FileUpload'
/**
* Function to map over an object.
* @param {Object} obj An object to map over
@ -172,3 +174,39 @@ export function regexForFormat (format) {
return regex.replace(format, formats[format])
}, escaped))
}
/**
* Check if
* @param {mixed} data
*/
export function isValueType (data) {
switch (typeof data) {
case 'symbol':
case 'number':
case 'string':
case 'boolean':
case 'undefined':
return true
default:
if (data === null) {
return true
}
return false
}
}
/**
* A simple (somewhat non-comprehensive) cloneDeep function, valid for our use
* case of needing to unbind reactive watchers.
*/
export function cloneDeep (obj) {
const newObj = {}
for (const key in obj) {
if (obj[key] instanceof FileUpload || isValueType(obj[key])) {
newObj[key] = obj
} else {
newObj[key] = cloneDeep(obj[key])
}
}
return newObj
}

View File

@ -55,6 +55,13 @@ export default {
return `${s(name)} must be between ${args[0]} and ${args[1]} characters long.`
},
/**
* The confirmation field does not match
*/
confirm: function ({ name, args }) {
return `${s(name)} does not match.`
},
/**
* Is not a valid date.
*/

View File

@ -2,6 +2,7 @@ import Vue from 'vue'
import { mount, shallowMount } from '@vue/test-utils'
import flushPromises from 'flush-promises'
import Formulate from '../src/Formulate.js'
import FormSubmission from '../src/FormSubmission.js'
import FormulateForm from '@/FormulateForm.vue'
import FormulateInput from '@/FormulateInput.vue'
@ -86,30 +87,34 @@ describe('FormulateForm', () => {
* @todo in vue-test-utils version 1.0.0-beta.29 has some bugs related to
* synchronous updating. Some details are here:
*
* @update this test was re-implemented in version 1.0.0-beta.31 and seems to
* be workign now with flushPromises(). Leaving these docs here for now.
*
* https://github.com/vuejs/vue-test-utils/issues/1130
*
* This test is being commented out until there is a resolution on this issue,
* and instead being replaced with a mock call.
*/
// it('updates initial form values when input contains a populated v-model', () => {
// const wrapper = mount({
// data () {
// return {
// formValues: {
// testinput: '',
// },
// fieldValue: '123'
// }
// },
// template: `
// <FormulateForm v-model="formValues">
// <FormulateInput type="text" name="testinput" v-model="fieldValue" />
// </FormulateForm>
// `
// })
// expect(wrapper.vm.formValues).toEqual({ testinput: '123' })
// })
it('updates initial form values when input contains a populated v-model', async () => {
const wrapper = mount({
data () {
return {
formValues: {
testinput: '',
},
fieldValue: '123'
}
},
template: `
<FormulateForm v-model="formValues">
<FormulateInput type="text" name="testinput" v-model="fieldValue" />
</FormulateForm>
`
})
await flushPromises()
expect(wrapper.vm.formValues).toEqual({ testinput: '123' })
})
// ===========================================================================
@ -125,4 +130,24 @@ describe('FormulateForm', () => {
})
expect(wrapper.emitted().input[wrapper.emitted().input.length - 1]).toEqual([{ testinput: 'override-data' }])
})
it('it emits an instance of FormSubmission', async () => {
const wrapper = mount(FormulateForm, {
slots: { default: '<FormulateInput type="text" formulate-value="123" name="testinput" />' }
})
wrapper.find('form').trigger('submit')
await flushPromises()
expect(wrapper.emitted('submit-raw')[0][0]).toBeInstanceOf(FormSubmission)
})
it('it resolves hasValidationErrors to true', async () => {
const wrapper = mount(FormulateForm, {
slots: { default: '<FormulateInput type="text" validation="required" name="testinput" />' }
})
wrapper.find('form').trigger('submit')
await flushPromises()
const submission = wrapper.emitted('submit-raw')[0][0]
expect(await submission.hasValidationErrors()).toBe(true)
})
})

View File

@ -1,4 +1,5 @@
import Vue from 'vue'
import flushPromises from 'flush-promises'
import { mount } from '@vue/test-utils'
import Formulate from '../src/Formulate.js'
import FormulateInput from '@/FormulateInput.vue'
@ -57,21 +58,25 @@ test('type "radio" auto generate ids if not provided', () => {
* Test data binding
*/
test('type "checkbox" sets array of values via v-model', () => {
test('type "checkbox" sets array of values via v-model', async () => {
const wrapper = mount({
data () {
return {
checkboxValues: []
checkboxValues: [],
options: {foo: 'Foo', bar: 'Bar', fooey: 'Fooey'}
}
},
template: `
<div>
<FormulateInput type="checkbox" v-model="checkboxValues" :options="{foo: 'Foo', bar: 'Bar', fooey: 'Fooey'}" />
<FormulateInput type="checkbox" v-model="checkboxValues" :options="options" />
</div>
`
})
const fooInputs = wrapper.findAll('input[value^="foo"]')
expect(fooInputs.length).toBe(2)
fooInputs.setChecked(true)
fooInputs.at(0).setChecked()
await flushPromises()
fooInputs.at(1).setChecked()
await flushPromises()
expect(wrapper.vm.checkboxValues).toEqual(['foo', 'fooey'])
})

View File

@ -1,4 +1,5 @@
import Vue from 'vue'
import flushPromises from 'flush-promises'
import { mount } from '@vue/test-utils'
import Formulate from '../src/Formulate.js'
import FormulateInput from '@/FormulateInput.vue'
@ -113,7 +114,7 @@ describe('FormulateInputText', () => {
})
it('doesnt re-context itself if there were no changes', () => {
it('doesnt re-context itself if there were no changes', async () => {
const wrapper = mount({
data () {
return {
@ -128,9 +129,11 @@ describe('FormulateInputText', () => {
</div>
`
})
await flushPromises()
const firstContext = wrapper.find({ref: "first"}).vm.context
const secondContext = wrapper.find({ref: "second"}).vm.context
wrapper.find('input').setValue('new value')
await flushPromises()
expect(firstContext).toBeTruthy()
expect(wrapper.vm.valueA === 'new value').toBe(true)
expect(wrapper.vm.valueB === 'second value').toBe(true)
@ -205,12 +208,13 @@ describe('FormulateInputText', () => {
expect(wrapper.findAll('.formulate-input-error').length).toBe(1)
})
it('shows errors on blur with error-behavior blur', () => {
it('shows errors on blur with error-behavior blur', async () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', errorBehavior: 'blur', errors: ['Bad input'] } })
expect(wrapper.find('[data-has-errors]').exists()).toBe(true)
expect(wrapper.find('[data-is-showing-errors]').exists()).toBe(false)
expect(wrapper.findAll('.formulate-input-errors').exists()).toBe(false)
wrapper.find('input').trigger('blur')
await flushPromises()
expect(wrapper.find('[data-is-showing-errors]').exists()).toBe(true)
expect(wrapper.findAll('.formulate-input-errors').exists()).toBe(true)
expect(wrapper.findAll('.formulate-input-error').length).toBe(1)

View File

@ -5,25 +5,25 @@ import FileUpload from '../src/FileUpload'
* Required rule
*/
describe('required', () => {
it('fails on empty string', async () => expect(await rules.required('')).toBe(false))
it('fails on empty string', async () => expect(await rules.required({ value: '' })).toBe(false))
it('fails on empty array', async () => expect(await rules.required([])).toBe(false))
it('fails on empty array', async () => expect(await rules.required({ value: [] })).toBe(false))
it('fails on empty object', async () => expect(await rules.required({})).toBe(false))
it('fails on empty object', async () => expect(await rules.required({ value: {} })).toBe(false))
it('fails on null', async () => expect(await rules.required(null)).toBe(false))
it('fails on null', async () => expect(await rules.required({ value: null })).toBe(false))
it('passes with the number zero', async () => expect(await rules.required(0)).toBe(true))
it('passes with the number zero', async () => expect(await rules.required({ value: 0 })).toBe(true))
it('passes with the boolean false', async () => expect(await rules.required(false)).toBe(true))
it('passes with the boolean false', async () => expect(await rules.required({ value: false })).toBe(true))
it('passes with a non empty array', async () => expect(await rules.required(['123'])).toBe(true))
it('passes with a non empty array', async () => expect(await rules.required({ value: ['123'] })).toBe(true))
it('passes with a non empty object', async () => expect(await rules.required({a: 'b'})).toBe(true))
it('passes with a non empty object', async () => expect(await rules.required({ value: {a: 'b'} })).toBe(true))
it('passes with empty value if second argument is false', async () => expect(await rules.required('', false)).toBe(true))
it('passes with empty value if second argument is false', async () => expect(await rules.required({ value: '' }, false)).toBe(true))
it('passes with empty value if second argument is false string', async () => expect(await rules.required('', 'false')).toBe(true))
it('passes with empty value if second argument is false string', async () => expect(await rules.required({ value: '' }, 'false')).toBe(true))
})
@ -32,27 +32,27 @@ describe('required', () => {
*/
describe('in', () => {
it('fails when not in stack', async () => {
expect(await rules.in('third', 'first', 'second')).toBe(false)
expect(await rules.in({ value: 'third' }, 'first', 'second')).toBe(false)
})
it('fails when case sensitive mismatch is in stack', async () => {
expect(await rules.in('third', 'first', 'second', 'Third')).toBe(false)
expect(await rules.in({ value: 'third' }, 'first', 'second', 'Third')).toBe(false)
})
it('fails comparing dissimilar objects', async () => {
expect(await rules.in({f: 'abc'}, {a: 'cdf'}, {b: 'abc'})).toBe(false)
expect(await rules.in({ value: {f: 'abc'} }, {a: 'cdf'}, {b: 'abc'})).toBe(false)
})
it('passes when case sensitive match is in stack', async () => {
expect(await rules.in('third', 'first', 'second', 'third')).toBe(true)
expect(await rules.in({ value: 'third' }, 'first', 'second', 'third')).toBe(true)
})
it('passes a shallow array compare', async () => {
expect(await rules.in(['abc'], ['cdf'], ['abc'])).toBe(true)
expect(await rules.in({ value: ['abc'] }, ['cdf'], ['abc'])).toBe(true)
})
it('passes a shallow object compare', async () => {
expect(await rules.in({f: 'abc'}, {a: 'cdf'}, {f: 'abc'},)).toBe(true)
expect(await rules.in({ value: {f: 'abc'} }, {a: 'cdf'}, {f: 'abc'},)).toBe(true)
})
})
@ -61,23 +61,23 @@ describe('in', () => {
*/
describe('matches', () => {
it('simple strings fail if they arent equal', async () => {
expect(await rules.matches('third', 'first')).toBe(false)
expect(await rules.matches({ value: 'third' }, 'first')).toBe(false)
})
it('fails on non matching regex', async () => {
expect(await rules.matches('third', /^thirds/)).toBe(false)
expect(await rules.matches({ value: 'third' }, /^thirds/)).toBe(false)
})
it('passes if simple strings match', async () => {
expect(await rules.matches('second', 'third', 'second')).toBe(true)
expect(await rules.matches({ value: 'second' }, 'third', 'second')).toBe(true)
})
it('passes on matching regex', async () => {
expect(await rules.matches('third', /^third/)).toBe(true)
expect(await rules.matches({ value: 'third' }, /^third/)).toBe(true)
})
it('passes on matching mixed regex and string', async () => {
expect(await rules.matches('first-fourth', 'second', /^third/, /fourth$/)).toBe(true)
expect(await rules.matches({ value: 'first-fourth' }, 'second', /^third/, /fourth$/)).toBe(true)
})
})
@ -85,19 +85,19 @@ describe('matches', () => {
* Accepted rule
*/
describe('accepted', () => {
it('passes with true', async () => expect(await rules.accepted('yes')).toBe(true))
it('passes with true', async () => expect(await rules.accepted({ value: 'yes' })).toBe(true))
it('passes with on', async () => expect(await rules.accepted('on')).toBe(true))
it('passes with on', async () => expect(await rules.accepted({ value: 'on' })).toBe(true))
it('passes with 1', async () => expect(await rules.accepted('1')).toBe(true))
it('passes with 1', async () => expect(await rules.accepted({ value: '1' })).toBe(true))
it('passes with number 1', async () => expect(await rules.accepted(1)).toBe(true))
it('passes with number 1', async () => expect(await rules.accepted({ value: 1 })).toBe(true))
it('passes with boolean true', async () => expect(await rules.accepted(true)).toBe(true))
it('passes with boolean true', async () => expect(await rules.accepted({ value: true })).toBe(true))
it('fail with boolean false', async () => expect(await rules.accepted(false)).toBe(false))
it('fail with boolean false', async () => expect(await rules.accepted({ value: false })).toBe(false))
it('fail with "false"', async () => expect(await rules.accepted('false')).toBe(false))
it('fail with "false"', async () => expect(await rules.accepted({ value: 'false' })).toBe(false))
})
@ -108,36 +108,36 @@ describe('accepted', () => {
* well tested: https://github.com/segmentio/is-url/blob/master/test/index.js
*/
describe('url', () => {
it('passes with http://google.com', async () => expect(await rules.url('http://google.com')).toBe(true))
it('passes with http://google.com', async () => expect(await rules.url({ value: 'http://google.com' })).toBe(true))
it('fails with google.com', async () => expect(await rules.url('google.com')).toBe(false))
it('fails with google.com', async () => expect(await rules.url({ value: 'google.com' })).toBe(false))
})
/**
* Determines if the string is a date
*/
describe('date', () => {
it('passes with month day year', async () => expect(await rules.date('December 17, 2020')).toBe(true))
it('passes with month day year', async () => expect(await rules.date({ value: 'December 17, 2020' })).toBe(true))
it('passes with month day', async () => expect(await rules.date('December 17')).toBe(true))
it('passes with month day', async () => expect(await rules.date({ value: 'December 17' })).toBe(true))
it('passes with short month day', async () => expect(await rules.date('Dec 17')).toBe(true))
it('passes with short month day', async () => expect(await rules.date({ value: 'Dec 17' })).toBe(true))
it('passes with short month day', async () => expect(await rules.date('Dec 17 12:34:15')).toBe(true))
it('passes with short month day', async () => expect(await rules.date({ value: 'Dec 17 12:34:15' })).toBe(true))
it('passes with out of bounds number', async () => expect(await rules.date('January 77')).toBe(true))
it('passes with out of bounds number', async () => expect(await rules.date({ value: 'January 77' })).toBe(true))
it('passes with only month', async () => expect(await rules.date('January')).toBe(false))
it('passes with only month', async () => expect(await rules.date({ value: 'January' })).toBe(false))
it('passes with valid date format', async () => expect(await rules.date('12/17/1987', 'MM/DD/YYYY')).toBe(true))
it('passes with valid date format', async () => expect(await rules.date({ value: '12/17/1987' }, 'MM/DD/YYYY')).toBe(true))
it('fails with simple number and date format', async () => expect(await rules.date('1234', 'MM/DD/YYYY')).toBe(false))
it('fails with simple number and date format', async () => expect(await rules.date({ value: '1234' }, 'MM/DD/YYYY')).toBe(false))
it('fails with only day of week', async () => expect(await rules.date('saturday')).toBe(false))
it('fails with only day of week', async () => expect(await rules.date({ value: 'saturday' })).toBe(false))
it('fails with random string', async () => expect(await rules.date('Pepsi 17')).toBe(false))
it('fails with random string', async () => expect(await rules.date({ value: 'Pepsi 17' })).toBe(false))
it('fails with random number', async () => expect(await rules.date('1872301237')).toBe(false))
it('fails with random number', async () => expect(await rules.date({ value: '1872301237' })).toBe(false))
})
@ -152,17 +152,17 @@ describe('after', () => {
tomorrow.setDate(today.getDate() + 1)
yesterday.setDate(today.getDate() - 1)
it('passes with tomorrows date object', async () => expect(await rules.after(tomorrow)).toBe(true))
it('passes with tomorrows date object', async () => expect(await rules.after({ value: tomorrow })).toBe(true))
it('passes with future date', async () => expect(await rules.after('January 15, 2999')).toBe(true))
it('passes with future date', async () => expect(await rules.after({ value: 'January 15, 2999' })).toBe(true))
it('passes with long past date', async () => expect(await rules.after(yesterday, 'Jan 15, 2000')).toBe(true))
it('passes with long past date', async () => expect(await rules.after({ value: yesterday }, 'Jan 15, 2000')).toBe(true))
it('fails with yesterdays date', async () => expect(await rules.after(yesterday)).toBe(false))
it('fails with yesterdays date', async () => expect(await rules.after({ value: yesterday })).toBe(false))
it('fails with old date string', async () => expect(await rules.after('January, 2000')).toBe(false))
it('fails with old date string', async () => expect(await rules.after({ value: 'January, 2000' })).toBe(false))
it('fails with invalid value', async () => expect(await rules.after('')).toBe(false))
it('fails with invalid value', async () => expect(await rules.after({ value: '' })).toBe(false))
})
/**
@ -175,17 +175,17 @@ describe('before', () => {
tomorrow.setDate(today.getDate() + 1)
yesterday.setDate(today.getDate() - 1)
it('fails with tomorrows date object', async () => expect(await rules.before(tomorrow)).toBe(false))
it('fails with tomorrows date object', async () => expect(await rules.before({ value: tomorrow })).toBe(false))
it('fails with future date', async () => expect(await rules.before('January 15, 2999')).toBe(false))
it('fails with future date', async () => expect(await rules.before({ value: 'January 15, 2999' })).toBe(false))
it('fails with long past date', async () => expect(await rules.before(yesterday, 'Jan 15, 2000')).toBe(false))
it('fails with long past date', async () => expect(await rules.before({ value: yesterday }, 'Jan 15, 2000')).toBe(false))
it('passes with yesterdays date', async () => expect(await rules.before(yesterday)).toBe(true))
it('passes with yesterdays date', async () => expect(await rules.before({ value: yesterday })).toBe(true))
it('passes with old date string', async () => expect(await rules.before('January, 2000')).toBe(true))
it('passes with old date string', async () => expect(await rules.before({ value: 'January, 2000' })).toBe(true))
it('fails with invalid value', async () => expect(await rules.after('')).toBe(false))
it('fails with invalid value', async () => expect(await rules.after({ value: '' })).toBe(false))
})
@ -193,82 +193,82 @@ describe('before', () => {
* Checks if a date is after another date
*/
describe('alpha', () => {
it('passes with simple string', async () => expect(await rules.alpha('abc')).toBe(true))
it('passes with simple string', async () => expect(await rules.alpha({ value: 'abc' })).toBe(true))
it('passes with long string', async () => expect(await rules.alpha('lkashdflaosuihdfaisudgflakjsdbflasidufg')).toBe(true))
it('passes with long string', async () => expect(await rules.alpha({ value: 'lkashdflaosuihdfaisudgflakjsdbflasidufg' })).toBe(true))
it('passes with single character', async () => expect(await rules.alpha('z')).toBe(true))
it('passes with single character', async () => expect(await rules.alpha({ value: 'z' })).toBe(true))
it('passes with accented character', async () => expect(await rules.alpha('jüstin')).toBe(true))
it('passes with accented character', async () => expect(await rules.alpha({ value: 'jüstin' })).toBe(true))
it('passes with lots of accented characters', async () => expect(await rules.alpha('àáâäïíôöÆ')).toBe(true))
it('passes with lots of accented characters', async () => expect(await rules.alpha({ value: 'àáâäïíôöÆ' })).toBe(true))
it('passes with lots of accented characters if invalid set', async () => expect(await rules.alpha('àáâäïíôöÆ', 'russian')).toBe(true))
it('passes with lots of accented characters if invalid set', async () => expect(await rules.alpha({ value: 'àáâäïíôöÆ' }, 'russian')).toBe(true))
it('fails with lots of accented characters if latin', async () => expect(await rules.alpha('àáâäïíôöÆ', 'latin')).toBe(false))
it('fails with lots of accented characters if latin', async () => expect(await rules.alpha({ value: 'àáâäïíôöÆ' }, 'latin')).toBe(false))
it('fails with numbers', async () => expect(await rules.alpha('justin83')).toBe(false))
it('fails with numbers', async () => expect(await rules.alpha({ value: 'justin83' })).toBe(false))
it('fails with symbols', async () => expect(await rules.alpha('-justin')).toBe(false))
it('fails with symbols', async () => expect(await rules.alpha({ value: '-justin' })).toBe(false))
})
/**
* Checks if a date is after another date
*/
describe('number', () => {
it('passes with simple number string', async () => expect(await rules.number('123')).toBe(true))
it('passes with simple number string', async () => expect(await rules.number({ value: '123' })).toBe(true))
it('passes with simple number', async () => expect(await rules.number(19832461234)).toBe(true))
it('passes with simple number', async () => expect(await rules.number({ value: 19832461234 })).toBe(true))
it('passes with float', async () => expect(await rules.number(198.32464)).toBe(true))
it('passes with float', async () => expect(await rules.number({ value: 198.32464 })).toBe(true))
it('passes with decimal in string', async () => expect(await rules.number('567.23')).toBe(true))
it('passes with decimal in string', async () => expect(await rules.number({ value: '567.23' })).toBe(true))
it('fails with comma in number string', async () => expect(await rules.number('123,456')).toBe(false))
it('fails with comma in number string', async () => expect(await rules.number({ value: '123,456' })).toBe(false))
it('fails with alpha', async () => expect(await rules.number('123sdf')).toBe(false))
it('fails with alpha', async () => expect(await rules.number({ value: '123sdf' })).toBe(false))
})
/**
* Checks if a date alpha and numeric
*/
describe('alphanumeric', () => {
it('passes with simple string', async () => expect(await rules.alphanumeric('567abc')).toBe(true))
it('passes with simple string', async () => expect(await rules.alphanumeric({ value: '567abc' })).toBe(true))
it('passes with long string', async () => expect(await rules.alphanumeric('lkashdfla234osuihdfaisudgflakjsdbfla567sidufg')).toBe(true))
it('passes with long string', async () => expect(await rules.alphanumeric({ value: 'lkashdfla234osuihdfaisudgflakjsdbfla567sidufg' })).toBe(true))
it('passes with single character', async () => expect(await rules.alphanumeric('z')).toBe(true))
it('passes with single character', async () => expect(await rules.alphanumeric({ value: 'z' })).toBe(true))
it('passes with accented character', async () => expect(await rules.alphanumeric('jüst56in')).toBe(true))
it('passes with accented character', async () => expect(await rules.alphanumeric({ value: 'jüst56in' })).toBe(true))
it('passes with lots of accented characters', async () => expect(await rules.alphanumeric('àáâ7567567äïíôöÆ')).toBe(true))
it('passes with lots of accented characters', async () => expect(await rules.alphanumeric({ value: 'àáâ7567567äïíôöÆ' })).toBe(true))
it('passes with lots of accented characters if invalid set', async () => expect(await rules.alphanumeric('123123àáâäï67íôöÆ', 'russian')).toBe(true))
it('passes with lots of accented characters if invalid set', async () => expect(await rules.alphanumeric({ value: '123123àáâäï67íôöÆ' }, 'russian')).toBe(true))
it('fails with lots of accented characters if latin', async () => expect(await rules.alphanumeric('àáâäï123123íôöÆ', 'latin')).toBe(false))
it('fails with lots of accented characters if latin', async () => expect(await rules.alphanumeric({ value: 'àáâäï123123íôöÆ' }, 'latin')).toBe(false))
it('fails with decimals in', async () => expect(await rules.alphanumeric('abcABC99.123')).toBe(false))
it('fails with decimals in', async () => expect(await rules.alphanumeric({ value: 'abcABC99.123' })).toBe(false))
})
/**
* Checks if between
*/
describe('between', () => {
it('passes with simple number', async () => expect(await rules.between(5, 0, 10)).toBe(true))
it('passes with simple number', async () => expect(await rules.between({ value: 5 }, 0, 10)).toBe(true))
it('passes with simple number string', async () => expect(await rules.between('5', '0', '10')).toBe(true))
it('passes with simple number string', async () => expect(await rules.between({ value: '5' }, '0', '10')).toBe(true))
it('passes with decimal number string', async () => expect(await rules.between('0.5', '0', '1')).toBe(true))
it('passes with decimal number string', async () => expect(await rules.between({ value: '0.5' }, '0', '1')).toBe(true))
it('passes with string length', async () => expect(await rules.between('abc', 2, 4)).toBe(true))
it('passes with string length', async () => expect(await rules.between({ value: 'abc' }, 2, 4)).toBe(true))
it('fails with string length too long', async () => expect(await rules.between('abcdef', 2, 4)).toBe(false))
it('fails with string length too long', async () => expect(await rules.between({ value: 'abcdef' }, 2, 4)).toBe(false))
it('fails with string length too short', async () => expect(await rules.between('abc', 3, 10)).toBe(false))
it('fails with string length too short', async () => expect(await rules.between({ value: 'abc' }, 3, 10)).toBe(false))
it('fails with number to small', async () => expect(await rules.between(0, 3, 10)).toBe(false))
it('fails with number to small', async () => expect(await rules.between({ value: 0 }, 3, 10)).toBe(false))
it('fails with number to large', async () => expect(await rules.between(15, 3, 10)).toBe(false))
it('fails with number to large', async () => expect(await rules.between({ value: 15 }, 3, 10)).toBe(false))
})
/**
@ -277,19 +277,19 @@ describe('between', () => {
* Note: testing is light, regular expression used is here: http://jsfiddle.net/ghvj4gy9/embedded/result,js/
*/
describe('email', () => {
it('passes normal email', async () => expect(await rules.email('dev+123@wearebraid.com')).toBe(true))
it('passes normal email', async () => expect(await rules.email({ value: 'dev+123@wearebraid.com' })).toBe(true))
it('passes numeric email', async () => expect(await rules.email('12345@google.com')).toBe(true))
it('passes numeric email', async () => expect(await rules.email({ value: '12345@google.com' })).toBe(true))
it('passes unicode email', async () => expect(await rules.email('àlphä@❤️.ly')).toBe(true))
it('passes unicode email', async () => expect(await rules.email({ value: 'àlphä@❤️.ly' })).toBe(true))
it('passes numeric with new tld', async () => expect(await rules.email('12345@google.photography')).toBe(true))
it('passes numeric with new tld', async () => expect(await rules.email({ value: '12345@google.photography' })).toBe(true))
it('fails string without tld', async () => expect(await rules.email('12345@localhost')).toBe(false))
it('fails string without tld', async () => expect(await rules.email({ value: '12345@localhost' })).toBe(false))
it('fails string without tld', async () => expect(await rules.email('12345@localhost')).toBe(false))
it('fails string without tld', async () => expect(await rules.email({ value: '12345@localhost' })).toBe(false))
it('fails string without invalid name', async () => expect(await rules.email('1*(123)2345@localhost')).toBe(false))
it('fails string without invalid name', async () => expect(await rules.email({ value: '1*(123)2345@localhost' })).toBe(false))
})
/**
@ -300,21 +300,21 @@ describe('mime', () => {
const fileUpload = new FileUpload({
files: [ { type: 'image/jpeg' } ]
})
expect(await rules.mime(fileUpload, 'image/png', 'image/jpeg')).toBe(true)
expect(await rules.mime({ value: fileUpload }, 'image/png', 'image/jpeg')).toBe(true)
})
it('passes when match is at begining of stack', async () => {
const fileUpload = new FileUpload({
files: [ { type: 'document/pdf' } ]
})
expect(await rules.mime(fileUpload, 'document/pdf')).toBe(true)
expect(await rules.mime({ value: fileUpload }, 'document/pdf')).toBe(true)
})
it('fails when not in stack', async () => {
const fileUpload = new FileUpload({
files: [ { type: 'application/json' } ]
})
expect(await rules.mime(fileUpload, 'image/png', 'image/jpeg')).toBe(false)
expect(await rules.mime({ value: fileUpload }, 'image/png', 'image/jpeg')).toBe(false)
})
})
@ -322,27 +322,27 @@ describe('mime', () => {
* Minimum.
*/
describe('min', () => {
it('passes when a number string', async () => expect(await rules.min('5', '5')).toBe(true))
it('passes when a number string', async () => expect(await rules.min({ value: '5' }, '5')).toBe(true))
it('passes when a number', async () => expect(await rules.min(6, 5)).toBe(true))
it('passes when a number', async () => expect(await rules.min({ value: 6 }, 5)).toBe(true))
it('passes when a string length', async () => expect(await rules.min('foobar', '6')).toBe(true))
it('passes when a string length', async () => expect(await rules.min({ value: 'foobar' }, '6')).toBe(true))
it('passes when a array length', async () => expect(await rules.min(Array(6), '6')).toBe(true))
it('passes when a array length', async () => expect(await rules.min({ value: Array(6) }, '6')).toBe(true))
it('passes when string is forced to value', async () => expect(await rules.min('bcd', 'aaa', 'value')).toBe(true))
it('passes when string is forced to value', async () => expect(await rules.min({ value: 'bcd' }, 'aaa', 'value')).toBe(true))
it('fails when string is forced to lesser value', async () => expect(await rules.min('a', 'b', 'value')).toBe(false))
it('fails when string is forced to lesser value', async () => expect(await rules.min({ value: 'a' }, 'b', 'value')).toBe(false))
it('passes when a number is forced to length', async () => expect(await rules.min('000', 3, 'length')).toBe(true))
it('passes when a number is forced to length', async () => expect(await rules.min({ value: '000' }, 3, 'length')).toBe(true))
it('fails when a number is forced to length', async () => expect(await rules.min('44', 3, 'length')).toBe(false))
it('fails when a number is forced to length', async () => expect(await rules.min({ value: '44' }, 3, 'length')).toBe(false))
it('fails when a array length', async () => expect(await rules.min(Array(6), '7')).toBe(false))
it('fails when a array length', async () => expect(await rules.min({ value: Array(6) }, '7')).toBe(false))
it('fails when a string length', async () => expect(await rules.min('bar', 4)).toBe(false))
it('fails when a string length', async () => expect(await rules.min({ value: 'bar' }, 4)).toBe(false))
it('fails when a number', async () => expect(await rules.min(3, '7')).toBe(false))
it('fails when a number', async () => expect(await rules.min({ value: 3 }, '7')).toBe(false))
})
@ -350,42 +350,72 @@ describe('min', () => {
* Maximum.
*/
describe('max', () => {
it('passes when a number string', async () => expect(await rules.max('5', '5')).toBe(true))
it('passes when a number string', async () => expect(await rules.max({ value: '5' }, '5')).toBe(true))
it('passes when a number', async () => expect(await rules.max(5, 6)).toBe(true))
it('passes when a number', async () => expect(await rules.max({ value: 5 }, 6)).toBe(true))
it('passes when a string length', async () => expect(await rules.max('foobar', '6')).toBe(true))
it('passes when a string length', async () => expect(await rules.max({ value: 'foobar' }, '6')).toBe(true))
it('passes when a array length', async () => expect(await rules.max(Array(6), '6')).toBe(true))
it('passes when a array length', async () => expect(await rules.max({ value: Array(6) }, '6')).toBe(true))
it('passes when forced to validate on length', async () => expect(await rules.max(10, 3, 'length')).toBe(true))
it('passes when forced to validate on length', async () => expect(await rules.max({ value: 10 }, 3, 'length')).toBe(true))
it('passes when forced to validate string on value', async () => expect(await rules.max('b', 'e', 'value')).toBe(true))
it('passes when forced to validate string on value', async () => expect(await rules.max({ value: 'b' }, 'e', 'value')).toBe(true))
it('fails when a array length', async () => expect(await rules.max(Array(6), '5')).toBe(false))
it('fails when a array length', async () => expect(await rules.max({ value: Array(6) }, '5')).toBe(false))
it('fails when a string length', async () => expect(await rules.max('bar', 2)).toBe(false))
it('fails when a string length', async () => expect(await rules.max({ value: 'bar' }, 2)).toBe(false))
it('fails when a number', async () => expect(await rules.max(10, '7')).toBe(false))
it('fails when a number', async () => expect(await rules.max({ value: 10 }, '7')).toBe(false))
it('fails when a number', async () => expect(await rules.max(10, '7')).toBe(false))
it('fails when a number', async () => expect(await rules.max({ value: 10 }, '7')).toBe(false))
it('fails when forced to validate on length', async () => expect(await rules.max(-10, '1', 'length')).toBe(false))
it('fails when forced to validate on length', async () => expect(await rules.max({ value: -10 }, '1', 'length')).toBe(false))
})
/**
* Maximum.
*/
describe('not', () => {
it('passes when a number string', async () => expect(await rules.not('5', '6')).toBe(true))
it('passes when a number string', async () => expect(await rules.not({ value: '5' }, '6')).toBe(true))
it('passes when a number', async () => expect(await rules.not(1, 30)).toBe(true))
it('passes when a number', async () => expect(await rules.not({ value: 1 }, 30)).toBe(true))
it('passes when a string', async () => expect(await rules.not('abc', 'def')).toBe(true))
it('passes when a string', async () => expect(await rules.not({ value: 'abc' }, 'def')).toBe(true))
it('fails when a shallow equal array', async () => expect(await rules.not(['abc'], ['abc'])).toBe(false))
it('fails when a shallow equal array', async () => expect(await rules.not({ value: ['abc'] }, ['abc'])).toBe(false))
it('fails when a shallow equal object', async () => expect(await rules.not({a: 'abc'}, ['123'], {a: 'abc'})).toBe(false))
it('fails when a shallow equal object', async () => expect(await rules.not({ value: {a: 'abc'} }, ['123'], {a: 'abc'})).toBe(false))
it('fails when string is in stack', async () => expect(await rules.not('a', 'b', 'c', 'd', 'a', 'f')).toBe(false))
it('fails when string is in stack', async () => expect(await rules.not({ value: 'a' }, 'b', 'c', 'd', 'a', 'f')).toBe(false))
})
/**
* Confirm
*/
describe('confirm', () => {
it('passes when the values are the same strings', async () => expect(await rules.confirm(
{ value: 'abc', name: 'password', getFormValues: () => ({ password_confirm: 'abc' }) }
)).toBe(true))
it('passes when the values are the same integers', async () => expect(await rules.confirm(
{ value: 4422132, name: 'xyz', getFormValues: () => ({ xyz_confirm: 4422132 }) }
)).toBe(true))
it('passes when using a custom field', async () => expect(await rules.confirm(
{ value: 4422132, name: 'name', getFormValues: () => ({ other_field: 4422132 }) },
'other_field'
)).toBe(true))
it('passes when using a field ends in _confirm', async () => expect(await rules.confirm(
{ value: '$ecret', name: 'password_confirm', getFormValues: () => ({ password: '$ecret' }) }
)).toBe(true))
it('fails when using different strings', async () => expect(await rules.confirm(
{ value: 'Justin', name: 'name', getFormValues: () => ({ name_confirm: 'Daniel' }) },
)).toBe(false))
it('fails when the types are different', async () => expect(await rules.confirm(
{ value: '1234', name: 'num', getFormValues: () => ({ num_confirm: 1234 }) },
)).toBe(false))
})