effectScope.spec.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. import { nextTick, watch, watchEffect } from '@vue/runtime-core'
  2. import {
  3. reactive,
  4. effect,
  5. EffectScope,
  6. onScopeDispose,
  7. computed,
  8. ref,
  9. ComputedRef
  10. } from '../src'
  11. describe('reactivity/effect/scope', () => {
  12. it('should run', () => {
  13. const fnSpy = jest.fn(() => {})
  14. new EffectScope().run(fnSpy)
  15. expect(fnSpy).toHaveBeenCalledTimes(1)
  16. })
  17. it('should accept zero argument', () => {
  18. const scope = new EffectScope()
  19. expect(scope.effects.length).toBe(0)
  20. })
  21. it('should return run value', () => {
  22. expect(new EffectScope().run(() => 1)).toBe(1)
  23. })
  24. it('should collect the effects', () => {
  25. const scope = new EffectScope()
  26. scope.run(() => {
  27. let dummy
  28. const counter = reactive({ num: 0 })
  29. effect(() => (dummy = counter.num))
  30. expect(dummy).toBe(0)
  31. counter.num = 7
  32. expect(dummy).toBe(7)
  33. })
  34. expect(scope.effects.length).toBe(1)
  35. })
  36. it('stop', () => {
  37. let dummy, doubled
  38. const counter = reactive({ num: 0 })
  39. const scope = new EffectScope()
  40. scope.run(() => {
  41. effect(() => (dummy = counter.num))
  42. effect(() => (doubled = counter.num * 2))
  43. })
  44. expect(scope.effects.length).toBe(2)
  45. expect(dummy).toBe(0)
  46. counter.num = 7
  47. expect(dummy).toBe(7)
  48. expect(doubled).toBe(14)
  49. scope.stop()
  50. counter.num = 6
  51. expect(dummy).toBe(7)
  52. expect(doubled).toBe(14)
  53. })
  54. it('should collect nested scope', () => {
  55. let dummy, doubled
  56. const counter = reactive({ num: 0 })
  57. const scope = new EffectScope()
  58. scope.run(() => {
  59. effect(() => (dummy = counter.num))
  60. // nested scope
  61. new EffectScope().run(() => {
  62. effect(() => (doubled = counter.num * 2))
  63. })
  64. })
  65. expect(scope.effects.length).toBe(2)
  66. expect(scope.effects[1]).toBeInstanceOf(EffectScope)
  67. expect(dummy).toBe(0)
  68. counter.num = 7
  69. expect(dummy).toBe(7)
  70. expect(doubled).toBe(14)
  71. // stop the nested scope as well
  72. scope.stop()
  73. counter.num = 6
  74. expect(dummy).toBe(7)
  75. expect(doubled).toBe(14)
  76. })
  77. it('nested scope can be escaped', () => {
  78. let dummy, doubled
  79. const counter = reactive({ num: 0 })
  80. const scope = new EffectScope()
  81. scope.run(() => {
  82. effect(() => (dummy = counter.num))
  83. // nested scope
  84. new EffectScope(true).run(() => {
  85. effect(() => (doubled = counter.num * 2))
  86. })
  87. })
  88. expect(scope.effects.length).toBe(1)
  89. expect(dummy).toBe(0)
  90. counter.num = 7
  91. expect(dummy).toBe(7)
  92. expect(doubled).toBe(14)
  93. scope.stop()
  94. counter.num = 6
  95. expect(dummy).toBe(7)
  96. // nested scope should not be stoped
  97. expect(doubled).toBe(12)
  98. })
  99. it('able to run the scope', () => {
  100. let dummy, doubled
  101. const counter = reactive({ num: 0 })
  102. const scope = new EffectScope()
  103. scope.run(() => {
  104. effect(() => (dummy = counter.num))
  105. })
  106. expect(scope.effects.length).toBe(1)
  107. scope.run(() => {
  108. effect(() => (doubled = counter.num * 2))
  109. })
  110. expect(scope.effects.length).toBe(2)
  111. counter.num = 7
  112. expect(dummy).toBe(7)
  113. expect(doubled).toBe(14)
  114. scope.stop()
  115. })
  116. it('can not run an inactive scope', () => {
  117. let dummy, doubled
  118. const counter = reactive({ num: 0 })
  119. const scope = new EffectScope()
  120. scope.run(() => {
  121. effect(() => (dummy = counter.num))
  122. })
  123. expect(scope.effects.length).toBe(1)
  124. scope.stop()
  125. scope.run(() => {
  126. effect(() => (doubled = counter.num * 2))
  127. })
  128. expect('[Vue warn] cannot run an inactive effect scope.').toHaveBeenWarned()
  129. expect(scope.effects.length).toBe(1)
  130. counter.num = 7
  131. expect(dummy).toBe(0)
  132. expect(doubled).toBe(undefined)
  133. })
  134. it('should fire onDispose hook', () => {
  135. let dummy = 0
  136. const scope = new EffectScope()
  137. scope.run(() => {
  138. onScopeDispose(() => (dummy += 1))
  139. onScopeDispose(() => (dummy += 2))
  140. })
  141. scope.run(() => {
  142. onScopeDispose(() => (dummy += 4))
  143. })
  144. expect(dummy).toBe(0)
  145. scope.stop()
  146. expect(dummy).toBe(7)
  147. })
  148. it('should derefence child scope from parent scope after stopping child scope (no memleaks)', async () => {
  149. const parent = new EffectScope()
  150. const child = parent.run(() => new EffectScope())!
  151. expect(parent.effects.includes(child)).toBe(true)
  152. child.stop()
  153. expect(parent.effects.includes(child)).toBe(false)
  154. })
  155. it('test with higher level APIs', async () => {
  156. const r = ref(1)
  157. const computedSpy = jest.fn()
  158. const watchSpy = jest.fn()
  159. const watchEffectSpy = jest.fn()
  160. let c: ComputedRef
  161. const scope = new EffectScope()
  162. scope.run(() => {
  163. c = computed(() => {
  164. computedSpy()
  165. return r.value + 1
  166. })
  167. watch(r, watchSpy)
  168. watchEffect(() => {
  169. watchEffectSpy()
  170. r.value
  171. })
  172. })
  173. c!.value // computed is lazy so trigger collection
  174. expect(computedSpy).toHaveBeenCalledTimes(1)
  175. expect(watchSpy).toHaveBeenCalledTimes(0)
  176. expect(watchEffectSpy).toHaveBeenCalledTimes(1)
  177. r.value++
  178. c!.value
  179. await nextTick()
  180. expect(computedSpy).toHaveBeenCalledTimes(2)
  181. expect(watchSpy).toHaveBeenCalledTimes(1)
  182. expect(watchEffectSpy).toHaveBeenCalledTimes(2)
  183. scope.stop()
  184. r.value++
  185. c!.value
  186. await nextTick()
  187. // should not trigger anymore
  188. expect(computedSpy).toHaveBeenCalledTimes(2)
  189. expect(watchSpy).toHaveBeenCalledTimes(1)
  190. expect(watchEffectSpy).toHaveBeenCalledTimes(2)
  191. })
  192. })