From dbc0582587f90e78867809bb6ae683301cd0626b Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 25 Jan 2019 23:21:58 -0500 Subject: [PATCH] feat: dynamic directive arguments for v-on, v-bind and custom directives (#9373) --- flow/compiler.js | 12 +- flow/vnode.js | 1 + src/compiler/codegen/events.js | 27 ++-- src/compiler/codegen/index.js | 32 +++-- src/compiler/helpers.js | 50 +++++-- src/compiler/parser/index.js | 73 +++++++---- .../render-helpers/bind-dynamic-keys.js | 35 +++++ src/core/instance/render-helpers/index.js | 3 + src/core/vdom/modules/directives.js | 1 + test/unit/features/directives/bind.spec.js | 124 ++++++++++++++++++ test/unit/features/directives/on.spec.js | 117 +++++++++++++++++ test/unit/features/options/directives.spec.js | 22 ++++ test/unit/modules/compiler/codegen.spec.js | 20 +-- test/unit/modules/compiler/parser.spec.js | 9 +- types/vnode.d.ts | 1 + 15 files changed, 459 insertions(+), 68 deletions(-) create mode 100644 src/core/instance/render-helpers/bind-dynamic-keys.js diff --git a/flow/compiler.js b/flow/compiler.js index a1af7a6c..ad09a2e2 100644 --- a/flow/compiler.js +++ b/flow/compiler.js @@ -61,12 +61,20 @@ declare type ModuleOptions = { declare type ASTModifiers = { [key: string]: boolean }; declare type ASTIfCondition = { exp: ?string; block: ASTElement }; declare type ASTIfConditions = Array; -declare type ASTAttr = { name: string; value: any; start?: number; end?: number }; + +declare type ASTAttr = { + name: string; + value: any; + dynamic?: boolean; + start?: number; + end?: number +}; declare type ASTElementHandler = { value: string; params?: Array; modifiers: ?ASTModifiers; + dynamic?: boolean; start?: number; end?: number; }; @@ -80,6 +88,7 @@ declare type ASTDirective = { rawName: string; value: string; arg: ?string; + isDynamicArg: boolean; modifiers: ?ASTModifiers; start?: number; end?: number; @@ -109,6 +118,7 @@ declare type ASTElement = { text?: string; attrs?: Array; + dynamicAttrs?: Array; props?: Array; plain?: boolean; pre?: true; diff --git a/flow/vnode.js b/flow/vnode.js index 46fe3dea..0521b33c 100644 --- a/flow/vnode.js +++ b/flow/vnode.js @@ -71,6 +71,7 @@ declare type VNodeDirective = { value?: any; oldValue?: any; arg?: string; + oldArg?: string; modifiers?: ASTModifiers; def?: Object; }; diff --git a/src/compiler/codegen/events.js b/src/compiler/codegen/events.js index 1beaed76..4a6f9af2 100644 --- a/src/compiler/codegen/events.js +++ b/src/compiler/codegen/events.js @@ -56,11 +56,23 @@ export function genHandlers ( events: ASTElementHandlers, isNative: boolean ): string { - let res = isNative ? 'nativeOn:{' : 'on:{' + const prefix = isNative ? 'nativeOn:' : 'on:' + let staticHandlers = `` + let dynamicHandlers = `` for (const name in events) { - res += `"${name}":${genHandler(name, events[name])},` + const handlerCode = genHandler(events[name]) + if (events[name] && events[name].dynamic) { + dynamicHandlers += `${name},${handlerCode},` + } else { + staticHandlers += `"${name}":${handlerCode},` + } + } + staticHandlers = `{${staticHandlers.slice(0, -1)}}` + if (dynamicHandlers) { + return prefix + `_d(${staticHandlers},[${dynamicHandlers.slice(0, -1)}])` + } else { + return prefix + staticHandlers } - return res.slice(0, -1) + '}' } // Generate handler code with binding params on Weex @@ -81,16 +93,13 @@ function genWeexHandler (params: Array, handlerCode: string) { '}' } -function genHandler ( - name: string, - handler: ASTElementHandler | Array -): string { +function genHandler (handler: ASTElementHandler | Array): string { if (!handler) { return 'function(){}' } if (Array.isArray(handler)) { - return `[${handler.map(handler => genHandler(name, handler)).join(',')}]` + return `[${handler.map(handler => genHandler(handler)).join(',')}]` } const isMethodPath = simplePathRE.test(handler.value) @@ -154,7 +163,7 @@ function genHandler ( } function genKeyFilter (keys: Array): string { - return `if(!('button' in $event)&&${keys.map(genFilterCode).join('&&')})return null;` + return `if(('keyCode' in $event)&&${keys.map(genFilterCode).join('&&')})return null;` } function genFilterCode (key: string): string { diff --git a/src/compiler/codegen/index.js b/src/compiler/codegen/index.js index 9f23b37e..c8b701f0 100644 --- a/src/compiler/codegen/index.js +++ b/src/compiler/codegen/index.js @@ -248,11 +248,11 @@ export function genData (el: ASTElement, state: CodegenState): string { } // attributes if (el.attrs) { - data += `attrs:{${genProps(el.attrs)}},` + data += `attrs:${genProps(el.attrs)},` } // DOM props if (el.props) { - data += `domProps:{${genProps(el.props)}},` + data += `domProps:${genProps(el.props)},` } // event handlers if (el.events) { @@ -288,6 +288,12 @@ export function genData (el: ASTElement, state: CodegenState): string { } } data = data.replace(/,$/, '') + '}' + // v-bind dynamic argument wrap + // v-bind with dynamic arguments must be applied using the same v-bind object + // merge helper so that class/style/mustUseProp attrs are handled correctly. + if (el.dynamicAttrs) { + data = `_b(${data},"${el.tag}",${genProps(el.dynamicAttrs)})` + } // v-bind data wrap if (el.wrapData) { data = el.wrapData(data) @@ -319,7 +325,7 @@ function genDirectives (el: ASTElement, state: CodegenState): string | void { res += `{name:"${dir.name}",rawName:"${dir.rawName}"${ dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : '' }${ - dir.arg ? `,arg:"${dir.arg}"` : '' + dir.arg ? `,arg:${dir.isDynamicArg ? dir.arg : `"${dir.arg}"`}` : '' }${ dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : '' }},` @@ -510,17 +516,25 @@ function genComponent ( } function genProps (props: Array): string { - let res = '' + let staticProps = `` + let dynamicProps = `` for (let i = 0; i < props.length; i++) { const prop = props[i] - /* istanbul ignore if */ - if (__WEEX__) { - res += `"${prop.name}":${generateValue(prop.value)},` + const value = __WEEX__ + ? generateValue(prop.value) + : transformSpecialNewlines(prop.value) + if (prop.dynamic) { + dynamicProps += `${prop.name},${value},` } else { - res += `"${prop.name}":${transformSpecialNewlines(prop.value)},` + staticProps += `"${prop.name}":${value},` } } - return res.slice(0, -1) + staticProps = `{${staticProps.slice(0, -1)}}` + if (dynamicProps) { + return `_d(${staticProps},[${dynamicProps.slice(0, -1)}])` + } else { + return staticProps + } } /* istanbul ignore next */ diff --git a/src/compiler/helpers.js b/src/compiler/helpers.js index 0dd80b03..7231d684 100644 --- a/src/compiler/helpers.js +++ b/src/compiler/helpers.js @@ -20,13 +20,16 @@ export function pluckModuleFunction ( : [] } -export function addProp (el: ASTElement, name: string, value: string, range?: Range) { - (el.props || (el.props = [])).push(rangeSetItem({ name, value }, range)) +export function addProp (el: ASTElement, name: string, value: string, range?: Range, dynamic?: boolean) { + (el.props || (el.props = [])).push(rangeSetItem({ name, value, dynamic }, range)) el.plain = false } -export function addAttr (el: ASTElement, name: string, value: any, range?: Range) { - (el.attrs || (el.attrs = [])).push(rangeSetItem({ name, value }, range)) +export function addAttr (el: ASTElement, name: string, value: any, range?: Range, dynamic?: boolean) { + const attrs = dynamic + ? (el.dynamicAttrs || (el.dynamicAttrs = [])) + : (el.attrs || (el.attrs = [])) + attrs.push(rangeSetItem({ name, value, dynamic }, range)) el.plain = false } @@ -42,13 +45,27 @@ export function addDirective ( rawName: string, value: string, arg: ?string, + isDynamicArg: boolean, modifiers: ?ASTModifiers, range?: Range ) { - (el.directives || (el.directives = [])).push(rangeSetItem({ name, rawName, value, arg, modifiers }, range)) + (el.directives || (el.directives = [])).push(rangeSetItem({ + name, + rawName, + value, + arg, + isDynamicArg, + modifiers + }, range)) el.plain = false } +function prependModifierMarker (symbol: string, name: string, dynamic?: boolean): string { + return dynamic + ? `_p(${name},"${symbol}")` + : symbol + name // mark the event as captured +} + export function addHandler ( el: ASTElement, name: string, @@ -56,7 +73,8 @@ export function addHandler ( modifiers: ?ASTModifiers, important?: boolean, warn?: ?Function, - range?: Range + range?: Range, + dynamic?: boolean ) { modifiers = modifiers || emptyObject // warn prevent and passive modifier @@ -75,11 +93,17 @@ export function addHandler ( // normalize click.right and click.middle since they don't actually fire // this is technically browser-specific, but at least for now browsers are // the only target envs that have right/middle clicks. - if (name === 'click') { - if (modifiers.right) { + if (modifiers.right) { + if (dynamic) { + name = `(${name})==='click'?'contextmenu':(${name})` + } else if (name === 'click') { name = 'contextmenu' delete modifiers.right - } else if (modifiers.middle) { + } + } else if (modifiers.middle) { + if (dynamic) { + name = `(${name})==='click'?'mouseup':(${name})` + } else if (name === 'click') { name = 'mouseup' } } @@ -87,16 +111,16 @@ export function addHandler ( // check capture modifier if (modifiers.capture) { delete modifiers.capture - name = '!' + name // mark the event as captured + name = prependModifierMarker('!', name, dynamic) } if (modifiers.once) { delete modifiers.once - name = '~' + name // mark the event as once + name = prependModifierMarker('~', name, dynamic) } /* istanbul ignore if */ if (modifiers.passive) { delete modifiers.passive - name = '&' + name // mark the event as passive + name = prependModifierMarker('&', name, dynamic) } let events @@ -107,7 +131,7 @@ export function addHandler ( events = el.events || (el.events = {}) } - const newHandler: any = rangeSetItem({ value: value.trim() }, range) + const newHandler: any = rangeSetItem({ value: value.trim(), dynamic }, range) if (modifiers !== emptyObject) { newHandler.modifiers = modifiers } diff --git a/src/compiler/parser/index.js b/src/compiler/parser/index.js index 601dd8f2..e3433928 100644 --- a/src/compiler/parser/index.js +++ b/src/compiler/parser/index.js @@ -26,7 +26,7 @@ export const dirRE = /^v-|^@|^:|^\./ export const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/ export const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/ const stripParensRE = /^\(|\)$/g -const dynamicKeyRE = /^\[.*\]$/ +const dynamicArgRE = /^\[.*\]$/ const argRE = /:(.*)$/ export const bindRE = /^:|^\.|^v-bind:/ @@ -666,7 +666,7 @@ function getSlotName (binding) { ) } } - return dynamicKeyRE.test(name) + return dynamicArgRE.test(name) // dynamic [name] ? { name: name.slice(1, -1), dynamic: true } // static name @@ -700,7 +700,7 @@ function processComponent (el) { function processAttrs (el) { const list = el.attrsList - let i, l, name, rawName, value, modifiers, isProp, syncGen + let i, l, name, rawName, value, modifiers, syncGen, isDynamic for (i = 0, l = list.length; i < l; i++) { name = rawName = list[i].name value = list[i].value @@ -719,7 +719,10 @@ function processAttrs (el) { if (bindRE.test(name)) { // v-bind name = name.replace(bindRE, '') value = parseFilters(value) - isProp = false + isDynamic = dynamicArgRE.test(name) + if (isDynamic) { + name = name.slice(1, -1) + } if ( process.env.NODE_ENV !== 'production' && value.trim().length === 0 @@ -729,57 +732,79 @@ function processAttrs (el) { ) } if (modifiers) { - if (modifiers.prop) { - isProp = true + if (modifiers.prop && !isDynamic) { name = camelize(name) if (name === 'innerHtml') name = 'innerHTML' } - if (modifiers.camel) { + if (modifiers.camel && !isDynamic) { name = camelize(name) } if (modifiers.sync) { syncGen = genAssignmentCode(value, `$event`) - addHandler( - el, - `update:${camelize(name)}`, - syncGen, - null, - false, - warn, - list[i] - ) - if (hyphenate(name) !== camelize(name)) { + if (!isDynamic) { addHandler( el, - `update:${hyphenate(name)}`, + `update:${camelize(name)}`, syncGen, null, false, warn, list[i] ) + if (hyphenate(name) !== camelize(name)) { + addHandler( + el, + `update:${hyphenate(name)}`, + syncGen, + null, + false, + warn, + list[i] + ) + } + } else { + // handler w/ dynamic event name + addHandler( + el, + `"update:"+(${name})`, + syncGen, + null, + false, + warn, + list[i], + true // dynamic + ) } } } - if (isProp || ( + if ((modifiers && modifiers.prop) || ( !el.component && platformMustUseProp(el.tag, el.attrsMap.type, name) )) { - addProp(el, name, value, list[i]) + addProp(el, name, value, list[i], isDynamic) } else { - addAttr(el, name, value, list[i]) + addAttr(el, name, value, list[i], isDynamic) } } else if (onRE.test(name)) { // v-on name = name.replace(onRE, '') - addHandler(el, name, value, modifiers, false, warn, list[i]) + isDynamic = dynamicArgRE.test(name) + if (isDynamic) { + name = name.slice(1, -1) + } + addHandler(el, name, value, modifiers, false, warn, list[i], isDynamic) } else { // normal directives name = name.replace(dirRE, '') // parse arg const argMatch = name.match(argRE) - const arg = argMatch && argMatch[1] + let arg = argMatch && argMatch[1] + isDynamic = false if (arg) { name = name.slice(0, -(arg.length + 1)) + if (dynamicArgRE.test(arg)) { + arg = arg.slice(1, -1) + isDynamic = true + } } - addDirective(el, name, rawName, value, arg, modifiers, list[i]) + addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i]) if (process.env.NODE_ENV !== 'production' && name === 'model') { checkForAliasModel(el, value) } diff --git a/src/core/instance/render-helpers/bind-dynamic-keys.js b/src/core/instance/render-helpers/bind-dynamic-keys.js new file mode 100644 index 00000000..ff37fc48 --- /dev/null +++ b/src/core/instance/render-helpers/bind-dynamic-keys.js @@ -0,0 +1,35 @@ +/* @flow */ + +// helper to process dynamic keys for dynamic arguments in v-bind and v-on. +// For example, the following template: +// +//
+// +// compiles to the following: +// +// _c('div', { attrs: bindDynamicKeys({ "id": "app" }, [key, value]) }) + +import { warn } from 'core/util/debug' + +export function bindDynamicKeys (baseObj: Object, values: Array): Object { + for (let i = 0; i < values.length; i += 2) { + const key = values[i] + if (typeof key === 'string' && key) { + baseObj[values[i]] = values[i + 1] + } else if (process.env.NODE_ENV !== 'production' && key !== '' && key !== null) { + // null is a speical value for explicitly removing a binding + warn( + `Invalid value for dynamic directive argument (expected string or null): ${key}`, + this + ) + } + } + return baseObj +} + +// helper to dynamically append modifier runtime markers to event names. +// ensure only append when value is already string, otherwise it will be cast +// to string and cause the type check to miss. +export function prependModifier (value: any, symbol: string): any { + return typeof value === 'string' ? symbol + value : value +} diff --git a/src/core/instance/render-helpers/index.js b/src/core/instance/render-helpers/index.js index 38414a9a..e9280160 100644 --- a/src/core/instance/render-helpers/index.js +++ b/src/core/instance/render-helpers/index.js @@ -10,6 +10,7 @@ import { bindObjectProps } from './bind-object-props' import { renderStatic, markOnce } from './render-static' import { bindObjectListeners } from './bind-object-listeners' import { resolveScopedSlots } from './resolve-slots' +import { bindDynamicKeys, prependModifier } from './bind-dynamic-keys' export function installRenderHelpers (target: any) { target._o = markOnce @@ -27,4 +28,6 @@ export function installRenderHelpers (target: any) { target._e = createEmptyVNode target._u = resolveScopedSlots target._g = bindObjectListeners + target._d = bindDynamicKeys + target._p = prependModifier } diff --git a/src/core/vdom/modules/directives.js b/src/core/vdom/modules/directives.js index 639982fa..ed76c627 100644 --- a/src/core/vdom/modules/directives.js +++ b/src/core/vdom/modules/directives.js @@ -40,6 +40,7 @@ function _update (oldVnode, vnode) { } else { // existing directive, update dir.oldValue = oldDir.value + dir.oldArg = oldDir.arg callHook(dir, 'update', vnode, oldVnode) if (dir.def && dir.def.componentUpdated) { dirsWithPostpatch.push(dir) diff --git a/test/unit/features/directives/bind.spec.js b/test/unit/features/directives/bind.spec.js index 8d7f7c48..d0222450 100644 --- a/test/unit/features/directives/bind.spec.js +++ b/test/unit/features/directives/bind.spec.js @@ -473,4 +473,128 @@ describe('Directive v-bind', () => { expect(vm.$el.innerHTML).toBe('
comp
') }) }) + + describe('dynamic arguments', () => { + it('basic', done => { + const vm = new Vue({ + template: `
`, + data: { + key: 'id', + value: 'hello' + } + }).$mount() + expect(vm.$el.id).toBe('hello') + vm.key = 'class' + waitForUpdate(() => { + expect(vm.$el.id).toBe('') + expect(vm.$el.className).toBe('hello') + // explicit null value + vm.key = null + }).then(() => { + expect(vm.$el.className).toBe('') + expect(vm.$el.id).toBe('') + vm.key = undefined + }).then(() => { + expect(`Invalid value for dynamic directive argument`).toHaveBeenWarned() + }).then(done) + }) + + it('shorthand', done => { + const vm = new Vue({ + template: `
`, + data: { + key: 'id', + value: 'hello' + } + }).$mount() + expect(vm.$el.id).toBe('hello') + vm.key = 'class' + waitForUpdate(() => { + expect(vm.$el.className).toBe('hello') + }).then(done) + }) + + it('with .prop modifier', done => { + const vm = new Vue({ + template: `
`, + data: { + key: 'id', + value: 'hello' + } + }).$mount() + expect(vm.$el.id).toBe('hello') + vm.key = 'textContent' + waitForUpdate(() => { + expect(vm.$el.textContent).toBe('hello') + }).then(done) + }) + + it('.prop shorthand', done => { + const vm = new Vue({ + template: `
`, + data: { + key: 'id', + value: 'hello' + } + }).$mount() + expect(vm.$el.id).toBe('hello') + vm.key = 'textContent' + waitForUpdate(() => { + expect(vm.$el.textContent).toBe('hello') + }).then(done) + }) + + it('handle class and style', () => { + const vm = new Vue({ + template: `
`, + data: { + key: 'class', + value: ['hello', 'world'], + key2: 'style', + value2: { + color: 'red' + } + } + }).$mount() + expect(vm.$el.className).toBe('hello world') + expect(vm.$el.style.color).toBe('red') + }) + + it('handle shouldUseProp', done => { + const vm = new Vue({ + template: ``, + data: { + key: 'value', + value: 'foo' + } + }).$mount() + expect(vm.$el.value).toBe('foo') + vm.value = 'bar' + waitForUpdate(() => { + expect(vm.$el.value).toBe('bar') + }).then(done) + }) + + it('with .sync modifier', done => { + const vm = new Vue({ + template: ``, + data: { + key: 'foo', + value: 'bar' + }, + components: { + foo: { + props: ['foo'], + template: `
{{ foo }}
` + } + } + }).$mount() + expect(vm.$el.textContent).toBe('bar') + vm.$refs.child.$emit('update:foo', 'baz') + waitForUpdate(() => { + expect(vm.value).toBe('baz') + expect(vm.$el.textContent).toBe('baz') + }).then(done) + }) + }) }) diff --git a/test/unit/features/directives/on.spec.js b/test/unit/features/directives/on.spec.js index 74d65376..a97ddaa8 100644 --- a/test/unit/features/directives/on.spec.js +++ b/test/unit/features/directives/on.spec.js @@ -947,4 +947,121 @@ describe('Directive v-on', () => { }).$mount() expect(value).toBe(1) }) + + describe('dynamic arguments', () => { + it('basic', done => { + const spy = jasmine.createSpy() + const vm = new Vue({ + template: `
`, + data: { + key: 'click' + }, + methods: { + spy + } + }).$mount() + triggerEvent(vm.$el, 'click') + expect(spy.calls.count()).toBe(1) + vm.key = 'mouseup' + waitForUpdate(() => { + triggerEvent(vm.$el, 'click') + expect(spy.calls.count()).toBe(1) + triggerEvent(vm.$el, 'mouseup') + expect(spy.calls.count()).toBe(2) + // explicit null value + vm.key = null + }).then(() => { + triggerEvent(vm.$el, 'click') + expect(spy.calls.count()).toBe(2) + triggerEvent(vm.$el, 'mouseup') + expect(spy.calls.count()).toBe(2) + }).then(done) + }) + + it('shorthand', done => { + const spy = jasmine.createSpy() + const vm = new Vue({ + template: `
`, + data: { + key: 'click' + }, + methods: { + spy + } + }).$mount() + triggerEvent(vm.$el, 'click') + expect(spy.calls.count()).toBe(1) + vm.key = 'mouseup' + waitForUpdate(() => { + triggerEvent(vm.$el, 'click') + expect(spy.calls.count()).toBe(1) + triggerEvent(vm.$el, 'mouseup') + expect(spy.calls.count()).toBe(2) + }).then(done) + }) + + it('with .middle modifier', () => { + const spy = jasmine.createSpy() + const vm = new Vue({ + template: `
`, + data: { + key: 'click' + }, + methods: { + spy + } + }).$mount() + triggerEvent(vm.$el, 'mouseup', e => { e.button = 0 }) + expect(spy).not.toHaveBeenCalled() + triggerEvent(vm.$el, 'mouseup', e => { e.button = 1 }) + expect(spy).toHaveBeenCalled() + }) + + it('with .right modifier', () => { + const spy = jasmine.createSpy() + const vm = new Vue({ + template: `
`, + data: { + key: 'click' + }, + methods: { + spy + } + }).$mount() + triggerEvent(vm.$el, 'contextmenu') + expect(spy).toHaveBeenCalled() + }) + + it('with .capture modifier', () => { + const callOrder = [] + const vm = new Vue({ + template: ` +
+
+
+ `, + data: { + key: 'click' + }, + methods: { + foo () { callOrder.push(1) }, + bar () { callOrder.push(2) } + } + }).$mount() + triggerEvent(vm.$el.firstChild, 'click') + expect(callOrder.toString()).toBe('1,2') + }) + + it('with .once modifier', () => { + const vm = new Vue({ + template: `
`, + data: { key: 'click' }, + methods: { foo: spy } + }).$mount() + triggerEvent(vm.$el, 'click') + expect(spy.calls.count()).toBe(1) + triggerEvent(vm.$el, 'click') + expect(spy.calls.count()).toBe(1) // should no longer trigger + }) + }) }) diff --git a/test/unit/features/options/directives.spec.js b/test/unit/features/options/directives.spec.js index f15dd77f..4f9a23ce 100644 --- a/test/unit/features/options/directives.spec.js +++ b/test/unit/features/options/directives.spec.js @@ -265,4 +265,26 @@ describe('Options directives', () => { expect(dir.unbind.calls.argsFor(0)[0]).toBe(oldEl) }).then(done) }) + + it('dynamic arguments', done => { + const vm = new Vue({ + template: `
`, + data: { + key: 'foo' + }, + directives: { + my: { + bind(el, binding) { + expect(binding.arg).toBe('foo') + }, + update(el, binding) { + expect(binding.arg).toBe('bar') + expect(binding.oldArg).toBe('foo') + done() + } + } + } + }).$mount() + vm.key = 'bar' + }) }) diff --git a/test/unit/modules/compiler/codegen.spec.js b/test/unit/modules/compiler/codegen.spec.js index d857ecbc..d243f24a 100644 --- a/test/unit/modules/compiler/codegen.spec.js +++ b/test/unit/modules/compiler/codegen.spec.js @@ -356,37 +356,37 @@ describe('codegen', () => { it('generate events with keycode', () => { assertCodegen( '', - `with(this){return _c('input',{on:{"input":function($event){if(!('button' in $event)&&_k($event.keyCode,"enter",13,$event.key,"Enter"))return null;return onInput($event)}}})}` + `with(this){return _c('input',{on:{"input":function($event){if(('keyCode' in $event)&&_k($event.keyCode,"enter",13,$event.key,"Enter"))return null;return onInput($event)}}})}` ) // multiple keycodes (delete) assertCodegen( '', - `with(this){return _c('input',{on:{"input":function($event){if(!('button' in $event)&&_k($event.keyCode,"delete",[8,46],$event.key,["Backspace","Delete","Del"]))return null;return onInput($event)}}})}` + `with(this){return _c('input',{on:{"input":function($event){if(('keyCode' in $event)&&_k($event.keyCode,"delete",[8,46],$event.key,["Backspace","Delete","Del"]))return null;return onInput($event)}}})}` ) // multiple keycodes (esc) assertCodegen( '', - `with(this){return _c('input',{on:{"input":function($event){if(!('button' in $event)&&_k($event.keyCode,"esc",27,$event.key,["Esc","Escape"]))return null;return onInput($event)}}})}` + `with(this){return _c('input',{on:{"input":function($event){if(('keyCode' in $event)&&_k($event.keyCode,"esc",27,$event.key,["Esc","Escape"]))return null;return onInput($event)}}})}` ) // multiple keycodes (space) assertCodegen( '', - `with(this){return _c('input',{on:{"input":function($event){if(!('button' in $event)&&_k($event.keyCode,"space",32,$event.key,[" ","Spacebar"]))return null;return onInput($event)}}})}` + `with(this){return _c('input',{on:{"input":function($event){if(('keyCode' in $event)&&_k($event.keyCode,"space",32,$event.key,[" ","Spacebar"]))return null;return onInput($event)}}})}` ) // multiple keycodes (chained) assertCodegen( '', - `with(this){return _c('input',{on:{"keydown":function($event){if(!('button' in $event)&&_k($event.keyCode,"enter",13,$event.key,"Enter")&&_k($event.keyCode,"delete",[8,46],$event.key,["Backspace","Delete","Del"]))return null;return onInput($event)}}})}` + `with(this){return _c('input',{on:{"keydown":function($event){if(('keyCode' in $event)&&_k($event.keyCode,"enter",13,$event.key,"Enter")&&_k($event.keyCode,"delete",[8,46],$event.key,["Backspace","Delete","Del"]))return null;return onInput($event)}}})}` ) // number keycode assertCodegen( '', - `with(this){return _c('input',{on:{"input":function($event){if(!('button' in $event)&&$event.keyCode!==13)return null;return onInput($event)}}})}` + `with(this){return _c('input',{on:{"input":function($event){if(('keyCode' in $event)&&$event.keyCode!==13)return null;return onInput($event)}}})}` ) // custom keycode assertCodegen( '', - `with(this){return _c('input',{on:{"input":function($event){if(!('button' in $event)&&_k($event.keyCode,"custom",undefined,$event.key,undefined))return null;return onInput($event)}}})}` + `with(this){return _c('input',{on:{"input":function($event){if(('keyCode' in $event)&&_k($event.keyCode,"custom",undefined,$event.key,undefined))return null;return onInput($event)}}})}` ) }) @@ -409,12 +409,12 @@ describe('codegen', () => { it('generate events with generic modifiers and keycode correct order', () => { assertCodegen( '', - `with(this){return _c('input',{on:{"keydown":function($event){if(!('button' in $event)&&_k($event.keyCode,"enter",13,$event.key,"Enter"))return null;$event.preventDefault();return onInput($event)}}})}` + `with(this){return _c('input',{on:{"keydown":function($event){if(('keyCode' in $event)&&_k($event.keyCode,"enter",13,$event.key,"Enter"))return null;$event.preventDefault();return onInput($event)}}})}` ) assertCodegen( '', - `with(this){return _c('input',{on:{"keydown":function($event){if(!('button' in $event)&&_k($event.keyCode,"enter",13,$event.key,"Enter"))return null;$event.stopPropagation();return onInput($event)}}})}` + `with(this){return _c('input',{on:{"keydown":function($event){if(('keyCode' in $event)&&_k($event.keyCode,"enter",13,$event.key,"Enter"))return null;$event.stopPropagation();return onInput($event)}}})}` ) }) @@ -521,7 +521,7 @@ describe('codegen', () => { // with modifiers assertCodegen( ``, - `with(this){return _c('input',{on:{"keyup":function($event){if(!('button' in $event)&&_k($event.keyCode,"enter",13,$event.key,"Enter"))return null;return (e=>current++)($event)}}})}` + `with(this){return _c('input',{on:{"keyup":function($event){if(('keyCode' in $event)&&_k($event.keyCode,"enter",13,$event.key,"Enter"))return null;return (e=>current++)($event)}}})}` ) }) diff --git a/test/unit/modules/compiler/parser.spec.js b/test/unit/modules/compiler/parser.spec.js index 4b7b0937..fcfb8be6 100644 --- a/test/unit/modules/compiler/parser.spec.js +++ b/test/unit/modules/compiler/parser.spec.js @@ -537,12 +537,17 @@ describe('parser', () => { it('v-bind.prop shorthand syntax', () => { const ast = parse('
', baseOptions) - expect(ast.props).toEqual([{ name: 'id', value: 'foo'}]) + expect(ast.props).toEqual([{ name: 'id', value: 'foo', dynamic: false }]) }) it('v-bind.prop shorthand syntax w/ modifiers', () => { const ast = parse('
', baseOptions) - expect(ast.props).toEqual([{ name: 'id', value: 'foo'}]) + expect(ast.props).toEqual([{ name: 'id', value: 'foo', dynamic: false }]) + }) + + it('v-bind dynamic argument', () => { + const ast = parse('
', baseOptions) + expect(ast.props).toEqual([{ name: 'id', value: 'foo', dynamic: true }]) }) // #6887 diff --git a/types/vnode.d.ts b/types/vnode.d.ts index 11161506..d296ee2e 100644 --- a/types/vnode.d.ts +++ b/types/vnode.d.ts @@ -67,5 +67,6 @@ export interface VNodeDirective { oldValue?: any; expression?: any; arg?: string; + oldArg?: string; modifiers?: { [key: string]: boolean }; }