computed.spec.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import {
  2. computed,
  3. reactive,
  4. effect,
  5. stop,
  6. ref,
  7. WritableComputedRef
  8. } from '../src'
  9. import { mockWarn } from '@vue/shared'
  10. describe('reactivity/computed', () => {
  11. mockWarn()
  12. it('should return updated value', () => {
  13. const value = reactive<{ foo?: number }>({})
  14. const cValue = computed(() => value.foo)
  15. expect(cValue.value).toBe(undefined)
  16. value.foo = 1
  17. expect(cValue.value).toBe(1)
  18. })
  19. it('should compute lazily', () => {
  20. const value = reactive<{ foo?: number }>({})
  21. const getter = jest.fn(() => value.foo)
  22. const cValue = computed(getter)
  23. // lazy
  24. expect(getter).not.toHaveBeenCalled()
  25. expect(cValue.value).toBe(undefined)
  26. expect(getter).toHaveBeenCalledTimes(1)
  27. // should not compute again
  28. cValue.value
  29. expect(getter).toHaveBeenCalledTimes(1)
  30. // should not compute until needed
  31. value.foo = 1
  32. expect(getter).toHaveBeenCalledTimes(1)
  33. // now it should compute
  34. expect(cValue.value).toBe(1)
  35. expect(getter).toHaveBeenCalledTimes(2)
  36. // should not compute again
  37. cValue.value
  38. expect(getter).toHaveBeenCalledTimes(2)
  39. })
  40. it('should trigger effect', () => {
  41. const value = reactive<{ foo?: number }>({})
  42. const cValue = computed(() => value.foo)
  43. let dummy
  44. effect(() => {
  45. dummy = cValue.value
  46. })
  47. expect(dummy).toBe(undefined)
  48. value.foo = 1
  49. expect(dummy).toBe(1)
  50. })
  51. it('should work when chained', () => {
  52. const value = reactive({ foo: 0 })
  53. const c1 = computed(() => value.foo)
  54. const c2 = computed(() => c1.value + 1)
  55. expect(c2.value).toBe(1)
  56. expect(c1.value).toBe(0)
  57. value.foo++
  58. expect(c2.value).toBe(2)
  59. expect(c1.value).toBe(1)
  60. })
  61. it('should trigger effect when chained', () => {
  62. const value = reactive({ foo: 0 })
  63. const getter1 = jest.fn(() => value.foo)
  64. const getter2 = jest.fn(() => {
  65. return c1.value + 1
  66. })
  67. const c1 = computed(getter1)
  68. const c2 = computed(getter2)
  69. let dummy
  70. effect(() => {
  71. dummy = c2.value
  72. })
  73. expect(dummy).toBe(1)
  74. expect(getter1).toHaveBeenCalledTimes(1)
  75. expect(getter2).toHaveBeenCalledTimes(1)
  76. value.foo++
  77. expect(dummy).toBe(2)
  78. // should not result in duplicate calls
  79. expect(getter1).toHaveBeenCalledTimes(2)
  80. expect(getter2).toHaveBeenCalledTimes(2)
  81. })
  82. it('should trigger effect when chained (mixed invocations)', () => {
  83. const value = reactive({ foo: 0 })
  84. const getter1 = jest.fn(() => value.foo)
  85. const getter2 = jest.fn(() => {
  86. return c1.value + 1
  87. })
  88. const c1 = computed(getter1)
  89. const c2 = computed(getter2)
  90. let dummy
  91. effect(() => {
  92. dummy = c1.value + c2.value
  93. })
  94. expect(dummy).toBe(1)
  95. expect(getter1).toHaveBeenCalledTimes(1)
  96. expect(getter2).toHaveBeenCalledTimes(1)
  97. value.foo++
  98. expect(dummy).toBe(3)
  99. // should not result in duplicate calls
  100. expect(getter1).toHaveBeenCalledTimes(2)
  101. expect(getter2).toHaveBeenCalledTimes(2)
  102. })
  103. it('should no longer update when stopped', () => {
  104. const value = reactive<{ foo?: number }>({})
  105. const cValue = computed(() => value.foo)
  106. let dummy
  107. effect(() => {
  108. dummy = cValue.value
  109. })
  110. expect(dummy).toBe(undefined)
  111. value.foo = 1
  112. expect(dummy).toBe(1)
  113. stop(cValue.effect)
  114. value.foo = 2
  115. expect(dummy).toBe(1)
  116. })
  117. it('should support setter', () => {
  118. const n = ref(1)
  119. const plusOne = computed({
  120. get: () => n.value + 1,
  121. set: val => {
  122. n.value = val - 1
  123. }
  124. })
  125. expect(plusOne.value).toBe(2)
  126. n.value++
  127. expect(plusOne.value).toBe(3)
  128. plusOne.value = 0
  129. expect(n.value).toBe(-1)
  130. })
  131. it('should trigger effect w/ setter', () => {
  132. const n = ref(1)
  133. const plusOne = computed({
  134. get: () => n.value + 1,
  135. set: val => {
  136. n.value = val - 1
  137. }
  138. })
  139. let dummy
  140. effect(() => {
  141. dummy = n.value
  142. })
  143. expect(dummy).toBe(1)
  144. plusOne.value = 0
  145. expect(dummy).toBe(-1)
  146. })
  147. it('should warn if trying to set a readonly computed', () => {
  148. const n = ref(1)
  149. const plusOne = computed(() => n.value + 1)
  150. ;(plusOne as WritableComputedRef<number>).value++ // Type cast to prevent TS from preventing the error
  151. expect(
  152. 'Write operation failed: computed value is readonly'
  153. ).toHaveBeenWarnedLast()
  154. })
  155. })