diff --git a/packages/runtime-core/__tests__/component.spec.ts b/packages/runtime-core/__tests__/component.spec.ts index 929b35770a8..848509af8c7 100644 --- a/packages/runtime-core/__tests__/component.spec.ts +++ b/packages/runtime-core/__tests__/component.spec.ts @@ -1,4 +1,11 @@ -import { h, ref, render, nodeOps, nextTick } from '@vue/runtime-test' +import { + h, + ref, + render, + nodeOps, + nextTick, + defineComponent +} from '@vue/runtime-test' describe('renderer: component', () => { test.todo('should work') @@ -52,4 +59,35 @@ describe('renderer: component', () => { 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, { + onBar: () => 1, + onBaz: [() => Promise.resolve(2), () => Promise.resolve(3)] + }) + } + } + + render(h(App), nodeOps.createElement('div')) + + expect(noMatchEmitResult).toMatchObject([]) + expect(singleEmitResult).toMatchObject([1]) + expect(await Promise.all(multiEmitResult)).toMatchObject([2, 3]) + }) }) diff --git a/packages/runtime-core/__tests__/errorHandling.spec.ts b/packages/runtime-core/__tests__/errorHandling.spec.ts index 39aebe43c14..be809d5ff98 100644 --- a/packages/runtime-core/__tests__/errorHandling.spec.ts +++ b/packages/runtime-core/__tests__/errorHandling.spec.ts @@ -362,7 +362,7 @@ describe('error handling', () => { expect(fn).toHaveBeenCalledWith(err, 'watcher cleanup function') }) - test('in component event handler', () => { + test('in component event handler via emit', () => { const err = new Error('foo') const fn = jest.fn() @@ -392,6 +392,78 @@ describe('error handling', () => { expect(fn).toHaveBeenCalledWith(err, 'component event handler') }) + test('in component event handler via emit (async)', async () => { + const err = new Error('foo') + const fn = jest.fn() + + const Comp = { + setup() { + onErrorCaptured((err, instance, info) => { + fn(err, info) + return true + }) + return () => + h(Child, { + async onFoo() { + throw err + } + }) + } + } + + let res: any + const Child = { + setup(props: any, { emit }: any) { + res = emit('foo') + return () => null + } + } + + render(h(Comp), nodeOps.createElement('div')) + + try { + await Promise.all(res) + } catch (e) { + expect(e).toBe(err) + } + expect(fn).toHaveBeenCalledWith(err, 'component event handler') + }) + + test('in component event handler via emit (async + array)', async () => { + const err = new Error('foo') + const fn = jest.fn() + + const Comp = { + setup() { + onErrorCaptured((err, instance, info) => { + fn(err, info) + return true + }) + return () => + h(Child, { + onFoo: [() => Promise.reject(err), () => Promise.resolve(1)] + }) + } + } + + let res: any + const Child = { + setup(props: any, { emit }: any) { + res = emit('foo') + return () => null + } + } + + render(h(Comp), nodeOps.createElement('div')) + + try { + await Promise.all(res) + } catch (e) { + expect(e).toBe(err) + } + expect(fn).toHaveBeenCalledWith(err, 'component event handler') + }) + it('should warn unhandled', () => { const onError = jest.spyOn(console, 'error') onError.mockImplementation(() => {}) diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 3eafc78df8c..9bfaf50b001 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -24,7 +24,8 @@ import { isObject, NO, makeMap, - isPromise + isPromise, + isArray } from '@vue/shared' import { SuspenseBoundary } from './components/Suspense' import { CompilerOptions } from '@vue/compiler-core' @@ -70,7 +71,7 @@ export const enum LifecycleHooks { ERROR_CAPTURED = 'ec' } -export type Emit = (event: string, ...args: unknown[]) => void +export type Emit = (event: string, ...args: unknown[]) => any[] export interface SetupContext { attrs: Data @@ -218,16 +219,19 @@ export function defineComponentInstance( rtc: null, ec: null, - emit: (event, ...args) => { + emit: (event, ...args): any[] => { const props = instance.vnode.props || EMPTY_OBJ const handler = props[`on${event}`] || props[`on${capitalize(event)}`] if (handler) { - callWithAsyncErrorHandling( + const res = callWithAsyncErrorHandling( handler, instance, ErrorCodes.COMPONENT_EVENT_HANDLER, args ) + return isArray(res) ? res : [res] + } else { + return [] } } } diff --git a/packages/runtime-core/src/errorHandling.ts b/packages/runtime-core/src/errorHandling.ts index 5d5592fba77..0d965c63c0c 100644 --- a/packages/runtime-core/src/errorHandling.ts +++ b/packages/runtime-core/src/errorHandling.ts @@ -74,7 +74,7 @@ export function callWithAsyncErrorHandling( instance: ComponentInternalInstance | null, type: ErrorTypes, args?: unknown[] -) { +): any[] { if (isFunction(fn)) { const res = callWithErrorHandling(fn, instance, type, args) if (res != null && !res._isVue && isPromise(res)) { @@ -85,9 +85,11 @@ export function callWithAsyncErrorHandling( return res } + const values = [] for (let i = 0; i < fn.length; i++) { - callWithAsyncErrorHandling(fn[i], instance, type, args) + values.push(callWithAsyncErrorHandling(fn[i], instance, type, args)) } + return values } export function handleError(