diff --git a/packages/runtime-core/__tests__/directives.spec.ts b/packages/runtime-core/__tests__/directives.spec.ts index 02d24711804..b649e966003 100644 --- a/packages/runtime-core/__tests__/directives.spec.ts +++ b/packages/runtime-core/__tests__/directives.spec.ts @@ -395,4 +395,81 @@ describe('directives', () => { expect(beforeUpdate).toHaveBeenCalledTimes(1) expect(count.value).toBe(1) }) + + it('directive install hooks', async () => { + const count = ref(0) + let d1_installed_created = jest.fn() + let d1_installed_mounted = jest.fn() + let d1_installed_beforeMounted = jest.fn() + let d1_installed_beforeupdate = jest.fn() + let d1_installed_updated = jest.fn() + let d1_installed_beforeUnmount = jest.fn() + let d1_installed_unmounted = jest.fn() + + let mounted_overwritten = jest.fn() + + const d1 = { + install() { + return { + created: d1_installed_created, + mounted: d1_installed_mounted, + beforeMount: d1_installed_beforeMounted, + beforeUpdate: d1_installed_beforeupdate, + updated: d1_installed_updated, + beforeUnmount: d1_installed_beforeUnmount, + unmounted: d1_installed_unmounted + } + }, + mounted: mounted_overwritten + } + + // test that install doesn't override hooks that it does not provide. + let d2_created = jest.fn() + let d2_installed_mounted = jest.fn() + let d2_install = jest + .fn() + .mockReturnValue({ mounted: d2_installed_mounted }) + const d2 = { + install: d2_install, + created: d2_created + } + + const Comp = { + render() { + return withDirectives(h('div', [count.value]), [[d1], [d2]]) + } + } + + const App = { + name: 'App', + render() { + return h('div', [h(Comp)]) + } + } + + const root = nodeOps.createElement('div') + render(h(App), root) + + count.value++ + + await nextTick() + + // all installed hooks called + expect(d1_installed_created).toHaveBeenCalled() + expect(d1_installed_mounted).toHaveBeenCalled() + expect(d1_installed_beforeMounted).toHaveBeenCalled() + expect(d1_installed_beforeupdate).toHaveBeenCalled() + expect(d1_installed_updated).toHaveBeenCalled() + + expect(mounted_overwritten).not.toHaveBeenCalled() + + expect(d2_created).toHaveBeenCalled() + expect(d2_install).toHaveBeenCalled() + expect(d2_installed_mounted).toHaveBeenCalled() + + render(null, root) + + expect(d1_installed_beforeUnmount).toHaveBeenCalledTimes(1) + expect(d1_installed_unmounted).toHaveBeenCalledTimes(1) + }) }) diff --git a/packages/runtime-core/src/directives.ts b/packages/runtime-core/src/directives.ts index 58e56df0522..549fe797e06 100644 --- a/packages/runtime-core/src/directives.ts +++ b/packages/runtime-core/src/directives.ts @@ -12,7 +12,7 @@ return withDirectives(h(comp), [ */ import { VNode } from './vnode' -import { isFunction, EMPTY_OBJ, makeMap } from '@vue/shared' +import { isFunction, EMPTY_OBJ, makeMap, extend } from '@vue/shared' import { warn } from './warning' import { ComponentInternalInstance, Data } from './component' import { currentRenderingInstance } from './componentRenderContext' @@ -53,6 +53,7 @@ export interface ObjectDirective { unmounted?: DirectiveHook getSSRProps?: SSRDirectiveHook deep?: boolean + install?: (binding: DirectiveBinding) => ObjectDirective } export type FunctionDirective = DirectiveHook @@ -106,14 +107,18 @@ export function withDirectives( if (dir.deep) { traverse(value) } - bindings.push({ + let binding = { dir, instance, value, oldValue: void 0, arg, modifiers - }) + } + if (dir.install) { + extend(binding.dir, dir.install(binding)) + } + bindings.push(binding) } return vnode }