Skip to content

Commit 2fa93ac

Browse files
committed
feat: directive lifecycle hooks in v-for/v-if
1 parent b9b3e02 commit 2fa93ac

10 files changed

+778
-215
lines changed

packages/runtime-vapor/__tests__/for.spec.ts

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
1-
import { createFor, nextTick, ref, renderEffect } from '../src'
1+
import { NOOP } from '@vue/shared'
2+
import {
3+
type Directive,
4+
children,
5+
createFor,
6+
nextTick,
7+
ref,
8+
renderEffect,
9+
template,
10+
withDirectives,
11+
} from '../src'
212
import { makeRender } from './_utils'
13+
import { unmountComponent } from '../src/apiRender'
314

415
const define = makeRender()
516

@@ -184,4 +195,92 @@ describe('createFor', () => {
184195
await nextTick()
185196
expect(host.innerHTML).toBe('<!--for-->')
186197
})
198+
199+
test('should work with directive hooks', async () => {
200+
const calls: string[] = []
201+
const list = ref([0])
202+
const update = ref(0)
203+
const add = () => list.value.push(list.value.length)
204+
const spySrcFn = vi.fn(() => list.value)
205+
206+
const vDirective: Directive = {
207+
created: (el, { value }) => calls.push(`${value} created`),
208+
beforeMount: (el, { value }) => calls.push(`${value} beforeMount`),
209+
mounted: (el, { value }) => calls.push(`${value} mounted`),
210+
beforeUpdate: (el, { value }) => calls.push(`${value} beforeUpdate`),
211+
updated: (el, { value }) => calls.push(`${value} updated`),
212+
beforeUnmount: (el, { value }) => calls.push(`${value} beforeUnmount`),
213+
unmounted: (el, { value }) => calls.push(`${value} unmounted`),
214+
}
215+
216+
const t0 = template('<p></p>')
217+
const { instance } = define(() => {
218+
const n1 = createFor(spySrcFn, block => {
219+
const n2 = t0()
220+
const n3 = children(n2, 0)
221+
withDirectives(n3, [[vDirective, () => block.s[0]]])
222+
return [n2, NOOP]
223+
})
224+
renderEffect(() => update.value)
225+
return [n1]
226+
}).render()
227+
228+
await nextTick()
229+
// `${item index} ${hook name}`
230+
expect(calls).toEqual(['0 created', '0 beforeMount', '0 mounted'])
231+
calls.length = 0
232+
expect(spySrcFn).toHaveBeenCalledTimes(1)
233+
234+
add()
235+
await nextTick()
236+
expect(calls).toEqual([
237+
'0 beforeUpdate',
238+
'1 created',
239+
'1 beforeMount',
240+
'0 updated',
241+
'1 mounted',
242+
])
243+
calls.length = 0
244+
expect(spySrcFn).toHaveBeenCalledTimes(2)
245+
246+
list.value.reverse()
247+
await nextTick()
248+
expect(calls).toEqual([
249+
'1 beforeUpdate',
250+
'0 beforeUpdate',
251+
'1 updated',
252+
'0 updated',
253+
])
254+
expect(spySrcFn).toHaveBeenCalledTimes(3)
255+
list.value.reverse()
256+
await nextTick()
257+
calls.length = 0
258+
expect(spySrcFn).toHaveBeenCalledTimes(4)
259+
260+
update.value++
261+
await nextTick()
262+
expect(calls).toEqual([
263+
'0 beforeUpdate',
264+
'1 beforeUpdate',
265+
'0 updated',
266+
'1 updated',
267+
])
268+
calls.length = 0
269+
expect(spySrcFn).toHaveBeenCalledTimes(4)
270+
271+
list.value.pop()
272+
await nextTick()
273+
expect(calls).toEqual([
274+
'0 beforeUpdate',
275+
'1 beforeUnmount',
276+
'0 updated',
277+
'1 unmounted',
278+
])
279+
calls.length = 0
280+
expect(spySrcFn).toHaveBeenCalledTimes(5)
281+
282+
unmountComponent(instance)
283+
expect(calls).toEqual(['0 beforeUnmount', '0 unmounted'])
284+
expect(spySrcFn).toHaveBeenCalledTimes(5)
285+
})
187286
})

packages/runtime-vapor/__tests__/if.spec.ts

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import {
2+
children,
23
createIf,
34
insert,
45
nextTick,
56
ref,
67
renderEffect,
78
setText,
89
template,
10+
withDirectives,
911
} from '../src'
1012
import type { Mock } from 'vitest'
1113
import { makeRender } from './_utils'
14+
import { unmountComponent } from '../src/apiRender'
1215

1316
const define = makeRender()
1417

