test for v-model text

This commit is contained in:
Evan You 2016-05-26 13:54:37 -04:00
parent 20f6e6e3fe
commit 056cb7f295
11 changed files with 194 additions and 11 deletions

View File

@ -182,7 +182,7 @@ function genDirectives (el: ASTElement): string | void {
if (gen) {
// compile-time directive that manipulates AST.
// returns true if it also needs a runtime counterpart.
needRuntime = !!gen(el, dir)
needRuntime = !!gen(el, dir, warn)
}
if (needRuntime) {
hasRuntime = true

View File

@ -2,7 +2,11 @@
import { addHandler, addProp, getBindingAttr } from 'compiler/helpers'
export default function model (el: ASTElement, dir: ASTDirective) {
export default function model (
el: ASTElement,
dir: ASTDirective,
warn: Function
): ?boolean {
const value = dir.value
const modifiers = dir.modifiers
if (el.tag === 'select') {
@ -20,12 +24,12 @@ export default function model (el: ASTElement, dir: ASTDirective) {
genRadioModel(el, value)
break
default:
return genDefaultModel(el, value, modifiers)
return genDefaultModel(el, value, modifiers, warn)
}
}
}
function genCheckboxModel (el, value) {
function genCheckboxModel (el: ASTElement, value: ?string) {
const valueBinding = getBindingAttr(el, 'value')
addProp(el, 'checked',
`Array.isArray(${value})` +
@ -45,13 +49,35 @@ function genCheckboxModel (el, value) {
)
}
function genRadioModel (el, value) {
function genRadioModel (el: ASTElement, value: ?string) {
const valueBinding = getBindingAttr(el, 'value')
addProp(el, 'checked', `(${value}==${valueBinding})`)
addHandler(el, 'change', `${value}=${valueBinding}`)
}
function genDefaultModel (el, value, modifiers) {
function genDefaultModel (
el: ASTElement,
value: ?string,
modifiers: ?Object,
warn: Function
): ?boolean {
if (process.env.NODE_ENV !== 'production') {
if (el.tag === 'input' && el.attrsMap.value) {
warn(
`<${el.tag} v-model="${value}" value="${el.attrsMap.value}">:\n` +
'inline value attributes will be ignored when using v-model. ' +
'Declare initial values in the component\'s data option instead.'
)
}
if (el.tag === 'textarea' && el.children.length) {
warn(
`<textarea v-model="${value}">:\n` +
'inline content inside <textarea> will be ignored when using v-model. ' +
'Declare initial values in the component\'s data option instead.'
)
}
}
const type = el.attrsMap.type
const { lazy, number, trim } = modifiers || {}
const event = lazy ? 'change' : 'input'
@ -77,7 +103,7 @@ const getSelectedValueCode =
'.call($event.target.options,function(o){return o.selected})' +
'.map(function(o){return "_value" in o ? o._value : o.value})'
function patchChildOptions (el, fn) {
function patchChildOptions (el: ASTElement, fn: (arg: ?string) => string) {
for (let i = 0; i < el.children.length; i++) {
const c = el.children[i]
if (c.type === 1 && c.tag === 'option') {
@ -86,12 +112,12 @@ function patchChildOptions (el, fn) {
}
}
function genSelect (el, value) {
function genSelect (el: ASTElement, value: ?string) {
addHandler(el, 'change', `${value}=${getSelectedValueCode}[0]`)
patchChildOptions(el, valueBinding => `$(${value})===(${valueBinding})`)
}
function genMultiSelect (el, value) {
function genMultiSelect (el: ASTElement, value: ?string) {
addHandler(el, 'change', `${value}=${getSelectedValueCode}`)
patchChildOptions(el, valueBinding => `$(${value}).indexOf(${valueBinding})>-1`)
}

View File

@ -3,8 +3,10 @@
* properties to Elements.
*/
import { warn } from 'core/util/index'
import { isAndroid, isIE9 } from 'web/util/index'
/* istanbul ignore if */
if (isIE9) {
// http://www.matts411.com/post/internet-explorer-9-oninput/
document.addEventListener('selectionchange', () => {
@ -16,7 +18,18 @@ if (isIE9) {
}
export default {
bind (el) {
bind (el, value, vnode) {
if (process.env.NODE_ENV !== 'production') {
const tag = el.tagName.toLowerCase()
if (!tag.match(/input|select|textarea/)) {
warn(
`v-model is not supported on element type: <${tag}>. ` +
'If you are working with contenteditable, it\'s recommended to ' +
'wrap a library dedicated for that purpose inside a custom component.',
vnode.context
)
}
}
if (!isAndroid) {
el.addEventListener('compositionstart', onCompositionStart)
el.addEventListener('compositionend', onCompositionEnd)

View File

@ -0,0 +1,6 @@
window.triggerEvent = function triggerEvent (target, event, process) {
var e = document.createEvent('HTMLEvents')
e.initEvent(event, true, true)
if (process) process(e)
target.dispatchEvent(e)
}

View File

@ -3,6 +3,7 @@
"jasmine": true
},
"globals": {
"waitForUpdate": true
"waitForUpdate": true,
"triggerEvent": true
}
}

View File

@ -0,0 +1,136 @@
import Vue from 'vue'
import { isIE9, isAndroid } from 'web/util/index'
describe('Directive v-model text', () => {
it('should update value both ways', done => {
const vm = new Vue({
data: {
test: 'b'
},
template: '<input v-model="test">'
}).$mount()
expect(vm.$el.value).toBe('b')
vm.test = 'a'
waitForUpdate(() => {
expect(vm.$el.value).toBe('a')
vm.$el.value = 'c'
triggerEvent(vm.$el, 'input')
expect(vm.test).toBe('c')
}).then(done)
})
it('.lazy modifier', () => {
const vm = new Vue({
data: {
test: 'b'
},
template: '<input v-model.lazy="test">'
}).$mount()
expect(vm.$el.value).toBe('b')
expect(vm.test).toBe('b')
vm.$el.value = 'c'
triggerEvent(vm.$el, 'input')
expect(vm.test).toBe('b')
triggerEvent(vm.$el, 'change')
expect(vm.test).toBe('c')
})
it('.number modifier', () => {
const vm = new Vue({
data: {
test: 1
},
template: '<input v-model.number="test">'
}).$mount()
expect(vm.test).toBe(1)
vm.$el.value = '2'
triggerEvent(vm.$el, 'input')
expect(vm.test).toBe(2)
})
it('.trim modifier', () => {
const vm = new Vue({
data: {
test: 'hi'
},
template: '<input v-model.trim="test">'
}).$mount()
expect(vm.test).toBe('hi')
vm.$el.value = ' what '
triggerEvent(vm.$el, 'input')
expect(vm.test).toBe('what')
})
if (isIE9) {
it('cut and delete', done => {
const vm = new Vue({
data: {
test: 'foo'
},
template: '<input v-model="test">'
}).$mount()
const input = vm.$el
input.value = 'bar'
triggerEvent(input, 'cut')
waitForUpdate(() => {
expect(vm.test).toBe('bar')
input.value = 'a'
triggerEvent(input, 'keyup', (e) => { e.keyCode = 8 })
expect(vm.test).toBe('a')
}).then(done)
})
}
if (!isAndroid) {
it('compositionevents', function (done) {
const vm = new Vue({
data: {
test: 'foo'
},
template: '<input v-model="test">'
}).$mount()
const input = vm.$el
triggerEvent(input, 'compositionstart')
input.value = 'baz'
// input before composition unlock should not call set
triggerEvent(input, 'input')
expect(vm.test).toBe('foo')
// after composition unlock it should work
triggerEvent(input, 'compositionend')
triggerEvent(input, 'input')
expect(vm.test).toBe('baz')
done()
})
}
it('warn inline value attribute', () => {
const vm = new Vue({
data: {
test: 'foo'
},
template: '<input v-model="test" value="bar">'
}).$mount()
expect(vm.test).toBe('foo')
expect(vm.$el.value).toBe('foo')
expect('inline value attributes will be ignored').toHaveBeenWarned()
})
it('warn textarea inline content', function () {
const vm = new Vue({
data: {
test: 'foo'
},
template: '<textarea v-model="test">bar</textarea>'
}).$mount()
expect(vm.test).toBe('foo')
expect(vm.$el.value).toBe('foo')
expect('inline content inside <textarea> will be ignored').toHaveBeenWarned()
})
it('warn invalid tag', () => {
new Vue({
template: '<div v-model="test"></div>'
}).$mount()
expect('v-model is not supported on element type: <div>').toHaveBeenWarned()
})
})

View File

@ -1,6 +1,7 @@
import Vue from 'vue'
import '../helpers/to-have-been-warned.js'
import '../helpers/wait-for-update.js'
import '../helpers/trigger-event.js'
Vue.config.preserveWhitespace = false