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

Co-authored-by: Rizumu Ayaka <rizumu@ayaka.moe>
Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
XiaoDong 2024-06-19 22:36:14 +08:00 committed by GitHub
parent 97f0b3bc33
commit d4511150a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 418 additions and 0 deletions

View File

@ -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()
})
})