Parcourir la source

test(runtime-vapor): add directives test (#240)

Co-authored-by: Rizumu Ayaka <rizumu@ayaka.moe>
Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
XiaoDong il y a 1 an
Parent
commit
d4511150a5
1 fichiers modifiés avec 418 ajouts et 0 suppressions
  1. 418 0
      packages/runtime-vapor/__tests__/directives.spec.ts

+ 418 - 0
packages/runtime-vapor/__tests__/directives.spec.ts

@@ -0,0 +1,418 @@
+import {
+  type DirectiveBinding,
+  type DirectiveHook,
+  createComponent,
+  defineComponent,
+  nextTick,
+  ref,
+  renderEffect,
+  setText,
+  template,
+  withDirectives,
+} from '../src'
+import {
+  type Component,
+  type ComponentInternalInstance,
+  currentInstance,
+} from '../src/component'
+import { makeRender } from './_utils'
+
+const define = makeRender()
+
+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)
+      expect(binding.modifiers && binding.modifiers.ok).toBe(true)
+    }
+
+    const beforeMount = vi.fn(((el, binding) => {
+      expect(el.tagName).toBe('DIV')
+      // should not be inserted yet
+      expect(el.parentNode).toBe(null)
+      expect(host.children.length).toBe(0)
+
+      assertBindings(binding)
+
+      expect(el).toBe(_node)
+    }) as DirectiveHook)
+
+    const mounted = vi.fn(((el, binding) => {
+      expect(el.tagName).toBe('DIV')
+      // should be inserted now
+      expect(el.parentNode).toBe(host)
+      expect(host.children[0]).toBe(el)
+
+      assertBindings(binding)
+    }) as DirectiveHook)
+
+    const beforeUpdate = vi.fn(((el, binding) => {
+      expect(el.tagName).toBe('DIV')
+      expect(el.parentNode).toBe(host)
+      expect(host.children[0]).toBe(el)
+
+      // node should not have been updated yet
+      expect(el.childNodes[0].textContent).toBe(`${count.value - 1}`)
+
+      assertBindings(binding)
+    }) as DirectiveHook)
+
+    const updated = vi.fn(((el, binding) => {
+      expect(el.tagName).toBe('DIV')
+      expect(el.parentNode).toBe(host)
+      expect(host.children[0]).toBe(el)
+
+      // node should have been updated
+      expect(el.childNodes[0].textContent).toBe(`${count.value}`)
+
+      assertBindings(binding)
+    }) as DirectiveHook)
+
+    const beforeUnmount = vi.fn(((el, binding) => {
+      expect(el.tagName).toBe('DIV')
+      // should be removed now
+      expect(el.parentNode).toBe(host)
+      expect(host.children[0]).toBe(el)
+
+      assertBindings(binding)
+    }) as DirectiveHook)
+
+    const unmounted = vi.fn(((el, binding) => {
+      expect(el.tagName).toBe('DIV')
+      // should have been removed
+      expect(el.parentNode).toBe(null)
+      expect(host.children.length).toBe(0)
+
+      assertBindings(binding)
+    }) as DirectiveHook)
+
+    const dir = {
+      beforeMount,
+      mounted,
+      beforeUpdate,
+      updated,
+      beforeUnmount,
+      unmounted,
+    }
+
+    let _instance: ComponentInternalInstance | null = null
+    let _node: Node | null = null
+
+    const { host, render } = define({
+      setup() {
+        _instance = currentInstance
+      },
+      render() {
+        _node = template('<div>')()
+        renderEffect(() => {
+          setText(_node!, count.value)
+        })
+        withDirectives(_node, [
+          [
+            dir,
+            // value
+            () => count.value,
+            // argument
+            'foo',
+            // modifiers
+            { ok: true },
+          ],
+        ])
+        return _node
+      },
+    })
+    const { app } = render()
+
+    expect(beforeMount).toHaveBeenCalledTimes(1)
+    expect(mounted).toHaveBeenCalledTimes(1)
+
+    count.value++
+    await nextTick()
+    expect(beforeUpdate).toHaveBeenCalledTimes(1)
+    expect(updated).toHaveBeenCalledTimes(1)
+
+    app.unmount()
+    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)
+      expect(binding.modifiers && binding.modifiers.ok).toBe(true)
+    }
+
+    const fn = vi.fn(((el, binding) => {
+      expect(el.tagName).toBe('DIV')
+      expect(el.parentNode).toBe(host)
+
+      assertBindings(binding)
+    }) as DirectiveHook)
+
+    let _instance: ComponentInternalInstance | null = null
+    let _node: Node | null = null
+    const Comp = {
+      setup() {
+        _instance = currentInstance
+      },
+      render() {
+        _node = template('<div>')()
+        renderEffect(() => {
+          setText(_node!, count.value)
+        })
+        withDirectives(_node, [
+          [
+            fn,
+            // value
+            () => count.value,
+            // argument
+            'foo',
+            // modifiers
+            { ok: true },
+          ],
+        ])
+        return _node
+      },
+    }
+
+    const { host, render } = define(Comp)
+    render()
+
+    expect(fn).toHaveBeenCalledTimes(1)
+
+    count.value++
+    await nextTick()
+    expect(fn).toHaveBeenCalledTimes(2)
+  })
+
+  it('should work on components', 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)
+      expect(binding.modifiers && binding.modifiers.ok).toBe(true)
+    }
+
+    const beforeMount = vi.fn(((el, binding) => {
+      expect(el.tagName).toBe('DIV')
+      // should not be inserted yet
+      expect(el.parentNode).toBe(null)
+      expect(host.children.length).toBe(0)
+
+      assertBindings(binding)
+    }) as DirectiveHook)
+
+    const mounted = vi.fn(((el, binding) => {
+      expect(el.tagName).toBe('DIV')
+      // should be inserted now
+      expect(el.parentNode).toBe(host)
+      expect(host.children[0]).toBe(el)
+
+      assertBindings(binding)
+    }) as DirectiveHook)
+
+    const beforeUpdate = vi.fn(((el, binding) => {
+      expect(el.tagName).toBe('DIV')
+      expect(el.parentNode).toBe(host)
+      expect(host.children[0]).toBe(el)
+
+      // node should not have been updated yet
+      expect(el.childNodes[0].textContent).toBe(`${count.value - 1}`)
+
+      assertBindings(binding)
+    }) as DirectiveHook)
+
+    const updated = vi.fn(((el, binding) => {
+      expect(el.tagName).toBe('DIV')
+      expect(el.parentNode).toBe(host)
+      expect(host.children[0]).toBe(el)
+
+      // node should have been updated
+      expect(el.childNodes[0].textContent).toBe(`${count.value}`)
+
+      assertBindings(binding)
+    }) as DirectiveHook)
+
+    const beforeUnmount = vi.fn(((el, binding) => {
+      expect(el.tagName).toBe('DIV')
+      // should be removed now
+      expect(el.parentNode).toBe(host)
+      expect(host.children[0]).toBe(el)
+
+      assertBindings(binding)
+    }) as DirectiveHook)
+
+    const unmounted = vi.fn(((el, binding) => {
+      expect(el.tagName).toBe('DIV')
+      // should have been removed
+      expect(el.parentNode).toBe(null)
+      expect(host.children.length).toBe(0)
+
+      assertBindings(binding)
+    }) as DirectiveHook)
+
+    const dir = {
+      beforeMount,
+      mounted,
+      beforeUpdate,
+      updated,
+      beforeUnmount,
+      unmounted,
+    }
+
+    let _instance: ComponentInternalInstance | null = null
+    let _node: Node | null = null
+
+    const Child = (props: { count: number }) => {
+      _node = template('<div>')()
+      renderEffect(() => {
+        setText(_node!, props.count)
+      })
+      return _node
+    }
+
+    const Comp = {
+      setup() {
+        _instance = currentInstance
+      },
+      render() {
+        _node = template('<div>')()
+        return withDirectives(
+          createComponent(Child, { count: () => count.value }),
+          [
+            [
+              dir,
+              // value
+              () => count.value,
+              // argument
+              'foo',
+              // modifiers
+              { ok: true },
+            ],
+          ],
+        )
+      },
+    }
+
+    const { host, render } = define(Comp)
+    render()
+
+    expect(beforeMount).toHaveBeenCalledTimes(1)
+    expect(mounted).toHaveBeenCalledTimes(1)
+
+    count.value++
+    await nextTick()
+    expect(beforeUpdate).toHaveBeenCalledTimes(1)
+    expect(updated).toHaveBeenCalledTimes(1)
+
+    render(null, host)
+    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: Component = {
+      render() {
+        return withDirectives(template('<div>')(), [[d2]])
+      },
+    }
+
+    const App = {
+      name: 'App',
+      render() {
+        return withDirectives(createComponent(Comp), [[d1]])
+      },
+    }
+
+    define(App).render()
+    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() {
+        const node = template('<p>')()
+        renderEffect(() => {
+          setText(node, text.value)
+        })
+        return withDirectives(node, [
+          [
+            {
+              beforeUpdate,
+            },
+          ],
+        ])
+      },
+    }
+
+    define(App).render()
+    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(template('<p>')(), [
+          [
+            {
+              mounted(el, { instance }) {
+                res = instance.exposed!.msg
+              },
+            },
+          ],
+        ])
+      },
+    })
+    define(App).render()
+    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(template('<div>')(), [[undefined], [d1]])
+      },
+    }
+
+    define(App).render()
+    expect(d1.mounted).toHaveBeenCalled()
+  })
+})