ref.spec.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. import {
  2. ref,
  3. effect,
  4. reactive,
  5. isRef,
  6. toRef,
  7. toRefs,
  8. Ref,
  9. isReactive
  10. } from '../src/index'
  11. import { computed } from '@vue/runtime-dom'
  12. import { shallowRef, unref, customRef, triggerRef } from '../src/ref'
  13. import { isShallow } from '../src/reactive'
  14. describe('reactivity/ref', () => {
  15. it('should hold a value', () => {
  16. const a = ref(1)
  17. expect(a.value).toBe(1)
  18. a.value = 2
  19. expect(a.value).toBe(2)
  20. })
  21. it('should be reactive', () => {
  22. const a = ref(1)
  23. let dummy
  24. let calls = 0
  25. effect(() => {
  26. calls++
  27. dummy = a.value
  28. })
  29. expect(calls).toBe(1)
  30. expect(dummy).toBe(1)
  31. a.value = 2
  32. expect(calls).toBe(2)
  33. expect(dummy).toBe(2)
  34. // same value should not trigger
  35. a.value = 2
  36. expect(calls).toBe(2)
  37. })
  38. it('should make nested properties reactive', () => {
  39. const a = ref({
  40. count: 1
  41. })
  42. let dummy
  43. effect(() => {
  44. dummy = a.value.count
  45. })
  46. expect(dummy).toBe(1)
  47. a.value.count = 2
  48. expect(dummy).toBe(2)
  49. })
  50. it('should work without initial value', () => {
  51. const a = ref()
  52. let dummy
  53. effect(() => {
  54. dummy = a.value
  55. })
  56. expect(dummy).toBe(undefined)
  57. a.value = 2
  58. expect(dummy).toBe(2)
  59. })
  60. it('should work like a normal property when nested in a reactive object', () => {
  61. const a = ref(1)
  62. const obj = reactive({
  63. a,
  64. b: {
  65. c: a
  66. }
  67. })
  68. let dummy1: number
  69. let dummy2: number
  70. effect(() => {
  71. dummy1 = obj.a
  72. dummy2 = obj.b.c
  73. })
  74. const assertDummiesEqualTo = (val: number) =>
  75. [dummy1, dummy2].forEach(dummy => expect(dummy).toBe(val))
  76. assertDummiesEqualTo(1)
  77. a.value++
  78. assertDummiesEqualTo(2)
  79. obj.a++
  80. assertDummiesEqualTo(3)
  81. obj.b.c++
  82. assertDummiesEqualTo(4)
  83. })
  84. it('should unwrap nested ref in types', () => {
  85. const a = ref(0)
  86. const b = ref(a)
  87. expect(typeof (b.value + 1)).toBe('number')
  88. })
  89. it('should unwrap nested values in types', () => {
  90. const a = {
  91. b: ref(0)
  92. }
  93. const c = ref(a)
  94. expect(typeof (c.value.b + 1)).toBe('number')
  95. })
  96. it('should NOT unwrap ref types nested inside arrays', () => {
  97. const arr = ref([1, ref(3)]).value
  98. expect(isRef(arr[0])).toBe(false)
  99. expect(isRef(arr[1])).toBe(true)
  100. expect((arr[1] as Ref).value).toBe(3)
  101. })
  102. it('should unwrap ref types as props of arrays', () => {
  103. const arr = [ref(0)]
  104. const symbolKey = Symbol('')
  105. arr['' as any] = ref(1)
  106. arr[symbolKey as any] = ref(2)
  107. const arrRef = ref(arr).value
  108. expect(isRef(arrRef[0])).toBe(true)
  109. expect(isRef(arrRef['' as any])).toBe(false)
  110. expect(isRef(arrRef[symbolKey as any])).toBe(false)
  111. expect(arrRef['' as any]).toBe(1)
  112. expect(arrRef[symbolKey as any]).toBe(2)
  113. })
  114. it('should keep tuple types', () => {
  115. const tuple: [number, string, { a: number }, () => number, Ref<number>] = [
  116. 0,
  117. '1',
  118. { a: 1 },
  119. () => 0,
  120. ref(0)
  121. ]
  122. const tupleRef = ref(tuple)
  123. tupleRef.value[0]++
  124. expect(tupleRef.value[0]).toBe(1)
  125. tupleRef.value[1] += '1'
  126. expect(tupleRef.value[1]).toBe('11')
  127. tupleRef.value[2].a++
  128. expect(tupleRef.value[2].a).toBe(2)
  129. expect(tupleRef.value[3]()).toBe(0)
  130. tupleRef.value[4].value++
  131. expect(tupleRef.value[4].value).toBe(1)
  132. })
  133. it('should keep symbols', () => {
  134. const customSymbol = Symbol()
  135. const obj = {
  136. [Symbol.asyncIterator]: ref(1),
  137. [Symbol.hasInstance]: { a: ref('a') },
  138. [Symbol.isConcatSpreadable]: { b: ref(true) },
  139. [Symbol.iterator]: [ref(1)],
  140. [Symbol.match]: new Set<Ref<number>>(),
  141. [Symbol.matchAll]: new Map<number, Ref<string>>(),
  142. [Symbol.replace]: { arr: [ref('a')] },
  143. [Symbol.search]: { set: new Set<Ref<number>>() },
  144. [Symbol.species]: { map: new Map<number, Ref<string>>() },
  145. [Symbol.split]: new WeakSet<Ref<boolean>>(),
  146. [Symbol.toPrimitive]: new WeakMap<Ref<boolean>, string>(),
  147. [Symbol.toStringTag]: { weakSet: new WeakSet<Ref<boolean>>() },
  148. [Symbol.unscopables]: { weakMap: new WeakMap<Ref<boolean>, string>() },
  149. [customSymbol]: { arr: [ref(1)] }
  150. }
  151. const objRef = ref(obj)
  152. const keys: (keyof typeof obj)[] = [
  153. Symbol.asyncIterator,
  154. Symbol.hasInstance,
  155. Symbol.isConcatSpreadable,
  156. Symbol.iterator,
  157. Symbol.match,
  158. Symbol.matchAll,
  159. Symbol.replace,
  160. Symbol.search,
  161. Symbol.species,
  162. Symbol.split,
  163. Symbol.toPrimitive,
  164. Symbol.toStringTag,
  165. Symbol.unscopables,
  166. customSymbol
  167. ]
  168. keys.forEach(key => {
  169. expect(objRef.value[key]).toStrictEqual(obj[key])
  170. })
  171. })
  172. test('unref', () => {
  173. expect(unref(1)).toBe(1)
  174. expect(unref(ref(1))).toBe(1)
  175. })
  176. test('shallowRef', () => {
  177. const sref = shallowRef({ a: 1 })
  178. expect(isReactive(sref.value)).toBe(false)
  179. let dummy
  180. effect(() => {
  181. dummy = sref.value.a
  182. })
  183. expect(dummy).toBe(1)
  184. sref.value = { a: 2 }
  185. expect(isReactive(sref.value)).toBe(false)
  186. expect(dummy).toBe(2)
  187. })
  188. test('shallowRef force trigger', () => {
  189. const sref = shallowRef({ a: 1 })
  190. let dummy
  191. effect(() => {
  192. dummy = sref.value.a
  193. })
  194. expect(dummy).toBe(1)
  195. sref.value.a = 2
  196. expect(dummy).toBe(1) // should not trigger yet
  197. // force trigger
  198. triggerRef(sref)
  199. expect(dummy).toBe(2)
  200. })
  201. test('shallowRef isShallow', () => {
  202. expect(isShallow(shallowRef({ a: 1 }))).toBe(true)
  203. })
  204. test('isRef', () => {
  205. expect(isRef(ref(1))).toBe(true)
  206. expect(isRef(computed(() => 1))).toBe(true)
  207. expect(isRef(0)).toBe(false)
  208. expect(isRef(1)).toBe(false)
  209. // an object that looks like a ref isn't necessarily a ref
  210. expect(isRef({ value: 0 })).toBe(false)
  211. })
  212. test('toRef', () => {
  213. const a = reactive({
  214. x: 1
  215. })
  216. const x = toRef(a, 'x')
  217. expect(isRef(x)).toBe(true)
  218. expect(x.value).toBe(1)
  219. // source -> proxy
  220. a.x = 2
  221. expect(x.value).toBe(2)
  222. // proxy -> source
  223. x.value = 3
  224. expect(a.x).toBe(3)
  225. // reactivity
  226. let dummyX
  227. effect(() => {
  228. dummyX = x.value
  229. })
  230. expect(dummyX).toBe(x.value)
  231. // mutating source should trigger effect using the proxy refs
  232. a.x = 4
  233. expect(dummyX).toBe(4)
  234. // should keep ref
  235. const r = { x: ref(1) }
  236. expect(toRef(r, 'x')).toBe(r.x)
  237. })
  238. test('toRef default value', () => {
  239. const a: { x: number | undefined } = { x: undefined }
  240. const x = toRef(a, 'x', 1)
  241. expect(x.value).toBe(1)
  242. a.x = 2
  243. expect(x.value).toBe(2)
  244. a.x = undefined
  245. expect(x.value).toBe(1)
  246. })
  247. test('toRefs', () => {
  248. const a = reactive({
  249. x: 1,
  250. y: 2
  251. })
  252. const { x, y } = toRefs(a)
  253. expect(isRef(x)).toBe(true)
  254. expect(isRef(y)).toBe(true)
  255. expect(x.value).toBe(1)
  256. expect(y.value).toBe(2)
  257. // source -> proxy
  258. a.x = 2
  259. a.y = 3
  260. expect(x.value).toBe(2)
  261. expect(y.value).toBe(3)
  262. // proxy -> source
  263. x.value = 3
  264. y.value = 4
  265. expect(a.x).toBe(3)
  266. expect(a.y).toBe(4)
  267. // reactivity
  268. let dummyX, dummyY
  269. effect(() => {
  270. dummyX = x.value
  271. dummyY = y.value
  272. })
  273. expect(dummyX).toBe(x.value)
  274. expect(dummyY).toBe(y.value)
  275. // mutating source should trigger effect using the proxy refs
  276. a.x = 4
  277. a.y = 5
  278. expect(dummyX).toBe(4)
  279. expect(dummyY).toBe(5)
  280. })
  281. test('toRefs should warn on plain object', () => {
  282. toRefs({})
  283. expect(`toRefs() expects a reactive object`).toHaveBeenWarned()
  284. })
  285. test('toRefs should warn on plain array', () => {
  286. toRefs([])
  287. expect(`toRefs() expects a reactive object`).toHaveBeenWarned()
  288. })
  289. test('toRefs reactive array', () => {
  290. const arr = reactive(['a', 'b', 'c'])
  291. const refs = toRefs(arr)
  292. expect(Array.isArray(refs)).toBe(true)
  293. refs[0].value = '1'
  294. expect(arr[0]).toBe('1')
  295. arr[1] = '2'
  296. expect(refs[1].value).toBe('2')
  297. })
  298. test('customRef', () => {
  299. let value = 1
  300. let _trigger: () => void
  301. const custom = customRef((track, trigger) => ({
  302. get() {
  303. track()
  304. return value
  305. },
  306. set(newValue: number) {
  307. value = newValue
  308. _trigger = trigger
  309. }
  310. }))
  311. expect(isRef(custom)).toBe(true)
  312. let dummy
  313. effect(() => {
  314. dummy = custom.value
  315. })
  316. expect(dummy).toBe(1)
  317. custom.value = 2
  318. // should not trigger yet
  319. expect(dummy).toBe(1)
  320. _trigger!()
  321. expect(dummy).toBe(2)
  322. })
  323. test('should not trigger when setting value to same proxy', () => {
  324. const obj = reactive({ count: 0 })
  325. const a = ref(obj)
  326. const spy1 = jest.fn(() => a.value)
  327. effect(spy1)
  328. a.value = obj
  329. expect(spy1).toBeCalledTimes(1)
  330. const b = shallowRef(obj)
  331. const spy2 = jest.fn(() => b.value)
  332. effect(spy2)
  333. b.value = obj
  334. expect(spy2).toBeCalledTimes(1)
  335. })
  336. })