@@ -24,6 +27,8 @@ describe('createIf', () => {
2427
let spyElseFn: Mock<any, any>
2528
const count = ref(0)
2629

30+
const spyConditionFn = vi.fn(() => count.value)
31+
2732
// templates can be reused through caching.
2833
const t0 = template('<div></div>')
2934
const t1 = template('<p></p>')
@@ -34,7 +39,7 @@ describe('createIf', () => {
3439

3540
insert(
3641
createIf(
37-
() => count.value,
42+
spyConditionFn,
3843
// v-if
3944
(spyIfFn ||= vi.fn(() => {
4045
const n2 = t1()
@@ -55,24 +60,28 @@ describe('createIf', () => {
5560
}).render()
5661

5762
expect(host.innerHTML).toBe('<div><p>zero</p><!--if--></div>')
63+
expect(spyConditionFn).toHaveBeenCalledTimes(1)
5864
expect(spyIfFn!).toHaveBeenCalledTimes(0)
5965
expect(spyElseFn!).toHaveBeenCalledTimes(1)
6066

6167
count.value++
6268
await nextTick()
6369
expect(host.innerHTML).toBe('<div><p>1</p><!--if--></div>')
70+
expect(spyConditionFn).toHaveBeenCalledTimes(2)
6471
expect(spyIfFn!).toHaveBeenCalledTimes(1)
6572
expect(spyElseFn!).toHaveBeenCalledTimes(1)
6673

6774
count.value++
6875
await nextTick()
6976
expect(host.innerHTML).toBe('<div><p>2</p><!--if--></div>')
77+
expect(spyConditionFn).toHaveBeenCalledTimes(3)
7078
expect(spyIfFn!).toHaveBeenCalledTimes(1)
7179
expect(spyElseFn!).toHaveBeenCalledTimes(1)
7280

7381
count.value = 0
7482
await nextTick()
7583
expect(host.innerHTML).toBe('<div><p>zero</p><!--if--></div>')
84+
expect(spyConditionFn).toHaveBeenCalledTimes(4)
7685
expect(spyIfFn!).toHaveBeenCalledTimes(1)
7786
expect(spyElseFn!).toHaveBeenCalledTimes(2)
7887
})
@@ -124,4 +133,113 @@ describe('createIf', () => {
124133
await nextTick()
125134
expect(host.innerHTML).toBe('<!--if-->')
126135
})
136+
137+
test('should work with directive hooks', async () => {
138+
const calls: string[] = []
139+
const show1 = ref(true)
140+
const show2 = ref(true)
141+
const update = ref(0)
142+
143+
const spyConditionFn1 = vi.fn(() => show1.value)
144+
const spyConditionFn2 = vi.fn(() => show2.value)
145+
146+
const vDirective: any = {
147+
created: (el: any, { value }: any) => calls.push(`${value} created`),
148+
beforeMount: (el: any, { value }: any) =>
149+
calls.push(`${value} beforeMount`),
150+
mounted: (el: any, { value }: any) => calls.push(`${value} mounted`),
151+
beforeUpdate: (el: any, { value }: any) =>
152+
calls.push(`${value} beforeUpdate`),
153+
updated: (el: any, { value }: any) => calls.push(`${value} updated`),
154+
beforeUnmount: (el: any, { value }: any) =>
155+
calls.push(`${value} beforeUnmount`),
156+
unmounted: (el: any, { value }: any) => calls.push(`${value} unmounted`),
157+
}
158+
159+
const t0 = template('<p></p>')
160+
const { instance } = define(() => {
161+
const n1 = createIf(
162+
spyConditionFn1,
163+
() => {
164+
const n2 = t0()
165+
withDirectives(children(n2, 0), [
166+
[vDirective, () => (update.value, '1')],
167+
])
168+
return n2
169+
},
170+
() =>
171+
createIf(
172+
spyConditionFn2,
173+
() => {
174+
const n2 = t0()
175+
withDirectives(children(n2, 0), [[vDirective, () => '2']])
176+
return n2
177+
},
178+
() => {
179+
const n2 = t0()
180+
withDirectives(children(n2, 0), [[vDirective, () => '3']])
181+
return n2
182+
},
183+
),
184+
)
185+
return [n1]
186+
}).render()
187+
188+
await nextTick()
189+
expect(calls).toEqual(['1 created', '1 beforeMount', '1 mounted'])
190+
calls.length = 0
191+
expect(spyConditionFn1).toHaveBeenCalledTimes(1)
192+
expect(spyConditionFn2).toHaveBeenCalledTimes(0)
193+
194+
show1.value = false
195+
await nextTick()
196+
expect(calls).toEqual([
197+
'1 beforeUnmount',
198+
'2 created',
199+
'2 beforeMount',
200+
'1 unmounted',
201+
'2 mounted',
202+
])
203+
calls.length = 0
204+
expect(spyConditionFn1).toHaveBeenCalledTimes(2)
205+
expect(spyConditionFn2).toHaveBeenCalledTimes(1)
206+
207+
show2.value = false
208+
await nextTick()
209+
expect(calls).toEqual([
210+
'2 beforeUnmount',
211+
'3 created',
212+
'3 beforeMount',
213+
'2 unmounted',
214+
'3 mounted',
215+
])
216+
calls.length = 0
217+
expect(spyConditionFn1).toHaveBeenCalledTimes(2)
218+
expect(spyConditionFn2).toHaveBeenCalledTimes(2)
219+
220+
show1.value = true
221+
await nextTick()
222+
expect(calls).toEqual([
223+
'3 beforeUnmount',
224+
'1 created',
225+
'1 beforeMount',
226+
'3 unmounted',
227+
'1 mounted',
228+
])
229+
calls.length = 0
230+
expect(spyConditionFn1).toHaveBeenCalledTimes(3)
231+
expect(spyConditionFn2).toHaveBeenCalledTimes(2)
232+
233+
update.value++
234+
await nextTick()
235+
expect(calls).toEqual(['1 beforeUpdate', '1 updated'])
236+
calls.length = 0
237+
expect(spyConditionFn1).toHaveBeenCalledTimes(3)
238+
expect(spyConditionFn2).toHaveBeenCalledTimes(2)
239+
240+
unmountComponent(instance)
241+
expect(calls).toEqual(['1 beforeUnmount', '1 unmounted'])
242+
expect(spyConditionFn1).toHaveBeenCalledTimes(3)
243+
expect(spyConditionFn2).toHaveBeenCalledTimes(2)
244+
})
127245
})

0 commit comments

Comments
 (0)