@@ -16,6 +16,7 @@ import {
1616 isShallow,
1717 readonly,
1818 shallowReactive,
19+ shallowReadonly,
1920} from '../src/reactive'
2021
2122describe('reactivity/ref', () => {
@@ -308,18 +309,83 @@ describe('reactivity/ref', () => {
308309 a.x = 4
309310 expect(dummyX).toBe(4)
310311
311- // should keep ref
312- const r = { x: ref(1) }
313- expect(toRef(r, 'x')).toBe(r.x)
312+ // a ref in a non-reactive object should be unwrapped
313+ const r: any = { x: ref(1) }
314+ const t = toRef(r, 'x')
315+ expect(t.value).toBe(1)
316+
317+ r.x.value = 2
318+ expect(t.value).toBe(2)
319+
320+ t.value = 3
321+ expect(t.value).toBe(3)
322+ expect(r.x.value).toBe(3)
323+
324+ // with a default
325+ const u = toRef(r, 'x', 7)
326+ expect(u.value).toBe(3)
327+
328+ r.x.value = undefined
329+ expect(r.x.value).toBeUndefined()
330+ expect(t.value).toBeUndefined()
331+ expect(u.value).toBe(7)
332+
333+ u.value = 7
334+ expect(r.x.value).toBe(7)
335+ expect(t.value).toBe(7)
336+ expect(u.value).toBe(7)
314337 })
315338
316339 test('toRef on array', () => {
317- const a = reactive(['a', 'b'])
340+ const a: any = reactive(['a', 'b'])
318341 const r = toRef(a, 1)
319342 expect(r.value).toBe('b')
320343 r.value = 'c'
321344 expect(r.value).toBe('c')
322345 expect(a[1]).toBe('c')
346+
347+ a[1] = ref('d')
348+ expect(isRef(a[1])).toBe(true)
349+ expect(r.value).toBe('d')
350+ r.value = 'e'
351+ expect(isRef(a[1])).toBe(true)
352+ expect(a[1].value).toBe('e')
353+
354+ const s = toRef(a, 2, 'def')
355+ const len = toRef(a, 'length')
356+
357+ expect(s.value).toBe('def')
358+ expect(len.value).toBe(2)
359+
360+ a.push('f')
361+ expect(s.value).toBe('f')
362+ expect(len.value).toBe(3)
363+
364+ len.value = 2
365+
366+ expect(s.value).toBe('def')
367+ expect(len.value).toBe(2)
368+
369+ const symbol = Symbol()
370+ const t = toRef(a, 'foo')
371+ const u = toRef(a, symbol)
372+ expect(t.value).toBeUndefined()
373+ expect(u.value).toBeUndefined()
374+
375+ const foo = ref(3)
376+ const bar = ref(5)
377+ a.foo = foo
378+ a[symbol] = bar
379+ expect(t.value).toBe(3)
380+ expect(u.value).toBe(5)
381+
382+ t.value = 4
383+ u.value = 6
384+
385+ expect(a.foo).toBe(4)
386+ expect(foo.value).toBe(4)
387+ expect(a[symbol]).toBe(6)
388+ expect(bar.value).toBe(6)
323389 })
324390
325391 test('toRef default value', () => {
@@ -345,6 +411,148 @@ describe('reactivity/ref', () => {
345411 expect(isReadonly(x)).toBe(true)
346412 })
347413
414+ test('toRef lazy evaluation of properties inside a proxy', () => {
415+ const fn = vi.fn(() => 5)
416+ const num = computed(fn)
417+ const a = toRef({ num }, 'num')
418+ const b = toRef(reactive({ num }), 'num')
419+ const c = toRef(readonly({ num }), 'num')
420+ const d = toRef(shallowReactive({ num }), 'num')
421+ const e = toRef(shallowReadonly({ num }), 'num')
422+
423+ expect(fn).not.toHaveBeenCalled()
424+
425+ expect(a.value).toBe(5)
426+ expect(b.value).toBe(5)
427+ expect(c.value).toBe(5)
428+ expect(d.value).toBe(5)
429+ expect(e.value).toBe(5)
430+ expect(fn).toHaveBeenCalledTimes(1)
431+ })
432+
433+ test('toRef with shallowReactive/shallowReadonly', () => {
434+ const r = ref(0)
435+ const s1 = shallowReactive<{ foo: any }>({ foo: r })
436+ const t1 = toRef(s1, 'foo', 2)
437+ const s2 = shallowReadonly(s1)
438+ const t2 = toRef(s2, 'foo', 3)
439+
440+ expect(r.value).toBe(0)
441+ expect(s1.foo.value).toBe(0)
442+ expect(t1.value).toBe(0)
443+ expect(s2.foo.value).toBe(0)
444+ expect(t2.value).toBe(0)
445+
446+ s1.foo = ref(1)
447+
448+ expect(r.value).toBe(0)
449+ expect(s1.foo.value).toBe(1)
450+ expect(t1.value).toBe(1)
451+ expect(s2.foo.value).toBe(1)
452+ expect(t2.value).toBe(1)
453+
454+ s1.foo.value = undefined
455+
456+ expect(r.value).toBe(0)
457+ expect(s1.foo.value).toBeUndefined()
458+ expect(t1.value).toBe(2)
459+ expect(s2.foo.value).toBeUndefined()
460+ expect(t2.value).toBe(3)
461+
462+ t1.value = 2
463+
464+ expect(r.value).toBe(0)
465+ expect(s1.foo.value).toBe(2)
466+ expect(t1.value).toBe(2)
467+ expect(s2.foo.value).toBe(2)
468+ expect(t2.value).toBe(2)
469+
470+ t2.value = 4
471+
472+ expect(r.value).toBe(0)
473+ expect(s1.foo.value).toBe(4)
474+ expect(t1.value).toBe(4)
475+ expect(s2.foo.value).toBe(4)
476+ expect(t2.value).toBe(4)
477+
478+ s1.foo = undefined
479+
480+ expect(r.value).toBe(0)
481+ expect(s1.foo).toBeUndefined()
482+ expect(t1.value).toBe(2)
483+ expect(s2.foo).toBeUndefined()
484+ expect(t2.value).toBe(3)
485+ })
486+
487+ test('toRef for shallowReadonly around reactive', () => {
488+ const get = vi.fn(() => 3)
489+ const set = vi.fn()
490+ const num = computed({ get, set })
491+ const t = toRef(shallowReadonly(reactive({ num })), 'num')
492+
493+ expect(get).not.toHaveBeenCalled()
494+ expect(set).not.toHaveBeenCalled()
495+
496+ t.value = 1
497+
498+ expect(
499+ 'Set operation on key "num" failed: target is readonly',
500+ ).toHaveBeenWarned()
501+
502+ expect(get).not.toHaveBeenCalled()
503+ expect(set).not.toHaveBeenCalled()
504+
505+ expect(t.value).toBe(3)
506+
507+ expect(get).toHaveBeenCalledTimes(1)
508+ expect(set).not.toHaveBeenCalled()
509+ })
510+
511+ test('toRef for readonly around shallowReactive', () => {
512+ const get = vi.fn(() => 3)
513+ const set = vi.fn()
514+ const num = computed({ get, set })
515+ const t: Ref<number> = toRef(readonly(shallowReactive({ num })), 'num')
516+
517+ expect(get).not.toHaveBeenCalled()
518+ expect(set).not.toHaveBeenCalled()
519+
520+ t.value = 1
521+
522+ expect(
523+ 'Set operation on key "num" failed: target is readonly',
524+ ).toHaveBeenWarned()
525+
526+ expect(get).not.toHaveBeenCalled()
527+ expect(set).not.toHaveBeenCalled()
528+
529+ expect(t.value).toBe(3)
530+
531+ expect(get).toHaveBeenCalledTimes(1)
532+ expect(set).not.toHaveBeenCalled()
533+ })
534+
535+ test(`toRef doesn't bypass the proxy when getting/setting a nested ref`, () => {
536+ const r = ref(2)
537+ const obj = shallowReactive({ num: r })
538+ const t = toRef(obj, 'num')
539+
540+ expect(t.value).toBe(2)
541+
542+ effect(() => {
543+ t.value = 3
544+ })
545+
546+ expect(t.value).toBe(3)
547+ expect(r.value).toBe(3)
548+
549+ const s = ref(4)
550+ obj.num = s
551+
552+ expect(t.value).toBe(3)
553+ expect(s.value).toBe(3)
554+ })
555+
348556 test('toRefs', () => {
349557 const a = reactive({
350558 x: 1,
0 commit comments