renderWatch.spec.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import { defineComponent } from 'vue'
  2. import {
  3. nextTick,
  4. onBeforeUpdate,
  5. onEffectCleanup,
  6. onUpdated,
  7. ref,
  8. render,
  9. renderEffect,
  10. renderWatch,
  11. template,
  12. watchEffect,
  13. watchPostEffect,
  14. watchSyncEffect,
  15. } from '../src'
  16. let host: HTMLElement
  17. const initHost = () => {
  18. host = document.createElement('div')
  19. host.setAttribute('id', 'host')
  20. document.body.appendChild(host)
  21. }
  22. beforeEach(() => {
  23. initHost()
  24. })
  25. afterEach(() => {
  26. host.remove()
  27. })
  28. const createDemo = (
  29. setupFn: (porps: any, ctx: any) => any,
  30. renderFn: (ctx: any) => any,
  31. ) => {
  32. const demo = defineComponent({
  33. setup(...args) {
  34. const returned = setupFn(...args)
  35. Object.defineProperty(returned, '__isScriptSetup', {
  36. enumerable: false,
  37. value: true,
  38. })
  39. return returned
  40. },
  41. })
  42. demo.render = (ctx: any) => {
  43. const t0 = template('<div></div>')
  44. renderFn(ctx)
  45. return t0()
  46. }
  47. return () => render(demo as any, {}, '#host')
  48. }
  49. describe('renderWatch', () => {
  50. test('effect', async () => {
  51. let dummy: any
  52. const source = ref(0)
  53. renderEffect(() => {
  54. dummy = source.value
  55. })
  56. await nextTick()
  57. expect(dummy).toBe(0)
  58. source.value++
  59. await nextTick()
  60. expect(dummy).toBe(1)
  61. })
  62. test('watch', async () => {
  63. let dummy: any
  64. const source = ref(0)
  65. renderWatch(source, () => {
  66. dummy = source.value
  67. })
  68. await nextTick()
  69. expect(dummy).toBe(undefined)
  70. source.value++
  71. await nextTick()
  72. expect(dummy).toBe(1)
  73. })
  74. test('should run with the scheduling order', async () => {
  75. const calls: string[] = []
  76. const mount = createDemo(
  77. () => {
  78. // setup
  79. const source = ref(0)
  80. const renderSource = ref(0)
  81. const change = () => source.value++
  82. const changeRender = () => renderSource.value++
  83. // Life Cycle Hooks
  84. onUpdated(() => {
  85. calls.push(`updated ${source.value}`)
  86. })
  87. onBeforeUpdate(() => {
  88. calls.push(`beforeUpdate ${source.value}`)
  89. })
  90. // Watch API
  91. watchPostEffect(() => {
  92. const current = source.value
  93. calls.push(`post ${current}`)
  94. onEffectCleanup(() => calls.push(`post cleanup ${current}`))
  95. })
  96. watchEffect(() => {
  97. const current = source.value
  98. calls.push(`pre ${current}`)
  99. onEffectCleanup(() => calls.push(`pre cleanup ${current}`))
  100. })
  101. watchSyncEffect(() => {
  102. const current = source.value
  103. calls.push(`sync ${current}`)
  104. onEffectCleanup(() => calls.push(`sync cleanup ${current}`))
  105. })
  106. return { source, change, renderSource, changeRender }
  107. },
  108. // render
  109. (_ctx) => {
  110. // Render Watch API
  111. renderEffect(() => {
  112. const current = _ctx.renderSource
  113. calls.push(`renderEffect ${current}`)
  114. onEffectCleanup(() => calls.push(`renderEffect cleanup ${current}`))
  115. })
  116. renderWatch(
  117. () => _ctx.renderSource,
  118. (value) => {
  119. calls.push(`renderWatch ${value}`)
  120. onEffectCleanup(() => calls.push(`renderWatch cleanup ${value}`))
  121. },
  122. )
  123. },
  124. )
  125. // Mount
  126. const instance = mount()
  127. const { change, changeRender } = instance.setupState as any
  128. expect(calls).toEqual(['pre 0', 'sync 0', 'renderEffect 0'])
  129. calls.length = 0
  130. await nextTick()
  131. expect(calls).toEqual(['post 0'])
  132. calls.length = 0
  133. // Update
  134. changeRender()
  135. change()
  136. expect(calls).toEqual(['sync cleanup 0', 'sync 1'])
  137. calls.length = 0
  138. await nextTick()
  139. expect(calls).toEqual([
  140. 'pre cleanup 0',
  141. 'pre 1',
  142. 'beforeUpdate 1',
  143. 'renderEffect cleanup 0',
  144. 'renderEffect 1',
  145. 'renderWatch 1',
  146. 'post cleanup 0',
  147. 'post 1',
  148. 'updated 1',
  149. ])
  150. })
  151. test('errors should include the execution location with beforeUpdate hook', async () => {
  152. const mount = createDemo(
  153. // setup
  154. () => {
  155. const source = ref()
  156. const update = () => source.value++
  157. onBeforeUpdate(() => {
  158. throw 'error in beforeUpdate'
  159. })
  160. return { source, update }
  161. },
  162. // render
  163. (ctx) => {
  164. renderEffect(() => {
  165. ctx.source
  166. })
  167. },
  168. )
  169. const instance = mount()
  170. const { update } = instance.setupState as any
  171. await expect(async () => {
  172. update()
  173. await nextTick()
  174. }).rejects.toThrow('error in beforeUpdate')
  175. expect(
  176. '[Vue warn] Unhandled error during execution of beforeUpdate hook',
  177. ).toHaveBeenWarned()
  178. })
  179. test('errors should include the execution location with updated hook', async () => {
  180. const mount = createDemo(
  181. // setup
  182. () => {
  183. const source = ref(0)
  184. const update = () => source.value++
  185. onUpdated(() => {
  186. throw 'error in updated'
  187. })
  188. return { source, update }
  189. },
  190. // render
  191. (ctx) => {
  192. renderEffect(() => {
  193. ctx.source
  194. })
  195. },
  196. )
  197. const instance = mount()
  198. const { update } = instance.setupState as any
  199. await expect(async () => {
  200. update()
  201. await nextTick()
  202. }).rejects.toThrow('error in updated')
  203. expect(
  204. '[Vue warn] Unhandled error during execution of updated',
  205. ).toHaveBeenWarned()
  206. })
  207. })