computed.spec.ts 4.7 KB

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