Skip to content

Commit 0bb529a

Browse files
committed
also warn when listening to camelCase events in in-DOM templates
1 parent e47b1e5 commit 0bb529a

File tree

6 files changed

+80
-43
lines changed

6 files changed

+80
-43
lines changed

src/core/instance/events.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* @flow */
22

3-
import { toArray } from '../util/index'
43
import { updateListeners } from '../vdom/helpers/index'
4+
import { toArray, tip, hyphenate, formatComponentName } from '../util/index'
55

66
export function initEvents (vm: Component) {
77
vm._events = Object.create(null)
@@ -104,6 +104,18 @@ export function eventsMixin (Vue: Class<Component>) {
104104

105105
Vue.prototype.$emit = function (event: string): Component {
106106
const vm: Component = this
107+
if (process.env.NODE_ENV !== 'production') {
108+
const lowerCaseEvent = event.toLowerCase()
109+
if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
110+
tip(
111+
`Event "${lowerCaseEvent}" is emitted in component ` +
112+
`${formatComponentName(vm)} but the handler is registered for "${event}". ` +
113+
`Note that HTML attributes are case-insensitive and you cannot use ` +
114+
`v-on to listen to camelCase events when using in-DOM templates. ` +
115+
`You should probably use "${hyphenate(event)}" instead of "${event}".`
116+
)
117+
}
118+
}
107119
let cbs = vm._events[event]
108120
if (cbs) {
109121
cbs = cbs.length > 1 ? toArray(cbs) : cbs

src/core/util/debug.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@ if (process.env.NODE_ENV !== 'production') {
3232
if (vm.$root === vm) {
3333
return '<Root>'
3434
}
35-
let name = typeof vm === 'function' && vm.options
36-
? vm.options.name
37-
: vm._isVue
38-
? vm.$options.name || vm.$options._componentTag
39-
: vm.name
35+
let name = typeof vm === 'string'
36+
? vm
37+
: typeof vm === 'function' && vm.options
38+
? vm.options.name
39+
: vm._isVue
40+
? vm.$options.name || vm.$options._componentTag
41+
: vm.name
4042

4143
const file = vm._isVue && vm.$options.__file
4244
if (!name && file) {

src/core/vdom/create-component.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { resolveConstructorOptions } from '../instance/init'
66
import { resolveSlots } from '../instance/render-helpers/resolve-slots'
77

88
import {
9+
tip,
910
warn,
1011
isObject,
1112
hasOwn,
@@ -133,7 +134,7 @@ export function createComponent (
133134
}
134135

135136
// extract props
136-
const propsData = extractProps(data, Ctor)
137+
const propsData = extractProps(data, Ctor, tag)
137138

138139
// functional component
139140
if (Ctor.options.functional) {
@@ -274,7 +275,7 @@ function resolveAsyncComponent (
274275
}
275276
}
276277

277-
function extractProps (data: VNodeData, Ctor: Class<Component>): ?Object {
278+
function extractProps (data: VNodeData, Ctor: Class<Component>, tag?: string): ?Object {
278279
// we are only extracting raw values here.
279280
// validation and default values are handled in the child
280281
// component itself.
@@ -293,12 +294,13 @@ function extractProps (data: VNodeData, Ctor: Class<Component>): ?Object {
293294
key !== keyInLowerCase &&
294295
attrs && attrs.hasOwnProperty(keyInLowerCase)
295296
) {
296-
warn(
297-
`Prop "${keyInLowerCase}" is not declared in component ` +
298-
`${formatComponentName(Ctor)}. Note that HTML attributes are ` +
299-
`case-insensitive and camelCased props need to use their kebab-case ` +
300-
`equivalents when using in-DOM templates. You should probably use ` +
301-
`"${altKey}" instead of "${key}".`
297+
tip(
298+
`Prop "${keyInLowerCase}" is passed to component ` +
299+
`${formatComponentName(tag || Ctor)}, but the delared prop name is` +
300+
` "${key}". ` +
301+
`Note that HTML attributes are case-insensitive and camelCased ` +
302+
`props need to use their kebab-case equivalents when using in-DOM ` +
303+
`templates. You should probably use "${altKey}" instead of "${key}".`
302304
)
303305
}
304306
}

test/helpers/to-have-been-warned.js

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,42 +11,47 @@ if (typeof console === 'undefined') {
1111
console.info = noop
1212

1313
let asserted
14-
function hasWarned (msg) {
15-
var count = console.error.calls.count()
16-
var args
17-
while (count--) {
18-
args = console.error.calls.argsFor(count)
19-
if (args.some(containsMsg)) {
20-
return true
14+
15+
function createCompareFn (spy) {
16+
const hasWarned = msg => {
17+
var count = spy.calls.count()
18+
var args
19+
while (count--) {
20+
args = spy.calls.argsFor(count)
21+
if (args.some(containsMsg)) {
22+
return true
23+
}
24+
}
25+
26+
function containsMsg (arg) {
27+
return arg.toString().indexOf(msg) > -1
2128
}
2229
}
2330

24-
function containsMsg (arg) {
25-
return arg.toString().indexOf(msg) > -1
31+
return {
32+
compare: msg => {
33+
asserted = asserted.concat(msg)
34+
var warned = Array.isArray(msg)
35+
? msg.some(hasWarned)
36+
: hasWarned(msg)
37+
return {
38+
pass: warned,
39+
message: warned
40+
? 'Expected message "' + msg + '" not to have been warned'
41+
: 'Expected message "' + msg + '" to have been warned'
42+
}
43+
}
2644
}
2745
}
2846

2947
// define custom matcher for warnings
3048
beforeEach(() => {
3149
asserted = []
50+
spyOn(console, 'warn')
3251
spyOn(console, 'error')
3352
jasmine.addMatchers({
34-
toHaveBeenWarned: () => {
35-
return {
36-
compare: msg => {
37-
asserted = asserted.concat(msg)
38-
var warned = Array.isArray(msg)
39-
? msg.some(hasWarned)
40-
: hasWarned(msg)
41-
return {
42-
pass: warned,
43-
message: warned
44-
? 'Expected message "' + msg + '" not to have been warned'
45-
: 'Expected message "' + msg + '" to have been warned'
46-
}
47-
}
48-
}
49-
}
53+
toHaveBeenWarned: () => createCompareFn(console.error),
54+
toHaveBeenTipped: () => createCompareFn(console.warn)
5055
})
5156
})
5257

test/unit/features/component/component.spec.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,12 +258,12 @@ describe('Component', () => {
258258
expect(vm.$el.outerHTML).toBe('<ul><li>1</li><li>2</li></ul>')
259259
})
260260

261-
it('should warn when not passing props in kebab-case', () => {
261+
it('should warn when using camelCased props in in-DOM template', () => {
262262
new Vue({
263263
data: {
264264
list: [{ a: 1 }, { a: 2 }]
265265
},
266-
template: '<test :somecollection="list"></test>',
266+
template: '<test :somecollection="list"></test>', // <-- simulate lowercased template
267267
components: {
268268
test: {
269269
template: '<ul><li v-for="item in someCollection">{{item.a}}</li></ul>',
@@ -273,7 +273,24 @@ describe('Component', () => {
273273
}).$mount()
274274
expect(
275275
'You should probably use "some-collection" instead of "someCollection".'
276-
).toHaveBeenWarned()
276+
).toHaveBeenTipped()
277+
})
278+
279+
it('should warn when using camelCased events in in-DOM template', () => {
280+
new Vue({
281+
template: '<test @foobar="a++"></test>', // <-- simulate lowercased template
282+
components: {
283+
test: {
284+
template: '<div></div>',
285+
created () {
286+
this.$emit('fooBar')
287+
}
288+
}
289+
}
290+
}).$mount()
291+
expect(
292+
'You should probably use "foo-bar" instead of "fooBar".'
293+
).toHaveBeenTipped()
277294
})
278295

279296
it('not found component should not throw', () => {

test/unit/modules/vdom/patch/hydration.spec.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import VNode from 'core/vdom/vnode'
55
describe('vdom patch: hydration', () => {
66
let vnode0
77
beforeEach(() => {
8-
spyOn(console, 'warn')
98
vnode0 = new VNode('p', { attrs: { id: '1' }}, [createTextVNode('hello world')])
109
patch(null, vnode0)
1110
})

0 commit comments

Comments
 (0)