| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- import {
- type DirectiveBinding,
- type DirectiveHook,
- type VNode,
- defineComponent,
- h,
- nextTick,
- nodeOps,
- ref,
- render,
- withDirectives,
- } from '@vue/runtime-test'
- import {
- type ComponentInternalInstance,
- currentInstance,
- } from '../src/component'
- describe('directives', () => {
- it('should work', async () => {
- const count = ref(0)
- function assertBindings(binding: DirectiveBinding) {
- expect(binding.value).toBe(count.value)
- expect(binding.arg).toBe('foo')
- expect(binding.instance).toBe(_instance && _instance.proxy)
- expect(binding.modifiers && binding.modifiers.ok).toBe(true)
- }
- const beforeMount = vi.fn(((el, binding, vnode, prevVNode) => {
- expect(el.tag).toBe('div')
- // should not be inserted yet
- expect(el.parentNode).toBe(null)
- expect(root.children.length).toBe(0)
- assertBindings(binding)
- expect(vnode).toBe(_vnode)
- expect(prevVNode).toBe(null)
- }) as DirectiveHook)
- const mounted = vi.fn(((el, binding, vnode, prevVNode) => {
- expect(el.tag).toBe('div')
- // should be inserted now
- expect(el.parentNode).toBe(root)
- expect(root.children[0]).toBe(el)
- assertBindings(binding)
- expect(vnode).toBe(_vnode)
- expect(prevVNode).toBe(null)
- }) as DirectiveHook)
- const beforeUpdate = vi.fn(((el, binding, vnode, prevVNode) => {
- expect(el.tag).toBe('div')
- expect(el.parentNode).toBe(root)
- expect(root.children[0]).toBe(el)
- // node should not have been updated yet
- expect(el.children[0].text).toBe(`${count.value - 1}`)
- assertBindings(binding)
- expect(vnode).toBe(_vnode)
- expect(prevVNode).toBe(_prevVnode)
- }) as DirectiveHook)
- const updated = vi.fn(((el, binding, vnode, prevVNode) => {
- expect(el.tag).toBe('div')
- expect(el.parentNode).toBe(root)
- expect(root.children[0]).toBe(el)
- // node should have been updated
- expect(el.children[0].text).toBe(`${count.value}`)
- assertBindings(binding)
- expect(vnode).toBe(_vnode)
- expect(prevVNode).toBe(_prevVnode)
- }) as DirectiveHook)
- const beforeUnmount = vi.fn(((el, binding, vnode, prevVNode) => {
- expect(el.tag).toBe('div')
- // should be removed now
- expect(el.parentNode).toBe(root)
- expect(root.children[0]).toBe(el)
- assertBindings(binding)
- expect(vnode).toBe(_vnode)
- expect(prevVNode).toBe(null)
- }) as DirectiveHook)
- const unmounted = vi.fn(((el, binding, vnode, prevVNode) => {
- expect(el.tag).toBe('div')
- // should have been removed
- expect(el.parentNode).toBe(null)
- expect(root.children.length).toBe(0)
- assertBindings(binding)
- expect(vnode).toBe(_vnode)
- expect(prevVNode).toBe(null)
- }) as DirectiveHook)
- const dir = {
- beforeMount,
- mounted,
- beforeUpdate,
- updated,
- beforeUnmount,
- unmounted,
- }
- let _instance: ComponentInternalInstance | null = null
- let _vnode: VNode | null = null
- let _prevVnode: VNode | null = null
- const Comp = {
- setup() {
- _instance = currentInstance
- },
- render() {
- _prevVnode = _vnode
- _vnode = withDirectives(h('div', count.value), [
- [
- dir,
- // value
- count.value,
- // argument
- 'foo',
- // modifiers
- { ok: true },
- ],
- ])
- return _vnode
- },
- }
- const root = nodeOps.createElement('div')
- render(h(Comp), root)
- expect(beforeMount).toHaveBeenCalledTimes(1)
- expect(mounted).toHaveBeenCalledTimes(1)
- count.value++
- await nextTick()
- expect(beforeUpdate).toHaveBeenCalledTimes(1)
- expect(updated).toHaveBeenCalledTimes(1)
- render(null, root)
- expect(beforeUnmount).toHaveBeenCalledTimes(1)
- expect(unmounted).toHaveBeenCalledTimes(1)
- })
- it('should work with a function directive', async () => {
- const count = ref(0)
- function assertBindings(binding: DirectiveBinding) {
- expect(binding.value).toBe(count.value)
- expect(binding.arg).toBe('foo')
- expect(binding.instance).toBe(_instance && _instance.proxy)
- expect(binding.modifiers && binding.modifiers.ok).toBe(true)
- }
- const fn = vi.fn(((el, binding, vnode, prevVNode) => {
- expect(el.tag).toBe('div')
- expect(el.parentNode).toBe(root)
- assertBindings(binding)
- expect(vnode).toBe(_vnode)
- expect(prevVNode).toBe(_prevVnode)
- }) as DirectiveHook)
- let _instance: ComponentInternalInstance | null = null
- let _vnode: VNode | null = null
- let _prevVnode: VNode | null = null
- const Comp = {
- setup() {
- _instance = currentInstance
- },
- render() {
- _prevVnode = _vnode
- _vnode = withDirectives(h('div', count.value), [
- [
- fn,
- // value
- count.value,
- // argument
- 'foo',
- // modifiers
- { ok: true },
- ],
- ])
- return _vnode
- },
- }
- const root = nodeOps.createElement('div')
- render(h(Comp), root)
- expect(fn).toHaveBeenCalledTimes(1)
- count.value++
- await nextTick()
- expect(fn).toHaveBeenCalledTimes(2)
- })
- it('should work on component vnode', async () => {
- const count = ref(0)
- function assertBindings(binding: DirectiveBinding) {
- expect(binding.value).toBe(count.value)
- expect(binding.arg).toBe('foo')
- expect(binding.instance).toBe(_instance && _instance.proxy)
- expect(binding.modifiers && binding.modifiers.ok).toBe(true)
- }
- const beforeMount = vi.fn(((el, binding, vnode, prevVNode) => {
- expect(el.tag).toBe('div')
- // should not be inserted yet
- expect(el.parentNode).toBe(null)
- expect(root.children.length).toBe(0)
- assertBindings(binding)
- expect(vnode.type).toBe(_vnode!.type)
- expect(prevVNode).toBe(null)
- }) as DirectiveHook)
- const mounted = vi.fn(((el, binding, vnode, prevVNode) => {
- expect(el.tag).toBe('div')
- // should be inserted now
- expect(el.parentNode).toBe(root)
- expect(root.children[0]).toBe(el)
- assertBindings(binding)
- expect(vnode.type).toBe(_vnode!.type)
- expect(prevVNode).toBe(null)
- }) as DirectiveHook)
- const beforeUpdate = vi.fn(((el, binding, vnode, prevVNode) => {
- expect(el.tag).toBe('div')
- expect(el.parentNode).toBe(root)
- expect(root.children[0]).toBe(el)
- // node should not have been updated yet
- expect(el.children[0].text).toBe(`${count.value - 1}`)
- assertBindings(binding)
- expect(vnode.type).toBe(_vnode!.type)
- expect(prevVNode!.type).toBe(_prevVnode!.type)
- }) as DirectiveHook)
- const updated = vi.fn(((el, binding, vnode, prevVNode) => {
- expect(el.tag).toBe('div')
- expect(el.parentNode).toBe(root)
- expect(root.children[0]).toBe(el)
- // node should have been updated
- expect(el.children[0].text).toBe(`${count.value}`)
- assertBindings(binding)
- expect(vnode.type).toBe(_vnode!.type)
- expect(prevVNode!.type).toBe(_prevVnode!.type)
- }) as DirectiveHook)
- const beforeUnmount = vi.fn(((el, binding, vnode, prevVNode) => {
- expect(el.tag).toBe('div')
- // should be removed now
- expect(el.parentNode).toBe(root)
- expect(root.children[0]).toBe(el)
- assertBindings(binding)
- expect(vnode.type).toBe(_vnode!.type)
- expect(prevVNode).toBe(null)
- }) as DirectiveHook)
- const unmounted = vi.fn(((el, binding, vnode, prevVNode) => {
- expect(el.tag).toBe('div')
- // should have been removed
- expect(el.parentNode).toBe(null)
- expect(root.children.length).toBe(0)
- assertBindings(binding)
- expect(vnode.type).toBe(_vnode!.type)
- expect(prevVNode).toBe(null)
- }) as DirectiveHook)
- const dir = {
- beforeMount,
- mounted,
- beforeUpdate,
- updated,
- beforeUnmount,
- unmounted,
- }
- let _instance: ComponentInternalInstance | null = null
- let _vnode: VNode | null = null
- let _prevVnode: VNode | null = null
- const Child = (props: { count: number }) => {
- _prevVnode = _vnode
- _vnode = h('div', props.count)
- return _vnode
- }
- const Comp = {
- setup() {
- _instance = currentInstance
- },
- render() {
- return withDirectives(h(Child, { count: count.value }), [
- [
- dir,
- // value
- count.value,
- // argument
- 'foo',
- // modifiers
- { ok: true },
- ],
- ])
- },
- }
- const root = nodeOps.createElement('div')
- render(h(Comp), root)
- expect(beforeMount).toHaveBeenCalledTimes(1)
- expect(mounted).toHaveBeenCalledTimes(1)
- count.value++
- await nextTick()
- expect(beforeUpdate).toHaveBeenCalledTimes(1)
- expect(updated).toHaveBeenCalledTimes(1)
- render(null, root)
- expect(beforeUnmount).toHaveBeenCalledTimes(1)
- expect(unmounted).toHaveBeenCalledTimes(1)
- })
- // #2298
- it('directive merging on component root', () => {
- const d1 = {
- mounted: vi.fn(),
- }
- const d2 = {
- mounted: vi.fn(),
- }
- const Comp = {
- render() {
- return withDirectives(h('div'), [[d2]])
- },
- }
- const App = {
- name: 'App',
- render() {
- return h('div', [withDirectives(h(Comp), [[d1]])])
- },
- }
- const root = nodeOps.createElement('div')
- render(h(App), root)
- expect(d1.mounted).toHaveBeenCalled()
- expect(d2.mounted).toHaveBeenCalled()
- })
- test('should disable tracking inside directive lifecycle hooks', async () => {
- const count = ref(0)
- const text = ref('')
- const beforeUpdate = vi.fn(() => count.value++)
- const App = {
- render() {
- return withDirectives(h('p', text.value), [
- [
- {
- beforeUpdate,
- },
- ],
- ])
- },
- }
- const root = nodeOps.createElement('div')
- render(h(App), root)
- expect(beforeUpdate).toHaveBeenCalledTimes(0)
- expect(count.value).toBe(0)
- text.value = 'foo'
- await nextTick()
- expect(beforeUpdate).toHaveBeenCalledTimes(1)
- expect(count.value).toBe(1)
- })
- test('should receive exposeProxy for closed instances', async () => {
- let res: string
- const App = defineComponent({
- setup(_, { expose }) {
- expose({
- msg: 'Test',
- })
- return () =>
- withDirectives(h('p', 'Lore Ipsum'), [
- [
- {
- mounted(el, { instance }) {
- res = (instance as any).msg as string
- },
- },
- ],
- ])
- },
- })
- const root = nodeOps.createElement('div')
- render(h(App), root)
- expect(res!).toBe('Test')
- })
- test('should not throw with unknown directive', async () => {
- const d1 = {
- mounted: vi.fn(),
- }
- const App = {
- name: 'App',
- render() {
- // simulates the code generated on an unknown directive
- return withDirectives(h('div'), [[undefined], [d1]])
- },
- }
- const root = nodeOps.createElement('div')
- render(h(App), root)
- expect(d1.mounted).toHaveBeenCalled()
- })
- })
|