diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 58b1bd642..72a595258 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -386,6 +386,10 @@ export interface GenericComponentInstance { * @internal */ setupState?: Data + /** + * @internal + */ + setupContext?: any /** * devtools access to additional info * @internal @@ -594,10 +598,6 @@ export interface ComponentInternalInstance extends GenericComponentInstance { ceReload?: (newStyles?: string[]) => void // the rest are only for stateful components --------------------------------- - /** - * @internal - */ - setupContext: SetupContext | null /** * setup related * @internal diff --git a/packages/runtime-core/src/componentEmits.ts b/packages/runtime-core/src/componentEmits.ts index c77e8536e..20b14ee2f 100644 --- a/packages/runtime-core/src/componentEmits.ts +++ b/packages/runtime-core/src/componentEmits.ts @@ -223,7 +223,7 @@ export function baseEmit( if (handler) { callWithAsyncErrorHandling( - handler as Function, + handler as Function | Function[], instance, ErrorCodes.COMPONENT_EVENT_HANDLER, args, diff --git a/packages/runtime-vapor/__tests__/errorHandling.spec.ts b/packages/runtime-vapor/__tests__/errorHandling.spec.ts index 0cc3de878..d0f5d1c4c 100644 --- a/packages/runtime-vapor/__tests__/errorHandling.spec.ts +++ b/packages/runtime-vapor/__tests__/errorHandling.spec.ts @@ -1,12 +1,15 @@ -import type { Component } from '../src/_old/component' -import { type RefEl, setRef } from '../src/dom/templateRef' -import { onErrorCaptured, onMounted } from '../src/_old/apiLifecycle' -import { createComponent } from '../src/_old/apiCreateComponent' +import { + nextTick, + onErrorCaptured, + onMounted, + ref, + watch, + watchEffect, +} from '@vue/runtime-dom' +import { createComponent, setRef, template } from '../src' import { makeRender } from './_utils' -import { template } from '../src/dom/template' -import { watch, watchEffect } from '../src/_old/apiWatch' -import { nextTick } from '../src/_old/scheduler' -import { ref } from '@vue/reactivity' +import type { VaporComponent } from '../src/component' +import type { RefEl } from '../src/dom/templateRef' const define = makeRender() @@ -15,7 +18,7 @@ describe('error handling', () => { const err = new Error('foo') const fn = vi.fn() - const Comp: Component = { + const Comp: VaporComponent = { setup() { onErrorCaptured((err, instance, info) => { fn(err, info, 'root') @@ -26,7 +29,7 @@ describe('error handling', () => { }, } - const Child: Component = { + const Child: VaporComponent = { name: 'Child', setup() { onErrorCaptured((err, instance, info) => { @@ -36,11 +39,12 @@ describe('error handling', () => { }, } - const GrandChild: Component = { + const GrandChild: VaporComponent = { setup() { onMounted(() => { throw err }) + return [] }, } @@ -54,7 +58,7 @@ describe('error handling', () => { const err = new Error('foo') const fn = vi.fn() - const Comp = { + const Comp: VaporComponent = { setup() { onErrorCaptured((err, instance, info) => { fn(err, info, 'root') @@ -64,7 +68,7 @@ describe('error handling', () => { }, } - const Child = { + const Child: VaporComponent = { setup() { onErrorCaptured((err, instance, info) => { fn(err, info, 'child') @@ -74,11 +78,12 @@ describe('error handling', () => { }, } - const GrandChild = { + const GrandChild: VaporComponent = { setup() { onMounted(() => { throw err }) + return [] }, } @@ -91,7 +96,7 @@ describe('error handling', () => { const err = new Error('foo') const fn = vi.fn() - const Comp = { + const Comp: VaporComponent = { setup() { onErrorCaptured((err, instance, info) => { fn(err, info) @@ -101,11 +106,12 @@ describe('error handling', () => { }, } - const Child = { + const Child: VaporComponent = { setup() { onMounted(async () => { throw err }) + return [] }, } @@ -120,7 +126,7 @@ describe('error handling', () => { const err2 = new Error('bar') const fn = vi.fn() - const Comp = { + const Comp: VaporComponent = { setup() { onErrorCaptured((err, instance, info) => { fn(err, info) @@ -130,7 +136,7 @@ describe('error handling', () => { }, } - const Child = { + const Child: VaporComponent = { setup() { onErrorCaptured(() => { throw err2 @@ -139,11 +145,12 @@ describe('error handling', () => { }, } - const GrandChild = { + const GrandChild: VaporComponent = { setup() { onMounted(() => { throw err }) + return [] }, } @@ -175,6 +182,7 @@ describe('error handling', () => { define(Comp).render() expect(fn).toHaveBeenCalledWith(err, 'setup function') + expect(`returned non-block value`).toHaveBeenWarned() }) test('in render function', () => { @@ -201,7 +209,7 @@ describe('error handling', () => { expect(fn).toHaveBeenCalledWith(err, 'render function') }) - test('in function ref', () => { + test.todo('in function ref', () => { const err = new Error('foo') const ref = () => { throw err @@ -234,7 +242,7 @@ describe('error handling', () => { const err = new Error('foo') const fn = vi.fn() - const Comp = { + const Comp: VaporComponent = { setup() { onErrorCaptured((err, instance, info) => { fn(err, info) @@ -244,11 +252,12 @@ describe('error handling', () => { }, } - const Child = { + const Child: VaporComponent = { setup() { watchEffect(() => { throw err }) + return [] }, } @@ -270,7 +279,7 @@ describe('error handling', () => { }, } - const Child = { + const Child: VaporComponent = { setup() { watch( () => { @@ -278,6 +287,7 @@ describe('error handling', () => { }, () => {}, ) + return [] }, } @@ -300,7 +310,7 @@ describe('error handling', () => { } const count = ref(0) - const Child = { + const Child: VaporComponent = { setup() { watch( () => count.value, @@ -308,6 +318,7 @@ describe('error handling', () => { throw err }, ) + return [] }, } @@ -333,7 +344,7 @@ describe('error handling', () => { }, } - const Child = { + const Child: VaporComponent = { setup() { watchEffect(onCleanup => { count.value @@ -341,6 +352,7 @@ describe('error handling', () => { throw err }) }) + return [] }, } @@ -362,24 +374,25 @@ describe('error handling', () => { return false }) return createComponent(Child, { - onFoo: () => { + onFoo: () => () => { throw err }, }) }, } - const Child = { + const Child: VaporComponent = { setup(props: any, { emit }: any) { emit('foo') + return [] }, } define(Comp).render() - expect(fn).toHaveBeenCalledWith(err, 'setup function') + expect(fn).toHaveBeenCalledWith(err, 'component event handler') }) - test.todo('in component event handler via emit (async)', async () => { + test('in component event handler via emit (async)', async () => { const err = new Error('foo') const fn = vi.fn() @@ -390,26 +403,27 @@ describe('error handling', () => { return false }) return createComponent(Child, { - async onFoo() { + onFoo: () => async () => { throw err }, }) }, } - const Child = { + const Child: VaporComponent = { props: ['onFoo'], setup(props: any, { emit }: any) { emit('foo') + return [] }, } define(Comp).render() await nextTick() - expect(fn).toHaveBeenCalledWith(err, 'setup function') + expect(fn).toHaveBeenCalledWith(err, 'component event handler') }) - test.todo('in component event handler via emit (async + array)', async () => { + test('in component event handler via emit (async + array)', async () => { const err = new Error('foo') const fn = vi.fn() @@ -419,36 +433,33 @@ describe('error handling', () => { return p } + const handlers = [ + createAsyncHandler(Promise.reject(err)), + createAsyncHandler(Promise.resolve(1)), + ] + const Comp = { setup() { onErrorCaptured((err, instance, info) => { fn(err, info) return false }) - return createComponent(Child, [ - { - onFoo: () => { - createAsyncHandler(Promise.reject(err)) - createAsyncHandler(Promise.resolve(1)) - }, - }, - ]) + return createComponent(Child, { + onFoo: () => handlers, + }) }, } - const Child = { + const Child: VaporComponent = { setup(props: any, { emit }: any) { emit('foo') + return [] }, } define(Comp).render() - try { - await Promise.all(res) - } catch (e: any) { - expect(e).toBe(err) - } + await expect(() => Promise.all(res)).rejects.toThrowError() expect(fn).toHaveBeenCalledWith(err, 'component event handler') }) @@ -523,6 +534,7 @@ describe('error handling', () => { watchEffect(async () => { throw error4 }) + return [] }, }).create() diff --git a/packages/runtime-vapor/__tests__/rendererElement.spec.ts b/packages/runtime-vapor/__tests__/rendererElement.spec.ts deleted file mode 100644 index c33f2cba9..000000000 --- a/packages/runtime-vapor/__tests__/rendererElement.spec.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { makeRender } from './_utils' -import { template } from '../src/dom/template' - -const define = makeRender() - -describe('renderer: element', () => { - it('should create an element', () => { - const { html } = define({ - render() { - return template(`
`)() - }, - }).render() - - expect(html()).toBe('
') - }) - - it('should create an element with props', () => { - const { html } = define({ - render() { - return template(`
`)() - }, - }).render() - - expect(html()).toBe('
') - }) - - it('should create an element with direct text children', () => { - const { html } = define({ - render() { - return template(`
foo bar`)() - }, - }).render() - - expect(html()).toBe('
foo bar
') - }) - - it('should create an element with direct text children and props', () => { - const { html } = define({ - render() { - return template(`
bar`)() - }, - }).render() - - expect(html()).toBe('
bar
') - }) - - it.fails('should update an element tag which is already mounted', () => { - const { html } = define({ - render() { - return template(`
foo`)() - }, - }).render() - expect(html()).toBe('
foo
') - - define({ - render() { - return template(`foo`)() - }, - }).render() - expect(html()).toBe('foo') - }) - - it.fails('should update element props which is already mounted', () => { - const { html } = define({ - render() { - return template(`
foo`)() - }, - }).render() - expect(html()).toBe('
foo
') - - define({ - render() { - return template(`
foo`)() - }, - }).render() - expect(html()).toBe('
foo
') - }) -}) diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 501a86b8a..033b2fa6a 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -4,12 +4,14 @@ import { EffectScope, type EmitFn, type EmitsOptions, + ErrorCodes, type GenericAppContext, type GenericComponentInstance, type LifecycleHook, type NormalizedPropsOptions, type ObjectEmitsOptions, type SuspenseBoundary, + callWithErrorHandling, currentInstance, nextUid, popWarningContext, @@ -125,12 +127,13 @@ export function createComponent( } const setupFn = isFunction(component) ? component : component.setup + const setupContext = (instance.setupContext = + setupFn && setupFn.length > 1 ? new SetupContext(instance) : null) const setupResult = setupFn - ? setupFn( + ? callWithErrorHandling(setupFn, instance, ErrorCodes.SETUP_FUNCTION, [ instance.props, - // @ts-expect-error - setupFn.length > 1 ? new SetupContext(instance) : null, - ) || EMPTY_OBJ + setupContext, + ]) || EMPTY_OBJ : EMPTY_OBJ if (__DEV__ && !isBlock(setupResult)) { @@ -187,14 +190,19 @@ export function createComponent( * dev only */ export function devRender(instance: VaporComponentInstance): void { - instance.block = instance.type.render!.call( - null, - instance.setupState, - instance.props, - instance.emit, - instance.attrs, - instance.slots, - ) + instance.block = + callWithErrorHandling( + instance.type.render!, + instance, + ErrorCodes.RENDER_FUNCTION, + [ + instance.setupState, + instance.props, + instance.emit, + instance.attrs, + instance.slots, + ], + ) || [] } const emptyContext: GenericAppContext = { @@ -257,6 +265,8 @@ export class VaporComponentInstance implements GenericComponentInstance { ec?: LifecycleHook // LifecycleHooks.ERROR_CAPTURED sp?: LifecycleHook<() => Promise> // LifecycleHooks.SERVER_PREFETCH + setupContext?: SetupContext | null + // dev only setupState?: Record devtoolsRawSetupState?: any