import { type ComputedRef, type MaybeRef, type MaybeRefOrGetter, type Ref, type ShallowRef, type TemplateRef, type ToRefs, type WritableComputedRef, computed, customRef, isRef, proxyRefs, reactive, readonly, ref, shallowReactive, shallowRef, toRef, toRefs, toValue, unref, useTemplateRef, } from 'vue' import { type IsAny, type IsUnion, describe, expectType } from './utils' function plainType(arg: number | Ref) { // ref coercing const coerced = ref(arg) expectType>(coerced) // isRef as type guard if (isRef(arg)) { expectType>(arg) } // ref unwrapping expectType(unref(arg)) expectType(toValue(arg)) expectType(toValue(() => 123)) // ref inner type should be unwrapped const nestedRef = ref({ foo: ref(1), }) expectType<{ foo: number }>(nestedRef.value) // ref boolean const falseRef = ref(false) expectType>(falseRef) expectType(falseRef.value) // ref true const trueRef = ref(true) expectType>(trueRef) expectType(trueRef.value) // tuple expectType<[number, string]>(unref(ref([1, '1']))) interface IteratorFoo { [Symbol.iterator]: any } // with symbol expectType>( ref(), ) // should not unwrap ref inside arrays const arr = ref([1, new Map(), ref('1')]).value const value = arr[0] if (isRef(value)) { expectType(value) } else if (typeof value === 'number') { expectType(value) } else { // should narrow down to Map type // and not contain any Ref type expectType>(value) } // should still unwrap in objects nested in arrays const arr2 = ref([{ a: ref(1) }]).value expectType(arr2[0].a) // any value should return Ref, not any const a = ref(1 as any) expectType>(false) } plainType(1) function bailType(arg: HTMLElement | Ref) { // ref coercing const coerced = ref(arg) expectType>(coerced) // isRef as type guard if (isRef(arg)) { expectType>(arg) } // ref unwrapping expectType(unref(arg)) // ref inner type should be unwrapped const nestedRef = ref({ foo: ref(document.createElement('DIV')) }) expectType>(nestedRef) expectType<{ foo: HTMLElement }>(nestedRef.value) } const el = document.createElement('DIV') bailType(el) function withSymbol() { const customSymbol = Symbol() const obj = { [Symbol.asyncIterator]: ref(1), [Symbol.hasInstance]: { a: ref('a') }, [Symbol.isConcatSpreadable]: { b: ref(true) }, [Symbol.iterator]: [ref(1)], [Symbol.match]: new Set>(), [Symbol.matchAll]: new Map>(), [Symbol.replace]: { arr: [ref('a')] }, [Symbol.search]: { set: new Set>() }, [Symbol.species]: { map: new Map>() }, [Symbol.split]: new WeakSet>(), [Symbol.toPrimitive]: new WeakMap, string>(), [Symbol.toStringTag]: { weakSet: new WeakSet>() }, [Symbol.unscopables]: { weakMap: new WeakMap, string>() }, [customSymbol]: { arr: [ref(1)] }, } const objRef = ref(obj) expectType>(objRef.value[Symbol.asyncIterator]) expectType<{ a: Ref }>(objRef.value[Symbol.hasInstance]) expectType<{ b: Ref }>(objRef.value[Symbol.isConcatSpreadable]) expectType[]>(objRef.value[Symbol.iterator]) expectType>>(objRef.value[Symbol.match]) expectType>>(objRef.value[Symbol.matchAll]) expectType<{ arr: Ref[] }>(objRef.value[Symbol.replace]) expectType<{ set: Set> }>(objRef.value[Symbol.search]) expectType<{ map: Map> }>(objRef.value[Symbol.species]) expectType>>(objRef.value[Symbol.split]) expectType, string>>(objRef.value[Symbol.toPrimitive]) expectType<{ weakSet: WeakSet> }>( objRef.value[Symbol.toStringTag], ) expectType<{ weakMap: WeakMap, string> }>( objRef.value[Symbol.unscopables], ) expectType<{ arr: Ref[] }>(objRef.value[customSymbol]) } withSymbol() const state = reactive({ foo: { value: 1, label: 'bar', }, }) expectType(state.foo.label) describe('ref with generic', () => { const r = {} as T const s = ref(r) expectType(s.value.name) const rr = {} as MaybeRef // should at least allow casting const ss = ref(rr) as Ref expectType(ss.value.name) }) describe('allow getter and setter types to be unrelated', () => { const a = { b: ref(0) } const c = ref(a) c.value = a const d = {} as T const e = ref(d) e.value = d const f = ref(ref(0)) expectType(f.value) // @ts-expect-error f.value = ref(1) }) describe('correctly unwraps nested refs', () => { const obj = { n: 24, ref: ref(24), nestedRef: ref({ n: ref(0) }), } const a = ref(obj) expectType(a.value.n) expectType(a.value.ref) expectType(a.value.nestedRef.n) const b = reactive({ a }) expectType(b.a.n) expectType(b.a.ref) expectType(b.a.nestedRef.n) }) // computed describe('allow computed getter and setter types to be unrelated', () => { const obj = ref({ name: 'foo', }) const c = computed({ get() { return JSON.stringify(obj.value) }, set(val: typeof obj.value) { obj.value = val }, }) c.value = { name: 'bar' } // object expectType(c.value) }) describe('Type safety for `WritableComputedRef` and `ComputedRef`', () => { // @ts-expect-error const writableComputed: WritableComputedRef = computed(() => '') // should allow const immutableComputed: ComputedRef = writableComputed expectType>(immutableComputed) }) // shallowRef type Status = 'initial' | 'ready' | 'invalidating' const shallowStatus = shallowRef('initial') if (shallowStatus.value === 'initial') { expectType>(shallowStatus) expectType(shallowStatus.value) shallowStatus.value = 'invalidating' } const refStatus = ref('initial') if (refStatus.value === 'initial') { expectType>(shallowStatus) expectType(shallowStatus.value) refStatus.value = 'invalidating' } { const shallow = shallowRef(1) expectType>(shallow) expectType>(shallow) } { //#7852 type Steps = { step: '1' } | { step: '2' } const shallowUnionGenParam = shallowRef({ step: '1' }) const shallowUnionAsCast = shallowRef({ step: '1' } as Steps) expectType>(false) expectType>(false) } { // any value should return Ref, not any const a = shallowRef(1 as any) expectType>(false) } describe('shallowRef with generic', () => { const r = {} as T const s = shallowRef(r) expectType(s.value.name) expectType>(shallowRef(r)) const rr = {} as MaybeRef // should at least allow casting const ss = shallowRef(rr) as Ref | ShallowRef expectType(ss.value.name) }) { // should return ShallowRef | Ref, not ShallowRef> expectType | Ref<{ name: string }>>( shallowRef({} as MaybeRef<{ name: string }>), ) expectType | Ref | ShallowRef>( shallowRef('' as Ref | string | number), ) } // proxyRefs: should return `reactive` directly const r1 = reactive({ k: 'v', }) const p1 = proxyRefs(r1) expectType(p1) // proxyRefs: `ShallowUnwrapRef` const r2 = { a: ref(1), c: computed(() => 1), u: undefined, obj: { k: ref('foo'), }, union: Math.random() > 0 - 5 ? ref({ name: 'yo' }) : null, } const p2 = proxyRefs(r2) expectType(p2.a) expectType(p2.c) expectType(p2.u) expectType>(p2.obj.k) expectType<{ name: string } | null>(p2.union) const r3 = shallowReactive({ n: ref(1), }) const p3 = proxyRefs(r3) expectType>(p3.n) // toRef and toRefs { const obj: { a: number b: Ref c: number | string } = { a: 1, b: ref(1), c: 1, } // toRef expectType>(toRef(obj, 'a')) expectType>(toRef(obj, 'b')) // Should not distribute Refs over union expectType>(toRef(obj, 'c')) const array = reactive(['a', 'b']) expectType>(toRef(array, '1')) expectType>(toRef(array, '1', 'fallback')) const tuple: [string, number] = ['a', 1] expectType>(toRef(tuple, '0')) expectType>(toRef(tuple, '1')) expectType>(toRef(() => 123)) expectType>(toRef(() => obj.c)) const r = toRef(() => 123) // @ts-expect-error r.value = 234 // toRefs expectType<{ a: Ref b: Ref // Should not distribute Refs over union c: Ref }>(toRefs(obj)) // Both should not do any unwrapping const someReactive = shallowReactive({ a: { b: ref(42), }, }) const toRefResult = toRef(someReactive, 'a') const toRefsResult = toRefs(someReactive) expectType>(toRefResult.value.b) expectType>(toRefsResult.a.value.b) // #5188 const props = { foo: 1 } as { foo: any } const { foo } = toRefs(props) expectType>(foo) } // toRef default value { const obj: { x?: number } = {} const x = toRef(obj, 'x', 1) expectType>(x) } // readonly() + ref() expectType>>(readonly(ref(1))) // #2687 interface AppData { state: 'state1' | 'state2' | 'state3' } const data: ToRefs = toRefs( reactive({ state: 'state1', }), ) switch (data.state.value) { case 'state1': data.state.value = 'state2' break case 'state2': data.state.value = 'state3' break case 'state3': data.state.value = 'state1' break } // #3954 function testUnrefGenerics(p: T | Ref) { expectType(unref(p)) } testUnrefGenerics(1) // #4771 describe('shallow reactive in reactive', () => { const baz = reactive({ foo: shallowReactive({ a: { b: ref(42), }, }), }) const foo = toRef(baz, 'foo') expectType>(foo.value.a.b) expectType(foo.value.a.b.value) }) describe('shallow reactive collection in reactive', () => { const baz = reactive({ foo: shallowReactive(new Map([['a', ref(42)]])), }) const foo = toRef(baz, 'foo') expectType | undefined>(foo.value.get('a')) }) describe('shallow ref in reactive', () => { const x = reactive({ foo: shallowRef({ bar: { baz: ref(123), qux: reactive({ z: ref(123), }), }, }), }) expectType>(x.foo.bar.baz) expectType(x.foo.bar.qux.z) }) describe('ref in shallow ref', () => { const x = shallowRef({ a: ref(123), }) expectType>(x.value.a) }) describe('reactive in shallow ref', () => { const x = shallowRef({ a: reactive({ b: ref(0), }), }) expectType(x.value.a.b) }) describe('toRef <-> toValue', () => { function foo( a: MaybeRef, b: () => string, c: MaybeRefOrGetter, d: ComputedRef, ) { const r = toRef(a) expectType>(r) // writable r.value = 'foo' const rb = toRef(b) expectType>>(rb) // @ts-expect-error ref created from getter should be readonly rb.value = 'foo' const rc = toRef(c) expectType | Ref>>(rc) // @ts-expect-error ref created from MaybeReadonlyRef should be readonly rc.value = 'foo' const rd = toRef(d) expectType>(rd) // @ts-expect-error ref created from computed ref should be readonly rd.value = 'foo' expectType(toValue(a)) expectType(toValue(b)) expectType(toValue(c)) expectType(toValue(d)) return { r: toValue(r), rb: toValue(rb), rc: toValue(rc), rd: toValue(rd), } } expectType<{ r: string rb: string rc: string rd: string }>( foo( 'foo', () => 'bar', ref('baz'), computed(() => 'hi'), ), ) }) // unref // #8747 declare const unref1: number | Ref | ComputedRef expectType(unref(unref1)) // #11356 declare const unref2: | MaybeRef | ShallowRef | ComputedRef | WritableComputedRef expectType(unref(unref2)) // toValue expectType(toValue(unref1)) expectType(toValue(unref2)) // useTemplateRef const tRef = useTemplateRef('foo') expectType(tRef) const tRef2 = useTemplateRef('bar') expectType>(tRef2) // #14637 customRef with different getter/setter types describe('customRef with different getter/setter types', () => { // customRef should support different getter/setter types like Ref const cr = customRef((track, trigger) => ({ get: () => 'hello', set: (val: number) => { // setter accepts number, getter returns string trigger() }, })) // getter returns string expectType(cr.value) // setter accepts number cr.value = 123 // @ts-expect-error setter doesn't accept string cr.value = 'world' })