refactor(runtime-core): remove emit return value
BREAKING CHANGE: this.$emit() and setupContext.emit() no longer return values. For logic that relies on return value of listeners, the listener should be declared as an `onXXX` prop and be called directly. This still allows the parent component to pass in a handler using `v-on`, since `v-on:foo` internally compiles to `onFoo`. ref: https://github.com/vuejs/rfcs/pull/16
This commit is contained in:
parent
a6e2b1052a
commit
55566e8f52
@ -1,146 +0,0 @@
|
||||
import {
|
||||
h,
|
||||
ref,
|
||||
render,
|
||||
nodeOps,
|
||||
nextTick,
|
||||
defineComponent
|
||||
} from '@vue/runtime-test'
|
||||
|
||||
describe('renderer: component', () => {
|
||||
test.todo('should work')
|
||||
|
||||
test.todo('shouldUpdateComponent')
|
||||
|
||||
test.todo('componentProxy')
|
||||
|
||||
describe('componentProps', () => {
|
||||
test.todo('should work')
|
||||
|
||||
test('should convert empty booleans to true', () => {
|
||||
let b1: any, b2: any, b3: any
|
||||
|
||||
const Comp = defineComponent({
|
||||
props: {
|
||||
b1: Boolean,
|
||||
b2: [Boolean, String],
|
||||
b3: [String, Boolean]
|
||||
},
|
||||
setup(props) {
|
||||
;({ b1, b2, b3 } = props)
|
||||
return () => ''
|
||||
}
|
||||
})
|
||||
|
||||
render(
|
||||
h(Comp, <any>{ b1: '', b2: '', b3: '' }),
|
||||
nodeOps.createElement('div')
|
||||
)
|
||||
|
||||
expect(b1).toBe(true)
|
||||
expect(b2).toBe(true)
|
||||
expect(b3).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('slots', () => {
|
||||
test('should respect $stable flag', async () => {
|
||||
const flag1 = ref(1)
|
||||
const flag2 = ref(2)
|
||||
const spy = jest.fn()
|
||||
|
||||
const Child = () => {
|
||||
spy()
|
||||
return 'child'
|
||||
}
|
||||
|
||||
const App = {
|
||||
setup() {
|
||||
return () => [
|
||||
flag1.value,
|
||||
h(
|
||||
Child,
|
||||
{ n: flag2.value },
|
||||
{
|
||||
foo: () => 'foo',
|
||||
$stable: true
|
||||
}
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
render(h(App), nodeOps.createElement('div'))
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
|
||||
// parent re-render, props didn't change, slots are stable
|
||||
// -> child should not update
|
||||
flag1.value++
|
||||
await nextTick()
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
|
||||
// parent re-render, props changed
|
||||
// -> child should update
|
||||
flag2.value++
|
||||
await nextTick()
|
||||
expect(spy).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
|
||||
test('emit', async () => {
|
||||
let noMatchEmitResult: any
|
||||
let singleEmitResult: any
|
||||
let multiEmitResult: any
|
||||
|
||||
const Child = defineComponent({
|
||||
setup(_, { emit }) {
|
||||
noMatchEmitResult = emit('foo')
|
||||
singleEmitResult = emit('bar')
|
||||
multiEmitResult = emit('baz')
|
||||
return () => h('div')
|
||||
}
|
||||
})
|
||||
|
||||
const App = {
|
||||
setup() {
|
||||
return () =>
|
||||
h(Child, {
|
||||
// emit triggering single handler
|
||||
onBar: () => 1,
|
||||
// emit triggering multiple handlers
|
||||
onBaz: [() => Promise.resolve(2), () => Promise.resolve(3)]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render(h(App), nodeOps.createElement('div'))
|
||||
|
||||
// assert return values from emit
|
||||
expect(noMatchEmitResult).toMatchObject([])
|
||||
expect(singleEmitResult).toMatchObject([1])
|
||||
expect(await Promise.all(multiEmitResult)).toMatchObject([2, 3])
|
||||
})
|
||||
|
||||
// for v-model:foo-bar usage in DOM templates
|
||||
test('emit update:xxx events should trigger kebab-case equivalent', () => {
|
||||
const Child = defineComponent({
|
||||
setup(_, { emit }) {
|
||||
emit('update:fooBar', 1)
|
||||
return () => h('div')
|
||||
}
|
||||
})
|
||||
|
||||
const handler = jest.fn()
|
||||
const App = {
|
||||
setup() {
|
||||
return () =>
|
||||
h(Child, {
|
||||
'onUpdate:foo-bar': handler
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render(h(App), nodeOps.createElement('div'))
|
||||
expect(handler).toHaveBeenCalled()
|
||||
})
|
||||
})
|
@ -5,7 +5,7 @@ import { mockWarn } from '@vue/shared'
|
||||
import { render, defineComponent, h, nodeOps } from '@vue/runtime-test'
|
||||
import { isEmitListener } from '../src/componentEmits'
|
||||
|
||||
describe('emits option', () => {
|
||||
describe('component: emit', () => {
|
||||
mockWarn()
|
||||
|
||||
test('trigger both raw event and capitalize handlers', () => {
|
||||
@ -27,6 +27,7 @@ describe('emits option', () => {
|
||||
expect(onBar).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// for v-model:foo-bar usage in DOM templates
|
||||
test('trigger hyphendated events for update:xxx events', () => {
|
||||
const Foo = defineComponent({
|
||||
render() {},
|
||||
@ -49,6 +50,33 @@ describe('emits option', () => {
|
||||
expect(barSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
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)
|
||||
expect(fn1).toHaveBeenCalledWith(1)
|
||||
})
|
||||
|
||||
test('warning for undeclared event (array)', () => {
|
||||
const Foo = defineComponent({
|
||||
emits: ['foo'],
|
||||
|
47
packages/runtime-core/__tests__/componentSlots.spec.ts
Normal file
47
packages/runtime-core/__tests__/componentSlots.spec.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { ref, render, h, nodeOps, nextTick } from '@vue/runtime-test'
|
||||
|
||||
describe('component: slots', () => {
|
||||
// TODO more tests for slots normalization etc.
|
||||
|
||||
test('should respect $stable flag', async () => {
|
||||
const flag1 = ref(1)
|
||||
const flag2 = ref(2)
|
||||
const spy = jest.fn()
|
||||
|
||||
const Child = () => {
|
||||
spy()
|
||||
return 'child'
|
||||
}
|
||||
|
||||
const App = {
|
||||
setup() {
|
||||
return () => [
|
||||
flag1.value,
|
||||
h(
|
||||
Child,
|
||||
{ n: flag2.value },
|
||||
{
|
||||
foo: () => 'foo',
|
||||
$stable: true
|
||||
}
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
render(h(App), nodeOps.createElement('div'))
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
|
||||
// parent re-render, props didn't change, slots are stable
|
||||
// -> child should not update
|
||||
flag1.value++
|
||||
await nextTick()
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
|
||||
// parent re-render, props changed
|
||||
// -> child should update
|
||||
flag2.value++
|
||||
await nextTick()
|
||||
expect(spy).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
@ -416,21 +416,16 @@ describe('error handling', () => {
|
||||
}
|
||||
}
|
||||
|
||||
let res: any
|
||||
const Child = {
|
||||
props: ['onFoo'],
|
||||
setup(props: any, { emit }: any) {
|
||||
res = emit('foo')
|
||||
emit('foo')
|
||||
return () => null
|
||||
}
|
||||
}
|
||||
|
||||
render(h(Comp), nodeOps.createElement('div'))
|
||||
|
||||
try {
|
||||
await Promise.all(res)
|
||||
} catch (e) {
|
||||
expect(e).toBe(err)
|
||||
}
|
||||
await nextTick()
|
||||
expect(fn).toHaveBeenCalledWith(err, 'component event handler')
|
||||
})
|
||||
|
||||
@ -438,6 +433,12 @@ describe('error handling', () => {
|
||||
const err = new Error('foo')
|
||||
const fn = jest.fn()
|
||||
|
||||
const res: Promise<any>[] = []
|
||||
const createAsyncHandler = (p: Promise<any>) => () => {
|
||||
res.push(p)
|
||||
return p
|
||||
}
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
onErrorCaptured((err, instance, info) => {
|
||||
@ -446,15 +447,17 @@ describe('error handling', () => {
|
||||
})
|
||||
return () =>
|
||||
h(Child, {
|
||||
onFoo: [() => Promise.reject(err), () => Promise.resolve(1)]
|
||||
onFoo: [
|
||||
createAsyncHandler(Promise.reject(err)),
|
||||
createAsyncHandler(Promise.resolve(1))
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let res: any
|
||||
const Child = {
|
||||
setup(props: any, { emit }: any) {
|
||||
res = emit('foo')
|
||||
emit('foo')
|
||||
return () => null
|
||||
}
|
||||
}
|
||||
|
@ -28,12 +28,12 @@ export type EmitFn<
|
||||
Options = ObjectEmitsOptions,
|
||||
Event extends keyof Options = keyof Options
|
||||
> = Options extends any[]
|
||||
? (event: Options[0], ...args: any[]) => unknown[]
|
||||
? (event: Options[0], ...args: any[]) => void
|
||||
: UnionToIntersection<
|
||||
{
|
||||
[key in Event]: Options[key] extends ((...args: infer Args) => any)
|
||||
? (event: key, ...args: Args) => unknown[]
|
||||
: (event: key, ...args: any[]) => unknown[]
|
||||
? (event: key, ...args: Args) => void
|
||||
: (event: key, ...args: any[]) => void
|
||||
}[Event]
|
||||
>
|
||||
|
||||
@ -41,7 +41,7 @@ export function emit(
|
||||
instance: ComponentInternalInstance,
|
||||
event: string,
|
||||
...args: any[]
|
||||
): any[] {
|
||||
) {
|
||||
const props = instance.vnode.props || EMPTY_OBJ
|
||||
|
||||
if (__DEV__) {
|
||||
@ -74,15 +74,12 @@ export function emit(
|
||||
handler = props[`on${event}`] || props[`on${capitalize(event)}`]
|
||||
}
|
||||
if (handler) {
|
||||
const res = callWithAsyncErrorHandling(
|
||||
callWithAsyncErrorHandling(
|
||||
handler,
|
||||
instance,
|
||||
ErrorCodes.COMPONENT_EVENT_HANDLER,
|
||||
args
|
||||
)
|
||||
return isArray(res) ? res : [res]
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user