2020-04-04 08:40:34 +08:00
|
|
|
// Note: emits and listener fallthrough is tested in
|
|
|
|
// ./rendererAttrsFallthrough.spec.ts.
|
|
|
|
|
2021-04-02 07:47:58 +08:00
|
|
|
import {
|
|
|
|
render,
|
|
|
|
defineComponent,
|
|
|
|
h,
|
|
|
|
nodeOps,
|
2022-04-14 11:47:24 +08:00
|
|
|
toHandlers,
|
|
|
|
nextTick
|
2021-04-02 07:47:58 +08:00
|
|
|
} from '@vue/runtime-test'
|
2020-04-04 08:40:34 +08:00
|
|
|
import { isEmitListener } from '../src/componentEmits'
|
|
|
|
|
2020-04-10 22:59:46 +08:00
|
|
|
describe('component: emit', () => {
|
2020-04-10 23:57:07 +08:00
|
|
|
test('trigger handlers', () => {
|
2020-04-04 08:40:34 +08:00
|
|
|
const Foo = defineComponent({
|
|
|
|
render() {},
|
|
|
|
created() {
|
|
|
|
// the `emit` function is bound on component instances
|
|
|
|
this.$emit('foo')
|
|
|
|
this.$emit('bar')
|
2020-04-10 23:57:07 +08:00
|
|
|
this.$emit('!baz')
|
2020-04-04 08:40:34 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
const onfoo = jest.fn()
|
|
|
|
const onBar = jest.fn()
|
2020-04-10 23:57:07 +08:00
|
|
|
const onBaz = jest.fn()
|
|
|
|
const Comp = () => h(Foo, { onfoo, onBar, ['on!baz']: onBaz })
|
2020-04-04 08:40:34 +08:00
|
|
|
render(h(Comp), nodeOps.createElement('div'))
|
|
|
|
|
2020-04-10 23:57:07 +08:00
|
|
|
expect(onfoo).not.toHaveBeenCalled()
|
2020-07-08 18:32:42 +08:00
|
|
|
// only capitalized or special chars are considered event listeners
|
2020-04-04 08:40:34 +08:00
|
|
|
expect(onBar).toHaveBeenCalled()
|
2020-04-10 23:57:07 +08:00
|
|
|
expect(onBaz).toHaveBeenCalled()
|
2020-04-04 08:40:34 +08:00
|
|
|
})
|
|
|
|
|
2021-04-02 07:47:58 +08:00
|
|
|
test('trigger camelCase handler', () => {
|
2020-10-07 06:28:56 +08:00
|
|
|
const Foo = defineComponent({
|
|
|
|
render() {},
|
|
|
|
created() {
|
|
|
|
this.$emit('test-event')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
const fooSpy = jest.fn()
|
|
|
|
const Comp = () =>
|
|
|
|
h(Foo, {
|
|
|
|
onTestEvent: fooSpy
|
|
|
|
})
|
|
|
|
render(h(Comp), nodeOps.createElement('div'))
|
|
|
|
|
2021-04-02 07:47:58 +08:00
|
|
|
expect(fooSpy).toHaveBeenCalledTimes(1)
|
|
|
|
})
|
|
|
|
|
|
|
|
test('trigger kebab-case handler', () => {
|
|
|
|
const Foo = defineComponent({
|
|
|
|
render() {},
|
|
|
|
created() {
|
|
|
|
this.$emit('test-event')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
const fooSpy = jest.fn()
|
|
|
|
const Comp = () =>
|
|
|
|
h(Foo, {
|
|
|
|
'onTest-event': fooSpy
|
|
|
|
})
|
|
|
|
render(h(Comp), nodeOps.createElement('div'))
|
|
|
|
|
|
|
|
expect(fooSpy).toHaveBeenCalledTimes(1)
|
|
|
|
})
|
|
|
|
|
|
|
|
// #3527
|
|
|
|
test('trigger mixed case handlers', () => {
|
|
|
|
const Foo = defineComponent({
|
|
|
|
render() {},
|
|
|
|
created() {
|
|
|
|
this.$emit('test-event')
|
|
|
|
this.$emit('testEvent')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
const fooSpy = jest.fn()
|
|
|
|
const barSpy = jest.fn()
|
|
|
|
const Comp = () =>
|
|
|
|
// simulate v-on="obj" usage
|
|
|
|
h(
|
|
|
|
Foo,
|
|
|
|
toHandlers({
|
|
|
|
'test-event': fooSpy,
|
|
|
|
testEvent: barSpy
|
|
|
|
})
|
|
|
|
)
|
|
|
|
render(h(Comp), nodeOps.createElement('div'))
|
|
|
|
|
|
|
|
expect(fooSpy).toHaveBeenCalledTimes(1)
|
2021-04-07 20:19:12 +08:00
|
|
|
expect(barSpy).toHaveBeenCalledTimes(1)
|
2020-10-07 06:28:56 +08:00
|
|
|
})
|
|
|
|
|
2020-04-10 22:59:46 +08:00
|
|
|
// for v-model:foo-bar usage in DOM templates
|
2020-07-08 18:32:42 +08:00
|
|
|
test('trigger hyphenated events for update:xxx events', () => {
|
2020-04-04 08:40:34 +08:00
|
|
|
const Foo = defineComponent({
|
|
|
|
render() {},
|
|
|
|
created() {
|
|
|
|
this.$emit('update:fooProp')
|
|
|
|
this.$emit('update:barProp')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
const fooSpy = jest.fn()
|
|
|
|
const barSpy = jest.fn()
|
|
|
|
const Comp = () =>
|
|
|
|
h(Foo, {
|
|
|
|
'onUpdate:fooProp': fooSpy,
|
|
|
|
'onUpdate:bar-prop': barSpy
|
|
|
|
})
|
|
|
|
render(h(Comp), nodeOps.createElement('div'))
|
|
|
|
|
|
|
|
expect(fooSpy).toHaveBeenCalled()
|
|
|
|
expect(barSpy).toHaveBeenCalled()
|
|
|
|
})
|
|
|
|
|
2020-04-10 22:59:46 +08:00
|
|
|
test('should trigger array of listeners', async () => {
|
|
|
|
const Child = defineComponent({
|
|
|
|
setup(_, { emit }) {
|
|
|
|
emit('foo', 1)
|
|
|
|
return () => h('div')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
const fn1 = jest.fn()
|
|
|
|
const fn2 = jest.fn()
|
|
|
|
|
|
|
|
const App = {
|
|
|
|
setup() {
|
|
|
|
return () =>
|
|
|
|
h(Child, {
|
|
|
|
onFoo: [fn1, fn2]
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
render(h(App), nodeOps.createElement('div'))
|
|
|
|
expect(fn1).toHaveBeenCalledTimes(1)
|
|
|
|
expect(fn1).toHaveBeenCalledWith(1)
|
|
|
|
expect(fn2).toHaveBeenCalledTimes(1)
|
2021-04-26 17:57:15 +08:00
|
|
|
expect(fn2).toHaveBeenCalledWith(1)
|
2020-04-10 22:59:46 +08:00
|
|
|
})
|
|
|
|
|
2020-04-04 08:40:34 +08:00
|
|
|
test('warning for undeclared event (array)', () => {
|
|
|
|
const Foo = defineComponent({
|
|
|
|
emits: ['foo'],
|
|
|
|
render() {},
|
|
|
|
created() {
|
2022-05-12 08:40:59 +08:00
|
|
|
// @ts-expect-error
|
2020-04-04 08:40:34 +08:00
|
|
|
this.$emit('bar')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
render(h(Foo), nodeOps.createElement('div'))
|
|
|
|
expect(
|
2020-04-16 23:27:52 +08:00
|
|
|
`Component emitted event "bar" but it is neither declared`
|
2020-04-04 08:40:34 +08:00
|
|
|
).toHaveBeenWarned()
|
|
|
|
})
|
|
|
|
|
|
|
|
test('warning for undeclared event (object)', () => {
|
|
|
|
const Foo = defineComponent({
|
|
|
|
emits: {
|
|
|
|
foo: null
|
|
|
|
},
|
|
|
|
render() {},
|
|
|
|
created() {
|
2022-05-12 08:40:59 +08:00
|
|
|
// @ts-expect-error
|
2020-04-04 08:40:34 +08:00
|
|
|
this.$emit('bar')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
render(h(Foo), nodeOps.createElement('div'))
|
|
|
|
expect(
|
2020-04-16 23:27:52 +08:00
|
|
|
`Component emitted event "bar" but it is neither declared`
|
2020-04-04 08:40:34 +08:00
|
|
|
).toHaveBeenWarned()
|
|
|
|
})
|
|
|
|
|
2020-04-16 23:27:52 +08:00
|
|
|
test('should not warn if has equivalent onXXX prop', () => {
|
|
|
|
const Foo = defineComponent({
|
|
|
|
props: ['onFoo'],
|
|
|
|
emits: [],
|
|
|
|
render() {},
|
|
|
|
created() {
|
2022-05-12 08:40:59 +08:00
|
|
|
// @ts-expect-error
|
2020-04-16 23:27:52 +08:00
|
|
|
this.$emit('foo')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
render(h(Foo), nodeOps.createElement('div'))
|
|
|
|
expect(
|
2021-02-06 03:06:21 +08:00
|
|
|
`Component emitted event "foo" but it is neither declared`
|
2020-04-16 23:27:52 +08:00
|
|
|
).not.toHaveBeenWarned()
|
|
|
|
})
|
|
|
|
|
2020-04-04 08:40:34 +08:00
|
|
|
test('validator warning', () => {
|
|
|
|
const Foo = defineComponent({
|
|
|
|
emits: {
|
|
|
|
foo: (arg: number) => arg > 0
|
|
|
|
},
|
|
|
|
render() {},
|
|
|
|
created() {
|
|
|
|
this.$emit('foo', -1)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
render(h(Foo), nodeOps.createElement('div'))
|
|
|
|
expect(`event validation failed for event "foo"`).toHaveBeenWarned()
|
|
|
|
})
|
|
|
|
|
2020-07-13 23:55:46 +08:00
|
|
|
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()
|
|
|
|
})
|
|
|
|
|
2021-03-27 04:29:40 +08:00
|
|
|
// #2651
|
|
|
|
test('should not attach normalized object when mixins do not contain emits', () => {
|
|
|
|
const Foo = defineComponent({
|
|
|
|
mixins: [{}],
|
|
|
|
render() {},
|
|
|
|
created() {
|
|
|
|
this.$emit('foo')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
render(h(Foo), nodeOps.createElement('div'))
|
|
|
|
expect(
|
|
|
|
`Component emitted event "foo" but it is neither declared`
|
|
|
|
).not.toHaveBeenWarned()
|
|
|
|
})
|
|
|
|
|
2020-07-14 23:48:05 +08:00
|
|
|
test('.once', () => {
|
|
|
|
const Foo = defineComponent({
|
|
|
|
render() {},
|
|
|
|
emits: {
|
2021-06-08 21:52:45 +08:00
|
|
|
foo: null,
|
|
|
|
bar: null
|
2020-07-14 23:48:05 +08:00
|
|
|
},
|
|
|
|
created() {
|
|
|
|
this.$emit('foo')
|
|
|
|
this.$emit('foo')
|
2021-06-08 21:52:45 +08:00
|
|
|
this.$emit('bar')
|
|
|
|
this.$emit('bar')
|
2020-07-14 23:48:05 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
const fn = jest.fn()
|
2021-06-08 21:52:45 +08:00
|
|
|
const barFn = jest.fn()
|
2020-07-14 23:48:05 +08:00
|
|
|
render(
|
|
|
|
h(Foo, {
|
2021-06-08 21:52:45 +08:00
|
|
|
onFooOnce: fn,
|
|
|
|
onBarOnce: barFn
|
2020-07-14 23:48:05 +08:00
|
|
|
}),
|
|
|
|
nodeOps.createElement('div')
|
|
|
|
)
|
|
|
|
expect(fn).toHaveBeenCalledTimes(1)
|
2021-06-08 21:52:45 +08:00
|
|
|
expect(barFn).toHaveBeenCalledTimes(1)
|
2020-07-14 23:48:05 +08:00
|
|
|
})
|
|
|
|
|
2020-10-20 21:49:53 +08:00
|
|
|
test('.once with normal listener of the same name', () => {
|
|
|
|
const Foo = defineComponent({
|
|
|
|
render() {},
|
|
|
|
emits: {
|
|
|
|
foo: null
|
|
|
|
},
|
|
|
|
created() {
|
|
|
|
this.$emit('foo')
|
|
|
|
this.$emit('foo')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
const onFoo = jest.fn()
|
|
|
|
const onFooOnce = jest.fn()
|
|
|
|
render(
|
|
|
|
h(Foo, {
|
|
|
|
onFoo,
|
|
|
|
onFooOnce
|
|
|
|
}),
|
|
|
|
nodeOps.createElement('div')
|
|
|
|
)
|
|
|
|
expect(onFoo).toHaveBeenCalledTimes(2)
|
|
|
|
expect(onFooOnce).toHaveBeenCalledTimes(1)
|
|
|
|
})
|
|
|
|
|
2020-10-20 21:59:27 +08:00
|
|
|
test('.number modifier should work with v-model on component', () => {
|
|
|
|
const Foo = defineComponent({
|
|
|
|
render() {},
|
|
|
|
created() {
|
|
|
|
this.$emit('update:modelValue', '1')
|
|
|
|
this.$emit('update:foo', '2')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
const fn1 = jest.fn()
|
|
|
|
const fn2 = jest.fn()
|
|
|
|
|
|
|
|
const Comp = () =>
|
|
|
|
h(Foo, {
|
|
|
|
modelValue: null,
|
|
|
|
modelModifiers: { number: true },
|
|
|
|
'onUpdate:modelValue': fn1,
|
|
|
|
|
|
|
|
foo: null,
|
|
|
|
fooModifiers: { number: true },
|
|
|
|
'onUpdate:foo': fn2
|
|
|
|
})
|
|
|
|
|
|
|
|
render(h(Comp), nodeOps.createElement('div'))
|
|
|
|
|
|
|
|
expect(fn1).toHaveBeenCalledTimes(1)
|
|
|
|
expect(fn1).toHaveBeenCalledWith(1)
|
|
|
|
expect(fn2).toHaveBeenCalledTimes(1)
|
|
|
|
expect(fn2).toHaveBeenCalledWith(2)
|
|
|
|
})
|
|
|
|
|
|
|
|
test('.trim modifier should work with v-model on component', () => {
|
|
|
|
const Foo = defineComponent({
|
|
|
|
render() {},
|
|
|
|
created() {
|
|
|
|
this.$emit('update:modelValue', ' one ')
|
|
|
|
this.$emit('update:foo', ' two ')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
const fn1 = jest.fn()
|
|
|
|
const fn2 = jest.fn()
|
|
|
|
|
|
|
|
const Comp = () =>
|
|
|
|
h(Foo, {
|
|
|
|
modelValue: null,
|
|
|
|
modelModifiers: { trim: true },
|
|
|
|
'onUpdate:modelValue': fn1,
|
|
|
|
|
|
|
|
foo: null,
|
|
|
|
fooModifiers: { trim: true },
|
|
|
|
'onUpdate:foo': fn2
|
|
|
|
})
|
|
|
|
|
|
|
|
render(h(Comp), nodeOps.createElement('div'))
|
|
|
|
|
|
|
|
expect(fn1).toHaveBeenCalledTimes(1)
|
|
|
|
expect(fn1).toHaveBeenCalledWith('one')
|
|
|
|
expect(fn2).toHaveBeenCalledTimes(1)
|
|
|
|
expect(fn2).toHaveBeenCalledWith('two')
|
|
|
|
})
|
|
|
|
|
2022-05-13 07:52:16 +08:00
|
|
|
test('.trim and .number modifiers should work with v-model on component', () => {
|
|
|
|
const Foo = defineComponent({
|
|
|
|
render() {},
|
|
|
|
created() {
|
|
|
|
this.$emit('update:modelValue', ' +01.2 ')
|
|
|
|
this.$emit('update:foo', ' 1 ')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
const fn1 = jest.fn()
|
|
|
|
const fn2 = jest.fn()
|
|
|
|
|
|
|
|
const Comp = () =>
|
|
|
|
h(Foo, {
|
|
|
|
modelValue: null,
|
|
|
|
modelModifiers: { trim: true, number: true },
|
|
|
|
'onUpdate:modelValue': fn1,
|
|
|
|
|
|
|
|
foo: null,
|
|
|
|
fooModifiers: { trim: true, number: true },
|
|
|
|
'onUpdate:foo': fn2
|
|
|
|
})
|
|
|
|
|
|
|
|
render(h(Comp), nodeOps.createElement('div'))
|
|
|
|
|
|
|
|
expect(fn1).toHaveBeenCalledTimes(1)
|
|
|
|
expect(fn1).toHaveBeenCalledWith(1.2)
|
|
|
|
expect(fn2).toHaveBeenCalledTimes(1)
|
|
|
|
expect(fn2).toHaveBeenCalledWith(1)
|
|
|
|
})
|
|
|
|
|
2020-09-01 06:32:07 +08:00
|
|
|
test('isEmitListener', () => {
|
2020-11-28 04:24:29 +08:00
|
|
|
const options = {
|
|
|
|
click: null,
|
|
|
|
'test-event': null,
|
|
|
|
fooBar: null,
|
|
|
|
FooBaz: null
|
|
|
|
}
|
2020-09-01 06:32:07 +08:00
|
|
|
expect(isEmitListener(options, 'onClick')).toBe(true)
|
|
|
|
expect(isEmitListener(options, 'onclick')).toBe(false)
|
|
|
|
expect(isEmitListener(options, 'onBlick')).toBe(false)
|
|
|
|
// .once listeners
|
|
|
|
expect(isEmitListener(options, 'onClickOnce')).toBe(true)
|
|
|
|
expect(isEmitListener(options, 'onclickOnce')).toBe(false)
|
2020-11-28 04:24:29 +08:00
|
|
|
// kebab-case option
|
|
|
|
expect(isEmitListener(options, 'onTestEvent')).toBe(true)
|
|
|
|
// camelCase option
|
|
|
|
expect(isEmitListener(options, 'onFooBar')).toBe(true)
|
|
|
|
// PascalCase option
|
|
|
|
expect(isEmitListener(options, 'onFooBaz')).toBe(true)
|
2020-04-04 08:40:34 +08:00
|
|
|
})
|
2022-04-14 11:47:24 +08:00
|
|
|
|
|
|
|
test('does not emit after unmount', async () => {
|
|
|
|
const fn = jest.fn()
|
|
|
|
const Foo = defineComponent({
|
|
|
|
emits: ['closing'],
|
|
|
|
async beforeUnmount() {
|
|
|
|
await this.$nextTick()
|
|
|
|
this.$emit('closing', true)
|
|
|
|
},
|
|
|
|
render() {
|
|
|
|
return h('div')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
const Comp = () =>
|
|
|
|
h(Foo, {
|
|
|
|
onClosing: fn
|
|
|
|
})
|
|
|
|
|
|
|
|
const el = nodeOps.createElement('div')
|
|
|
|
render(h(Comp), el)
|
|
|
|
await nextTick()
|
|
|
|
render(null, el)
|
|
|
|
await nextTick()
|
|
|
|
expect(fn).not.toHaveBeenCalled()
|
|
|
|
})
|
2020-04-04 08:40:34 +08:00
|
|
|
})
|