ref.spec.ts 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. import {
  2. ref,
  3. isRef,
  4. shallowRef,
  5. unref,
  6. triggerRef,
  7. toRef,
  8. toRefs,
  9. customRef,
  10. Ref,
  11. isReactive,
  12. isShallow,
  13. reactive,
  14. computed,
  15. readonly
  16. } from 'v3'
  17. import { effect } from 'v3/reactivity/effect'
  18. describe('reactivity/ref', () => {
  19. it('should hold a value', () => {
  20. const a = ref(1)
  21. expect(a.value).toBe(1)
  22. a.value = 2
  23. expect(a.value).toBe(2)
  24. })
  25. it('should be reactive', () => {
  26. const a = ref(1)
  27. let dummy
  28. let calls = 0
  29. effect(() => {
  30. calls++
  31. dummy = a.value
  32. })
  33. expect(calls).toBe(1)
  34. expect(dummy).toBe(1)
  35. a.value = 2
  36. expect(calls).toBe(2)
  37. expect(dummy).toBe(2)
  38. // same value should not trigger
  39. a.value = 2
  40. expect(calls).toBe(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. // @discrepancy Vue 2 does not observe array properties
  107. // it('should unwrap ref types as props of arrays', () => {
  108. // const arr = [ref(0)]
  109. // const symbolKey = Symbol('')
  110. // arr['' as any] = ref(1)
  111. // arr[symbolKey as any] = ref(2)
  112. // const arrRef = ref(arr).value
  113. // expect(isRef(arrRef[0])).toBe(true)
  114. // expect(isRef(arrRef['' as any])).toBe(false)
  115. // expect(isRef(arrRef[symbolKey as any])).toBe(false)
  116. // expect(arrRef['' as any]).toBe(1)
  117. // expect(arrRef[symbolKey as any]).toBe(2)
  118. // })
  119. it('should keep tuple types', () => {
  120. const tuple: [number, string, { a: number }, () => number, Ref<number>] = [
  121. 0,
  122. '1',
  123. { a: 1 },
  124. () => 0,
  125. ref(0)
  126. ]
  127. const tupleRef = ref(tuple)
  128. tupleRef.value[0]++
  129. expect(tupleRef.value[0]).toBe(1)
  130. tupleRef.value[1] += '1'
  131. expect(tupleRef.value[1]).toBe('11')
  132. tupleRef.value[2].a++
  133. expect(tupleRef.value[2].a).toBe(2)
  134. expect(tupleRef.value[3]()).toBe(0)
  135. tupleRef.value[4].value++
  136. expect(tupleRef.value[4].value).toBe(1)
  137. })
  138. it('should keep symbols', () => {
  139. const customSymbol = Symbol()
  140. const obj = {
  141. [Symbol.asyncIterator]: ref(1),
  142. [Symbol.hasInstance]: { a: ref('a') },
  143. [Symbol.isConcatSpreadable]: { b: ref(true) },
  144. [Symbol.iterator]: [ref(1)],
  145. [Symbol.match]: new Set<Ref<number>>(),
  146. [Symbol.matchAll]: new Map<number, Ref<string>>(),
  147. [Symbol.replace]: { arr: [ref('a')] },
  148. [Symbol.search]: { set: new Set<Ref<number>>() },
  149. [Symbol.species]: { map: new Map<number, Ref<string>>() },
  150. [Symbol.split]: new WeakSet<Ref<boolean>>(),
  151. [Symbol.toPrimitive]: new WeakMap<Ref<boolean>, string>(),
  152. [Symbol.toStringTag]: { weakSet: new WeakSet<Ref<boolean>>() },
  153. [Symbol.unscopables]: { weakMap: new WeakMap<Ref<boolean>, string>() },
  154. [customSymbol]: { arr: [ref(1)] }
  155. }
  156. const objRef = ref(obj)
  157. const keys: (keyof typeof obj)[] = [
  158. Symbol.asyncIterator,
  159. Symbol.hasInstance,
  160. Symbol.isConcatSpreadable,
  161. Symbol.iterator,
  162. Symbol.match,
  163. Symbol.matchAll,
  164. Symbol.replace,
  165. Symbol.search,
  166. Symbol.species,
  167. Symbol.split,
  168. Symbol.toPrimitive,
  169. Symbol.toStringTag,
  170. Symbol.unscopables,
  171. customSymbol
  172. ]
  173. keys.forEach(key => {
  174. expect(objRef.value[key]).toStrictEqual(obj[key])
  175. })
  176. })
  177. test('unref', () => {
  178. expect(unref(1)).toBe(1)
  179. expect(unref(ref(1))).toBe(1)
  180. })
  181. test('shallowRef', () => {
  182. const sref = shallowRef({ a: 1 })
  183. expect(isReactive(sref.value)).toBe(false)
  184. let dummy
  185. effect(() => {
  186. dummy = sref.value.a
  187. })
  188. expect(dummy).toBe(1)
  189. sref.value = { a: 2 }
  190. expect(isReactive(sref.value)).toBe(false)
  191. expect(dummy).toBe(2)
  192. })
  193. test('shallowRef force trigger', () => {
  194. const sref = shallowRef({ a: 1 })
  195. let dummy
  196. effect(() => {
  197. dummy = sref.value.a
  198. })
  199. expect(dummy).toBe(1)
  200. sref.value.a = 2
  201. expect(dummy).toBe(1) // should not trigger yet
  202. // force trigger
  203. triggerRef(sref)
  204. expect(dummy).toBe(2)
  205. })
  206. test('shallowRef isShallow', () => {
  207. expect(isShallow(shallowRef({ a: 1 }))).toBe(true)
  208. })
  209. test('isRef', () => {
  210. expect(isRef(ref(1))).toBe(true)
  211. expect(isRef(computed(() => 1))).toBe(true)
  212. expect(isRef(0)).toBe(false)
  213. expect(isRef(1)).toBe(false)
  214. // an object that looks like a ref isn't necessarily a ref
  215. expect(isRef({ value: 0 })).toBe(false)
  216. })
  217. test('toRef', () => {
  218. const a = reactive({
  219. x: 1
  220. })
  221. const x = toRef(a, 'x')
  222. expect(isRef(x)).toBe(true)
  223. expect(x.value).toBe(1)
  224. // source -> proxy
  225. a.x = 2
  226. expect(x.value).toBe(2)
  227. // proxy -> source
  228. x.value = 3
  229. expect(a.x).toBe(3)
  230. // reactivity
  231. let dummyX
  232. effect(() => {
  233. dummyX = x.value
  234. })
  235. expect(dummyX).toBe(x.value)
  236. // mutating source should trigger effect using the proxy refs
  237. a.x = 4
  238. expect(dummyX).toBe(4)
  239. // should keep ref
  240. const r = { x: ref(1) }
  241. expect(toRef(r, 'x')).toBe(r.x)
  242. })
  243. test('toRef default value', () => {
  244. const a: { x: number | undefined } = { x: undefined }
  245. const x = toRef(a, 'x', 1)
  246. expect(x.value).toBe(1)
  247. a.x = 2
  248. expect(x.value).toBe(2)
  249. a.x = undefined
  250. expect(x.value).toBe(1)
  251. })
  252. test('toRefs', () => {
  253. const a = reactive({
  254. x: 1,
  255. y: 2
  256. })
  257. const { x, y } = toRefs(a)
  258. expect(isRef(x)).toBe(true)
  259. expect(isRef(y)).toBe(true)
  260. expect(x.value).toBe(1)
  261. expect(y.value).toBe(2)
  262. // source -> proxy
  263. a.x = 2
  264. a.y = 3
  265. expect(x.value).toBe(2)
  266. expect(y.value).toBe(3)
  267. // proxy -> source
  268. x.value = 3
  269. y.value = 4
  270. expect(a.x).toBe(3)
  271. expect(a.y).toBe(4)
  272. // reactivity
  273. let dummyX, dummyY
  274. effect(() => {
  275. dummyX = x.value
  276. dummyY = y.value
  277. })
  278. expect(dummyX).toBe(x.value)
  279. expect(dummyY).toBe(y.value)
  280. // mutating source should trigger effect using the proxy refs
  281. a.x = 4
  282. a.y = 5
  283. expect(dummyX).toBe(4)
  284. expect(dummyY).toBe(5)
  285. })
  286. test('toRefs should warn on plain object', () => {
  287. toRefs({})
  288. expect(`toRefs() expects a reactive object`).toHaveBeenWarned()
  289. })
  290. test('toRefs should warn on plain array', () => {
  291. toRefs([])
  292. expect(`toRefs() expects a reactive object`).toHaveBeenWarned()
  293. })
  294. test('toRefs reactive array', () => {
  295. const { arr } = reactive({ arr: ['a', 'b', 'c'] })
  296. const refs = toRefs(arr)
  297. expect(Array.isArray(refs)).toBe(true)
  298. refs[0].value = '1'
  299. expect(arr[0]).toBe('1')
  300. arr[1] = '2'
  301. expect(refs[1].value).toBe('2')
  302. })
  303. test('customRef', () => {
  304. let value = 1
  305. let _trigger: () => void
  306. const custom = customRef((track, trigger) => ({
  307. get() {
  308. track()
  309. return value
  310. },
  311. set(newValue: number) {
  312. value = newValue
  313. _trigger = trigger
  314. }
  315. }))
  316. expect(isRef(custom)).toBe(true)
  317. let dummy
  318. effect(() => {
  319. dummy = custom.value
  320. })
  321. expect(dummy).toBe(1)
  322. custom.value = 2
  323. // should not trigger yet
  324. expect(dummy).toBe(1)
  325. _trigger!()
  326. expect(dummy).toBe(2)
  327. })
  328. test('should not trigger when setting value to same proxy', () => {
  329. const obj = reactive({ count: 0 })
  330. const a = ref(obj)
  331. const spy1 = vi.fn(() => a.value)
  332. effect(spy1)
  333. a.value = obj
  334. expect(spy1).toBeCalledTimes(1)
  335. const b = shallowRef(obj)
  336. const spy2 = vi.fn(() => b.value)
  337. effect(spy2)
  338. b.value = obj
  339. expect(spy2).toBeCalledTimes(1)
  340. })
  341. test('ref should preserve value readonly-ness', () => {
  342. const original = {}
  343. const r = reactive(original)
  344. const rr = readonly(original)
  345. const a = ref(original)
  346. expect(a.value).toBe(r)
  347. a.value = rr
  348. expect(a.value).toBe(rr)
  349. expect(a.value).not.toBe(r)
  350. })
  351. })