renderWatch.spec.ts 4.9 KB

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