ref.spec.ts 9.7 KB

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