ref.spec.ts 8.8 KB

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