wip: ref tests passing

This commit is contained in:
Evan You 2022-05-25 15:36:10 +08:00
parent e1e5a75540
commit ac85a4217e
7 changed files with 424 additions and 281 deletions

View File

@ -8,6 +8,7 @@ import {
isArray, isArray,
emptyObject, emptyObject,
remove, remove,
hasChanged,
isServerRendering, isServerRendering,
invokeWithErrorHandling invokeWithErrorHandling
} from 'core/util' } from 'core/util'
@ -348,15 +349,6 @@ function doWatch(
} }
} }
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#polyfill
function hasChanged(x: unknown, y: unknown): boolean {
if (x === y) {
return x !== 0 || 1 / x === 1 / (y as number)
} else {
return x !== x && y !== y
}
}
function queuePostRenderEffect(fn: Function) { function queuePostRenderEffect(fn: Function) {
// TODO // TODO
} }

View File

@ -17,6 +17,23 @@ export {
CustomRefFactory CustomRefFactory
} from './reactivity/ref' } from './reactivity/ref'
export {
reactive,
// readonly,
isReactive,
isReadonly,
isShallow,
// isProxy,
// shallowReactive,
// shallowReadonly,
// markRaw,
// toRaw,
ReactiveFlags,
// DeepReadonly,
// ShallowReactive,
UnwrapNestedRefs
} from './reactivity/reactive'
export { export {
watch, watch,
watchEffect, watchEffect,

View File

@ -1,12 +1,44 @@
import { observe, Observer } from 'core/observer'
import { Ref, UnwrapRefSimple } from './ref'
export const enum ReactiveFlags {
SKIP = '__v_skip',
IS_READONLY = '__v_isReadonly',
IS_SHALLOW = '__v_isShallow',
RAW = '__v_raw'
}
export interface Target {
__ob__?: Observer
[ReactiveFlags.SKIP]?: boolean
[ReactiveFlags.IS_READONLY]?: boolean
[ReactiveFlags.IS_SHALLOW]?: boolean
[ReactiveFlags.RAW]?: any
}
export declare const ShallowReactiveMarker: unique symbol export declare const ShallowReactiveMarker: unique symbol
export function reactive() {} // only unwrap nested ref
export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (!isReadonly(target)) {
observe(target)
}
return target
}
export function isReactive(value: unknown): boolean { export function isReactive(value: unknown): boolean {
return !!(value && (value as any).__ob__) return !!(value && (value as Target).__ob__)
} }
export function isShallow(value: unknown): boolean { export function isShallow(value: unknown): boolean {
// TODO return !!(value && (value as Target).__v_isShallow)
return !!(value && (value as any).__ob__) }
export function isReadonly(value: unknown): boolean {
// TODO
return !!(value && (value as Target).__v_isReadonly)
} }

View File

@ -1,6 +1,8 @@
import { defineReactive } from 'core/observer/index' import { defineReactive } from 'core/observer/index'
import type { ShallowReactiveMarker } from './reactive' import { isReactive, ShallowReactiveMarker } from './reactive'
import type { IfAny } from 'typescript/utils' import type { IfAny } from 'typescript/utils'
import Dep from 'core/observer/dep'
import { warn, isArray } from 'core/util'
declare const RefSymbol: unique symbol declare const RefSymbol: unique symbol
export declare const RawSymbol: unique symbol export declare const RawSymbol: unique symbol
@ -13,6 +15,10 @@ export interface Ref<T = any> {
* autocomplete, so we use a private Symbol instead. * autocomplete, so we use a private Symbol instead.
*/ */
[RefSymbol]: true [RefSymbol]: true
/**
* @private
*/
dep: Dep
} }
export function isRef<T>(r: Ref<T> | unknown): r is Ref<T> export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
@ -46,13 +52,13 @@ function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) { if (isRef(rawValue)) {
return rawValue return rawValue
} }
const ref = { __v_isRef: true } const ref: any = { __v_isRef: true, __v_isShallow: shallow }
defineReactive(ref, 'value', rawValue, null, shallow) ref.dep = defineReactive(ref, 'value', rawValue, null, shallow)
return ref return ref
} }
export function triggerRef(ref: Ref) { export function triggerRef(ref: Ref) {
// TODO triggerRefValue(ref, __DEV__ ? ref.value : void 0) ref.dep.notify()
} }
export function unref<T>(ref: T | Ref<T>): T { export function unref<T>(ref: T | Ref<T>): T {
@ -67,22 +73,93 @@ export type CustomRefFactory<T> = (
set: (value: T) => void set: (value: T) => void
} }
export function customRef() { class CustomRefImpl<T> {
// TODO public dep?: Dep = undefined
private readonly _get: ReturnType<CustomRefFactory<T>>['get']
private readonly _set: ReturnType<CustomRefFactory<T>>['set']
public readonly __v_isRef = true
constructor(factory: CustomRefFactory<T>) {
const dep = new Dep()
const { get, set } = factory(
() => dep.depend(),
() => dep.notify()
)
this._get = get
this._set = set
}
get value() {
return this._get()
}
set value(newVal) {
this._set(newVal)
}
}
export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
return new CustomRefImpl(factory) as any
} }
export type ToRefs<T = any> = { export type ToRefs<T = any> = {
[K in keyof T]: ToRef<T[K]> [K in keyof T]: ToRef<T[K]>
} }
export function toRefs() { export function toRefs<T extends object>(object: T): ToRefs<T> {
// TODO if (__DEV__ && !isReactive(object)) {
warn(`toRefs() expects a reactive object but received a plain one.`)
}
const ret: any = isArray(object) ? new Array(object.length) : {}
for (const key in object) {
ret[key] = toRef(object, key)
}
return ret
}
class ObjectRefImpl<T extends object, K extends keyof T> {
public readonly __v_isRef = true
constructor(
private readonly _object: T,
private readonly _key: K,
private readonly _defaultValue?: T[K]
) {}
get value() {
const val = this._object[this._key]
return val === undefined ? (this._defaultValue as T[K]) : val
}
set value(newVal) {
this._object[this._key] = newVal
}
} }
export type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>> export type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>
export function toRef() { export function toRef<T extends object, K extends keyof T>(
// TODO object: T,
key: K
): ToRef<T[K]>
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K,
defaultValue: T[K]
): ToRef<Exclude<T[K], undefined>>
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K,
defaultValue?: T[K]
): ToRef<T[K]> {
const val = object[key]
return isRef(val)
? val
: (new ObjectRefImpl(object, key, defaultValue) as any)
} }
/** /**

View File

@ -12,8 +12,10 @@ import {
isPrimitive, isPrimitive,
isUndef, isUndef,
isValidArrayIndex, isValidArrayIndex,
isServerRendering isServerRendering,
hasChanged
} from '../util/index' } from '../util/index'
import { isRef } from '../../composition-api'
const arrayKeys = Object.getOwnPropertyNames(arrayMethods) const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
@ -107,7 +109,7 @@ function copyAugment(target: Object, src: Object, keys: Array<string>) {
* or the existing observer if the value already has one. * or the existing observer if the value already has one.
*/ */
export function observe(value: any, asRootData?: boolean): Observer | void { export function observe(value: any, asRootData?: boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) { if (!isObject(value) || isRef(value) || value instanceof VNode) {
return return
} }
let ob: Observer | void let ob: Observer | void
@ -167,22 +169,23 @@ export function defineReactive(
} }
} }
} }
return value return isRef(value) ? value.value : value
}, },
set: function reactiveSetter(newVal) { set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */ if (!hasChanged(value, newVal)) {
if (newVal === value || (newVal !== newVal && value !== value)) {
return return
} }
/* eslint-enable no-self-compare */
if (__DEV__ && customSetter) { if (__DEV__ && customSetter) {
customSetter() customSetter()
} }
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) { if (setter) {
setter.call(obj, newVal) setter.call(obj, newVal)
} else if (getter) {
// #7981: for accessor properties without setter
return
} else if (isRef(value) && !isRef(newVal)) {
value.value = newVal
} else { } else {
val = newVal val = newVal
} }
@ -190,6 +193,8 @@ export function defineReactive(
dep.notify() dep.notify()
} }
}) })
return dep
} }
/** /**

View File

@ -349,3 +349,12 @@ export function once(fn: Function): Function {
} }
} }
} }
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#polyfill
export function hasChanged(x: unknown, y: unknown): boolean {
if (x === y) {
return x === 0 && 1 / x !== 1 / (y as number)
} else {
return x === x && y === y
}
}

View File

@ -1,6 +1,16 @@
import { ref, shallowRef, unref } from 'vca/reactivity/ref' import {
ref,
isRef,
shallowRef,
unref,
triggerRef,
toRef,
toRefs,
customRef,
Ref
} from 'vca/reactivity/ref'
import { ReactiveEffect } from 'vca/reactivity/effect' import { ReactiveEffect } from 'vca/reactivity/effect'
import { isReactive } from 'vca/reactivity/reactive' import { isReactive, isShallow, reactive } from 'vca/reactivity/reactive'
const effect = (fn: () => any) => new ReactiveEffect(fn) const effect = (fn: () => any) => new ReactiveEffect(fn)
@ -54,59 +64,60 @@ describe('reactivity/ref', () => {
expect(dummy).toBe(2) expect(dummy).toBe(2)
}) })
// it('should work like a normal property when nested in a reactive object', () => { it('should work like a normal property when nested in a reactive object', () => {
// const a = ref(1) const a = ref(1)
// const obj = reactive({ const obj = reactive({
// a, a,
// b: { b: {
// c: a c: a
// } }
// }) })
// let dummy1: number let dummy1: number
// let dummy2: number let dummy2: number
// effect(() => { effect(() => {
// dummy1 = obj.a dummy1 = obj.a
// dummy2 = obj.b.c dummy2 = obj.b.c
// }) })
// const assertDummiesEqualTo = (val: number) => const assertDummiesEqualTo = (val: number) =>
// [dummy1, dummy2].forEach(dummy => expect(dummy).toBe(val)) [dummy1, dummy2].forEach(dummy => expect(dummy).toBe(val))
// assertDummiesEqualTo(1) assertDummiesEqualTo(1)
// a.value++ a.value++
// assertDummiesEqualTo(2) assertDummiesEqualTo(2)
// obj.a++ obj.a++
// assertDummiesEqualTo(3) assertDummiesEqualTo(3)
// obj.b.c++ obj.b.c++
// assertDummiesEqualTo(4) assertDummiesEqualTo(4)
// }) })
// it('should unwrap nested ref in types', () => { it('should unwrap nested ref in types', () => {
// const a = ref(0) const a = ref(0)
// const b = ref(a) const b = ref(a)
// expect(typeof (b.value + 1)).toBe('number') expect(typeof (b.value + 1)).toBe('number')
// }) })
// it('should unwrap nested values in types', () => { it('should unwrap nested values in types', () => {
// const a = { const a = {
// b: ref(0) b: ref(0)
// } }
// const c = ref(a) const c = ref(a)
// expect(typeof (c.value.b + 1)).toBe('number') expect(typeof (c.value.b + 1)).toBe('number')
// }) })
// it('should NOT unwrap ref types nested inside arrays', () => { it('should NOT unwrap ref types nested inside arrays', () => {
// const arr = ref([1, ref(3)]).value const arr = ref([1, ref(3)]).value
// expect(isRef(arr[0])).toBe(false) expect(isRef(arr[0])).toBe(false)
// expect(isRef(arr[1])).toBe(true) expect(isRef(arr[1])).toBe(true)
// expect((arr[1] as Ref).value).toBe(3) expect((arr[1] as Ref).value).toBe(3)
// }) })
// Vue 2 does not observe array properties
// it('should unwrap ref types as props of arrays', () => { // it('should unwrap ref types as props of arrays', () => {
// const arr = [ref(0)] // const arr = [ref(0)]
// const symbolKey = Symbol('') // const symbolKey = Symbol('')
@ -120,69 +131,69 @@ describe('reactivity/ref', () => {
// expect(arrRef[symbolKey as any]).toBe(2) // expect(arrRef[symbolKey as any]).toBe(2)
// }) // })
// it('should keep tuple types', () => { it('should keep tuple types', () => {
// const tuple: [number, string, { a: number }, () => number, Ref<number>] = [ const tuple: [number, string, { a: number }, () => number, Ref<number>] = [
// 0, 0,
// '1', '1',
// { a: 1 }, { a: 1 },
// () => 0, () => 0,
// ref(0) ref(0)
// ] ]
// const tupleRef = ref(tuple) const tupleRef = ref(tuple)
// tupleRef.value[0]++ tupleRef.value[0]++
// expect(tupleRef.value[0]).toBe(1) expect(tupleRef.value[0]).toBe(1)
// tupleRef.value[1] += '1' tupleRef.value[1] += '1'
// expect(tupleRef.value[1]).toBe('11') expect(tupleRef.value[1]).toBe('11')
// tupleRef.value[2].a++ tupleRef.value[2].a++
// expect(tupleRef.value[2].a).toBe(2) expect(tupleRef.value[2].a).toBe(2)
// expect(tupleRef.value[3]()).toBe(0) expect(tupleRef.value[3]()).toBe(0)
// tupleRef.value[4].value++ tupleRef.value[4].value++
// expect(tupleRef.value[4].value).toBe(1) expect(tupleRef.value[4].value).toBe(1)
// }) })
// it('should keep symbols', () => { it('should keep symbols', () => {
// const customSymbol = Symbol() const customSymbol = Symbol()
// const obj = { const obj = {
// [Symbol.asyncIterator]: ref(1), [Symbol.asyncIterator]: ref(1),
// [Symbol.hasInstance]: { a: ref('a') }, [Symbol.hasInstance]: { a: ref('a') },
// [Symbol.isConcatSpreadable]: { b: ref(true) }, [Symbol.isConcatSpreadable]: { b: ref(true) },
// [Symbol.iterator]: [ref(1)], [Symbol.iterator]: [ref(1)],
// [Symbol.match]: new Set<Ref<number>>(), [Symbol.match]: new Set<Ref<number>>(),
// [Symbol.matchAll]: new Map<number, Ref<string>>(), [Symbol.matchAll]: new Map<number, Ref<string>>(),
// [Symbol.replace]: { arr: [ref('a')] }, [Symbol.replace]: { arr: [ref('a')] },
// [Symbol.search]: { set: new Set<Ref<number>>() }, [Symbol.search]: { set: new Set<Ref<number>>() },
// [Symbol.species]: { map: new Map<number, Ref<string>>() }, [Symbol.species]: { map: new Map<number, Ref<string>>() },
// [Symbol.split]: new WeakSet<Ref<boolean>>(), [Symbol.split]: new WeakSet<Ref<boolean>>(),
// [Symbol.toPrimitive]: new WeakMap<Ref<boolean>, string>(), [Symbol.toPrimitive]: new WeakMap<Ref<boolean>, string>(),
// [Symbol.toStringTag]: { weakSet: new WeakSet<Ref<boolean>>() }, [Symbol.toStringTag]: { weakSet: new WeakSet<Ref<boolean>>() },
// [Symbol.unscopables]: { weakMap: new WeakMap<Ref<boolean>, string>() }, [Symbol.unscopables]: { weakMap: new WeakMap<Ref<boolean>, string>() },
// [customSymbol]: { arr: [ref(1)] } [customSymbol]: { arr: [ref(1)] }
// } }
// const objRef = ref(obj) const objRef = ref(obj)
// const keys: (keyof typeof obj)[] = [ const keys: (keyof typeof obj)[] = [
// Symbol.asyncIterator, Symbol.asyncIterator,
// Symbol.hasInstance, Symbol.hasInstance,
// Symbol.isConcatSpreadable, Symbol.isConcatSpreadable,
// Symbol.iterator, Symbol.iterator,
// Symbol.match, Symbol.match,
// Symbol.matchAll, Symbol.matchAll,
// Symbol.replace, Symbol.replace,
// Symbol.search, Symbol.search,
// Symbol.species, Symbol.species,
// Symbol.split, Symbol.split,
// Symbol.toPrimitive, Symbol.toPrimitive,
// Symbol.toStringTag, Symbol.toStringTag,
// Symbol.unscopables, Symbol.unscopables,
// customSymbol customSymbol
// ] ]
// keys.forEach(key => { keys.forEach(key => {
// expect(objRef.value[key]).toStrictEqual(obj[key]) expect(objRef.value[key]).toStrictEqual(obj[key])
// }) })
// }) })
test('unref', () => { test('unref', () => {
expect(unref(1)).toBe(1) expect(unref(1)).toBe(1)
@ -204,192 +215,192 @@ describe('reactivity/ref', () => {
expect(dummy).toBe(2) expect(dummy).toBe(2)
}) })
// test('shallowRef force trigger', () => { test('shallowRef force trigger', () => {
// const sref = shallowRef({ a: 1 }) const sref = shallowRef({ a: 1 })
// let dummy let dummy
// effect(() => { effect(() => {
// dummy = sref.value.a dummy = sref.value.a
// }) })
// expect(dummy).toBe(1) expect(dummy).toBe(1)
// sref.value.a = 2 sref.value.a = 2
// expect(dummy).toBe(1) // should not trigger yet expect(dummy).toBe(1) // should not trigger yet
// // force trigger // force trigger
// triggerRef(sref) triggerRef(sref)
// expect(dummy).toBe(2) expect(dummy).toBe(2)
// }) })
// test('shallowRef isShallow', () => { test('shallowRef isShallow', () => {
// expect(isShallow(shallowRef({ a: 1 }))).toBe(true) expect(isShallow(shallowRef({ a: 1 }))).toBe(true)
// }) })
// test('isRef', () => { test('isRef', () => {
// expect(isRef(ref(1))).toBe(true) expect(isRef(ref(1))).toBe(true)
// expect(isRef(computed(() => 1))).toBe(true) // TODO expect(isRef(computed(() => 1))).toBe(true)
// expect(isRef(0)).toBe(false) expect(isRef(0)).toBe(false)
// expect(isRef(1)).toBe(false) expect(isRef(1)).toBe(false)
// // an object that looks like a ref isn't necessarily a ref // an object that looks like a ref isn't necessarily a ref
// expect(isRef({ value: 0 })).toBe(false) expect(isRef({ value: 0 })).toBe(false)
// }) })
// test('toRef', () => { test('toRef', () => {
// const a = reactive({ const a = reactive({
// x: 1 x: 1
// }) })
// const x = toRef(a, 'x') const x = toRef(a, 'x')
// expect(isRef(x)).toBe(true) expect(isRef(x)).toBe(true)
// expect(x.value).toBe(1) expect(x.value).toBe(1)
// // source -> proxy // source -> proxy
// a.x = 2 a.x = 2
// expect(x.value).toBe(2) expect(x.value).toBe(2)
// // proxy -> source // proxy -> source
// x.value = 3 x.value = 3
// expect(a.x).toBe(3) expect(a.x).toBe(3)
// // reactivity // reactivity
// let dummyX let dummyX
// effect(() => { effect(() => {
// dummyX = x.value dummyX = x.value
// }) })
// expect(dummyX).toBe(x.value) expect(dummyX).toBe(x.value)
// // mutating source should trigger effect using the proxy refs // mutating source should trigger effect using the proxy refs
// a.x = 4 a.x = 4
// expect(dummyX).toBe(4) expect(dummyX).toBe(4)
// // should keep ref // should keep ref
// const r = { x: ref(1) } const r = { x: ref(1) }
// expect(toRef(r, 'x')).toBe(r.x) expect(toRef(r, 'x')).toBe(r.x)
// }) })
// test('toRef default value', () => { test('toRef default value', () => {
// const a: { x: number | undefined } = { x: undefined } const a: { x: number | undefined } = { x: undefined }
// const x = toRef(a, 'x', 1) const x = toRef(a, 'x', 1)
// expect(x.value).toBe(1) expect(x.value).toBe(1)
// a.x = 2 a.x = 2
// expect(x.value).toBe(2) expect(x.value).toBe(2)
// a.x = undefined a.x = undefined
// expect(x.value).toBe(1) expect(x.value).toBe(1)
// }) })
// test('toRefs', () => { test('toRefs', () => {
// const a = reactive({ const a = reactive({
// x: 1, x: 1,
// y: 2 y: 2
// }) })
// const { x, y } = toRefs(a) const { x, y } = toRefs(a)
// expect(isRef(x)).toBe(true) expect(isRef(x)).toBe(true)
// expect(isRef(y)).toBe(true) expect(isRef(y)).toBe(true)
// expect(x.value).toBe(1) expect(x.value).toBe(1)
// expect(y.value).toBe(2) expect(y.value).toBe(2)
// // source -> proxy // source -> proxy
// a.x = 2 a.x = 2
// a.y = 3 a.y = 3
// expect(x.value).toBe(2) expect(x.value).toBe(2)
// expect(y.value).toBe(3) expect(y.value).toBe(3)
// // proxy -> source // proxy -> source
// x.value = 3 x.value = 3
// y.value = 4 y.value = 4
// expect(a.x).toBe(3) expect(a.x).toBe(3)
// expect(a.y).toBe(4) expect(a.y).toBe(4)
// // reactivity // reactivity
// let dummyX, dummyY let dummyX, dummyY
// effect(() => { effect(() => {
// dummyX = x.value dummyX = x.value
// dummyY = y.value dummyY = y.value
// }) })
// expect(dummyX).toBe(x.value) expect(dummyX).toBe(x.value)
// expect(dummyY).toBe(y.value) expect(dummyY).toBe(y.value)
// // mutating source should trigger effect using the proxy refs // mutating source should trigger effect using the proxy refs
// a.x = 4 a.x = 4
// a.y = 5 a.y = 5
// expect(dummyX).toBe(4) expect(dummyX).toBe(4)
// expect(dummyY).toBe(5) expect(dummyY).toBe(5)
// }) })
// test('toRefs should warn on plain object', () => { test('toRefs should warn on plain object', () => {
// toRefs({}) toRefs({})
// expect(`toRefs() expects a reactive object`).toHaveBeenWarned() expect(`toRefs() expects a reactive object`).toHaveBeenWarned()
// }) })
// test('toRefs should warn on plain array', () => { test('toRefs should warn on plain array', () => {
// toRefs([]) toRefs([])
// expect(`toRefs() expects a reactive object`).toHaveBeenWarned() expect(`toRefs() expects a reactive object`).toHaveBeenWarned()
// }) })
// test('toRefs reactive array', () => { test('toRefs reactive array', () => {
// const arr = reactive(['a', 'b', 'c']) const arr = reactive(['a', 'b', 'c'])
// const refs = toRefs(arr) const refs = toRefs(arr)
// expect(Array.isArray(refs)).toBe(true) expect(Array.isArray(refs)).toBe(true)
// refs[0].value = '1' refs[0].value = '1'
// expect(arr[0]).toBe('1') expect(arr[0]).toBe('1')
// arr[1] = '2' arr[1] = '2'
// expect(refs[1].value).toBe('2') expect(refs[1].value).toBe('2')
// }) })
// test('customRef', () => { test('customRef', () => {
// let value = 1 let value = 1
// let _trigger: () => void let _trigger: () => void
// const custom = customRef((track, trigger) => ({ const custom = customRef((track, trigger) => ({
// get() { get() {
// track() track()
// return value return value
// }, },
// set(newValue: number) { set(newValue: number) {
// value = newValue value = newValue
// _trigger = trigger _trigger = trigger
// } }
// })) }))
// expect(isRef(custom)).toBe(true) expect(isRef(custom)).toBe(true)
// let dummy let dummy
// effect(() => { effect(() => {
// dummy = custom.value dummy = custom.value
// }) })
// expect(dummy).toBe(1) expect(dummy).toBe(1)
// custom.value = 2 custom.value = 2
// // should not trigger yet // should not trigger yet
// expect(dummy).toBe(1) expect(dummy).toBe(1)
// _trigger!() _trigger!()
// expect(dummy).toBe(2) expect(dummy).toBe(2)
// }) })
// test('should not trigger when setting value to same proxy', () => { test('should not trigger when setting value to same proxy', () => {
// const obj = reactive({ count: 0 }) const obj = reactive({ count: 0 })
// const a = ref(obj) const a = ref(obj)
// const spy1 = jest.fn(() => a.value) const spy1 = vi.fn(() => a.value)
// effect(spy1) effect(spy1)
// a.value = obj a.value = obj
// expect(spy1).toBeCalledTimes(1) expect(spy1).toBeCalledTimes(1)
// const b = shallowRef(obj) const b = shallowRef(obj)
// const spy2 = jest.fn(() => b.value) const spy2 = vi.fn(() => b.value)
// effect(spy2) effect(spy2)
// b.value = obj b.value = obj
// expect(spy2).toBeCalledTimes(1) expect(spy2).toBeCalledTimes(1)
// }) })
}) })