commit
b5edaa9b3c
2
dist/formulate.esm.js
vendored
2
dist/formulate.esm.js
vendored
File diff suppressed because one or more lines are too long
6
dist/formulate.min.js
vendored
6
dist/formulate.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/formulate.umd.js
vendored
2
dist/formulate.umd.js
vendored
File diff suppressed because one or more lines are too long
3
dist/snow.css
vendored
3
dist/snow.css
vendored
@ -397,7 +397,8 @@
|
||||
z-index: 2; }
|
||||
.formulate-input[data-classification="file"] .formulate-files .formualte-file-name {
|
||||
padding-left: 1.5em;
|
||||
padding-right: 2em; }
|
||||
padding-right: 2em;
|
||||
max-width: 100%; }
|
||||
.formulate-input[data-classification="file"] .formulate-files .formualte-file-name::before {
|
||||
position: absolute;
|
||||
left: .7em;
|
||||
|
2
dist/snow.min.css
vendored
2
dist/snow.min.css
vendored
File diff suppressed because one or more lines are too long
@ -1,4 +1,5 @@
|
||||
import nanoid from 'nanoid/non-secure'
|
||||
import mimes from './libs/mimes'
|
||||
|
||||
/**
|
||||
* The file upload class holds and represents a file’s upload state durring
|
||||
@ -15,9 +16,35 @@ class FileUpload {
|
||||
this.fileList = input.files
|
||||
this.files = []
|
||||
this.options = options
|
||||
this.addFileList(this.fileList)
|
||||
this.context = context
|
||||
this.results = false
|
||||
this.context = context
|
||||
if (Array.isArray(this.fileList)) {
|
||||
this.rehydrateFileList(this.fileList)
|
||||
} else {
|
||||
this.addFileList(this.fileList)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a pre-existing array of files, create a faux FileList.
|
||||
* @param {array} items expects an array of objects [{ url: '/uploads/file.pdf' }]
|
||||
* @param {string} pathKey the object-key to access the url (defaults to "url")
|
||||
*/
|
||||
rehydrateFileList (items) {
|
||||
const fauxFileList = items.reduce((fileList, item) => {
|
||||
const key = this.options ? this.options.fileUrlKey : 'url'
|
||||
const url = item[key]
|
||||
const ext = (url && url.lastIndexOf('.') !== -1) ? url.substr(url.lastIndexOf('.') + 1) : false
|
||||
const mime = mimes[ext] || false
|
||||
fileList.push(Object.assign({}, item, url ? {
|
||||
name: url.substr((url.lastIndexOf('/') + 1) || 0),
|
||||
type: item.type ? item.type : mime,
|
||||
previewData: url
|
||||
} : {}))
|
||||
return fileList
|
||||
}, [])
|
||||
this.results = items
|
||||
this.addFileList(fauxFileList)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -41,7 +68,7 @@ class FileUpload {
|
||||
uuid,
|
||||
path: false,
|
||||
removeFile: removeFile.bind(this),
|
||||
previewData: false
|
||||
previewData: file.previewData || false
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -54,15 +81,18 @@ class FileUpload {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given uploader is axios instance.
|
||||
* Check if the given uploader is axios instance. This isn't a great way of
|
||||
* testing if it is or not, but AFIK there isn't a better way right now:
|
||||
*
|
||||
* https://github.com/axios/axios/issues/737
|
||||
*/
|
||||
uploaderIsAxios () {
|
||||
if (
|
||||
this.hasUploader &&
|
||||
typeof this.hasUploader.request === 'function' &&
|
||||
typeof this.hasUploader.get === 'function' &&
|
||||
typeof this.hasUploader.delete === 'function' &&
|
||||
typeof this.hasUploader.post === 'function'
|
||||
typeof this.context.uploader.request === 'function' &&
|
||||
typeof this.context.uploader.get === 'function' &&
|
||||
typeof this.context.uploader.delete === 'function' &&
|
||||
typeof this.context.uploader.post === 'function'
|
||||
) {
|
||||
return true
|
||||
}
|
||||
@ -76,14 +106,19 @@ class FileUpload {
|
||||
if (this.uploaderIsAxios()) {
|
||||
const formData = new FormData()
|
||||
formData.append(this.context.name || 'file', args[0])
|
||||
return this.uploader.post(this.context.uploadUrl, formData, {
|
||||
if (this.context.uploadUrl === false) {
|
||||
throw new Error('No uploadURL specified: https://vueformulate.com/guide/inputs/file/#props')
|
||||
}
|
||||
return this.context.uploader.post(this.context.uploadUrl, formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
},
|
||||
onUploadProgress: progressEvent => {
|
||||
// args[1] here is the upload progress handler function
|
||||
args[1](Math.round((progressEvent.loaded * 100) / progressEvent.total))
|
||||
}
|
||||
})
|
||||
.then(res => res.data)
|
||||
.catch(err => args[2](err))
|
||||
}
|
||||
return this.context.uploader(...args)
|
||||
@ -135,7 +170,8 @@ class FileUpload {
|
||||
*/
|
||||
removeFile (uuid) {
|
||||
this.files = this.files.filter(file => file.uuid !== uuid)
|
||||
if (window) {
|
||||
this.context.performValidation()
|
||||
if (window && this.fileList instanceof FileList) {
|
||||
const transfer = new DataTransfer()
|
||||
this.files.map(file => transfer.items.add(file.file))
|
||||
this.fileList = transfer.files
|
||||
|
@ -43,6 +43,8 @@ class Formulate {
|
||||
rules,
|
||||
locale: 'en',
|
||||
uploader: fauxUploader,
|
||||
uploadUrl: false,
|
||||
fileUrlKey: 'url',
|
||||
uploadJustCompleteDuration: 1000,
|
||||
plugins: [],
|
||||
locales: {
|
||||
@ -162,6 +164,21 @@ class Formulate {
|
||||
return this.options.uploader || false
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the global upload url.
|
||||
*/
|
||||
getUploadUrl () {
|
||||
return this.options.uploadUrl || false
|
||||
}
|
||||
|
||||
/**
|
||||
* When re-hydrating a file uploader with an array, get the sub-object key to
|
||||
* access the url of the file. Usually this is just "url".
|
||||
*/
|
||||
getFileUrlKey () {
|
||||
return this.options.fileUrlKey || 'url'
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of an upload.
|
||||
*/
|
||||
|
@ -92,21 +92,33 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
immediate: false
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.applyInitialValues()
|
||||
},
|
||||
methods: {
|
||||
applyInitialValues () {
|
||||
if (this.hasInitialValue) {
|
||||
this.internalFormModelProxy = this.initialValues
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setFieldValue (field, value) {
|
||||
Object.assign(this.internalFormModelProxy, { [field]: value })
|
||||
this.$emit('input', Object.assign({}, this.internalFormModelProxy))
|
||||
},
|
||||
getUniqueRegistryName (base, count = 0) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.registry, base + (count || ''))) {
|
||||
return this.getUniqueRegistryName(base, count + 1)
|
||||
}
|
||||
return base + (count || '')
|
||||
},
|
||||
register (field, component) {
|
||||
// Don't re-register fields... @todo come up with another way of handling this that doesn't break multi option
|
||||
if (Object.prototype.hasOwnProperty.call(this.registry, field)) {
|
||||
return false
|
||||
}
|
||||
this.registry[field] = component
|
||||
const hasVModelValue = Object.prototype.hasOwnProperty.call(component.$options.propsData, 'formulateValue')
|
||||
const hasValue = Object.prototype.hasOwnProperty.call(component.$options.propsData, 'value')
|
||||
|
@ -173,16 +173,17 @@ export default {
|
||||
validationRules: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
checked: {
|
||||
type: [String, Boolean],
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
/**
|
||||
* @todo consider swapping out nanoid for this._uid
|
||||
*/
|
||||
defaultId: nanoid(9),
|
||||
localAttributes: {},
|
||||
internalModelProxy: this.formulateValue || this.value,
|
||||
internalModelProxy: this.getInitialValue(),
|
||||
behavioralErrorVisibility: (this.errorBehavior === 'live'),
|
||||
formShouldShowErrors: false,
|
||||
validationErrors: [],
|
||||
@ -219,6 +220,7 @@ export default {
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.applyInitialValue()
|
||||
if (this.formulateFormRegister && typeof this.formulateFormRegister === 'function') {
|
||||
this.formulateFormRegister(this.nameOrFallback, this)
|
||||
}
|
||||
@ -226,6 +228,30 @@ export default {
|
||||
this.performValidation()
|
||||
},
|
||||
methods: {
|
||||
getInitialValue () {
|
||||
// Manually request classification, pre-computed props
|
||||
var classification = this.$formulate.classify(this.type)
|
||||
classification = (classification === 'box' && this.options) ? 'group' : classification
|
||||
if (classification === 'box' && this.checked) {
|
||||
return this.value || true
|
||||
} else if (Object.prototype.hasOwnProperty.call(this.$options.propsData, 'value') && classification !== 'box') {
|
||||
return this.value
|
||||
} else if (Object.prototype.hasOwnProperty.call(this.$options.propsData, 'formulateValue')) {
|
||||
return this.formulateValue
|
||||
}
|
||||
return ''
|
||||
},
|
||||
applyInitialValue () {
|
||||
// This should only be run immediately on created and ensures that the
|
||||
// proxy and the model are both the same before any additional registration.
|
||||
if (
|
||||
!shallowEqualObjects(this.context.model, this.internalModelProxy) &&
|
||||
// we dont' want to set the model if we are a sub-box of a multi-box field
|
||||
(Object.prototype.hasOwnProperty(this.$options.propsData, 'options') && this.classification === 'box')
|
||||
) {
|
||||
this.context.model = this.internalModelProxy
|
||||
}
|
||||
},
|
||||
updateLocalAttributes (value) {
|
||||
if (!shallowEqualObjects(value, this.localAttributes)) {
|
||||
this.localAttributes = value
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="formulate-input-group">
|
||||
<component
|
||||
:is="optionContext.component"
|
||||
:is="subComponent"
|
||||
v-for="optionContext in optionsWithContext"
|
||||
:key="optionContext.id"
|
||||
v-model="context.model"
|
||||
@ -24,16 +24,36 @@ export default {
|
||||
options () {
|
||||
return this.context.options || []
|
||||
},
|
||||
subComponent () {
|
||||
// @todo - rough-in for future flexible input-groups
|
||||
return 'FormulateInput'
|
||||
},
|
||||
optionsWithContext () {
|
||||
const { options, labelPosition, attributes, classification, ...context } = this.context
|
||||
return this.options.map(option => this.groupItemContext(context, option))
|
||||
const {
|
||||
// The following are a list of items to pull out of the context object
|
||||
options,
|
||||
labelPosition,
|
||||
attributes: { id, ...groupApplicableAttributes },
|
||||
classification,
|
||||
blurHandler,
|
||||
performValidation,
|
||||
hasValidationErrors,
|
||||
component,
|
||||
hasLabel,
|
||||
...context
|
||||
} = this.context
|
||||
return this.options.map(option => this.groupItemContext(
|
||||
context,
|
||||
option,
|
||||
groupApplicableAttributes
|
||||
))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
groupItemContext (...args) {
|
||||
return Object.assign({}, ...args, {
|
||||
component: 'FormulateInput'
|
||||
})
|
||||
groupItemContext (context, option, groupAttributes) {
|
||||
const optionAttributes = {}
|
||||
const ctx = Object.assign({}, context, option, groupAttributes, optionAttributes)
|
||||
return ctx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,15 @@ export default {
|
||||
return !!(this.context.model instanceof FileUpload && this.context.model.files.length)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (Array.isArray(this.context.model)) {
|
||||
if (typeof this.context.model[0][this.$formulate.getFileUrlKey()] === 'string') {
|
||||
this.context.model = this.$formulate.createUpload({
|
||||
files: this.context.model
|
||||
}, this.context)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// Add a listener to the window to prevent drag/drops that miss the dropzone
|
||||
// from opening the file and navigating the user away from the page.
|
||||
|
@ -1,5 +1,5 @@
|
||||
import nanoid from 'nanoid/non-secure'
|
||||
import { map, arrayify } from './utils'
|
||||
import { map, arrayify, shallowEqualObjects } from './utils'
|
||||
|
||||
/**
|
||||
* For a single instance of an input, export all of the context needed to fully
|
||||
@ -19,9 +19,10 @@ export default {
|
||||
label: this.label,
|
||||
labelPosition: this.logicalLabelPosition,
|
||||
attributes: this.elementAttributes,
|
||||
performValidation: this.performValidation.bind(this),
|
||||
blurHandler: blurHandler.bind(this),
|
||||
imageBehavior: this.imageBehavior,
|
||||
uploadUrl: this.uploadUrl,
|
||||
uploadUrl: this.mergedUploadUrl,
|
||||
uploader: this.uploader || this.$formulate.getUploader(),
|
||||
uploadBehavior: this.uploadBehavior,
|
||||
preventWindowDrops: this.preventWindowDrops,
|
||||
@ -37,7 +38,8 @@ export default {
|
||||
mergedErrors,
|
||||
hasErrors,
|
||||
showFieldErrors,
|
||||
mergedValidationName
|
||||
mergedValidationName,
|
||||
mergedUploadUrl
|
||||
}
|
||||
|
||||
/**
|
||||
@ -111,6 +113,14 @@ function mergedValidationName () {
|
||||
return this.type
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the uploadURL on the input if it exists, otherwise use the uploadURL
|
||||
* that is defined as a plugin option.
|
||||
*/
|
||||
function mergedUploadUrl () {
|
||||
return this.uploadUrl || this.$formulate.getUploadUrl()
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the field should show it's error (if it has one)
|
||||
* @return {boolean}
|
||||
@ -220,7 +230,9 @@ function modelGetter () {
|
||||
* Set the value from a model.
|
||||
**/
|
||||
function modelSetter (value) {
|
||||
if (!shallowEqualObjects(value, this.internalModelProxy)) {
|
||||
this.internalModelProxy = value
|
||||
}
|
||||
this.$emit('input', value)
|
||||
if (this.context.name && typeof this.formulateFormSetter === 'function') {
|
||||
this.formulateFormSetter(this.context.name, value)
|
||||
|
74
src/libs/mimes.js
Normal file
74
src/libs/mimes.js
Normal file
@ -0,0 +1,74 @@
|
||||
export default {
|
||||
'aac': 'audio/aac',
|
||||
'abw': 'application/x-abiword',
|
||||
'arc': 'application/x-freearc',
|
||||
'avi': 'video/x-msvideo',
|
||||
'azw': 'application/vnd.amazon.ebook',
|
||||
'bin': 'application/octet-stream',
|
||||
'bmp': 'image/bmp',
|
||||
'bz': 'application/x-bzip',
|
||||
'bz2': 'application/x-bzip2',
|
||||
'csh': 'application/x-csh',
|
||||
'css': 'text/css',
|
||||
'csv': 'text/csv',
|
||||
'doc': 'application/msword',
|
||||
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'eot': 'application/vnd.ms-fontobject',
|
||||
'epub': 'application/epub+zip',
|
||||
'gz': 'application/gzip',
|
||||
'gif': 'image/gif',
|
||||
'htm': 'text/html',
|
||||
'html': 'text/html',
|
||||
'ico': 'image/vnd.microsoft.icon',
|
||||
'ics': 'text/calendar',
|
||||
'jar': 'application/java-archive',
|
||||
'jpg': 'image/jpeg',
|
||||
'jpeg': 'image/jpeg',
|
||||
'js': 'text/javascript',
|
||||
'json': 'application/json',
|
||||
'jsonld': 'application/ld+json',
|
||||
'midi': 'audio/x-midi',
|
||||
'mid': 'audio/midi',
|
||||
'mjs': 'text/javascript',
|
||||
'mp3': 'audio/mpeg',
|
||||
'mpeg': 'video/mpeg',
|
||||
'mpkg': 'application/vnd.apple.installer+xml',
|
||||
'odp': 'application/vnd.oasis.opendocument.presentation',
|
||||
'ods': 'application/vnd.oasis.opendocument.spreadsheet',
|
||||
'odt': 'application/vnd.oasis.opendocument.text',
|
||||
'oga': 'audio/ogg',
|
||||
'ogv': 'video/ogg',
|
||||
'ogx': 'application/ogg',
|
||||
'opus': 'audio/opus',
|
||||
'otf': 'font/otf',
|
||||
'png': 'image/png',
|
||||
'pdf': 'application/pdf',
|
||||
'php': 'application/php',
|
||||
'ppt': 'application/vnd.ms-powerpoint',
|
||||
'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'rar': 'application/vnd.rar',
|
||||
'rtf': 'application/rtf',
|
||||
'sh': 'application/x-sh',
|
||||
'svg': 'image/svg+xml',
|
||||
'swf': 'application/x-shockwave-flash',
|
||||
'tar': 'application/x-tar',
|
||||
'tif': 'image/tiff',
|
||||
'tiff': 'image/tiff',
|
||||
'ts': 'video/mp2t',
|
||||
'ttf': 'font/ttf',
|
||||
'txt': 'text/plain',
|
||||
'vsd': 'application/vnd.visio',
|
||||
'wav': 'audio/wav',
|
||||
'weba': 'audio/webm',
|
||||
'webm': 'video/webm',
|
||||
'webp': 'image/webp',
|
||||
'woff': 'font/woff',
|
||||
'woff2': 'font/woff2',
|
||||
'xhtml': 'application/xhtml+xml',
|
||||
'xls': 'application/vnd.ms-excel',
|
||||
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'xml': 'text/xml',
|
||||
'xul': 'application/vnd.mozilla.xul+xml',
|
||||
'zip': 'application/zip',
|
||||
'7z': 'application/x-7z-compressed'
|
||||
}
|
@ -164,9 +164,9 @@ export default {
|
||||
mime: function ({ value }, ...types) {
|
||||
return Promise.resolve((() => {
|
||||
if (value instanceof FileUpload) {
|
||||
const fileList = value.getFileList()
|
||||
const fileList = value.getFiles()
|
||||
for (let i = 0; i < fileList.length; i++) {
|
||||
const file = fileList[i]
|
||||
const file = fileList[i].file
|
||||
if (!types.includes(file.type)) {
|
||||
return false
|
||||
}
|
||||
@ -227,6 +227,9 @@ export default {
|
||||
if (Array.isArray(value)) {
|
||||
return !!value.length
|
||||
}
|
||||
if (value instanceof FileUpload) {
|
||||
return value.getFiles().length > 0
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return !!value
|
||||
}
|
||||
|
@ -46,11 +46,12 @@ describe('FormulateForm', () => {
|
||||
expect(Object.keys(wrapper.vm.registry)).toEqual(['subinput1', 'subinput2'])
|
||||
})
|
||||
|
||||
it('can set a field’s initial value', () => {
|
||||
it('can set a field’s initial value', async () => {
|
||||
const wrapper = mount(FormulateForm, {
|
||||
propsData: { formulateValue: { testinput: 'has initial value' } },
|
||||
slots: { default: '<FormulateInput type="text" name="testinput" />' }
|
||||
})
|
||||
await flushPromises()
|
||||
expect(wrapper.find('input').element.value).toBe('has initial value')
|
||||
})
|
||||
|
||||
@ -76,6 +77,31 @@ describe('FormulateForm', () => {
|
||||
expect(wrapper.vm.formValues).toEqual({ name: '123' })
|
||||
})
|
||||
|
||||
it('can set initial checked attribute on single checkboxes', () => {
|
||||
const wrapper = mount(FormulateForm, {
|
||||
propsData: { formulateValue: { box1: true } },
|
||||
slots: { default: '<FormulateInput type="checkbox" name="box1" />' }
|
||||
})
|
||||
expect(wrapper.find('input[type="checkbox"]').is(':checked')).toBe(true)
|
||||
});
|
||||
|
||||
it('can set initial unchecked attribute on single checkboxes', () => {
|
||||
const wrapper = mount(FormulateForm, {
|
||||
propsData: { formulateValue: { box1: false } },
|
||||
slots: { default: '<FormulateInput type="checkbox" name="box1" />' }
|
||||
})
|
||||
expect(wrapper.find('input[type="checkbox"]').is(':checked')).toBe(false)
|
||||
});
|
||||
|
||||
it('can set checkbox initial value with options', async () => {
|
||||
const wrapper = mount(FormulateForm, {
|
||||
propsData: { formulateValue: { box2: ['second', 'third'] } },
|
||||
slots: { default: '<FormulateInput type="checkbox" name="box2" :options="{first: \'First\', second: \'Second\', third: \'Third\'}" />' }
|
||||
})
|
||||
await flushPromises()
|
||||
expect(wrapper.findAll('input').length).toBe(3)
|
||||
});
|
||||
|
||||
it('receives updates to form model when individual fields are edited', () => {
|
||||
const wrapper = mount({
|
||||
data () {
|
||||
@ -184,4 +210,15 @@ describe('FormulateForm', () => {
|
||||
expect(wrapper.find('input[type="text"]').element.value).toBe('Dave Barnett')
|
||||
expect(wrapper.find('input[type="checkbox"]').element.checked).toBe(true)
|
||||
})
|
||||
|
||||
it('shows error messages when it includes a checkbox with options', async () => {
|
||||
const wrapper = mount(FormulateForm, {
|
||||
propsData: { formulateValue: { box3: [] } },
|
||||
slots: { default: '<FormulateInput type="checkbox" name="box3" validation="required" :options="{first: \'First\', second: \'Second\', third: \'Third\'}" />' }
|
||||
})
|
||||
wrapper.trigger('submit')
|
||||
await wrapper.vm.$nextTick()
|
||||
await flushPromises()
|
||||
expect(wrapper.find('.formulate-input-error').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
@ -8,57 +8,97 @@ import FormulateInputGroup from '@/FormulateInputGroup.vue'
|
||||
|
||||
Vue.use(Formulate)
|
||||
|
||||
test('type "checkbox" renders a box element', () => {
|
||||
describe('FormulateInputBox', () => {
|
||||
it('renders a box element when type "checkbox" ', () => {
|
||||
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox' } })
|
||||
expect(wrapper.contains(FormulateInputBox)).toBe(true)
|
||||
})
|
||||
|
||||
test('type "radio" renders a box element', () => {
|
||||
it('renders a box element when type "radio"', () => {
|
||||
const wrapper = mount(FormulateInput, { propsData: { type: 'radio' } })
|
||||
expect(wrapper.contains(FormulateInputBox)).toBe(true)
|
||||
})
|
||||
|
||||
test('box inputs properly process options object in context library', () => {
|
||||
it('box inputs properly process options object in context library', () => {
|
||||
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', options: {a: '1', b: '2'} } })
|
||||
expect(Array.isArray(wrapper.vm.context.options)).toBe(true)
|
||||
})
|
||||
|
||||
test('type "checkbox" with options renders a group', () => {
|
||||
it('renders a group when type "checkbox" with options', () => {
|
||||
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', options: {a: '1', b: '2'} } })
|
||||
expect(wrapper.contains(FormulateInputGroup)).toBe(true)
|
||||
})
|
||||
|
||||
test('type "radio" with options renders a group', () => {
|
||||
it('renders a group when type "radio" with options', () => {
|
||||
const wrapper = mount(FormulateInput, { propsData: { type: 'radio', options: {a: '1', b: '2'} } })
|
||||
expect(wrapper.contains(FormulateInputGroup)).toBe(true)
|
||||
})
|
||||
|
||||
test('labelPosition of type "checkbox" defaults to after', () => {
|
||||
it('defaults labelPosition to "after" when type "checkbox"', () => {
|
||||
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox' } })
|
||||
expect(wrapper.vm.context.labelPosition).toBe('after')
|
||||
})
|
||||
|
||||
test('labelPosition of type "checkbox" with options defaults to before', () => {
|
||||
it('labelPosition of defaults to before when type "checkbox" with options', () => {
|
||||
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', options: {a: '1', b: '2'}}})
|
||||
expect(wrapper.vm.context.labelPosition).toBe('before')
|
||||
})
|
||||
|
||||
|
||||
test('type radio renders multiple inputs with options', () => {
|
||||
it('renders multiple inputs with options when type "radio"', () => {
|
||||
const wrapper = mount(FormulateInput, { propsData: { type: 'radio', options: {a: '1', b: '2'} } })
|
||||
expect(wrapper.findAll('input[type="radio"]').length).toBe(2)
|
||||
})
|
||||
|
||||
test('type "radio" auto generate ids if not provided', () => {
|
||||
it('generates ids if not provided when type "radio"', () => {
|
||||
const wrapper = mount(FormulateInput, { propsData: { type: 'radio', options: {a: '1', b: '2'} } })
|
||||
expect(wrapper.findAll('input[type="radio"]').is('[id]')).toBe(true)
|
||||
})
|
||||
|
||||
it('additional context does not bleed through to attributes with type "radio" and options', () => {
|
||||
const wrapper = mount(FormulateInput, { propsData: { type: 'radio', options: {a: '1', b: '2'} } })
|
||||
expect(Object.keys(wrapper.find('input[type="radio"]').attributes())).toEqual(["type", "id", "value"])
|
||||
})
|
||||
|
||||
it('additional context does not bleed through to attributes with type "checkbox" and options', () => {
|
||||
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', options: {a: '1', b: '2'} } })
|
||||
expect(Object.keys(wrapper.find('input[type="checkbox"]').attributes())).toEqual(["type", "id", "value"])
|
||||
})
|
||||
|
||||
it('allows external attributes to make it down to the inner box elements', () => {
|
||||
const wrapper = mount(FormulateInput, { propsData: { type: 'radio', options: {a: '1', b: '2'}, readonly: 'true' } })
|
||||
expect(Object.keys(wrapper.find('input[type="radio"]').attributes()).includes('readonly')).toBe(true)
|
||||
})
|
||||
|
||||
it('does not use the value attribute to be checked', () => {
|
||||
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', value: '123' } })
|
||||
expect(wrapper.find('input').is(':checked')).toBe(false)
|
||||
})
|
||||
|
||||
it('uses the checked attribute to be checked', async () => {
|
||||
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', checked: 'true' } })
|
||||
await flushPromises()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.find('input').is(':checked')).toBe(true)
|
||||
})
|
||||
|
||||
it('uses the value attribute to select "type" radio when using options', async () => {
|
||||
const wrapper = mount(FormulateInput, { propsData: { type: 'radio', options: {a: '1', b: '2'}, value: 'b' } })
|
||||
await flushPromises()
|
||||
expect(wrapper.findAll('input:checked').length).toBe(1)
|
||||
})
|
||||
|
||||
it('uses the value attribute to select "type" checkbox when using options', async () => {
|
||||
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', options: {a: '1', b: '2', c: '123'}, value: ['b', 'c'] } })
|
||||
await flushPromises()
|
||||
expect(wrapper.findAll('input:checked').length).toBe(2)
|
||||
})
|
||||
|
||||
/**
|
||||
* Test data binding
|
||||
* it data binding
|
||||
*/
|
||||
|
||||
test('type "checkbox" sets array of values via v-model', async () => {
|
||||
it('sets array of values via v-model when type "checkbox"', async () => {
|
||||
const wrapper = mount({
|
||||
data () {
|
||||
return {
|
||||
@ -80,3 +120,65 @@ test('type "radio" auto generate ids if not provided', () => {
|
||||
await flushPromises()
|
||||
expect(wrapper.vm.checkboxValues).toEqual(['foo', 'fooey'])
|
||||
})
|
||||
|
||||
it('does not pre-set internal value when type "radio" with options', async () => {
|
||||
const wrapper = mount({
|
||||
data () {
|
||||
return {
|
||||
radioValue: '',
|
||||
options: {foo: 'Foo', bar: 'Bar', fooey: 'Fooey'}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<FormulateInput type="radio" v-model="radioValue" :options="options" />
|
||||
</div>
|
||||
`
|
||||
})
|
||||
await wrapper.vm.$nextTick()
|
||||
await flushPromises()
|
||||
expect(wrapper.vm.radioValue).toBe('')
|
||||
})
|
||||
|
||||
it('does not pre-set internal value of FormulateForm when type "radio" with options', async () => {
|
||||
const wrapper = mount({
|
||||
data () {
|
||||
return {
|
||||
radioValue: '',
|
||||
formValues: {},
|
||||
options: {foo: 'Foo', bar: 'Bar', fooey: 'Fooey'}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<FormulateForm
|
||||
v-model="formValues"
|
||||
>
|
||||
<FormulateInput type="radio" v-model="radioValue" name="foobar" :options="options" />
|
||||
</FormulateForm>
|
||||
`
|
||||
})
|
||||
await wrapper.vm.$nextTick()
|
||||
await flushPromises()
|
||||
expect(wrapper.vm.formValues.foobar).toBe('')
|
||||
})
|
||||
|
||||
it('does precheck the correct input when radio with options', async () => {
|
||||
const wrapper = mount({
|
||||
data () {
|
||||
return {
|
||||
radioValue: 'fooey',
|
||||
options: {foo: 'Foo', bar: 'Bar', fooey: 'Fooey', gooey: 'Gooey'}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<FormulateInput type="radio" v-model="radioValue" :options="options" />
|
||||
</div>
|
||||
`
|
||||
})
|
||||
await flushPromises()
|
||||
const checkboxes = wrapper.findAll('input[type="radio"]:checked')
|
||||
expect(checkboxes.length).toBe(1)
|
||||
expect(checkboxes.at(0).element.value).toBe('fooey')
|
||||
})
|
||||
})
|
||||
|
@ -151,9 +151,9 @@ describe('FormulateInputText', () => {
|
||||
expect(wrapper.find('input').element.value).toBe('initial val')
|
||||
})
|
||||
|
||||
it('uses the v-model value as the initial value when value prop is provided', () => {
|
||||
it('uses the value prop as the initial value when v-model provided', () => {
|
||||
const wrapper = mount(FormulateInput, { propsData: { type: 'text', formulateValue: 'initial val', value: 'initial other val' } })
|
||||
expect(wrapper.find('input').element.value).toBe('initial val')
|
||||
expect(wrapper.find('input').element.value).toBe('initial other val')
|
||||
})
|
||||
|
||||
it('uses a proxy model internally if it doesnt have a v-model', () => {
|
||||
|
@ -24,6 +24,10 @@ describe('required', () => {
|
||||
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({ value: '' }, 'false')).toBe(true))
|
||||
|
||||
it('passes with FileUpload', async () => expect(await rules.required({ value: new FileUpload({ files: [{ name: 'j.png' }] }) })).toBe(true))
|
||||
|
||||
it('fails with empty FileUpload', async () => expect(await rules.required({ value: new FileUpload({ files: [] }) })).toBe(false))
|
||||
})
|
||||
|
||||
|
||||
|
@ -501,6 +501,7 @@
|
||||
.formualte-file-name {
|
||||
padding-left: 1.5em;
|
||||
padding-right: 2em;
|
||||
max-width: 100%;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
|
Loading…
x
Reference in New Issue
Block a user