computed.spec.ts 4.7 KB

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