Skip to content

Commit 1730ab4

Browse files
committed
feat(runtime-core, reactivity): baseWatch + onWatcherCleanup
Squashed commit of the following: commit dad9d0f Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Thu Mar 14 20:35:19 2024 +0800 feat: scheduler in reactivity commit 406c750 Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Thu Mar 14 14:08:12 2024 +0800 fix: revert export alias commit 74996b6 Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Wed Mar 13 22:21:27 2024 +0800 test: onWatcherCleanup in apiWatch commit a5769e1 Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Wed Mar 13 22:09:43 2024 +0800 fix: remove elusive code for once commit 589cd11 Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Wed Mar 13 21:14:34 2024 +0800 fix: errors related to immediateFirstRun commit 3694745 Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Tue Mar 12 18:16:52 2024 +0800 refactor: rename to onWatcherCleanup, getCurrentWatcher, remove middleware commit b3f45d2 Merge: 60a1b97 9a936aa Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Thu Mar 7 22:23:13 2024 +0800 chore: merge branch 'minor' into feat/onEffectCleanup-and-baseWatch commit 60a1b97 Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Tue Jan 9 20:45:31 2024 +0800 feat: middleware in baseWatch commit 2fdda65 Merge: 39f07cd 2701355 Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Mon Jan 8 17:40:54 2024 +0800 Merge branch 'main' into feat/onEffectCleanup-and-baseWatch commit 39f07cd Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Mon Jan 8 17:40:18 2024 +0800 fix: should export getCurrentEffect function commit 770c21d Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Sat Jan 6 00:07:41 2024 +0800 fix: sync code changes according to the review in PR vuejs/vue-vapor#82 commit a6eb043 Merge: 8dd0c1f 0275dd3 Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Fri Jan 5 23:43:02 2024 +0800 chore: merge branch 'main' into feat/onEffectCleanup-and-baseWatch commit 8dd0c1f Merge: 2213634 274f6f7 Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Sun Dec 31 20:30:29 2023 +0800 chore: merge remote-tracking branch 'origin/minor' into feat/onEffectCleanup-and-baseWatch commit 2213634 Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Sun Dec 31 19:21:12 2023 +0800 refactor: simplify unwatch implementation commit f44ef0b Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Sun Dec 31 18:45:04 2023 +0800 feat: implement getCurrentEffect commit a078ad1 Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Thu Dec 28 21:28:28 2023 +0800 chore: rename handleWarn to onWarn commit 90fd005 Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Thu Dec 28 21:05:03 2023 +0800 chore: organize exports commit e9555ce Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Thu Dec 28 20:36:56 2023 +0800 test: baseWatch commit d99e9a6 Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Thu Dec 28 20:04:42 2023 +0800 test: onEffectCleanup in runtime-core commit 56c87ec Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Thu Dec 28 19:44:43 2023 +0800 test: baseWatch with onEffectCleanup commit 7c5f05a Merge: a8dc8e6 75dbbb8 Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Thu Dec 28 17:32:00 2023 +0800 Merge branch 'minor' of https://github.com/vuejs/core into feat/onEffectCleanup-and-baseWatch commit a8dc8e6 Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Wed Dec 27 22:43:17 2023 +0800 fix: tracked in cleanup commit b57405c Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Wed Dec 27 20:28:49 2023 +0800 fix: treeshaking error commit 4d04f5e Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Wed Dec 27 20:19:53 2023 +0800 fix: treeshaking error commit d1f001b Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Wed Dec 27 20:10:05 2023 +0800 fix: lint commit 97179ed Merge: 2aef609 9183069 Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Tue Dec 26 23:24:47 2023 +0800 chore: merge branch 'minor' of https://github.com/vuejs/core into feat/onEffectCleanup-and-baseWatch commit 2aef609 Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Tue Dec 26 22:19:26 2023 +0800 fix: some cases for server-renderer commit db4463c Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Tue Dec 26 21:40:12 2023 +0800 fix: export onEffectCleanup commit 409b52a Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Tue Dec 26 21:31:27 2023 +0800 refactor: the watch API with baseWatch commit d8682e8 Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Mon Dec 25 22:09:38 2023 +0800 feat: initial code of baseWatch commit f1fe01e Author: Rizumu Ayaka <rizumu@ayaka.moe> Date: Mon Dec 25 20:50:35 2023 +0800 refactor: externalized COMPAT case
1 parent cffe866 commit 1730ab4

14 files changed

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

0 commit comments

Comments
 (0)