baseWatch.spec.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  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. const queue: SchedulerJob[] = []
  11. // these codes are a simple scheduler
  12. let isFlushPending = false
  13. const resolvedPromise = /*#__PURE__*/ Promise.resolve() as Promise<any>
  14. const nextTick = (fn?: () => any) =>
  15. fn ? resolvedPromise.then(fn) : resolvedPromise
  16. const scheduler: Scheduler = job => {
  17. queue.push(job)
  18. flushJobs()
  19. }
  20. const flushJobs = () => {
  21. if (isFlushPending) return
  22. isFlushPending = true
  23. resolvedPromise.then(() => {
  24. queue.forEach(job => job())
  25. queue.length = 0
  26. isFlushPending = false
  27. })
  28. }
  29. describe('baseWatch', () => {
  30. test('effect', () => {
  31. let dummy: any
  32. const source = ref(0)
  33. baseWatch(() => {
  34. dummy = source.value
  35. })
  36. expect(dummy).toBe(0)
  37. source.value++
  38. expect(dummy).toBe(1)
  39. })
  40. test('watch', () => {
  41. let dummy: any
  42. const source = ref(0)
  43. baseWatch(source, () => {
  44. dummy = source.value
  45. })
  46. expect(dummy).toBe(undefined)
  47. source.value++
  48. expect(dummy).toBe(1)
  49. })
  50. test('custom error handler', () => {
  51. const onError = vi.fn()
  52. baseWatch(
  53. () => {
  54. throw 'oops in effect'
  55. },
  56. null,
  57. { onError },
  58. )
  59. const source = ref(0)
  60. const effect = baseWatch(
  61. source,
  62. () => {
  63. onEffectCleanup(() => {
  64. throw 'oops in cleanup'
  65. })
  66. throw 'oops in watch'
  67. },
  68. { onError },
  69. )
  70. expect(onError.mock.calls.length).toBe(1)
  71. expect(onError.mock.calls[0]).toMatchObject([
  72. 'oops in effect',
  73. BaseWatchErrorCodes.WATCH_CALLBACK,
  74. ])
  75. source.value++
  76. expect(onError.mock.calls.length).toBe(2)
  77. expect(onError.mock.calls[1]).toMatchObject([
  78. 'oops in watch',
  79. BaseWatchErrorCodes.WATCH_CALLBACK,
  80. ])
  81. effect!.stop()
  82. source.value++
  83. expect(onError.mock.calls.length).toBe(3)
  84. expect(onError.mock.calls[2]).toMatchObject([
  85. 'oops in cleanup',
  86. BaseWatchErrorCodes.WATCH_CLEANUP,
  87. ])
  88. })
  89. test('baseWatch with onEffectCleanup', async () => {
  90. let dummy = 0
  91. let source: Ref<number>
  92. const scope = new EffectScope()
  93. scope.run(() => {
  94. source = ref(0)
  95. baseWatch(onCleanup => {
  96. source.value
  97. onCleanup(() => (dummy += 2))
  98. onEffectCleanup(() => (dummy += 3))
  99. onEffectCleanup(() => (dummy += 5))
  100. })
  101. })
  102. expect(dummy).toBe(0)
  103. scope.run(() => {
  104. source.value++
  105. })
  106. expect(dummy).toBe(10)
  107. scope.run(() => {
  108. source.value++
  109. })
  110. expect(dummy).toBe(20)
  111. scope.stop()
  112. expect(dummy).toBe(30)
  113. })
  114. test('nested calls to baseWatch and onEffectCleanup', async () => {
  115. let calls: string[] = []
  116. let source: Ref<number>
  117. let copyist: Ref<number>
  118. const scope = new EffectScope()
  119. scope.run(() => {
  120. source = ref(0)
  121. copyist = ref(0)
  122. // sync by default
  123. baseWatch(
  124. () => {
  125. const current = (copyist.value = source.value)
  126. onEffectCleanup(() => calls.push(`sync ${current}`))
  127. },
  128. null,
  129. {},
  130. )
  131. // with scheduler
  132. baseWatch(
  133. () => {
  134. const current = copyist.value
  135. onEffectCleanup(() => calls.push(`post ${current}`))
  136. },
  137. null,
  138. { scheduler },
  139. )
  140. })
  141. await nextTick()
  142. expect(calls).toEqual([])
  143. scope.run(() => source.value++)
  144. expect(calls).toEqual(['sync 0'])
  145. await nextTick()
  146. expect(calls).toEqual(['sync 0', 'post 0'])
  147. calls.length = 0
  148. scope.run(() => source.value++)
  149. expect(calls).toEqual(['sync 1'])
  150. await nextTick()
  151. expect(calls).toEqual(['sync 1', 'post 1'])
  152. calls.length = 0
  153. scope.stop()
  154. expect(calls).toEqual(['sync 2', 'post 2'])
  155. })
  156. test('baseWatch with middleware', async () => {
  157. let effectCalls: string[] = []
  158. let watchCalls: string[] = []
  159. const source = ref(0)
  160. // effect
  161. baseWatch(
  162. () => {
  163. source.value
  164. effectCalls.push('effect')
  165. onEffectCleanup(() => effectCalls.push('effect cleanup'))
  166. },
  167. null,
  168. {
  169. scheduler,
  170. middleware: next => {
  171. effectCalls.push('before effect running')
  172. next()
  173. effectCalls.push('effect ran')
  174. },
  175. },
  176. )
  177. // watch
  178. baseWatch(
  179. () => source.value,
  180. () => {
  181. watchCalls.push('watch')
  182. onEffectCleanup(() => watchCalls.push('watch cleanup'))
  183. },
  184. {
  185. scheduler,
  186. middleware: next => {
  187. watchCalls.push('before watch running')
  188. next()
  189. watchCalls.push('watch ran')
  190. },
  191. },
  192. )
  193. expect(effectCalls).toEqual([])
  194. expect(watchCalls).toEqual([])
  195. await nextTick()
  196. expect(effectCalls).toEqual([
  197. 'before effect running',
  198. 'effect',
  199. 'effect ran',
  200. ])
  201. expect(watchCalls).toEqual([])
  202. effectCalls.length = 0
  203. watchCalls.length = 0
  204. source.value++
  205. await nextTick()
  206. expect(effectCalls).toEqual([
  207. 'before effect running',
  208. 'effect cleanup',
  209. 'effect',
  210. 'effect ran',
  211. ])
  212. expect(watchCalls).toEqual(['before watch running', 'watch', 'watch ran'])
  213. effectCalls.length = 0
  214. watchCalls.length = 0
  215. source.value++
  216. await nextTick()
  217. expect(effectCalls).toEqual([
  218. 'before effect running',
  219. 'effect cleanup',
  220. 'effect',
  221. 'effect ran',
  222. ])
  223. expect(watchCalls).toEqual([
  224. 'before watch running',
  225. 'watch cleanup',
  226. 'watch',
  227. 'watch ran',
  228. ])
  229. })
  230. })