deferredComputed.spec.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import { vi } from 'vitest'
  2. import { computed, deferredComputed, effect, ref } from '../src'
  3. describe('deferred computed', () => {
  4. const tick = Promise.resolve()
  5. test('should only trigger once on multiple mutations', async () => {
  6. const src = ref(0)
  7. const c = deferredComputed(() => src.value)
  8. const spy = vi.fn()
  9. effect(() => {
  10. spy(c.value)
  11. })
  12. expect(spy).toHaveBeenCalledTimes(1)
  13. src.value = 1
  14. src.value = 2
  15. src.value = 3
  16. // not called yet
  17. expect(spy).toHaveBeenCalledTimes(1)
  18. await tick
  19. // should only trigger once
  20. expect(spy).toHaveBeenCalledTimes(2)
  21. expect(spy).toHaveBeenCalledWith(c.value)
  22. })
  23. test('should not trigger if value did not change', async () => {
  24. const src = ref(0)
  25. const c = deferredComputed(() => src.value % 2)
  26. const spy = vi.fn()
  27. effect(() => {
  28. spy(c.value)
  29. })
  30. expect(spy).toHaveBeenCalledTimes(1)
  31. src.value = 1
  32. src.value = 2
  33. await tick
  34. // should not trigger
  35. expect(spy).toHaveBeenCalledTimes(1)
  36. src.value = 3
  37. src.value = 4
  38. src.value = 5
  39. await tick
  40. // should trigger because latest value changes
  41. expect(spy).toHaveBeenCalledTimes(2)
  42. })
  43. test('chained computed trigger', async () => {
  44. const effectSpy = vi.fn()
  45. const c1Spy = vi.fn()
  46. const c2Spy = vi.fn()
  47. const src = ref(0)
  48. const c1 = deferredComputed(() => {
  49. c1Spy()
  50. return src.value % 2
  51. })
  52. const c2 = computed(() => {
  53. c2Spy()
  54. return c1.value + 1
  55. })
  56. effect(() => {
  57. effectSpy(c2.value)
  58. })
  59. expect(c1Spy).toHaveBeenCalledTimes(1)
  60. expect(c2Spy).toHaveBeenCalledTimes(1)
  61. expect(effectSpy).toHaveBeenCalledTimes(1)
  62. src.value = 1
  63. await tick
  64. expect(c1Spy).toHaveBeenCalledTimes(2)
  65. expect(c2Spy).toHaveBeenCalledTimes(2)
  66. expect(effectSpy).toHaveBeenCalledTimes(2)
  67. })
  68. test('chained computed avoid re-compute', async () => {
  69. const effectSpy = vi.fn()
  70. const c1Spy = vi.fn()
  71. const c2Spy = vi.fn()
  72. const src = ref(0)
  73. const c1 = deferredComputed(() => {
  74. c1Spy()
  75. return src.value % 2
  76. })
  77. const c2 = computed(() => {
  78. c2Spy()
  79. return c1.value + 1
  80. })
  81. effect(() => {
  82. effectSpy(c2.value)
  83. })
  84. expect(effectSpy).toHaveBeenCalledTimes(1)
  85. src.value = 2
  86. src.value = 4
  87. src.value = 6
  88. await tick
  89. // c1 should re-compute once.
  90. expect(c1Spy).toHaveBeenCalledTimes(2)
  91. // c2 should not have to re-compute because c1 did not change.
  92. expect(c2Spy).toHaveBeenCalledTimes(1)
  93. // effect should not trigger because c2 did not change.
  94. expect(effectSpy).toHaveBeenCalledTimes(1)
  95. })
  96. test('chained computed value invalidation', async () => {
  97. const effectSpy = vi.fn()
  98. const c1Spy = vi.fn()
  99. const c2Spy = vi.fn()
  100. const src = ref(0)
  101. const c1 = deferredComputed(() => {
  102. c1Spy()
  103. return src.value % 2
  104. })
  105. const c2 = deferredComputed(() => {
  106. c2Spy()
  107. return c1.value + 1
  108. })
  109. effect(() => {
  110. effectSpy(c2.value)
  111. })
  112. expect(effectSpy).toHaveBeenCalledTimes(1)
  113. expect(effectSpy).toHaveBeenCalledWith(1)
  114. expect(c2.value).toBe(1)
  115. expect(c1Spy).toHaveBeenCalledTimes(1)
  116. expect(c2Spy).toHaveBeenCalledTimes(1)
  117. src.value = 1
  118. // value should be available sync
  119. expect(c2.value).toBe(2)
  120. expect(c2Spy).toHaveBeenCalledTimes(2)
  121. })
  122. test('sync access of invalidated chained computed should not prevent final effect from running', async () => {
  123. const effectSpy = vi.fn()
  124. const c1Spy = vi.fn()
  125. const c2Spy = vi.fn()
  126. const src = ref(0)
  127. const c1 = deferredComputed(() => {
  128. c1Spy()
  129. return src.value % 2
  130. })
  131. const c2 = deferredComputed(() => {
  132. c2Spy()
  133. return c1.value + 1
  134. })
  135. effect(() => {
  136. effectSpy(c2.value)
  137. })
  138. expect(effectSpy).toHaveBeenCalledTimes(1)
  139. src.value = 1
  140. // sync access c2
  141. c2.value
  142. await tick
  143. expect(effectSpy).toHaveBeenCalledTimes(2)
  144. })
  145. test('should not compute if deactivated before scheduler is called', async () => {
  146. const c1Spy = vi.fn()
  147. const src = ref(0)
  148. const c1 = deferredComputed(() => {
  149. c1Spy()
  150. return src.value % 2
  151. })
  152. effect(() => c1.value)
  153. expect(c1Spy).toHaveBeenCalledTimes(1)
  154. c1.effect.stop()
  155. // trigger
  156. src.value++
  157. await tick
  158. expect(c1Spy).toHaveBeenCalledTimes(1)
  159. })
  160. })