reactive.spec.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. import { ref, isRef } from '../src/ref'
  2. import {
  3. reactive,
  4. isReactive,
  5. toRaw,
  6. markNonReactive,
  7. shallowReactive
  8. } from '../src/reactive'
  9. import { mockWarn } from '@vue/shared'
  10. import { computed } from '../src/computed'
  11. describe('reactivity/reactive', () => {
  12. mockWarn()
  13. test('Object', () => {
  14. const original = { foo: 1 }
  15. const observed = reactive(original)
  16. expect(observed).not.toBe(original)
  17. expect(isReactive(observed)).toBe(true)
  18. expect(isReactive(original)).toBe(false)
  19. // get
  20. expect(observed.foo).toBe(1)
  21. // has
  22. expect('foo' in observed).toBe(true)
  23. // ownKeys
  24. expect(Object.keys(observed)).toEqual(['foo'])
  25. })
  26. test('Array', () => {
  27. const original = [{ foo: 1 }]
  28. const observed = reactive(original)
  29. expect(observed).not.toBe(original)
  30. expect(isReactive(observed)).toBe(true)
  31. expect(isReactive(original)).toBe(false)
  32. expect(isReactive(observed[0])).toBe(true)
  33. // get
  34. expect(observed[0].foo).toBe(1)
  35. // has
  36. expect(0 in observed).toBe(true)
  37. // ownKeys
  38. expect(Object.keys(observed)).toEqual(['0'])
  39. })
  40. test('cloned reactive Array should point to observed values', () => {
  41. const original = [{ foo: 1 }]
  42. const observed = reactive(original)
  43. const clone = observed.slice()
  44. expect(isReactive(clone[0])).toBe(true)
  45. expect(clone[0]).not.toBe(original[0])
  46. expect(clone[0]).toBe(observed[0])
  47. })
  48. test('Array identity methods should work with raw values', () => {
  49. const raw = {}
  50. const arr = reactive([{}, {}])
  51. arr.push(raw)
  52. expect(arr.indexOf(raw)).toBe(2)
  53. expect(arr.indexOf(raw, 3)).toBe(-1)
  54. expect(arr.includes(raw)).toBe(true)
  55. expect(arr.includes(raw, 3)).toBe(false)
  56. expect(arr.lastIndexOf(raw)).toBe(2)
  57. expect(arr.lastIndexOf(raw, 1)).toBe(-1)
  58. // should work also for the observed version
  59. const observed = arr[2]
  60. expect(arr.indexOf(observed)).toBe(2)
  61. expect(arr.indexOf(observed, 3)).toBe(-1)
  62. expect(arr.includes(observed)).toBe(true)
  63. expect(arr.includes(observed, 3)).toBe(false)
  64. expect(arr.lastIndexOf(observed)).toBe(2)
  65. expect(arr.lastIndexOf(observed, 1)).toBe(-1)
  66. })
  67. test('nested reactives', () => {
  68. const original = {
  69. nested: {
  70. foo: 1
  71. },
  72. array: [{ bar: 2 }]
  73. }
  74. const observed = reactive(original)
  75. expect(isReactive(observed.nested)).toBe(true)
  76. expect(isReactive(observed.array)).toBe(true)
  77. expect(isReactive(observed.array[0])).toBe(true)
  78. })
  79. test('observed value should proxy mutations to original (Object)', () => {
  80. const original: any = { foo: 1 }
  81. const observed = reactive(original)
  82. // set
  83. observed.bar = 1
  84. expect(observed.bar).toBe(1)
  85. expect(original.bar).toBe(1)
  86. // delete
  87. delete observed.foo
  88. expect('foo' in observed).toBe(false)
  89. expect('foo' in original).toBe(false)
  90. })
  91. test('observed value should proxy mutations to original (Array)', () => {
  92. const original: any[] = [{ foo: 1 }, { bar: 2 }]
  93. const observed = reactive(original)
  94. // set
  95. const value = { baz: 3 }
  96. const reactiveValue = reactive(value)
  97. observed[0] = value
  98. expect(observed[0]).toBe(reactiveValue)
  99. expect(original[0]).toBe(value)
  100. // delete
  101. delete observed[0]
  102. expect(observed[0]).toBeUndefined()
  103. expect(original[0]).toBeUndefined()
  104. // mutating methods
  105. observed.push(value)
  106. expect(observed[2]).toBe(reactiveValue)
  107. expect(original[2]).toBe(value)
  108. })
  109. test('setting a property with an unobserved value should wrap with reactive', () => {
  110. const observed = reactive<{ foo?: object }>({})
  111. const raw = {}
  112. observed.foo = raw
  113. expect(observed.foo).not.toBe(raw)
  114. expect(isReactive(observed.foo)).toBe(true)
  115. })
  116. test('observing already observed value should return same Proxy', () => {
  117. const original = { foo: 1 }
  118. const observed = reactive(original)
  119. const observed2 = reactive(observed)
  120. expect(observed2).toBe(observed)
  121. })
  122. test('observing the same value multiple times should return same Proxy', () => {
  123. const original = { foo: 1 }
  124. const observed = reactive(original)
  125. const observed2 = reactive(original)
  126. expect(observed2).toBe(observed)
  127. })
  128. test('should not pollute original object with Proxies', () => {
  129. const original: any = { foo: 1 }
  130. const original2 = { bar: 2 }
  131. const observed = reactive(original)
  132. const observed2 = reactive(original2)
  133. observed.bar = observed2
  134. expect(observed.bar).toBe(observed2)
  135. expect(original.bar).toBe(original2)
  136. })
  137. test('unwrap', () => {
  138. const original = { foo: 1 }
  139. const observed = reactive(original)
  140. expect(toRaw(observed)).toBe(original)
  141. expect(toRaw(original)).toBe(original)
  142. })
  143. test('should not unwrap Ref<T>', () => {
  144. const observedNumberRef = reactive(ref(1))
  145. const observedObjectRef = reactive(ref({ foo: 1 }))
  146. expect(isRef(observedNumberRef)).toBe(true)
  147. expect(isRef(observedObjectRef)).toBe(true)
  148. })
  149. test('should unwrap computed refs', () => {
  150. // readonly
  151. const a = computed(() => 1)
  152. // writable
  153. const b = computed({
  154. get: () => 1,
  155. set: () => {}
  156. })
  157. const obj = reactive({ a, b })
  158. // check type
  159. obj.a + 1
  160. obj.b + 1
  161. expect(typeof obj.a).toBe(`number`)
  162. expect(typeof obj.b).toBe(`number`)
  163. })
  164. test('non-observable values', () => {
  165. const assertValue = (value: any) => {
  166. reactive(value)
  167. expect(
  168. `value cannot be made reactive: ${String(value)}`
  169. ).toHaveBeenWarnedLast()
  170. }
  171. // number
  172. assertValue(1)
  173. // string
  174. assertValue('foo')
  175. // boolean
  176. assertValue(false)
  177. // null
  178. assertValue(null)
  179. // undefined
  180. assertValue(undefined)
  181. // symbol
  182. const s = Symbol()
  183. assertValue(s)
  184. // built-ins should work and return same value
  185. const p = Promise.resolve()
  186. expect(reactive(p)).toBe(p)
  187. const r = new RegExp('')
  188. expect(reactive(r)).toBe(r)
  189. const d = new Date()
  190. expect(reactive(d)).toBe(d)
  191. })
  192. test('markNonReactive', () => {
  193. const obj = reactive({
  194. foo: { a: 1 },
  195. bar: markNonReactive({ b: 2 })
  196. })
  197. expect(isReactive(obj.foo)).toBe(true)
  198. expect(isReactive(obj.bar)).toBe(false)
  199. })
  200. describe('shallowReactive', () => {
  201. test('should not make non-reactive properties reactive', () => {
  202. const props = shallowReactive({ n: { foo: 1 } })
  203. expect(isReactive(props.n)).toBe(false)
  204. })
  205. test('should keep reactive properties reactive', () => {
  206. const props: any = shallowReactive({ n: reactive({ foo: 1 }) })
  207. props.n = reactive({ foo: 2 })
  208. expect(isReactive(props.n)).toBe(true)
  209. })
  210. })
  211. })