expect(`event validation failed for event "foo"`).toHaveBeenWarned()
})
+ test('merging from mixins', () => {
+ const mixin = {
+ emits: {
+ foo: (arg: number) => arg > 0
+ }
+ }
+ const Foo = defineComponent({
+ mixins: [mixin],
+ render() {},
+ created() {
+ this.$emit('foo', -1)
+ }
+ })
+ render(h(Foo), nodeOps.createElement('div'))
+ expect(`event validation failed for event "foo"`).toHaveBeenWarned()
+ })
+
test('isEmitListener', () => {
- expect(isEmitListener(['click'], 'onClick')).toBe(true)
- expect(isEmitListener(['click'], 'onclick')).toBe(false)
- expect(isEmitListener({ click: null }, 'onClick')).toBe(true)
- expect(isEmitListener({ click: null }, 'onclick')).toBe(false)
- expect(isEmitListener(['click'], 'onBlick')).toBe(false)
- expect(isEmitListener({ click: null }, 'onBlick')).toBe(false)
+ const def1 = { emits: ['click'] }
+ expect(isEmitListener(def1, 'onClick')).toBe(true)
+ expect(isEmitListener(def1, 'onclick')).toBe(false)
+ expect(isEmitListener(def1, 'onBlick')).toBe(false)
+
+ const def2 = { emits: { click: null } }
+ expect(isEmitListener(def2, 'onClick')).toBe(true)
+ expect(isEmitListener(def2, 'onclick')).toBe(false)
+ expect(isEmitListener(def2, 'onBlick')).toBe(false)
+
+ const mixin1 = { emits: ['foo'] }
+ const mixin2 = { emits: ['bar'] }
+ const extend = { emits: ['baz'] }
+ const def3 = {
+ emits: { click: null },
+ mixins: [mixin1, mixin2],
+ extends: extend
+ }
+ expect(isEmitListener(def3, 'onClick')).toBe(true)
+ expect(isEmitListener(def3, 'onFoo')).toBe(true)
+ expect(isEmitListener(def3, 'onBar')).toBe(true)
+ expect(isEmitListener(def3, 'onBaz')).toBe(true)
+ expect(isEmitListener(def3, 'onclick')).toBe(false)
+ expect(isEmitListener(def3, 'onBlick')).toBe(false)
})
})
* @internal
*/
__props?: NormalizedPropsOptions | []
+ /**
+ * @internal
+ */
+ __emits?: ObjectEmitsOptions
/**
* @internal
*/
capitalize,
hyphenate,
isFunction,
- def
+ extend
} from '@vue/shared'
-import { ComponentInternalInstance } from './component'
+import { ComponentInternalInstance, Component } from './component'
import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
import { warn } from './warning'
import { normalizePropsOptions } from './componentProps'
const props = instance.vnode.props || EMPTY_OBJ
if (__DEV__) {
- const options = normalizeEmitsOptions(instance.type.emits)
+ const options = normalizeEmitsOptions(instance.type)
if (options) {
if (!(event in options)) {
const propsOptions = normalizePropsOptions(instance.type)[0]
}
}
-export function normalizeEmitsOptions(
- options: EmitsOptions | undefined
+function normalizeEmitsOptions(
+ comp: Component
): ObjectEmitsOptions | undefined {
- if (!options) {
- return
- } else if (isArray(options)) {
- if ((options as any)._n) {
- return (options as any)._n
+ if (hasOwn(comp, '__emits')) {
+ return comp.__emits
+ }
+
+ const raw = comp.emits
+ let normalized: ObjectEmitsOptions = {}
+
+ // apply mixin/extends props
+ let hasExtends = false
+ if (__FEATURE_OPTIONS__ && !isFunction(comp)) {
+ if (comp.extends) {
+ hasExtends = true
+ extend(normalized, normalizeEmitsOptions(comp.extends))
}
- const normalized: ObjectEmitsOptions = {}
- options.forEach(key => (normalized[key] = null))
- def(options, '_n', normalized)
- return normalized
+ if (comp.mixins) {
+ hasExtends = true
+ comp.mixins.forEach(m => extend(normalized, normalizeEmitsOptions(m)))
+ }
+ }
+
+ if (!raw && !hasExtends) {
+ return (comp.__emits = undefined)
+ }
+
+ if (isArray(raw)) {
+ raw.forEach(key => (normalized[key] = null))
} else {
- return options
+ extend(normalized, raw)
}
+ return (comp.__emits = normalized)
}
// Check if an incoming prop key is a declared emit event listener.
// e.g. With `emits: { click: null }`, props named `onClick` and `onclick` are
// both considered matched listeners.
-export function isEmitListener(emits: EmitsOptions, key: string): boolean {
+export function isEmitListener(comp: Component, key: string): boolean {
+ if (!isOn(key)) {
+ return false
+ }
+ const emits = normalizeEmitsOptions(comp)
return (
- isOn(key) &&
- (hasOwn(
- (emits = normalizeEmitsOptions(emits) as ObjectEmitsOptions),
- key[2].toLowerCase() + key.slice(3)
- ) ||
+ !!emits &&
+ (hasOwn(emits, key[2].toLowerCase() + key.slice(3)) ||
hasOwn(emits, key.slice(2)))
)
}
attrs: Data
) {
const [options, needCastKeys] = normalizePropsOptions(instance.type)
- const emits = instance.type.emits
-
if (rawProps) {
for (const key in rawProps) {
const value = rawProps[key]
let camelKey
if (options && hasOwn(options, (camelKey = camelize(key)))) {
props[camelKey] = value
- } else if (!emits || !isEmitListener(emits, key)) {
+ } else if (!isEmitListener(instance.type, key)) {
// Any non-declared (either as a prop or an emitted event) props are put
// into a separate `attrs` object for spreading. Make sure to preserve
// original key casing