ref.spec.ts 9.7 KB

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