ref.spec.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735
  1. import {
  2. type Ref,
  3. effect,
  4. isReactive,
  5. isRef,
  6. reactive,
  7. ref,
  8. toRef,
  9. toRefs,
  10. toValue,
  11. } from '../src/index'
  12. import { computed } from '@vue/runtime-dom'
  13. import { customRef, shallowRef, triggerRef, unref } from '../src/ref'
  14. import {
  15. isReadonly,
  16. isShallow,
  17. readonly,
  18. shallowReactive,
  19. shallowReadonly,
  20. } from '../src/reactive'
  21. describe('reactivity/ref', () => {
  22. it('should hold a value', () => {
  23. const a = ref(1)
  24. expect(a.value).toBe(1)
  25. a.value = 2
  26. expect(a.value).toBe(2)
  27. })
  28. it('should be reactive', () => {
  29. const a = ref(1)
  30. let dummy
  31. const fn = vi.fn(() => {
  32. dummy = a.value
  33. })
  34. effect(fn)
  35. expect(fn).toHaveBeenCalledTimes(1)
  36. expect(dummy).toBe(1)
  37. a.value = 2
  38. expect(fn).toHaveBeenCalledTimes(2)
  39. expect(dummy).toBe(2)
  40. // same value should not trigger
  41. a.value = 2
  42. expect(fn).toHaveBeenCalledTimes(2)
  43. })
  44. it('ref wrapped in reactive should not track internal _value access', () => {
  45. const a = ref(1)
  46. const b = reactive(a)
  47. let dummy
  48. const fn = vi.fn(() => {
  49. dummy = b.value // this will observe both b.value and a.value access
  50. })
  51. effect(fn)
  52. expect(fn).toHaveBeenCalledTimes(1)
  53. expect(dummy).toBe(1)
  54. // mutating a.value should only trigger effect once
  55. a.value = 3
  56. expect(fn).toHaveBeenCalledTimes(2)
  57. expect(dummy).toBe(3)
  58. // mutating b.value should trigger the effect twice. (once for a.value change and once for b.value change)
  59. b.value = 5
  60. expect(fn).toHaveBeenCalledTimes(4)
  61. expect(dummy).toBe(5)
  62. })
  63. it('should make nested properties reactive', () => {
  64. const a = ref({
  65. count: 1,
  66. })
  67. let dummy
  68. effect(() => {
  69. dummy = a.value.count
  70. })
  71. expect(dummy).toBe(1)
  72. a.value.count = 2
  73. expect(dummy).toBe(2)
  74. })
  75. it('should work without initial value', () => {
  76. const a = ref()
  77. let dummy
  78. effect(() => {
  79. dummy = a.value
  80. })
  81. expect(dummy).toBe(undefined)
  82. a.value = 2
  83. expect(dummy).toBe(2)
  84. })
  85. it('should work like a normal property when nested in a reactive object', () => {
  86. const a = ref(1)
  87. const obj = reactive({
  88. a,
  89. b: {
  90. c: a,
  91. },
  92. })
  93. let dummy1: number
  94. let dummy2: number
  95. effect(() => {
  96. dummy1 = obj.a
  97. dummy2 = obj.b.c
  98. })
  99. const assertDummiesEqualTo = (val: number) =>
  100. [dummy1, dummy2].forEach(dummy => expect(dummy).toBe(val))
  101. assertDummiesEqualTo(1)
  102. a.value++
  103. assertDummiesEqualTo(2)
  104. obj.a++
  105. assertDummiesEqualTo(3)
  106. obj.b.c++
  107. assertDummiesEqualTo(4)
  108. })
  109. it('should unwrap nested ref in types', () => {
  110. const a = ref(0)
  111. const b = ref(a)
  112. expect(typeof (b.value + 1)).toBe('number')
  113. })
  114. it('should unwrap nested values in types', () => {
  115. const a = {
  116. b: ref(0),
  117. }
  118. const c = ref(a)
  119. expect(typeof (c.value.b + 1)).toBe('number')
  120. })
  121. it('should NOT unwrap ref types nested inside arrays', () => {
  122. const arr = ref([1, ref(3)]).value
  123. expect(isRef(arr[0])).toBe(false)
  124. expect(isRef(arr[1])).toBe(true)
  125. expect((arr[1] as Ref).value).toBe(3)
  126. })
  127. it('should unwrap ref types as props of arrays', () => {
  128. const arr = [ref(0)]
  129. const symbolKey = Symbol('')
  130. arr['' as any] = ref(1)
  131. arr[symbolKey as any] = ref(2)
  132. const arrRef = ref(arr).value
  133. expect(isRef(arrRef[0])).toBe(true)
  134. expect(isRef(arrRef['' as any])).toBe(false)
  135. expect(isRef(arrRef[symbolKey as any])).toBe(false)
  136. expect(arrRef['' as any]).toBe(1)
  137. expect(arrRef[symbolKey as any]).toBe(2)
  138. })
  139. it('should keep tuple types', () => {
  140. const tuple: [number, string, { a: number }, () => number, Ref<number>] = [
  141. 0,
  142. '1',
  143. { a: 1 },
  144. () => 0,
  145. ref(0),
  146. ]
  147. const tupleRef = ref(tuple)
  148. tupleRef.value[0]++
  149. expect(tupleRef.value[0]).toBe(1)
  150. tupleRef.value[1] += '1'
  151. expect(tupleRef.value[1]).toBe('11')
  152. tupleRef.value[2].a++
  153. expect(tupleRef.value[2].a).toBe(2)
  154. expect(tupleRef.value[3]()).toBe(0)
  155. tupleRef.value[4].value++
  156. expect(tupleRef.value[4].value).toBe(1)
  157. })
  158. it('should keep symbols', () => {
  159. const customSymbol = Symbol()
  160. const obj = {
  161. [Symbol.asyncIterator]: ref(1),
  162. [Symbol.hasInstance]: { a: ref('a') },
  163. [Symbol.isConcatSpreadable]: { b: ref(true) },
  164. [Symbol.iterator]: [ref(1)],
  165. [Symbol.match]: new Set<Ref<number>>(),
  166. [Symbol.matchAll]: new Map<number, Ref<string>>(),
  167. [Symbol.replace]: { arr: [ref('a')] },
  168. [Symbol.search]: { set: new Set<Ref<number>>() },
  169. [Symbol.species]: { map: new Map<number, Ref<string>>() },
  170. [Symbol.split]: new WeakSet<Ref<boolean>>(),
  171. [Symbol.toPrimitive]: new WeakMap<Ref<boolean>, string>(),
  172. [Symbol.toStringTag]: { weakSet: new WeakSet<Ref<boolean>>() },
  173. [Symbol.unscopables]: { weakMap: new WeakMap<Ref<boolean>, string>() },
  174. [customSymbol]: { arr: [ref(1)] },
  175. }
  176. const objRef = ref(obj)
  177. const keys: (keyof typeof obj)[] = [
  178. Symbol.asyncIterator,
  179. Symbol.hasInstance,
  180. Symbol.isConcatSpreadable,
  181. Symbol.iterator,
  182. Symbol.match,
  183. Symbol.matchAll,
  184. Symbol.replace,
  185. Symbol.search,
  186. Symbol.species,
  187. Symbol.split,
  188. Symbol.toPrimitive,
  189. Symbol.toStringTag,
  190. Symbol.unscopables,
  191. customSymbol,
  192. ]
  193. keys.forEach(key => {
  194. expect(objRef.value[key]).toStrictEqual(obj[key])
  195. })
  196. })
  197. test('unref', () => {
  198. expect(unref(1)).toBe(1)
  199. expect(unref(ref(1))).toBe(1)
  200. })
  201. test('shallowRef', () => {
  202. const sref = shallowRef({ a: 1 })
  203. expect(isReactive(sref.value)).toBe(false)
  204. let dummy
  205. effect(() => {
  206. dummy = sref.value.a
  207. })
  208. expect(dummy).toBe(1)
  209. sref.value = { a: 2 }
  210. expect(isReactive(sref.value)).toBe(false)
  211. expect(dummy).toBe(2)
  212. })
  213. test('shallowRef force trigger', () => {
  214. const sref = shallowRef({ a: 1 })
  215. let dummy
  216. effect(() => {
  217. dummy = sref.value.a
  218. })
  219. expect(dummy).toBe(1)
  220. sref.value.a = 2
  221. expect(dummy).toBe(1) // should not trigger yet
  222. // force trigger
  223. triggerRef(sref)
  224. expect(dummy).toBe(2)
  225. })
  226. test('shallowRef isShallow', () => {
  227. expect(isShallow(shallowRef({ a: 1 }))).toBe(true)
  228. })
  229. test('isRef', () => {
  230. expect(isRef(ref(1))).toBe(true)
  231. expect(isRef(computed(() => 1))).toBe(true)
  232. expect(isRef(0)).toBe(false)
  233. expect(isRef(1)).toBe(false)
  234. // an object that looks like a ref isn't necessarily a ref
  235. expect(isRef({ value: 0 })).toBe(false)
  236. })
  237. test('toRef', () => {
  238. const a = reactive({
  239. x: 1,
  240. })
  241. const x = toRef(a, 'x')
  242. const b = ref({ y: 1 })
  243. const c = toRef(b)
  244. const d = toRef({ z: 1 })
  245. expect(isRef(d)).toBe(true)
  246. expect(d.value.z).toBe(1)
  247. expect(c).toBe(b)
  248. expect(isRef(x)).toBe(true)
  249. expect(x.value).toBe(1)
  250. // source -> proxy
  251. a.x = 2
  252. expect(x.value).toBe(2)
  253. // proxy -> source
  254. x.value = 3
  255. expect(a.x).toBe(3)
  256. // reactivity
  257. let dummyX
  258. effect(() => {
  259. dummyX = x.value
  260. })
  261. expect(dummyX).toBe(x.value)
  262. // mutating source should trigger effect using the proxy refs
  263. a.x = 4
  264. expect(dummyX).toBe(4)
  265. // a ref in a non-reactive object should be unwrapped
  266. const r: any = { x: ref(1) }
  267. const t = toRef(r, 'x')
  268. expect(t.value).toBe(1)
  269. r.x.value = 2
  270. expect(t.value).toBe(2)
  271. t.value = 3
  272. expect(t.value).toBe(3)
  273. expect(r.x.value).toBe(3)
  274. // with a default
  275. const u = toRef(r, 'x', 7)
  276. expect(u.value).toBe(3)
  277. r.x.value = undefined
  278. expect(r.x.value).toBeUndefined()
  279. expect(t.value).toBeUndefined()
  280. expect(u.value).toBe(7)
  281. u.value = 7
  282. expect(r.x.value).toBe(7)
  283. expect(t.value).toBe(7)
  284. expect(u.value).toBe(7)
  285. })
  286. test('toRef on array', () => {
  287. const a: any = reactive(['a', 'b'])
  288. const r = toRef(a, 1)
  289. expect(r.value).toBe('b')
  290. r.value = 'c'
  291. expect(r.value).toBe('c')
  292. expect(a[1]).toBe('c')
  293. a[1] = ref('d')
  294. expect(isRef(a[1])).toBe(true)
  295. expect(r.value).toBe('d')
  296. r.value = 'e'
  297. expect(isRef(a[1])).toBe(true)
  298. expect(a[1].value).toBe('e')
  299. const s = toRef(a, 2, 'def')
  300. const len = toRef(a, 'length')
  301. expect(s.value).toBe('def')
  302. expect(len.value).toBe(2)
  303. a.push('f')
  304. expect(s.value).toBe('f')
  305. expect(len.value).toBe(3)
  306. len.value = 2
  307. expect(s.value).toBe('def')
  308. expect(len.value).toBe(2)
  309. const symbol = Symbol()
  310. const t = toRef(a, 'foo')
  311. const u = toRef(a, symbol)
  312. expect(t.value).toBeUndefined()
  313. expect(u.value).toBeUndefined()
  314. const foo = ref(3)
  315. const bar = ref(5)
  316. a.foo = foo
  317. a[symbol] = bar
  318. expect(t.value).toBe(3)
  319. expect(u.value).toBe(5)
  320. t.value = 4
  321. u.value = 6
  322. expect(a.foo).toBe(4)
  323. expect(foo.value).toBe(4)
  324. expect(a[symbol]).toBe(6)
  325. expect(bar.value).toBe(6)
  326. })
  327. test('toRef default value', () => {
  328. const a: { x: number | undefined } = { x: undefined }
  329. const x = toRef(a, 'x', 1)
  330. expect(x.value).toBe(1)
  331. a.x = 2
  332. expect(x.value).toBe(2)
  333. a.x = undefined
  334. expect(x.value).toBe(1)
  335. })
  336. test('toRef getter', () => {
  337. const x = toRef(() => 1)
  338. expect(x.value).toBe(1)
  339. expect(isRef(x)).toBe(true)
  340. expect(unref(x)).toBe(1)
  341. //@ts-expect-error
  342. expect(() => (x.value = 123)).toThrow()
  343. expect(isReadonly(x)).toBe(true)
  344. })
  345. test('toRef lazy evaluation of properties inside a proxy', () => {
  346. const fn = vi.fn(() => 5)
  347. const num = computed(fn)
  348. const a = toRef({ num }, 'num')
  349. const b = toRef(reactive({ num }), 'num')
  350. const c = toRef(readonly({ num }), 'num')
  351. const d = toRef(shallowReactive({ num }), 'num')
  352. const e = toRef(shallowReadonly({ num }), 'num')
  353. expect(fn).not.toHaveBeenCalled()
  354. expect(a.value).toBe(5)
  355. expect(b.value).toBe(5)
  356. expect(c.value).toBe(5)
  357. expect(d.value).toBe(5)
  358. expect(e.value).toBe(5)
  359. expect(fn).toHaveBeenCalledTimes(1)
  360. })
  361. test('toRef with shallowReactive/shallowReadonly', () => {
  362. const r = ref(0)
  363. const s1 = shallowReactive<{ foo: any }>({ foo: r })
  364. const t1 = toRef(s1, 'foo', 2)
  365. const s2 = shallowReadonly(s1)
  366. const t2 = toRef(s2, 'foo', 3)
  367. expect(r.value).toBe(0)
  368. expect(s1.foo.value).toBe(0)
  369. expect(t1.value).toBe(0)
  370. expect(s2.foo.value).toBe(0)
  371. expect(t2.value).toBe(0)
  372. s1.foo = ref(1)
  373. expect(r.value).toBe(0)
  374. expect(s1.foo.value).toBe(1)
  375. expect(t1.value).toBe(1)
  376. expect(s2.foo.value).toBe(1)
  377. expect(t2.value).toBe(1)
  378. s1.foo.value = undefined
  379. expect(r.value).toBe(0)
  380. expect(s1.foo.value).toBeUndefined()
  381. expect(t1.value).toBe(2)
  382. expect(s2.foo.value).toBeUndefined()
  383. expect(t2.value).toBe(3)
  384. t1.value = 2
  385. expect(r.value).toBe(0)
  386. expect(s1.foo.value).toBe(2)
  387. expect(t1.value).toBe(2)
  388. expect(s2.foo.value).toBe(2)
  389. expect(t2.value).toBe(2)
  390. t2.value = 4
  391. expect(r.value).toBe(0)
  392. expect(s1.foo.value).toBe(4)
  393. expect(t1.value).toBe(4)
  394. expect(s2.foo.value).toBe(4)
  395. expect(t2.value).toBe(4)
  396. s1.foo = undefined
  397. expect(r.value).toBe(0)
  398. expect(s1.foo).toBeUndefined()
  399. expect(t1.value).toBe(2)
  400. expect(s2.foo).toBeUndefined()
  401. expect(t2.value).toBe(3)
  402. })
  403. test('toRef for shallowReadonly around reactive', () => {
  404. const get = vi.fn(() => 3)
  405. const set = vi.fn()
  406. const num = computed({ get, set })
  407. const t = toRef(shallowReadonly(reactive({ num })), 'num')
  408. expect(get).not.toHaveBeenCalled()
  409. expect(set).not.toHaveBeenCalled()
  410. t.value = 1
  411. expect(
  412. 'Set operation on key "num" failed: target is readonly',
  413. ).toHaveBeenWarned()
  414. expect(get).not.toHaveBeenCalled()
  415. expect(set).not.toHaveBeenCalled()
  416. expect(t.value).toBe(3)
  417. expect(get).toHaveBeenCalledTimes(1)
  418. expect(set).not.toHaveBeenCalled()
  419. })
  420. test('toRef for readonly around shallowReactive', () => {
  421. const get = vi.fn(() => 3)
  422. const set = vi.fn()
  423. const num = computed({ get, set })
  424. const t: Ref<number> = toRef(readonly(shallowReactive({ num })), 'num')
  425. expect(get).not.toHaveBeenCalled()
  426. expect(set).not.toHaveBeenCalled()
  427. t.value = 1
  428. expect(
  429. 'Set operation on key "num" failed: target is readonly',
  430. ).toHaveBeenWarned()
  431. expect(get).not.toHaveBeenCalled()
  432. expect(set).not.toHaveBeenCalled()
  433. expect(t.value).toBe(3)
  434. expect(get).toHaveBeenCalledTimes(1)
  435. expect(set).not.toHaveBeenCalled()
  436. })
  437. test(`toRef doesn't bypass the proxy when getting/setting a nested ref`, () => {
  438. const r = ref(2)
  439. const obj = shallowReactive({ num: r })
  440. const t = toRef(obj, 'num')
  441. expect(t.value).toBe(2)
  442. effect(() => {
  443. t.value = 3
  444. })
  445. expect(t.value).toBe(3)
  446. expect(r.value).toBe(3)
  447. const s = ref(4)
  448. obj.num = s
  449. expect(t.value).toBe(3)
  450. expect(s.value).toBe(3)
  451. })
  452. test('toRefs', () => {
  453. const a = reactive({
  454. x: 1,
  455. y: 2,
  456. })
  457. const { x, y } = toRefs(a)
  458. expect(isRef(x)).toBe(true)
  459. expect(isRef(y)).toBe(true)
  460. expect(x.value).toBe(1)
  461. expect(y.value).toBe(2)
  462. // source -> proxy
  463. a.x = 2
  464. a.y = 3
  465. expect(x.value).toBe(2)
  466. expect(y.value).toBe(3)
  467. // proxy -> source
  468. x.value = 3
  469. y.value = 4
  470. expect(a.x).toBe(3)
  471. expect(a.y).toBe(4)
  472. // reactivity
  473. let dummyX, dummyY
  474. effect(() => {
  475. dummyX = x.value
  476. dummyY = y.value
  477. })
  478. expect(dummyX).toBe(x.value)
  479. expect(dummyY).toBe(y.value)
  480. // mutating source should trigger effect using the proxy refs
  481. a.x = 4
  482. a.y = 5
  483. expect(dummyX).toBe(4)
  484. expect(dummyY).toBe(5)
  485. })
  486. test('toRefs reactive array', () => {
  487. const arr = reactive(['a', 'b', 'c'])
  488. const refs = toRefs(arr)
  489. expect(Array.isArray(refs)).toBe(true)
  490. refs[0].value = '1'
  491. expect(arr[0]).toBe('1')
  492. arr[1] = '2'
  493. expect(refs[1].value).toBe('2')
  494. })
  495. test('customRef', () => {
  496. let value = 1
  497. let _trigger: () => void
  498. const custom = customRef((track, trigger) => ({
  499. get() {
  500. track()
  501. return value
  502. },
  503. set(newValue: number) {
  504. value = newValue
  505. _trigger = trigger
  506. },
  507. }))
  508. expect(isRef(custom)).toBe(true)
  509. let dummy
  510. effect(() => {
  511. dummy = custom.value
  512. })
  513. expect(dummy).toBe(1)
  514. custom.value = 2
  515. // should not trigger yet
  516. expect(dummy).toBe(1)
  517. _trigger!()
  518. expect(dummy).toBe(2)
  519. })
  520. test('should not trigger when setting value to same proxy', () => {
  521. const obj = reactive({ count: 0 })
  522. const a = ref(obj)
  523. const spy1 = vi.fn(() => a.value)
  524. effect(spy1)
  525. a.value = obj
  526. expect(spy1).toBeCalledTimes(1)
  527. const b = shallowRef(obj)
  528. const spy2 = vi.fn(() => b.value)
  529. effect(spy2)
  530. b.value = obj
  531. expect(spy2).toBeCalledTimes(1)
  532. })
  533. test('ref should preserve value shallow/readonly-ness', () => {
  534. const original = {}
  535. const r = reactive(original)
  536. const s = shallowReactive(original)
  537. const rr = readonly(original)
  538. const a = ref(original)
  539. expect(a.value).toBe(r)
  540. a.value = s
  541. expect(a.value).toBe(s)
  542. expect(a.value).not.toBe(r)
  543. a.value = rr
  544. expect(a.value).toBe(rr)
  545. expect(a.value).not.toBe(r)
  546. })
  547. test('should not trigger when setting the same raw object', () => {
  548. const obj = {}
  549. const r = ref(obj)
  550. const spy = vi.fn()
  551. effect(() => spy(r.value))
  552. expect(spy).toHaveBeenCalledTimes(1)
  553. r.value = obj
  554. expect(spy).toHaveBeenCalledTimes(1)
  555. })
  556. test('toValue', () => {
  557. const a = ref(1)
  558. const b = computed(() => a.value + 1)
  559. const c = () => a.value + 2
  560. const d = 4
  561. expect(toValue(a)).toBe(1)
  562. expect(toValue(b)).toBe(2)
  563. expect(toValue(c)).toBe(3)
  564. expect(toValue(d)).toBe(4)
  565. })
  566. test('ref w/ customRef w/ getterRef w/ objectRef should store value cache', () => {
  567. const refValue = ref(1)
  568. // @ts-expect-error private field
  569. expect(refValue._value).toBe(1)
  570. let customRefValueCache = 0
  571. const customRefValue = customRef((track, trigger) => {
  572. return {
  573. get() {
  574. track()
  575. return customRefValueCache
  576. },
  577. set(value: number) {
  578. customRefValueCache = value
  579. trigger()
  580. },
  581. }
  582. })
  583. customRefValue.value
  584. // @ts-expect-error internal field
  585. expect(customRefValue._value).toBe(0)
  586. const getterRefValue = toRef(() => 1)
  587. getterRefValue.value
  588. // @ts-expect-error internal field
  589. expect(getterRefValue._value).toBe(1)
  590. const objectRefValue = toRef({ value: 1 }, 'value')
  591. objectRefValue.value
  592. // @ts-expect-error internal field
  593. expect(objectRefValue._value).toBe(1)
  594. })
  595. })