1
0
mirror of synced 2024-11-25 14:56:03 +03:00

Initial tests written for validation parsing and rules

This commit is contained in:
Justin Schroeder 2019-11-06 17:17:19 -05:00
parent 9522459600
commit a53edaa342
18 changed files with 1115 additions and 377 deletions

View File

@ -1,3 +1,6 @@
{
"jest.showCoverageOnLoad": true
"jest.showCoverageOnLoad": true,
"cSpell.words": [
"arrayify"
]
}

280
dist/formulate.esm.js vendored
View File

@ -140,6 +140,26 @@ function shallowEqualObjects (objA, objB) {
return true
}
/**
* Given a string, object, falsey, or array - return an array.
* @param {mixed} item
*/
function arrayify (item) {
if (!item) {
return []
}
if (typeof item === 'string') {
return [item]
}
if (Array.isArray(item)) {
return item
}
if (typeof item === 'object') {
return Object.values(item)
}
return []
}
/**
* For a single instance of an input, export all of the context needed to fully
* render that element.
@ -165,7 +185,8 @@ var context = {
typeContext: typeContext,
elementAttributes: elementAttributes,
logicalLabelPosition: logicalLabelPosition,
isVmodeled: isVmodeled
isVmodeled: isVmodeled,
mergedErrors: mergedErrors
};
/**
@ -268,6 +289,16 @@ function createOptionList (options) {
return options
}
/**
* The merged errors computed property.
*/
function mergedErrors () {
return arrayify(this.errors)
.concat(arrayify(this.error))
.concat(arrayify(this.validationErrors))
.reduce(function (errors, err) { return !errors.includes(err) ? errors.concat(err) : errors; }, [])
}
/**
* Defines the model used throughout the existing context.
* @param {object} context
@ -361,6 +392,14 @@ var script = {
debug: {
type: Boolean,
default: false
},
errors: {
type: [String, Array, Boolean],
default: false
},
error: {
type: [String, Boolean],
default: false
}
},
data: function data () {
@ -560,8 +599,11 @@ var __vue_render__ = function() {
staticClass: "formulate-input-help",
domProps: { textContent: _vm._s(_vm.help) }
})
: _vm._e()
]
: _vm._e(),
_vm._v(" "),
_c("FormulateInputErrors", { attrs: { errors: _vm.mergedErrors } })
],
1
)
};
var __vue_staticRenderFns__ = [];
@ -742,6 +784,87 @@ __vue_render__$1._withStripped = true;
undefined
);
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
var script$2 = {
props: {
errors: {
type: [Boolean, Array],
required: true
}
}
};
/* script */
var __vue_script__$2 = script$2;
/* template */
var __vue_render__$2 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _vm.errors.length
? _c(
"ul",
{ staticClass: "formulate-input-errors" },
_vm._l(_vm.errors, function(error) {
return _c("li", {
key: error,
staticClass: "formulate-input-error",
domProps: { innerHTML: _vm._s(error) }
})
}),
0
)
: _vm._e()
};
var __vue_staticRenderFns__$2 = [];
__vue_render__$2._withStripped = true;
/* style */
var __vue_inject_styles__$2 = undefined;
/* scoped */
var __vue_scope_id__$2 = undefined;
/* module identifier */
var __vue_module_identifier__$2 = undefined;
/* functional template */
var __vue_is_functional_template__$2 = false;
/* style inject */
/* style inject SSR */
/* style inject shadow dom */
var FormulateInputErrors = normalizeComponent(
{ render: __vue_render__$2, staticRenderFns: __vue_staticRenderFns__$2 },
__vue_inject_styles__$2,
__vue_script__$2,
__vue_scope_id__$2,
__vue_is_functional_template__$2,
__vue_module_identifier__$2,
false,
undefined,
undefined,
undefined
);
//
//
//
@ -757,7 +880,7 @@ __vue_render__$1._withStripped = true;
//
function objectWithoutProperties (obj, exclude) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; }
var script$2 = {
var script$3 = {
name: 'FormulateInputGroup',
props: {
context: {
@ -795,10 +918,10 @@ var script$2 = {
};
/* script */
var __vue_script__$2 = script$2;
var __vue_script__$3 = script$3;
/* template */
var __vue_render__$2 = function() {
var __vue_render__$3 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
@ -830,17 +953,17 @@ var __vue_render__$2 = function() {
1
)
};
var __vue_staticRenderFns__$2 = [];
__vue_render__$2._withStripped = true;
var __vue_staticRenderFns__$3 = [];
__vue_render__$3._withStripped = true;
/* style */
var __vue_inject_styles__$2 = undefined;
var __vue_inject_styles__$3 = undefined;
/* scoped */
var __vue_scope_id__$2 = undefined;
var __vue_scope_id__$3 = undefined;
/* module identifier */
var __vue_module_identifier__$2 = undefined;
var __vue_module_identifier__$3 = undefined;
/* functional template */
var __vue_is_functional_template__$2 = false;
var __vue_is_functional_template__$3 = false;
/* style inject */
/* style inject SSR */
@ -850,12 +973,12 @@ __vue_render__$2._withStripped = true;
var FormulateInputGroup = normalizeComponent(
{ render: __vue_render__$2, staticRenderFns: __vue_staticRenderFns__$2 },
__vue_inject_styles__$2,
__vue_script__$2,
__vue_scope_id__$2,
__vue_is_functional_template__$2,
__vue_module_identifier__$2,
{ render: __vue_render__$3, staticRenderFns: __vue_staticRenderFns__$3 },
__vue_inject_styles__$3,
__vue_script__$3,
__vue_scope_id__$3,
__vue_is_functional_template__$3,
__vue_module_identifier__$3,
false,
undefined,
undefined,
@ -890,16 +1013,16 @@ var FormulateInputMixin = {
//
var script$3 = {
var script$4 = {
name: 'FormulateInputBox',
mixins: [FormulateInputMixin]
};
/* script */
var __vue_script__$3 = script$3;
var __vue_script__$4 = script$4;
/* template */
var __vue_render__$3 = function() {
var __vue_render__$4 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
@ -1028,17 +1151,17 @@ var __vue_render__$3 = function() {
]
)
};
var __vue_staticRenderFns__$3 = [];
__vue_render__$3._withStripped = true;
var __vue_staticRenderFns__$4 = [];
__vue_render__$4._withStripped = true;
/* style */
var __vue_inject_styles__$3 = undefined;
var __vue_inject_styles__$4 = undefined;
/* scoped */
var __vue_scope_id__$3 = undefined;
var __vue_scope_id__$4 = undefined;
/* module identifier */
var __vue_module_identifier__$3 = undefined;
var __vue_module_identifier__$4 = undefined;
/* functional template */
var __vue_is_functional_template__$3 = false;
var __vue_is_functional_template__$4 = false;
/* style inject */
/* style inject SSR */
@ -1048,12 +1171,12 @@ __vue_render__$3._withStripped = true;
var FormulateInputBox = normalizeComponent(
{ render: __vue_render__$3, staticRenderFns: __vue_staticRenderFns__$3 },
__vue_inject_styles__$3,
__vue_script__$3,
__vue_scope_id__$3,
__vue_is_functional_template__$3,
__vue_module_identifier__$3,
{ render: __vue_render__$4, staticRenderFns: __vue_staticRenderFns__$4 },
__vue_inject_styles__$4,
__vue_script__$4,
__vue_scope_id__$4,
__vue_is_functional_template__$4,
__vue_module_identifier__$4,
false,
undefined,
undefined,
@ -1062,16 +1185,16 @@ __vue_render__$3._withStripped = true;
//
var script$4 = {
var script$5 = {
name: 'FormulateInputText',
mixins: [FormulateInputMixin]
};
/* script */
var __vue_script__$4 = script$4;
var __vue_script__$5 = script$5;
/* template */
var __vue_render__$4 = function() {
var __vue_render__$5 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
@ -1187,17 +1310,17 @@ var __vue_render__$4 = function() {
]
)
};
var __vue_staticRenderFns__$4 = [];
__vue_render__$4._withStripped = true;
var __vue_staticRenderFns__$5 = [];
__vue_render__$5._withStripped = true;
/* style */
var __vue_inject_styles__$4 = undefined;
var __vue_inject_styles__$5 = undefined;
/* scoped */
var __vue_scope_id__$4 = undefined;
var __vue_scope_id__$5 = undefined;
/* module identifier */
var __vue_module_identifier__$4 = undefined;
var __vue_module_identifier__$5 = undefined;
/* functional template */
var __vue_is_functional_template__$4 = false;
var __vue_is_functional_template__$5 = false;
/* style inject */
/* style inject SSR */
@ -1207,12 +1330,12 @@ __vue_render__$4._withStripped = true;
var FormulateInputText = normalizeComponent(
{ render: __vue_render__$4, staticRenderFns: __vue_staticRenderFns__$4 },
__vue_inject_styles__$4,
__vue_script__$4,
__vue_scope_id__$4,
__vue_is_functional_template__$4,
__vue_module_identifier__$4,
{ render: __vue_render__$5, staticRenderFns: __vue_staticRenderFns__$5 },
__vue_inject_styles__$5,
__vue_script__$5,
__vue_scope_id__$5,
__vue_is_functional_template__$5,
__vue_module_identifier__$5,
false,
undefined,
undefined,
@ -1221,7 +1344,7 @@ __vue_render__$4._withStripped = true;
//
var script$5 = {
var script$6 = {
name: 'FormulateInputSelect',
mixins: [FormulateInputMixin],
computed: {
@ -1238,10 +1361,10 @@ var script$5 = {
};
/* script */
var __vue_script__$5 = script$5;
var __vue_script__$6 = script$6;
/* template */
var __vue_render__$5 = function() {
var __vue_render__$6 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
@ -1348,17 +1471,17 @@ var __vue_render__$5 = function() {
]
)
};
var __vue_staticRenderFns__$5 = [];
__vue_render__$5._withStripped = true;
var __vue_staticRenderFns__$6 = [];
__vue_render__$6._withStripped = true;
/* style */
var __vue_inject_styles__$5 = undefined;
var __vue_inject_styles__$6 = undefined;
/* scoped */
var __vue_scope_id__$5 = undefined;
var __vue_scope_id__$6 = undefined;
/* module identifier */
var __vue_module_identifier__$5 = undefined;
var __vue_module_identifier__$6 = undefined;
/* functional template */
var __vue_is_functional_template__$5 = false;
var __vue_is_functional_template__$6 = false;
/* style inject */
/* style inject SSR */
@ -1368,12 +1491,12 @@ __vue_render__$5._withStripped = true;
var FormulateInputSelect = normalizeComponent(
{ render: __vue_render__$5, staticRenderFns: __vue_staticRenderFns__$5 },
__vue_inject_styles__$5,
__vue_script__$5,
__vue_scope_id__$5,
__vue_is_functional_template__$5,
__vue_module_identifier__$5,
{ render: __vue_render__$6, staticRenderFns: __vue_staticRenderFns__$6 },
__vue_inject_styles__$6,
__vue_script__$6,
__vue_scope_id__$6,
__vue_is_functional_template__$6,
__vue_module_identifier__$6,
false,
undefined,
undefined,
@ -1382,16 +1505,16 @@ __vue_render__$5._withStripped = true;
//
var script$6 = {
var script$7 = {
name: 'FormulateInputTextArea',
mixins: [FormulateInputMixin]
};
/* script */
var __vue_script__$6 = script$6;
var __vue_script__$7 = script$7;
/* template */
var __vue_render__$6 = function() {
var __vue_render__$7 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
@ -1431,17 +1554,17 @@ var __vue_render__$6 = function() {
]
)
};
var __vue_staticRenderFns__$6 = [];
__vue_render__$6._withStripped = true;
var __vue_staticRenderFns__$7 = [];
__vue_render__$7._withStripped = true;
/* style */
var __vue_inject_styles__$6 = undefined;
var __vue_inject_styles__$7 = undefined;
/* scoped */
var __vue_scope_id__$6 = undefined;
var __vue_scope_id__$7 = undefined;
/* module identifier */
var __vue_module_identifier__$6 = undefined;
var __vue_module_identifier__$7 = undefined;
/* functional template */
var __vue_is_functional_template__$6 = false;
var __vue_is_functional_template__$7 = false;
/* style inject */
/* style inject SSR */
@ -1451,12 +1574,12 @@ __vue_render__$6._withStripped = true;
var FormulateInputTextArea = normalizeComponent(
{ render: __vue_render__$6, staticRenderFns: __vue_staticRenderFns__$6 },
__vue_inject_styles__$6,
__vue_script__$6,
__vue_scope_id__$6,
__vue_is_functional_template__$6,
__vue_module_identifier__$6,
{ render: __vue_render__$7, staticRenderFns: __vue_staticRenderFns__$7 },
__vue_inject_styles__$7,
__vue_script__$7,
__vue_scope_id__$7,
__vue_is_functional_template__$7,
__vue_module_identifier__$7,
false,
undefined,
undefined,
@ -1471,6 +1594,7 @@ var Formulate = function Formulate () {
components: {
FormulateForm: FormulateForm,
FormulateInput: FormulateInput,
FormulateInputErrors: FormulateInputErrors,
FormulateInputBox: FormulateInputBox,
FormulateInputText: FormulateInputText,
FormulateInputGroup: FormulateInputGroup,

280
dist/formulate.min.js vendored
View File

@ -143,6 +143,26 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
return true
}
/**
* Given a string, object, falsey, or array - return an array.
* @param {mixed} item
*/
function arrayify (item) {
if (!item) {
return []
}
if (typeof item === 'string') {
return [item]
}
if (Array.isArray(item)) {
return item
}
if (typeof item === 'object') {
return Object.values(item)
}
return []
}
/**
* For a single instance of an input, export all of the context needed to fully
* render that element.
@ -168,7 +188,8 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
typeContext: typeContext,
elementAttributes: elementAttributes,
logicalLabelPosition: logicalLabelPosition,
isVmodeled: isVmodeled
isVmodeled: isVmodeled,
mergedErrors: mergedErrors
};
/**
@ -271,6 +292,16 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
return options
}
/**
* The merged errors computed property.
*/
function mergedErrors () {
return arrayify(this.errors)
.concat(arrayify(this.error))
.concat(arrayify(this.validationErrors))
.reduce(function (errors, err) { return !errors.includes(err) ? errors.concat(err) : errors; }, [])
}
/**
* Defines the model used throughout the existing context.
* @param {object} context
@ -364,6 +395,14 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
debug: {
type: Boolean,
default: false
},
errors: {
type: [String, Array, Boolean],
default: false
},
error: {
type: [String, Boolean],
default: false
}
},
data: function data () {
@ -563,8 +602,11 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
staticClass: "formulate-input-help",
domProps: { textContent: _vm._s(_vm.help) }
})
: _vm._e()
]
: _vm._e(),
_vm._v(" "),
_c("FormulateInputErrors", { attrs: { errors: _vm.mergedErrors } })
],
1
)
};
var __vue_staticRenderFns__ = [];
@ -745,6 +787,87 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
undefined
);
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
var script$2 = {
props: {
errors: {
type: [Boolean, Array],
required: true
}
}
};
/* script */
var __vue_script__$2 = script$2;
/* template */
var __vue_render__$2 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _vm.errors.length
? _c(
"ul",
{ staticClass: "formulate-input-errors" },
_vm._l(_vm.errors, function(error) {
return _c("li", {
key: error,
staticClass: "formulate-input-error",
domProps: { innerHTML: _vm._s(error) }
})
}),
0
)
: _vm._e()
};
var __vue_staticRenderFns__$2 = [];
__vue_render__$2._withStripped = true;
/* style */
var __vue_inject_styles__$2 = undefined;
/* scoped */
var __vue_scope_id__$2 = undefined;
/* module identifier */
var __vue_module_identifier__$2 = undefined;
/* functional template */
var __vue_is_functional_template__$2 = false;
/* style inject */
/* style inject SSR */
/* style inject shadow dom */
var FormulateInputErrors = normalizeComponent(
{ render: __vue_render__$2, staticRenderFns: __vue_staticRenderFns__$2 },
__vue_inject_styles__$2,
__vue_script__$2,
__vue_scope_id__$2,
__vue_is_functional_template__$2,
__vue_module_identifier__$2,
false,
undefined,
undefined,
undefined
);
//
//
//
@ -760,7 +883,7 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
//
function objectWithoutProperties (obj, exclude) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; }
var script$2 = {
var script$3 = {
name: 'FormulateInputGroup',
props: {
context: {
@ -798,10 +921,10 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
};
/* script */
var __vue_script__$2 = script$2;
var __vue_script__$3 = script$3;
/* template */
var __vue_render__$2 = function() {
var __vue_render__$3 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
@ -833,17 +956,17 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
1
)
};
var __vue_staticRenderFns__$2 = [];
__vue_render__$2._withStripped = true;
var __vue_staticRenderFns__$3 = [];
__vue_render__$3._withStripped = true;
/* style */
var __vue_inject_styles__$2 = undefined;
var __vue_inject_styles__$3 = undefined;
/* scoped */
var __vue_scope_id__$2 = undefined;
var __vue_scope_id__$3 = undefined;
/* module identifier */
var __vue_module_identifier__$2 = undefined;
var __vue_module_identifier__$3 = undefined;
/* functional template */
var __vue_is_functional_template__$2 = false;
var __vue_is_functional_template__$3 = false;
/* style inject */
/* style inject SSR */
@ -853,12 +976,12 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
var FormulateInputGroup = normalizeComponent(
{ render: __vue_render__$2, staticRenderFns: __vue_staticRenderFns__$2 },
__vue_inject_styles__$2,
__vue_script__$2,
__vue_scope_id__$2,
__vue_is_functional_template__$2,
__vue_module_identifier__$2,
{ render: __vue_render__$3, staticRenderFns: __vue_staticRenderFns__$3 },
__vue_inject_styles__$3,
__vue_script__$3,
__vue_scope_id__$3,
__vue_is_functional_template__$3,
__vue_module_identifier__$3,
false,
undefined,
undefined,
@ -893,16 +1016,16 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
//
var script$3 = {
var script$4 = {
name: 'FormulateInputBox',
mixins: [FormulateInputMixin]
};
/* script */
var __vue_script__$3 = script$3;
var __vue_script__$4 = script$4;
/* template */
var __vue_render__$3 = function() {
var __vue_render__$4 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
@ -1031,17 +1154,17 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
]
)
};
var __vue_staticRenderFns__$3 = [];
__vue_render__$3._withStripped = true;
var __vue_staticRenderFns__$4 = [];
__vue_render__$4._withStripped = true;
/* style */
var __vue_inject_styles__$3 = undefined;
var __vue_inject_styles__$4 = undefined;
/* scoped */
var __vue_scope_id__$3 = undefined;
var __vue_scope_id__$4 = undefined;
/* module identifier */
var __vue_module_identifier__$3 = undefined;
var __vue_module_identifier__$4 = undefined;
/* functional template */
var __vue_is_functional_template__$3 = false;
var __vue_is_functional_template__$4 = false;
/* style inject */
/* style inject SSR */
@ -1051,12 +1174,12 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
var FormulateInputBox = normalizeComponent(
{ render: __vue_render__$3, staticRenderFns: __vue_staticRenderFns__$3 },
__vue_inject_styles__$3,
__vue_script__$3,
__vue_scope_id__$3,
__vue_is_functional_template__$3,
__vue_module_identifier__$3,
{ render: __vue_render__$4, staticRenderFns: __vue_staticRenderFns__$4 },
__vue_inject_styles__$4,
__vue_script__$4,
__vue_scope_id__$4,
__vue_is_functional_template__$4,
__vue_module_identifier__$4,
false,
undefined,
undefined,
@ -1065,16 +1188,16 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
//
var script$4 = {
var script$5 = {
name: 'FormulateInputText',
mixins: [FormulateInputMixin]
};
/* script */
var __vue_script__$4 = script$4;
var __vue_script__$5 = script$5;
/* template */
var __vue_render__$4 = function() {
var __vue_render__$5 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
@ -1190,17 +1313,17 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
]
)
};
var __vue_staticRenderFns__$4 = [];
__vue_render__$4._withStripped = true;
var __vue_staticRenderFns__$5 = [];
__vue_render__$5._withStripped = true;
/* style */
var __vue_inject_styles__$4 = undefined;
var __vue_inject_styles__$5 = undefined;
/* scoped */
var __vue_scope_id__$4 = undefined;
var __vue_scope_id__$5 = undefined;
/* module identifier */
var __vue_module_identifier__$4 = undefined;
var __vue_module_identifier__$5 = undefined;
/* functional template */
var __vue_is_functional_template__$4 = false;
var __vue_is_functional_template__$5 = false;
/* style inject */
/* style inject SSR */
@ -1210,12 +1333,12 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
var FormulateInputText = normalizeComponent(
{ render: __vue_render__$4, staticRenderFns: __vue_staticRenderFns__$4 },
__vue_inject_styles__$4,
__vue_script__$4,
__vue_scope_id__$4,
__vue_is_functional_template__$4,
__vue_module_identifier__$4,
{ render: __vue_render__$5, staticRenderFns: __vue_staticRenderFns__$5 },
__vue_inject_styles__$5,
__vue_script__$5,
__vue_scope_id__$5,
__vue_is_functional_template__$5,
__vue_module_identifier__$5,
false,
undefined,
undefined,
@ -1224,7 +1347,7 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
//
var script$5 = {
var script$6 = {
name: 'FormulateInputSelect',
mixins: [FormulateInputMixin],
computed: {
@ -1241,10 +1364,10 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
};
/* script */
var __vue_script__$5 = script$5;
var __vue_script__$6 = script$6;
/* template */
var __vue_render__$5 = function() {
var __vue_render__$6 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
@ -1351,17 +1474,17 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
]
)
};
var __vue_staticRenderFns__$5 = [];
__vue_render__$5._withStripped = true;
var __vue_staticRenderFns__$6 = [];
__vue_render__$6._withStripped = true;
/* style */
var __vue_inject_styles__$5 = undefined;
var __vue_inject_styles__$6 = undefined;
/* scoped */
var __vue_scope_id__$5 = undefined;
var __vue_scope_id__$6 = undefined;
/* module identifier */
var __vue_module_identifier__$5 = undefined;
var __vue_module_identifier__$6 = undefined;
/* functional template */
var __vue_is_functional_template__$5 = false;
var __vue_is_functional_template__$6 = false;
/* style inject */
/* style inject SSR */
@ -1371,12 +1494,12 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
var FormulateInputSelect = normalizeComponent(
{ render: __vue_render__$5, staticRenderFns: __vue_staticRenderFns__$5 },
__vue_inject_styles__$5,
__vue_script__$5,
__vue_scope_id__$5,
__vue_is_functional_template__$5,
__vue_module_identifier__$5,
{ render: __vue_render__$6, staticRenderFns: __vue_staticRenderFns__$6 },
__vue_inject_styles__$6,
__vue_script__$6,
__vue_scope_id__$6,
__vue_is_functional_template__$6,
__vue_module_identifier__$6,
false,
undefined,
undefined,
@ -1385,16 +1508,16 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
//
var script$6 = {
var script$7 = {
name: 'FormulateInputTextArea',
mixins: [FormulateInputMixin]
};
/* script */
var __vue_script__$6 = script$6;
var __vue_script__$7 = script$7;
/* template */
var __vue_render__$6 = function() {
var __vue_render__$7 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
@ -1434,17 +1557,17 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
]
)
};
var __vue_staticRenderFns__$6 = [];
__vue_render__$6._withStripped = true;
var __vue_staticRenderFns__$7 = [];
__vue_render__$7._withStripped = true;
/* style */
var __vue_inject_styles__$6 = undefined;
var __vue_inject_styles__$7 = undefined;
/* scoped */
var __vue_scope_id__$6 = undefined;
var __vue_scope_id__$7 = undefined;
/* module identifier */
var __vue_module_identifier__$6 = undefined;
var __vue_module_identifier__$7 = undefined;
/* functional template */
var __vue_is_functional_template__$6 = false;
var __vue_is_functional_template__$7 = false;
/* style inject */
/* style inject SSR */
@ -1454,12 +1577,12 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
var FormulateInputTextArea = normalizeComponent(
{ render: __vue_render__$6, staticRenderFns: __vue_staticRenderFns__$6 },
__vue_inject_styles__$6,
__vue_script__$6,
__vue_scope_id__$6,
__vue_is_functional_template__$6,
__vue_module_identifier__$6,
{ render: __vue_render__$7, staticRenderFns: __vue_staticRenderFns__$7 },
__vue_inject_styles__$7,
__vue_script__$7,
__vue_scope_id__$7,
__vue_is_functional_template__$7,
__vue_module_identifier__$7,
false,
undefined,
undefined,
@ -1474,6 +1597,7 @@ var Formulate = (function (exports, isPlainObject, nanoid) {
components: {
FormulateForm: FormulateForm,
FormulateInput: FormulateInput,
FormulateInputErrors: FormulateInputErrors,
FormulateInputBox: FormulateInputBox,
FormulateInputText: FormulateInputText,
FormulateInputGroup: FormulateInputGroup,

280
dist/formulate.umd.js vendored
View File

@ -146,6 +146,26 @@
return true
}
/**
* Given a string, object, falsey, or array - return an array.
* @param {mixed} item
*/
function arrayify (item) {
if (!item) {
return []
}
if (typeof item === 'string') {
return [item]
}
if (Array.isArray(item)) {
return item
}
if (typeof item === 'object') {
return Object.values(item)
}
return []
}
/**
* For a single instance of an input, export all of the context needed to fully
* render that element.
@ -171,7 +191,8 @@
typeContext: typeContext,
elementAttributes: elementAttributes,
logicalLabelPosition: logicalLabelPosition,
isVmodeled: isVmodeled
isVmodeled: isVmodeled,
mergedErrors: mergedErrors
};
/**
@ -274,6 +295,16 @@
return options
}
/**
* The merged errors computed property.
*/
function mergedErrors () {
return arrayify(this.errors)
.concat(arrayify(this.error))
.concat(arrayify(this.validationErrors))
.reduce(function (errors, err) { return !errors.includes(err) ? errors.concat(err) : errors; }, [])
}
/**
* Defines the model used throughout the existing context.
* @param {object} context
@ -367,6 +398,14 @@
debug: {
type: Boolean,
default: false
},
errors: {
type: [String, Array, Boolean],
default: false
},
error: {
type: [String, Boolean],
default: false
}
},
data: function data () {
@ -566,8 +605,11 @@
staticClass: "formulate-input-help",
domProps: { textContent: _vm._s(_vm.help) }
})
: _vm._e()
]
: _vm._e(),
_vm._v(" "),
_c("FormulateInputErrors", { attrs: { errors: _vm.mergedErrors } })
],
1
)
};
var __vue_staticRenderFns__ = [];
@ -748,6 +790,87 @@
undefined
);
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
var script$2 = {
props: {
errors: {
type: [Boolean, Array],
required: true
}
}
};
/* script */
var __vue_script__$2 = script$2;
/* template */
var __vue_render__$2 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _vm.errors.length
? _c(
"ul",
{ staticClass: "formulate-input-errors" },
_vm._l(_vm.errors, function(error) {
return _c("li", {
key: error,
staticClass: "formulate-input-error",
domProps: { innerHTML: _vm._s(error) }
})
}),
0
)
: _vm._e()
};
var __vue_staticRenderFns__$2 = [];
__vue_render__$2._withStripped = true;
/* style */
var __vue_inject_styles__$2 = undefined;
/* scoped */
var __vue_scope_id__$2 = undefined;
/* module identifier */
var __vue_module_identifier__$2 = undefined;
/* functional template */
var __vue_is_functional_template__$2 = false;
/* style inject */
/* style inject SSR */
/* style inject shadow dom */
var FormulateInputErrors = normalizeComponent(
{ render: __vue_render__$2, staticRenderFns: __vue_staticRenderFns__$2 },
__vue_inject_styles__$2,
__vue_script__$2,
__vue_scope_id__$2,
__vue_is_functional_template__$2,
__vue_module_identifier__$2,
false,
undefined,
undefined,
undefined
);
//
//
//
@ -763,7 +886,7 @@
//
function objectWithoutProperties (obj, exclude) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; }
var script$2 = {
var script$3 = {
name: 'FormulateInputGroup',
props: {
context: {
@ -801,10 +924,10 @@
};
/* script */
var __vue_script__$2 = script$2;
var __vue_script__$3 = script$3;
/* template */
var __vue_render__$2 = function() {
var __vue_render__$3 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
@ -836,17 +959,17 @@
1
)
};
var __vue_staticRenderFns__$2 = [];
__vue_render__$2._withStripped = true;
var __vue_staticRenderFns__$3 = [];
__vue_render__$3._withStripped = true;
/* style */
var __vue_inject_styles__$2 = undefined;
var __vue_inject_styles__$3 = undefined;
/* scoped */
var __vue_scope_id__$2 = undefined;
var __vue_scope_id__$3 = undefined;
/* module identifier */
var __vue_module_identifier__$2 = undefined;
var __vue_module_identifier__$3 = undefined;
/* functional template */
var __vue_is_functional_template__$2 = false;
var __vue_is_functional_template__$3 = false;
/* style inject */
/* style inject SSR */
@ -856,12 +979,12 @@
var FormulateInputGroup = normalizeComponent(
{ render: __vue_render__$2, staticRenderFns: __vue_staticRenderFns__$2 },
__vue_inject_styles__$2,
__vue_script__$2,
__vue_scope_id__$2,
__vue_is_functional_template__$2,
__vue_module_identifier__$2,
{ render: __vue_render__$3, staticRenderFns: __vue_staticRenderFns__$3 },
__vue_inject_styles__$3,
__vue_script__$3,
__vue_scope_id__$3,
__vue_is_functional_template__$3,
__vue_module_identifier__$3,
false,
undefined,
undefined,
@ -896,16 +1019,16 @@
//
var script$3 = {
var script$4 = {
name: 'FormulateInputBox',
mixins: [FormulateInputMixin]
};
/* script */
var __vue_script__$3 = script$3;
var __vue_script__$4 = script$4;
/* template */
var __vue_render__$3 = function() {
var __vue_render__$4 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
@ -1034,17 +1157,17 @@
]
)
};
var __vue_staticRenderFns__$3 = [];
__vue_render__$3._withStripped = true;
var __vue_staticRenderFns__$4 = [];
__vue_render__$4._withStripped = true;
/* style */
var __vue_inject_styles__$3 = undefined;
var __vue_inject_styles__$4 = undefined;
/* scoped */
var __vue_scope_id__$3 = undefined;
var __vue_scope_id__$4 = undefined;
/* module identifier */
var __vue_module_identifier__$3 = undefined;
var __vue_module_identifier__$4 = undefined;
/* functional template */
var __vue_is_functional_template__$3 = false;
var __vue_is_functional_template__$4 = false;
/* style inject */
/* style inject SSR */
@ -1054,12 +1177,12 @@
var FormulateInputBox = normalizeComponent(
{ render: __vue_render__$3, staticRenderFns: __vue_staticRenderFns__$3 },
__vue_inject_styles__$3,
__vue_script__$3,
__vue_scope_id__$3,
__vue_is_functional_template__$3,
__vue_module_identifier__$3,
{ render: __vue_render__$4, staticRenderFns: __vue_staticRenderFns__$4 },
__vue_inject_styles__$4,
__vue_script__$4,
__vue_scope_id__$4,
__vue_is_functional_template__$4,
__vue_module_identifier__$4,
false,
undefined,
undefined,
@ -1068,16 +1191,16 @@
//
var script$4 = {
var script$5 = {
name: 'FormulateInputText',
mixins: [FormulateInputMixin]
};
/* script */
var __vue_script__$4 = script$4;
var __vue_script__$5 = script$5;
/* template */
var __vue_render__$4 = function() {
var __vue_render__$5 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
@ -1193,17 +1316,17 @@
]
)
};
var __vue_staticRenderFns__$4 = [];
__vue_render__$4._withStripped = true;
var __vue_staticRenderFns__$5 = [];
__vue_render__$5._withStripped = true;
/* style */
var __vue_inject_styles__$4 = undefined;
var __vue_inject_styles__$5 = undefined;
/* scoped */
var __vue_scope_id__$4 = undefined;
var __vue_scope_id__$5 = undefined;
/* module identifier */
var __vue_module_identifier__$4 = undefined;
var __vue_module_identifier__$5 = undefined;
/* functional template */
var __vue_is_functional_template__$4 = false;
var __vue_is_functional_template__$5 = false;
/* style inject */
/* style inject SSR */
@ -1213,12 +1336,12 @@
var FormulateInputText = normalizeComponent(
{ render: __vue_render__$4, staticRenderFns: __vue_staticRenderFns__$4 },
__vue_inject_styles__$4,
__vue_script__$4,
__vue_scope_id__$4,
__vue_is_functional_template__$4,
__vue_module_identifier__$4,
{ render: __vue_render__$5, staticRenderFns: __vue_staticRenderFns__$5 },
__vue_inject_styles__$5,
__vue_script__$5,
__vue_scope_id__$5,
__vue_is_functional_template__$5,
__vue_module_identifier__$5,
false,
undefined,
undefined,
@ -1227,7 +1350,7 @@
//
var script$5 = {
var script$6 = {
name: 'FormulateInputSelect',
mixins: [FormulateInputMixin],
computed: {
@ -1244,10 +1367,10 @@
};
/* script */
var __vue_script__$5 = script$5;
var __vue_script__$6 = script$6;
/* template */
var __vue_render__$5 = function() {
var __vue_render__$6 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
@ -1354,17 +1477,17 @@
]
)
};
var __vue_staticRenderFns__$5 = [];
__vue_render__$5._withStripped = true;
var __vue_staticRenderFns__$6 = [];
__vue_render__$6._withStripped = true;
/* style */
var __vue_inject_styles__$5 = undefined;
var __vue_inject_styles__$6 = undefined;
/* scoped */
var __vue_scope_id__$5 = undefined;
var __vue_scope_id__$6 = undefined;
/* module identifier */
var __vue_module_identifier__$5 = undefined;
var __vue_module_identifier__$6 = undefined;
/* functional template */
var __vue_is_functional_template__$5 = false;
var __vue_is_functional_template__$6 = false;
/* style inject */
/* style inject SSR */
@ -1374,12 +1497,12 @@
var FormulateInputSelect = normalizeComponent(
{ render: __vue_render__$5, staticRenderFns: __vue_staticRenderFns__$5 },
__vue_inject_styles__$5,
__vue_script__$5,
__vue_scope_id__$5,
__vue_is_functional_template__$5,
__vue_module_identifier__$5,
{ render: __vue_render__$6, staticRenderFns: __vue_staticRenderFns__$6 },
__vue_inject_styles__$6,
__vue_script__$6,
__vue_scope_id__$6,
__vue_is_functional_template__$6,
__vue_module_identifier__$6,
false,
undefined,
undefined,
@ -1388,16 +1511,16 @@
//
var script$6 = {
var script$7 = {
name: 'FormulateInputTextArea',
mixins: [FormulateInputMixin]
};
/* script */
var __vue_script__$6 = script$6;
var __vue_script__$7 = script$7;
/* template */
var __vue_render__$6 = function() {
var __vue_render__$7 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
@ -1437,17 +1560,17 @@
]
)
};
var __vue_staticRenderFns__$6 = [];
__vue_render__$6._withStripped = true;
var __vue_staticRenderFns__$7 = [];
__vue_render__$7._withStripped = true;
/* style */
var __vue_inject_styles__$6 = undefined;
var __vue_inject_styles__$7 = undefined;
/* scoped */
var __vue_scope_id__$6 = undefined;
var __vue_scope_id__$7 = undefined;
/* module identifier */
var __vue_module_identifier__$6 = undefined;
var __vue_module_identifier__$7 = undefined;
/* functional template */
var __vue_is_functional_template__$6 = false;
var __vue_is_functional_template__$7 = false;
/* style inject */
/* style inject SSR */
@ -1457,12 +1580,12 @@
var FormulateInputTextArea = normalizeComponent(
{ render: __vue_render__$6, staticRenderFns: __vue_staticRenderFns__$6 },
__vue_inject_styles__$6,
__vue_script__$6,
__vue_scope_id__$6,
__vue_is_functional_template__$6,
__vue_module_identifier__$6,
{ render: __vue_render__$7, staticRenderFns: __vue_staticRenderFns__$7 },
__vue_inject_styles__$7,
__vue_script__$7,
__vue_scope_id__$7,
__vue_is_functional_template__$7,
__vue_module_identifier__$7,
false,
undefined,
undefined,
@ -1477,6 +1600,7 @@
components: {
FormulateForm: FormulateForm,
FormulateInput: FormulateInput,
FormulateInputErrors: FormulateInputErrors,
FormulateInputBox: FormulateInputBox,
FormulateInputText: FormulateInputText,
FormulateInputGroup: FormulateInputGroup,

10
dist/snow.css vendored
View File

@ -15,6 +15,16 @@
font-weight: 300;
line-height: 1.5;
margin-bottom: .25em; }
.formulate-input .formulate-input-errors {
list-style-type: none;
padding: 0;
margin: 0; }
.formulate-input .formulate-input-error {
color: #960505;
font-size: .8em;
font-weight: 300;
line-height: 1.5;
margin-bottom: .25em; }
.formulate-input .formulate-input-group-item {
margin-bottom: .5em; }
.formulate-input:last-child {

4
dist/snow.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -2,6 +2,7 @@ import library from './libs/library'
import isPlainObject from 'is-plain-object'
import FormulateInput from './FormulateInput.vue'
import FormulateForm from './FormulateForm.vue'
import FormulateInputErrors from './FormulateInputErrors.vue'
import FormulateInputGroup from './FormulateInputGroup.vue'
import FormulateInputBox from './inputs/FormulateInputBox.vue'
import FormulateInputText from './inputs/FormulateInputText.vue'
@ -19,6 +20,7 @@ class Formulate {
components: {
FormulateForm,
FormulateInput,
FormulateInputErrors,
FormulateInputBox,
FormulateInputText,
FormulateInputGroup,

View File

@ -2,6 +2,7 @@
<div
class="formulate-input"
:data-classification="classification"
:data-has-errors="hasErrors"
:data-type="type"
>
<div class="formulate-input-wrapper">
@ -39,6 +40,9 @@
class="formulate-input-help"
v-text="help"
/>
<FormulateInputErrors
:errors="mergedErrors"
/>
</div>
</template>
@ -102,6 +106,25 @@ export default {
debug: {
type: Boolean,
default: false
},
errors: {
type: [String, Array, Boolean],
default: false
},
validation: {
type: [String, Boolean, Array],
default: false
},
validationBehavior: {
type: String,
default: 'blur',
validator: function (value) {
return ['blur', 'live'].includes(value)
}
},
error: {
type: [String, Boolean],
default: false
}
},
data () {

View File

@ -0,0 +1,26 @@
<template>
<ul
v-if="errors.length"
class="formulate-input-errors"
>
<!-- eslint-disable -->
<li
v-for="error in errors"
:key="error"
v-html="error"
class="formulate-input-error"
/>
<!-- eslint-enable -->
</ul>
</template>
<script>
export default {
props: {
errors: {
type: [Boolean, Array],
required: true
}
}
}
</script>

View File

@ -1,5 +1,5 @@
import nanoid from 'nanoid'
import { map } from './utils'
import { map, arrayify, parseRules } from './utils'
/**
* For a single instance of an input, export all of the context needed to fully
@ -28,7 +28,10 @@ export default {
typeContext,
elementAttributes,
logicalLabelPosition,
isVmodeled
isVmodeled,
mergedErrors,
hasErrors,
validationRules
}
/**
@ -129,6 +132,30 @@ function createOptionList (options) {
return options
}
/**
* The merged errors computed property.
*/
function mergedErrors () {
return arrayify(this.errors)
.concat(arrayify(this.error))
.concat(arrayify(this.validationErrors))
.reduce((errors, err) => !errors.includes(err) ? errors.concat(err) : errors, [])
}
/**
* Does this computed property have errors.
*/
function hasErrors () {
return !!this.mergedErrors.length
}
/**
* An array of validation rules to pass.
*/
function validationRules () {
return parseRules(this.validation)
}
/**
* Defines the model used throughout the existing context.
* @param {object} context

41
src/libs/rules.js Normal file
View File

@ -0,0 +1,41 @@
import { shallowEqualObjects } from './utils'
/**
* Library of rules
*/
export default {
/**
* Rule: must be a value
*/
required: async function (value) {
if (Array.isArray(value)) {
return !!value.length
}
if (typeof value === 'string') {
return !!value
}
if (typeof value === 'object') {
return (!value) ? false : !!Object.keys(value).length
}
return true
},
/**
* Rule: Value is in an array (stack).
*/
in: async function (value, ...stack) {
return !!stack.find(item => shallowEqualObjects(item, value))
},
/**
* Rule: Match the value against a (stack) of patterns or strings
*/
matches: async function (value, ...stack) {
return !!stack.find(pattern => {
if (pattern instanceof RegExp) {
return pattern.test(value)
}
return pattern === value
})
}
}

View File

@ -69,3 +69,68 @@ export function shallowEqualObjects (objA, objB) {
}
return true
}
/**
* Given a string, object, falsey, or array - return an array.
* @param {mixed} item
*/
export function arrayify (item) {
if (!item) {
return []
}
if (typeof item === 'string') {
return [item]
}
if (Array.isArray(item)) {
return item
}
if (typeof item === 'object') {
return Object.values(item)
}
return []
}
/**
* Given an array or string return an array of callables.
* @param {array|string} validation
* @param {array} rules and array of functions
* @return {array} an array of functions
*/
export function parseRules (validation, rules) {
if (typeof validation === 'string') {
return parseRules(validation.split('|'), rules)
}
if (!Array.isArray(validation)) {
return []
}
return validation.map(rule => parseRule(rule, rules)).filter(f => !!f)
}
/**
* Given a string or function, parse it and return the an array in the format
* [fn, [...arguments]]
* @param {string|function} rule
*/
function parseRule (rule, rules) {
if (typeof rule === 'function') {
return [rule, []]
}
if (Array.isArray(rule) && rule.length) {
if (typeof rule[0] === 'string' && rules.hasOwnProperty(rule[0])) {
return [rules[rule.shift()], rule]
}
if (typeof rule[0] === 'function') {
return [rule.shift(), rule]
}
}
if (typeof rule === 'string') {
const segments = rule.split(':')
const functionName = segments.shift()
if (rules.hasOwnProperty(functionName)) {
return [rules[functionName], segments.length ? segments.join(':').split(',') : []]
} else {
throw new Error(`Unknown validation rule ${rule}`)
}
}
return false
}

View File

@ -50,6 +50,7 @@ test('installs on vue instance', () => {
const components = [
'FormulateForm',
'FormulateInput',
'FormulateInputErrors',
'FormulateInputBox',
'FormulateInputText',
'FormulateInputGroup',

View File

@ -11,145 +11,176 @@ Vue.use(Formulate)
* Test each type of text element
*/
test('type "text" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "search" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'search' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "email" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'email' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "number" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'number' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "color" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'color' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "date" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'date' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "month" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'month' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "password" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'password' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "range" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'range' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "tel" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'tel' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "time" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'time' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "url" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'url' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "week" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'week' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
/**
* Test rendering functionality to text inputs
*/
test('text inputs automatically have id assigned', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
expect(wrapper.vm.context).toHaveProperty('id')
expect(wrapper.find(`input[id="${wrapper.vm.context.attributes.id}"]`).exists()).toBe(true)
})
test('text inputs dont have labels', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
expect(wrapper.find('label').exists()).toBe(false)
})
test('text inputs can have labels', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', label: 'Field label' } })
expect(wrapper.find(`label[for="${wrapper.vm.context.attributes.id}"]`).exists()).toBe(true)
})
test('text inputs dont have help text', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
expect(wrapper.find(`.formulate-input-help`).exists()).toBe(false)
})
test('text inputs dont have help text', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', help: 'This is some help text' } })
expect(wrapper.find(`.formulate-input-help`).exists()).toBe(true)
})
/**
* Test data binding
*/
test('text inputs emit input (vmodel) event with value when edited', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
wrapper.find('input').setValue('Updated Value')
expect(wrapper.emitted().input).toBeTruthy()
expect(wrapper.emitted().input[0]).toEqual(['Updated Value'])
})
test('test that inputs that arent updated dont re-context themselves', () => {
const wrapper = mount({
data () {
return {
valueA: 'first value',
valueB: 'second value'
}
},
template: `
<div>
<FormulateInput type="text" ref="first" v-model="valueA" :placeholder="valueA" />
<FormulateInput type="text" ref="second" v-model="valueB" :placeholder="valueB" />
</div>
`
describe('FormulateInputText', () => {
it('renders text input when type is "text"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
const firstContext = wrapper.find({ref: "first"}).vm.context
const secondContext = wrapper.find({ref: "second"}).vm.context
wrapper.find('input').setValue('new value')
expect(firstContext).toBeTruthy()
expect(wrapper.vm.valueA === 'new value').toBe(true)
expect(wrapper.vm.valueB === 'second value').toBe(true)
expect(wrapper.find({ref: "first"}).vm.context === firstContext).toBe(false)
expect(wrapper.find({ref: "second"}).vm.context === secondContext).toBe(true)
})
test('test that inputs contain their v-model value as the initial input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', formulateValue: 'initial val' } })
expect(wrapper.find('input').element.value).toBe('initial val')
})
it('renders search input when type is "search"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'search' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('test that inputs without v-model set a proxy model', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'textarea' } })
const input = wrapper.find('textarea')
input.setValue('changed value')
expect(wrapper.vm.internalModelProxy).toBe('changed value')
it('renders email input when type is "email"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'email' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
it('renders number input when type is "number"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'number' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
it('renders color input when type is "color"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'color' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
it('renders date input when type is "date"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'date' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
it('renders month input when type is "month"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'month' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
it('renders password input when type is "password"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'password' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
it('renders range input when type is "range"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'range' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
it('renders tel input when type is "tel"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'tel' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
it('renders time input when type is "time"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'time' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
it('renders url input when type is "url"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'url' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
it('renders week input when type is "week"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'week' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
/**
* Test rendering functionality to text inputs
*/
it('automatically assigns an id', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
expect(wrapper.vm.context).toHaveProperty('id')
expect(wrapper.find(`input[id="${wrapper.vm.context.attributes.id}"]`).exists()).toBe(true)
})
it('doesnt automatically add a label', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
expect(wrapper.find('label').exists()).toBe(false)
})
it('renders labels', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', label: 'Field label' } })
expect(wrapper.find(`label[for="${wrapper.vm.context.attributes.id}"]`).exists()).toBe(true)
})
it('doesnt automatically render help text', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
expect(wrapper.find(`.formulate-input-help`).exists()).toBe(false)
})
it('renders help text', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', help: 'This is some help text' } })
expect(wrapper.find(`.formulate-input-help`).exists()).toBe(true)
})
/**
* Test data binding
*/
it('emits input (vmodel) event with value when edited', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
wrapper.find('input').setValue('Updated Value')
expect(wrapper.emitted().input).toBeTruthy()
expect(wrapper.emitted().input[0]).toEqual(['Updated Value'])
})
it('doesnt re-context itself if there were no changes', () => {
const wrapper = mount({
data () {
return {
valueA: 'first value',
valueB: 'second value'
}
},
template: `
<div>
<FormulateInput type="text" ref="first" v-model="valueA" :placeholder="valueA" />
<FormulateInput type="text" ref="second" v-model="valueB" :placeholder="valueB" />
</div>
`
})
const firstContext = wrapper.find({ref: "first"}).vm.context
const secondContext = wrapper.find({ref: "second"}).vm.context
wrapper.find('input').setValue('new value')
expect(firstContext).toBeTruthy()
expect(wrapper.vm.valueA === 'new value').toBe(true)
expect(wrapper.vm.valueB === 'second value').toBe(true)
expect(wrapper.find({ref: "first"}).vm.context === firstContext).toBe(false)
expect(wrapper.find({ref: "second"}).vm.context === secondContext).toBe(true)
})
it('uses the v-model value as the initial value', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', formulateValue: 'initial val' } })
expect(wrapper.find('input').element.value).toBe('initial val')
})
it('uses a proxy model internally if it doesnt have a v-model', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'textarea' } })
const input = wrapper.find('textarea')
input.setValue('changed value')
expect(wrapper.vm.internalModelProxy).toBe('changed value')
})
/**
* Test error handling
*/
it('doesnt automatically render errors', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
expect(wrapper.find('.formulate-input-errors').exists()).toBe(false)
})
it('accepts a single string as an error prop', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', error: 'This is an error' } })
expect(wrapper.find('.formulate-input-errors').exists()).toBe(true)
})
it('accepts an array as errors prop', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', errors: ['This is an error', 'this is another'] } })
expect(wrapper.findAll('.formulate-input-error').length).toBe(2)
})
it('removes any duplicate errors', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', errors: ['This is an error', 'This is an error'] } })
expect(wrapper.findAll('.formulate-input-error').length).toBe(1)
})
it('adds data-has-errors when there are errors', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', errors: ['This is an error', 'This is an error'] } })
expect(wrapper.find('[data-has-errors]').exists()).toBe(true)
})
})

77
test/rules.test.js Normal file
View File

@ -0,0 +1,77 @@
import rules from '@/libs/rules'
/**
* Required rule
*/
describe('required', () => {
it('fails on empty string', async () => expect(await rules.required('')).toBe(false))
it('fails on empty array', async () => expect(await rules.required([])).toBe(false))
it('fails on empty object', async () => expect(await rules.required({})).toBe(false))
it('fails on null', async () => expect(await rules.required(null)).toBe(false))
it('passes with the number zero', async () => expect(await rules.required(0)).toBe(true))
it('passes with the boolean false', async () => expect(await rules.required(false)).toBe(true))
it('passes with a non empty array', async () => expect(await rules.required(['123'])).toBe(true))
it('passes with a non empty object', async () => expect(await rules.required({a: 'b'})).toBe(true))
})
/**
* In rule
*/
describe('in', () => {
it('fails when not in stack', async () => {
expect(await rules.in('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)
})
it('fails comparing dissimilar objects', async () => {
expect(await rules.in({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)
})
it('passes a shallow array compare', async () => {
expect(await rules.in(['abc'], ['cdf'], ['abc'])).toBe(true)
})
it('passes a shallow object compare', async () => {
expect(await rules.in({f: 'abc'}, {a: 'cdf'}, {f: 'abc'},)).toBe(true)
})
})
/**
* Matches rule
*/
describe('matches', () => {
it('simple strings fail if they arent equal', async () => {
expect(await rules.matches('third', 'first')).toBe(false)
})
it('fails on non matching regex', async () => {
expect(await rules.matches('third', /^thirds/)).toBe(false)
})
it('passes if simple strings match', async () => {
expect(await rules.matches('second', 'third', 'second')).toBe(true)
})
it('passes on matching regex', async () => {
expect(await rules.matches('third', /^third/)).toBe(true)
})
it('passes on matching mixed regex and string', async () => {
expect(await rules.matches('first-fourth', 'second', /^third/, /fourth$/)).toBe(true)
})
})

44
test/utils.test.js Normal file
View File

@ -0,0 +1,44 @@
import { parseRules } from '@/libs/utils'
import rules from '@/libs/rules'
describe('parseRules', () => {
it('parses single string rules, returning empty arguments array', () => {
expect(parseRules('required', rules)).toEqual([
[rules.required, []]
])
})
it('throws errors for invalid validation rules', () => {
expect(() => {
parseRules('required|notarule', rules)
}).toThrow()
})
it('parses arguments for a rule', () => {
expect(parseRules('in:foo,bar', rules)).toEqual([
[rules.in, ['foo', 'bar']]
])
})
it('parses multiple string rules and arguments', () => {
expect(parseRules('required|in:foo,bar', rules)).toEqual([
[rules.required, []],
[rules.in, ['foo', 'bar']]
])
})
it('parses multiple array rules and arguments', () => {
expect(parseRules(['required', 'in:foo,bar'], rules)).toEqual([
[rules.required, []],
[rules.in, ['foo', 'bar']]
])
})
it('parses array rules with expression arguments', () => {
expect(parseRules([
['matches', /^abc/, '1234']
], rules)).toEqual([
[rules.matches, [/^abc/, '1234']]
])
})
})

View File

@ -25,6 +25,20 @@
margin-bottom: .25em;
}
.formulate-input-errors {
list-style-type: none;
padding: 0;
margin: 0;
}
.formulate-input-error {
color: $formulate-error;
font-size: .8em;
font-weight: 300;
line-height: 1.5;
margin-bottom: .25em;
}
.formulate-input-group-item {
margin-bottom: .5em;
}

View File

@ -15,6 +15,8 @@ $formulate-blue-l: #f3f4f4;
$formulate-green: #41b883;
$formulate-error: #960505;
$formulate-yellow-d: #6b5900;
$formulate-yellow: #e6c000;
$formulate-yellow-l: #fff8d2;