diff --git a/src/core/observer/index.ts b/src/core/observer/index.ts index 84744dd85f9..3770c489a9f 100644 --- a/src/core/observer/index.ts +++ b/src/core/observer/index.ts @@ -191,7 +191,7 @@ export function defineReactive( } else if (getter) { // #7981: for accessor properties without setter return - } else if (isRef(value) && !isRef(newVal)) { + } else if (!shallow && isRef(value) && !isRef(newVal)) { value.value = newVal return } else { diff --git a/test/unit/features/v3/reactivity/readonly.spec.ts b/test/unit/features/v3/reactivity/readonly.spec.ts index 11550ab56ed..012e142d451 100644 --- a/test/unit/features/v3/reactivity/readonly.spec.ts +++ b/test/unit/features/v3/reactivity/readonly.spec.ts @@ -483,7 +483,6 @@ describe('reactivity/readonly', () => { const ror = readonly(r) const obj = reactive({ ror }) obj.ror = true - expect(obj.ror).toBe(false) expect(`Set operation on key "value" failed`).toHaveBeenWarned() }) @@ -492,14 +491,20 @@ describe('reactivity/readonly', () => { const r = ref(false) const ror = readonly(r) const obj = reactive({ ror }) - try { - obj.ror = ref(true) as unknown as boolean - } catch (e) {} - + obj.ror = ref(true) as unknown as boolean expect(obj.ror).toBe(true) expect(toRaw(obj).ror).not.toBe(ror) // ref successfully replaced }) + test('setting readonly object to writable nested ref', () => { + const r = ref() + const obj = reactive({ r }) + const ro = readonly({}) + obj.r = ro + expect(obj.r).toBe(ro) + expect(r.value).toBe(ro) + }) + test('compatiblity with classes', () => { const spy = vi.fn() class Foo { diff --git a/test/unit/features/v3/reactivity/ref.spec.ts b/test/unit/features/v3/reactivity/ref.spec.ts index c61aaa71027..96212975359 100644 --- a/test/unit/features/v3/reactivity/ref.spec.ts +++ b/test/unit/features/v3/reactivity/ref.spec.ts @@ -11,7 +11,8 @@ import { isReactive, isShallow, reactive, - computed + computed, + readonly } from 'v3' import { effect } from 'v3/reactivity/effect' @@ -404,4 +405,17 @@ describe('reactivity/ref', () => { b.value = obj expect(spy2).toBeCalledTimes(1) }) + + test('ref should preserve value readonly-ness', () => { + const original = {} + const r = reactive(original) + const rr = readonly(original) + const a = ref(original) + + expect(a.value).toBe(r) + + a.value = rr + expect(a.value).toBe(rr) + expect(a.value).not.toBe(r) + }) }) diff --git a/test/unit/features/v3/reactivity/shallowReactive.spec.ts b/test/unit/features/v3/reactivity/shallowReactive.spec.ts index 1e2a0cbf808..d1125771b90 100644 --- a/test/unit/features/v3/reactivity/shallowReactive.spec.ts +++ b/test/unit/features/v3/reactivity/shallowReactive.spec.ts @@ -3,6 +3,7 @@ import { isRef, isShallow, reactive, + Ref, ref, shallowReactive, shallowReadonly @@ -45,6 +46,18 @@ describe('shallowReactive', () => { expect(foo.bar.value).toBe(123) }) + // #12688 + test('should not mutate refs', () => { + const original = ref(123) + const foo = shallowReactive<{ bar: Ref | number }>({ + bar: original + }) + expect(foo.bar).toBe(original) + foo.bar = 234 + expect(foo.bar).toBe(234) + expect(original.value).toBe(123) + }) + // @discrepancy no shallow/non-shallow versions from the same source - // cannot support this without real proxies // #2843