Skip to content

Commit bdf28de

Browse files
LittleSoundsxzz
andauthored
feat(runtime-core, reactivity): onEffectCleanup and baseWatch (#82)
Co-authored-by: 三咲智子 Kevin Deng <[email protected]>
1 parent c9fe3f1 commit bdf28de

File tree

11 files changed

+770
-293
lines changed

11 files changed

+770
-293
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import type { Scheduler, SchedulerJob } from '../src/baseWatch'
2+
import {
3+
BaseWatchErrorCodes,
4+
EffectScope,
5+
type Ref,
6+
baseWatch,
7+
onEffectCleanup,
8+
ref,
9+
} from '../src'
10+
11+
const queue: SchedulerJob[] = []
12+
13+
// these codes are a simple scheduler
14+
let isFlushPending = false
15+
const resolvedPromise = /*#__PURE__*/ Promise.resolve() as Promise<any>
16+
const nextTick = (fn?: () => any) =>
17+
fn ? resolvedPromise.then(fn) : resolvedPromise
18+
const scheduler: Scheduler = job => {
19+
queue.push(job)
20+
flushJobs()
21+
}
22+
const flushJobs = () => {
23+
if (isFlushPending) return
24+
isFlushPending = true
25+
resolvedPromise.then(() => {
26+
queue.forEach(job => job())
27+
queue.length = 0
28+
isFlushPending = false
29+
})
30+
}
31+
32+
describe('baseWatch', () => {
33+
test('effect', () => {
34+
let dummy: any
35+
const source = ref(0)
36+
baseWatch(() => {
37+
dummy = source.value
38+
})
39+
expect(dummy).toBe(0)
40+
source.value++
41+
expect(dummy).toBe(1)
42+
})
43+
44+
test('watch', () => {
45+
let dummy: any
46+
const source = ref(0)
47+
baseWatch(source, () => {
48+
dummy = source.value
49+
})
50+
expect(dummy).toBe(undefined)
51+
source.value++
52+
expect(dummy).toBe(1)
53+
})
54+
55+
test('custom error handler', () => {
56+
const onError = vi.fn()
57+
58+
baseWatch(
59+
() => {
60+
throw 'oops in effect'
61+
},
62+
null,
63+
{ onError },
64+
)
65+
66+
const source = ref(0)
67+
const effect = baseWatch(
68+
source,
69+
() => {
70+
onEffectCleanup(() => {
71+
throw 'oops in cleanup'
72+
})
73+
throw 'oops in watch'
74+
},
75+
{ onError },
76+
)
77+
78+
expect(onError.mock.calls.length).toBe(1)
79+
expect(onError.mock.calls[0]).toMatchObject([
80+
'oops in effect',
81+
BaseWatchErrorCodes.WATCH_CALLBACK,
82+
])
83+
84+
source.value++
85+
expect(onError.mock.calls.length).toBe(2)
86+
expect(onError.mock.calls[1]).toMatchObject([
87+
'oops in watch',
88+
BaseWatchErrorCodes.WATCH_CALLBACK,
89+
])
90+
91+
effect!.stop()
92+
source.value++
93+
expect(onError.mock.calls.length).toBe(3)
94+
expect(onError.mock.calls[2]).toMatchObject([
95+
'oops in cleanup',
96+
BaseWatchErrorCodes.WATCH_CLEANUP,
97+
])
98+
})
99+
100+
test('baseWatch with onEffectCleanup', async () => {
101+
let dummy = 0
102+
let source: Ref<number>
103+
const scope = new EffectScope()
104+
105+
scope.run(() => {
106+
source = ref(0)
107+
baseWatch(onCleanup => {
108+
source.value
109+
110+
onCleanup(() => (dummy += 2))
111+
onEffectCleanup(() => (dummy += 3))
112+
onEffectCleanup(() => (dummy += 5))
113+
})
114+
})
115+
expect(dummy).toBe(0)
116+
117+
scope.run(() => {
118+
source.value++
119+
})
120+
expect(dummy).toBe(10)
121+
122+
scope.run(() => {
123+
source.value++
124+
})
125+
expect(dummy).toBe(20)
126+
127+
scope.stop()
128+
expect(dummy).toBe(30)
129+
})
130+
131+
test('nested calls to baseWatch and onEffectCleanup', async () => {
132+
let calls: string[] = []
133+
let source: Ref<number>
134+
let copyist: Ref<number>
135+
const scope = new EffectScope()
136+
137+
scope.run(() => {
138+
source = ref(0)
139+
copyist = ref(0)
140+
// sync by default
141+
baseWatch(
142+
() => {
143+
const current = (copyist.value = source.value)
144+
onEffectCleanup(() => calls.push(`sync ${current}`))
145+
},
146+
null,
147+
{},
148+
)
149+
// with scheduler
150+
baseWatch(
151+
() => {
152+
const current = copyist.value
153+
onEffectCleanup(() => calls.push(`post ${current}`))
154+
},
155+
null,
156+
{ scheduler },
157+
)
158+
})
159+
160+
await nextTick()
161+
expect(calls).toEqual([])
162+
163+
scope.run(() => source.value++)
164+
expect(calls).toEqual(['sync 0'])
165+
await nextTick()
166+
expect(calls).toEqual(['sync 0', 'post 0'])
167+
calls.length = 0
168+
169+
scope.run(() => source.value++)
170+
expect(calls).toEqual(['sync 1'])
171+
await nextTick()
172+
expect(calls).toEqual(['sync 1', 'post 1'])
173+
calls.length = 0
174+
175+
scope.stop()
176+
expect(calls).toEqual(['sync 2', 'post 2'])
177+
})
178+
})

0 commit comments

Comments
 (0)