Skip to content

Commit 80acfa5

Browse files
authored
test(runtime-vapor): add directive test case (#231)
1 parent cf8be99 commit 80acfa5

File tree

1 file changed

+295
-0
lines changed

1 file changed

+295
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
import { ref } from '@vue/reactivity'
2+
import {
3+
type ComponentInternalInstance,
4+
type DirectiveBinding,
5+
type DirectiveHook,
6+
createComponent,
7+
getCurrentInstance,
8+
nextTick,
9+
renderEffect,
10+
setText,
11+
template,
12+
withDirectives,
13+
} from '@vue/runtime-vapor'
14+
import { makeRender } from '../_utils'
15+
16+
const define = makeRender()
17+
18+
describe('directives', () => {
19+
it('should work', async () => {
20+
const count = ref(0)
21+
22+
function assertBindings(binding: DirectiveBinding) {
23+
expect(binding.value).toBe(count.value)
24+
expect(binding.arg).toBe('foo')
25+
expect(binding.instance).toBe(_instance)
26+
expect(binding.modifiers && binding.modifiers.ok).toBe(true)
27+
}
28+
29+
const beforeMount = vi.fn(((el, binding) => {
30+
expect(el.tagName).toBe('DIV')
31+
// should not be inserted yet
32+
expect(el.parentNode).toBe(null)
33+
expect(root.children.length).toBe(0)
34+
35+
assertBindings(binding)
36+
37+
// expect(vnode).toBe(_vnode)
38+
// expect(prevVNode).toBe(null)
39+
}) as DirectiveHook<Element>)
40+
41+
const mounted = vi.fn(((el, binding) => {
42+
expect(el.tagName).toBe('DIV')
43+
// should be inserted now
44+
expect(el.parentNode).toBe(root)
45+
expect(root.children[0]).toBe(el)
46+
47+
assertBindings(binding)
48+
}) as DirectiveHook<Element>)
49+
50+
const beforeUpdate = vi.fn(((el, binding) => {
51+
expect(el.tagName).toBe('DIV')
52+
expect(el.parentNode).toBe(root)
53+
expect(root.children[0]).toBe(el)
54+
55+
// node should not have been updated yet
56+
expect(el.firstChild?.textContent).toBe(`${count.value - 1}`)
57+
58+
assertBindings(binding)
59+
}) as DirectiveHook<Element>)
60+
61+
const updated = vi.fn(((el, binding) => {
62+
expect(el.tagName).toBe('DIV')
63+
expect(el.parentNode).toBe(root)
64+
expect(root.children[0]).toBe(el)
65+
66+
// node should have been updated
67+
expect(el.firstChild?.textContent).toBe(`${count.value}`)
68+
69+
assertBindings(binding)
70+
}) as DirectiveHook<Element>)
71+
72+
const beforeUnmount = vi.fn(((el, binding) => {
73+
expect(el.tagName).toBe('DIV')
74+
// should be removed now
75+
expect(el.parentNode).toBe(root)
76+
expect(root.children[0]).toBe(el)
77+
78+
assertBindings(binding)
79+
}) as DirectiveHook<Element>)
80+
81+
const unmounted = vi.fn(((el, binding) => {
82+
expect(el.tagName).toBe('DIV')
83+
// should have been removed
84+
expect(el.parentNode).toBe(null)
85+
expect(root.children.length).toBe(0)
86+
87+
assertBindings(binding)
88+
}) as DirectiveHook<Element>)
89+
90+
const dir = {
91+
beforeMount,
92+
mounted,
93+
beforeUpdate,
94+
updated,
95+
beforeUnmount,
96+
unmounted,
97+
}
98+
99+
let _instance: ComponentInternalInstance | null = null
100+
const { render } = define({
101+
setup() {
102+
_instance = getCurrentInstance()
103+
},
104+
render() {
105+
const n0 = template('<div></div>')()
106+
renderEffect(() => setText(n0, count.value))
107+
withDirectives(n0, [
108+
[
109+
dir,
110+
// value
111+
() => count.value,
112+
// argument
113+
'foo',
114+
// modifiers
115+
{ ok: true },
116+
],
117+
])
118+
return n0
119+
},
120+
})
121+
122+
const root = document.createElement('div')
123+
124+
render(null, root)
125+
expect(beforeMount).toHaveBeenCalledTimes(1)
126+
expect(mounted).toHaveBeenCalledTimes(1)
127+
128+
count.value++
129+
await nextTick()
130+
expect(beforeUpdate).toHaveBeenCalledTimes(1)
131+
expect(updated).toHaveBeenCalledTimes(1)
132+
133+
render(null, root)
134+
expect(beforeUnmount).toHaveBeenCalledTimes(1)
135+
expect(unmounted).toHaveBeenCalledTimes(1)
136+
})
137+
138+
it('should work with a function directive', async () => {
139+
const count = ref(0)
140+
141+
function assertBindings(binding: DirectiveBinding) {
142+
expect(binding.value).toBe(count.value)
143+
expect(binding.arg).toBe('foo')
144+
expect(binding.instance).toBe(_instance)
145+
expect(binding.modifiers && binding.modifiers.ok).toBe(true)
146+
}
147+
148+
const fn = vi.fn(((el, binding) => {
149+
expect(el.tagName).toBe('DIV')
150+
expect(el.parentNode).toBe(root)
151+
152+
assertBindings(binding)
153+
}) as DirectiveHook<Element>)
154+
155+
let _instance: ComponentInternalInstance | null = null
156+
const { render } = define({
157+
setup() {
158+
_instance = getCurrentInstance()
159+
},
160+
render() {
161+
const n0 = template('<div></div>')()
162+
renderEffect(() => setText(n0, count.value))
163+
withDirectives(n0, [
164+
[
165+
fn,
166+
// value
167+
() => count.value,
168+
// argument
169+
'foo',
170+
// modifiers
171+
{ ok: true },
172+
],
173+
])
174+
return n0
175+
},
176+
})
177+
178+
const root = document.createElement('div')
179+
render(null, root)
180+
181+
expect(fn).toHaveBeenCalledTimes(1)
182+
183+
count.value++
184+
await nextTick()
185+
expect(fn).toHaveBeenCalledTimes(2)
186+
})
187+
188+
// #2298
189+
it('directive merging on component root', () => {
190+
const d1 = {
191+
mounted: vi.fn(),
192+
}
193+
const d2 = {
194+
mounted: vi.fn(),
195+
}
196+
const Comp = {
197+
render() {
198+
const n0 = template('<div></div>')()
199+
withDirectives(n0, [[d2]])
200+
return n0
201+
},
202+
}
203+
204+
const { render } = define({
205+
name: 'App',
206+
render() {
207+
const n0 = createComponent(Comp)
208+
withDirectives(n0, [[d1]])
209+
return n0
210+
},
211+
})
212+
213+
const root = document.createElement('div')
214+
render(null, root)
215+
expect(d1.mounted).toHaveBeenCalled()
216+
expect(d2.mounted).toHaveBeenCalled()
217+
})
218+
219+
test('should disable tracking inside directive lifecycle hooks', async () => {
220+
const count = ref(0)
221+
const text = ref('')
222+
const beforeUpdate = vi.fn(() => count.value++)
223+
224+
const { render } = define({
225+
render() {
226+
const n0 = template('<p></p>')()
227+
renderEffect(() => setText(n0, text.value))
228+
withDirectives(n0, [
229+
[
230+
{
231+
beforeUpdate,
232+
},
233+
],
234+
])
235+
return n0
236+
},
237+
})
238+
239+
const root = document.createElement('div')
240+
render(null, root)
241+
expect(beforeUpdate).toHaveBeenCalledTimes(0)
242+
expect(count.value).toBe(0)
243+
244+
text.value = 'foo'
245+
await nextTick()
246+
expect(beforeUpdate).toHaveBeenCalledTimes(1)
247+
expect(count.value).toBe(1)
248+
})
249+
250+
test('should receive exposeProxy for closed instances', async () => {
251+
let res: string
252+
const { render } = define({
253+
setup(_, { expose }) {
254+
expose({
255+
msg: 'Test',
256+
})
257+
},
258+
render() {
259+
const n0 = template('<p>Lore Ipsum</p>')()
260+
withDirectives(n0, [
261+
[
262+
{
263+
mounted(el, { instance }) {
264+
res = (instance.exposed as any).msg as string
265+
},
266+
},
267+
],
268+
])
269+
return n0
270+
},
271+
})
272+
const root = document.createElement('div')
273+
render(null, root)
274+
expect(res!).toBe('Test')
275+
})
276+
277+
test('should not throw with unknown directive', async () => {
278+
const d1 = {
279+
mounted: vi.fn(),
280+
}
281+
const { render } = define({
282+
name: 'App',
283+
render() {
284+
const n0 = template('<div></div>')()
285+
// simulates the code generated on an unknown directive
286+
withDirectives(n0, [[undefined], [d1]])
287+
return n0
288+
},
289+
})
290+
291+
const root = document.createElement('div')
292+
render(null, root)
293+
expect(d1.mounted).toHaveBeenCalled()
294+
})
295+
})

0 commit comments

Comments
 (0)