renderEffect.spec.ts 4.9 KB

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