diff --git a/README.md b/README.md index 2db0599c..457bfd77 100644 --- a/README.md +++ b/README.md @@ -38,13 +38,15 @@ import { ref, reactive } from '@vue/composition-api' Include `@vue/composition-api` after Vue and it will install itself automatically. + ```html ``` + -`@vue/composition-api` will be exposed to global variable `window.VueCompositionAPI`. +`@vue/composition-api` will be exposed to global variable `window.VueCompositionAPI`. ```ts const { ref, reactive } = VueCompositionAPI @@ -68,8 +70,6 @@ export default defineComponent({ To make JSX/TSX work with `@vue/composition-api`, check out [babel-preset-vca-jsx](https://github.com/luwanquan/babel-preset-vca-jsx) by [@luwanquan](https://github.com/luwanquan). - - ## SSR Even if there is no definitive Vue 3 API for SSR yet, this plugin implements the `onServerPrefetch` lifecycle hook that allows you to use the `serverPrefetch` hook found in the classic API. @@ -78,7 +78,7 @@ Even if there is no definitive Vue 3 API for SSR yet, this plugin implements the import { onServerPrefetch } from '@vue/composition-api' export default { - setup (props, { ssrContext }) { + setup(props, { ssrContext }) { const result = ref() onServerPrefetch(async () => { @@ -88,8 +88,8 @@ export default { return { result, } - }, -}; + } +} ``` ## Limitations @@ -124,7 +124,6 @@ state.list[1].value === 1 // true ❌ Should NOT use ref in a plain object when working with Array - ```js const a = { count: ref(0), @@ -162,8 +161,8 @@ const a = reactive({ list: [ reactive({ count: ref(0), - }) - ], + }), + ] }) // unwrapped a.list[0].count === 0 // true @@ -179,7 +178,6 @@ a.list[1].count === 1 // true - ### Template Refs
@@ -212,7 +210,6 @@ a.list[1].count === 1 // true
-
✅ String ref && return it from setup() && Render Function / JSX @@ -238,8 +235,8 @@ export default { }, } ``` -
+
@@ -266,7 +263,6 @@ export default {
-
❌ Render Function / JSX in setup() @@ -301,7 +297,6 @@ export default { If you really want to use template refs in this case, you can access `vm.$refs` via `SetupContext.refs` - ```jsx export default { setup(initProps, setupContext) { @@ -347,7 +342,6 @@ declare module '@vue/composition-api' { > :bulb: In Vue 3, it will return an new proxy object. -
### Watch @@ -359,11 +353,11 @@ declare module '@vue/composition-api' { ```js watch(() => { - /* ... */ + /* ... */ }, { immediate: true, - onTrack() {}, // not available - onTrigger() {}, // not available + onTrack() {}, // not available + onTrigger() {}, // not available }) ``` @@ -389,18 +383,26 @@ app2.component('Bar', Bar) // equivalent to Vue.use('Bar', Bar) +### shallowReadonly + +
+ +⚠️ shallowReadonly() will create a new object and with the same root properties, new properties added will not be readonly or reactive. + + +> :bulb: In Vue 3, it will return an new proxy object. + +
### Missing APIs The following APIs introduced in Vue 3 are not available in this plugin. - `readonly` -- `shallowReadonly` - `defineAsyncComponent` - `onRenderTracked` - `onRenderTriggered` - `isProxy` -- `isReadonly` - `isVNode` ### Reactive APIs in `data()` @@ -415,7 +417,7 @@ export default { data() { return { // will result { a: { value: 1 } } in template - a: ref(1) + a: ref(1), } }, } @@ -423,10 +425,8 @@ export default { - ### Performance Impact Due the the limitation of Vue2's public API. `@vue/composition-api` inevitably introduced some extract costs. It shouldn't bother you unless in extreme environments. You can check the [benchmark results](https://antfu.github.io/vue-composition-api-benchmark-results/) for more details. - diff --git a/README.zh-CN.md b/README.zh-CN.md index ee7554db..ba0a2219 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -78,7 +78,7 @@ export default defineComponent({ import { onServerPrefetch } from '@vue/composition-api' export default { - setup (props, { ssrContext }) { + setup(props, { ssrContext }) { const result = ref() onServerPrefetch(async () => { @@ -86,9 +86,9 @@ export default { }) return { - result, + result } - }, + } } ``` @@ -130,27 +130,27 @@ state.list[1].value === 1 // true ```js const a = { - count: ref(0), + count: ref(0) } const b = reactive({ - list: [a], // `a.count` 不会自动展开!! + list: [a] // `a.count` 不会自动展开!! }) // `count` 不会自动展开, 须使用 `.value` -b.list[0].count.value === 0; // true +b.list[0].count.value === 0 // true ``` ```js const b = reactive({ list: [ { - count: ref(0), // 不会自动展开!! - }, - ], + count: ref(0) // 不会自动展开!! + } + ] }) // `count` 不会自动展开, 须使用 `.value` -b.list[0].count.value === 0; // true +b.list[0].count.value === 0 // true ``` @@ -163,17 +163,17 @@ b.list[0].count.value === 0; // true ```js const a = reactive({ - count: ref(0), + count: ref(0) }) const b = reactive({ - list: [a], + list: [a] }) // 自动展开 b.list[0].count === 0 // true b.list.push( reactive({ - count: ref(1), + count: ref(1) }) ) // 自动展开 @@ -205,9 +205,9 @@ b.list[1].count === 1; // true }) return { - root, + root } - }, + } } ``` @@ -232,13 +232,13 @@ export default { }) return { - root, + root } }, render() { // 使用 JSX return () =>
- }, + } } ``` @@ -262,9 +262,9 @@ export default { const root = ref(null) return { - root, + root } - }, + } } ``` @@ -289,7 +289,7 @@ export default { // 使用 JSX return () =>
- }, + } } ``` @@ -348,7 +348,7 @@ declare module '@vue/composition-api' { 此行为与 Vue 2 中的 `Vue.observable` 一致 -> :bulb: 在 Vue 3 中,`reactive()` 会返回一个新的的代理对象. +> :bulb: 在 Vue 3 中,`reactive()` 会返回一个新的的代理对象 @@ -361,33 +361,42 @@ declare module '@vue/composition-api' { ```js -watch(() => { - /* ... */ -}, { - immediate: true, - onTrack() {}, // 不可用 - onTrigger() {}, // 不可用 -}) +watch( + () => { + /* ... */ + }, { + immediate: true, + onTrack() {}, // 不可用 + onTrigger() {}, // 不可用 + } +) ``` +### shallowReadonly + +
+ +⚠️ shallowReadonly() 会返回一个新的浅拷贝对象,在此之后新加的字段将不会获得只读或响应式状态。 + + +> :bulb: 在 Vue 3 中,`shallowReadonly()` 会返回一个新的的代理对象 + +
### 缺失的 API 以下在 Vue 3 新引入的 API ,在本插件中暂不适用: - `readonly` -- `shallowReadonly` - `defineAsyncComponent` - `onRenderTracked` - `onRenderTriggered` - `customRef` - `isProxy` -- `isReadonly` - `isVNode` - ### 在 `data()` 中使用组合式 API
@@ -402,7 +411,7 @@ export default { // 在模版中会成为 { a: { value: 1 } } a: ref(1) } - }, + } } ``` diff --git a/src/apis/computed.ts b/src/apis/computed.ts index baa2f659..a3753e70 100644 --- a/src/apis/computed.ts +++ b/src/apis/computed.ts @@ -42,15 +42,18 @@ export function computed( vm && vm.$on('hook:destroyed', () => computedHost.$destroy()) - return createRef({ - get: () => (computedHost as any).$$state, - set: (v: T) => { - if (__DEV__ && !set) { - warn('Computed property was assigned to but it has no setter.', vm!) - return - } - - ;(computedHost as any).$$state = v + return createRef( + { + get: () => (computedHost as any).$$state, + set: (v: T) => { + if (__DEV__ && !set) { + warn('Write operation failed: computed value is readonly.', vm!) + return + } + + ;(computedHost as any).$$state = v + }, }, - }) + !set + ) } diff --git a/src/apis/state.ts b/src/apis/state.ts index 772406f5..f797492e 100644 --- a/src/apis/state.ts +++ b/src/apis/state.ts @@ -16,4 +16,6 @@ export { triggerRef, unref, UnwrapRef, + isReadonly, + shallowReadonly, } from '../reactivity' diff --git a/src/reactivity/index.ts b/src/reactivity/index.ts index 115ee971..5b93932c 100644 --- a/src/reactivity/index.ts +++ b/src/reactivity/index.ts @@ -6,6 +6,8 @@ export { toRaw, isRaw, markReactive, + isReadonly, + shallowReadonly, } from './reactive' export { ref, diff --git a/src/reactivity/reactive.ts b/src/reactivity/reactive.ts index 2e2fcc49..ffee8fe0 100644 --- a/src/reactivity/reactive.ts +++ b/src/reactivity/reactive.ts @@ -6,6 +6,7 @@ import { AccessControlIdentifierKey, ReactiveIdentifierKey, RawIdentifierKey, + ReadonlyIdentifierKey, RefKey, } from '../utils/symbols' import { isRef, UnwrapRef } from './ref' @@ -20,6 +21,10 @@ export function isRaw(obj: any): boolean { ) } +export function isReadonly(obj: any): boolean { + return hasOwn(obj, ReadonlyIdentifierKey) && obj[ReadonlyIdentifierKey] +} + export function isReactive(obj: any): boolean { return ( isObject(obj) && @@ -253,6 +258,57 @@ export function reactive(obj: T): UnwrapRef { return observed as UnwrapRef } +export function shallowReadonly(obj: T): Readonly { + if (!isPlainObject(obj) || !Object.isExtensible(obj)) { + //@ts-ignore + return obj // just typing + } + + const readonlyObj = { + [ReadonlyIdentifierKey]: true, + } + + const source = reactive({}) + const ob = (source as any).__ob__ + + for (const key of Object.keys(obj)) { + let val = obj[key] + let getter: (() => any) | undefined + let setter: ((x: any) => void) | undefined + const property = Object.getOwnPropertyDescriptor(obj, key) + if (property) { + if (property.configurable === false) { + continue + } + getter = property.get + setter = property.set + if ( + (!getter || setter) /* not only have getter */ && + arguments.length === 2 + ) { + val = obj[key] + } + } + + Object.defineProperty(readonlyObj, key, { + enumerable: true, + configurable: true, + get: function getterHandler() { + const value = getter ? getter.call(obj) : val + ob.dep.depend() + return value + }, + set(v) { + if (__DEV__) { + warn(`Set operation on key "${key}" failed: target is readonly.`) + } + }, + }) + } + + return readonlyObj as any +} + /** * Make sure obj can't be a reactive */ diff --git a/src/reactivity/ref.ts b/src/reactivity/ref.ts index 577e736e..a3e3242d 100644 --- a/src/reactivity/ref.ts +++ b/src/reactivity/ref.ts @@ -1,5 +1,5 @@ import { Data } from '../component' -import { RefKey } from '../utils/symbols' +import { RefKey, ReadonlyIdentifierKey } from '../utils/symbols' import { proxy, isPlainObject, warn } from '../utils' import { reactive, isReactive, shallowReactive } from './reactive' import { ComputedRef } from '../apis/computed' @@ -81,11 +81,17 @@ class RefImpl implements Ref { } } -export function createRef(options: RefOption) { +export function createRef(options: RefOption, readonly = false) { + const r = new RefImpl(options) + if (readonly) { + //@ts-ignore + r[ReadonlyIdentifierKey] = readonly + } + // seal the ref, this could prevent ref from being observed // It's safe to seal the ref, since we really shouldn't extend it. // related issues: #79 - return Object.seal(new RefImpl(options)) + return Object.seal(r) } export function ref( diff --git a/src/utils/symbols.ts b/src/utils/symbols.ts index f36cfba7..4fa4e259 100644 --- a/src/utils/symbols.ts +++ b/src/utils/symbols.ts @@ -17,6 +17,9 @@ export const ReactiveIdentifierKey = createSymbol( 'composition-api.reactiveIdentifier' ) export const RawIdentifierKey = createSymbol('composition-api.rawIdentifierKey') +export const ReadonlyIdentifierKey = createSymbol( + 'composition-api.readonlyIdentifierKey' +) // must be a string, symbol key is ignored in reactive export const RefKey = 'composition-api.refKey' diff --git a/test/apis/computed.spec.js b/test/apis/computed.spec.js index 9700e1f6..78606e4f 100644 --- a/test/apis/computed.spec.js +++ b/test/apis/computed.spec.js @@ -1,5 +1,5 @@ const Vue = require('vue/dist/vue.common.js') -const { ref, computed } = require('../../src') +const { ref, computed, isReadonly } = require('../../src') describe('Hooks computed', () => { beforeEach(() => { @@ -71,7 +71,7 @@ describe('Hooks computed', () => { }) vm.b = 2 expect(warn.mock.calls[0][0]).toMatch( - '[Vue warn]: Computed property was assigned to but it has no setter.' + '[Vue warn]: Write operation failed: computed value is readonly.' ) }) @@ -194,4 +194,22 @@ describe('Hooks computed', () => { expect(app.$children[0].example).toBe('A') expect(app.$children[1].example).toBe('B') }) + + it('should be readonly', () => { + let a = { a: 1 } + const x = computed(() => a) + expect(isReadonly(x)).toBe(true) + expect(isReadonly(x.value)).toBe(false) + expect(isReadonly(x.value.a)).toBe(false) + const z = computed({ + get() { + return a + }, + set(v) { + a = v + }, + }) + expect(isReadonly(z.value)).toBe(false) + expect(isReadonly(z.value.a)).toBe(false) + }) }) diff --git a/test/v3/reactivity/computed.spec.ts b/test/v3/reactivity/computed.spec.ts index 999b07d2..756022d3 100644 --- a/test/v3/reactivity/computed.spec.ts +++ b/test/v3/reactivity/computed.spec.ts @@ -1,9 +1,15 @@ -import { computed, reactive, ref, watchEffect } from '../../../src' +import { + computed, + reactive, + ref, + watchEffect, + WritableComputedRef, +} from '../../../src' import { mockWarn } from '../../helpers/mockWarn' import { nextTick } from '../../helpers/utils' describe('reactivity/computed', () => { - mockWarn() + mockWarn(true) it('should return updated value', async () => { const value = reactive<{ foo?: number }>({ foo: undefined }) @@ -175,12 +181,14 @@ describe('reactivity/computed', () => { expect(dummy).toBe(-1) }) - // it('should warn if trying to set a readonly computed', async () => { - // const n = ref(1); - // const plusOne = computed(() => n.value + 1); - // (plusOne as WritableComputedRef).value++; // Type cast to prevent TS from preventing the error - // await nextTick(); + it('should warn if trying to set a readonly computed', async () => { + const n = ref(1) + const plusOne = computed(() => n.value + 1) + ;(plusOne as WritableComputedRef).value++ // Type cast to prevent TS from preventing the error + await nextTick() - // expect('Write operation failed: computed value is readonly').toHaveBeenWarnedLast(); - // }); + expect( + 'Write operation failed: computed value is readonly' + ).toHaveBeenWarnedLast() + }) }) diff --git a/test/v3/reactivity/readonly.spec.ts b/test/v3/reactivity/readonly.spec.ts new file mode 100644 index 00000000..b6cc56d7 --- /dev/null +++ b/test/v3/reactivity/readonly.spec.ts @@ -0,0 +1,376 @@ +import { mockWarn } from '../../helpers/mockWarn' +import { shallowReadonly, isReactive } from '../../../src' + +// /** +// * @see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html +// */ +// type Writable = { -readonly [P in keyof T]: T[P] } + +describe('reactivity/readonly', () => { + mockWarn(true) + + // describe('Object', () => { + // it('should make nested values readonly', () => { + // const original = { foo: 1, bar: { baz: 2 } } + // const wrapped = readonly(original) + // expect(wrapped).not.toBe(original) + // expect(isProxy(wrapped)).toBe(true) + // expect(isReactive(wrapped)).toBe(false) + // expect(isReadonly(wrapped)).toBe(true) + // expect(isReactive(original)).toBe(false) + // expect(isReadonly(original)).toBe(false) + // expect(isReactive(wrapped.bar)).toBe(false) + // expect(isReadonly(wrapped.bar)).toBe(true) + // expect(isReactive(original.bar)).toBe(false) + // expect(isReadonly(original.bar)).toBe(false) + // // get + // expect(wrapped.foo).toBe(1) + // // has + // expect('foo' in wrapped).toBe(true) + // // ownKeys + // expect(Object.keys(wrapped)).toEqual(['foo', 'bar']) + // }) + + // it('should not allow mutation', () => { + // const qux = Symbol('qux') + // const original = { + // foo: 1, + // bar: { + // baz: 2, + // }, + // [qux]: 3, + // } + // const wrapped: Writable = readonly(original) + + // wrapped.foo = 2 + // expect(wrapped.foo).toBe(1) + // expect( + // `Set operation on key "foo" failed: target is readonly.` + // ).toHaveBeenWarnedLast() + + // wrapped.bar.baz = 3 + // expect(wrapped.bar.baz).toBe(2) + // expect( + // `Set operation on key "baz" failed: target is readonly.` + // ).toHaveBeenWarnedLast() + + // wrapped[qux] = 4 + // expect(wrapped[qux]).toBe(3) + // expect( + // `Set operation on key "Symbol(qux)" failed: target is readonly.` + // ).toHaveBeenWarnedLast() + + // delete wrapped.foo + // expect(wrapped.foo).toBe(1) + // expect( + // `Delete operation on key "foo" failed: target is readonly.` + // ).toHaveBeenWarnedLast() + + // delete wrapped.bar.baz + // expect(wrapped.bar.baz).toBe(2) + // expect( + // `Delete operation on key "baz" failed: target is readonly.` + // ).toHaveBeenWarnedLast() + + // delete wrapped[qux] + // expect(wrapped[qux]).toBe(3) + // expect( + // `Delete operation on key "Symbol(qux)" failed: target is readonly.` + // ).toHaveBeenWarnedLast() + // }) + + // it('should not trigger effects', () => { + // const wrapped: any = readonly({ a: 1 }) + // let dummy + // effect(() => { + // dummy = wrapped.a + // }) + // expect(dummy).toBe(1) + // wrapped.a = 2 + // expect(wrapped.a).toBe(1) + // expect(dummy).toBe(1) + // expect(`target is readonly`).toHaveBeenWarned() + // }) + // }) + + // describe('Array', () => { + // it('should make nested values readonly', () => { + // const original = [{ foo: 1 }] + // const wrapped = readonly(original) + // expect(wrapped).not.toBe(original) + // expect(isProxy(wrapped)).toBe(true) + // expect(isReactive(wrapped)).toBe(false) + // expect(isReadonly(wrapped)).toBe(true) + // expect(isReactive(original)).toBe(false) + // expect(isReadonly(original)).toBe(false) + // expect(isReactive(wrapped[0])).toBe(false) + // expect(isReadonly(wrapped[0])).toBe(true) + // expect(isReactive(original[0])).toBe(false) + // expect(isReadonly(original[0])).toBe(false) + // // get + // expect(wrapped[0].foo).toBe(1) + // // has + // expect(0 in wrapped).toBe(true) + // // ownKeys + // expect(Object.keys(wrapped)).toEqual(['0']) + // }) + + // it('should not allow mutation', () => { + // const wrapped: any = readonly([{ foo: 1 }]) + // wrapped[0] = 1 + // expect(wrapped[0]).not.toBe(1) + // expect( + // `Set operation on key "0" failed: target is readonly.` + // ).toHaveBeenWarned() + // wrapped[0].foo = 2 + // expect(wrapped[0].foo).toBe(1) + // expect( + // `Set operation on key "foo" failed: target is readonly.` + // ).toHaveBeenWarned() + + // // should block length mutation + // wrapped.length = 0 + // expect(wrapped.length).toBe(1) + // expect(wrapped[0].foo).toBe(1) + // expect( + // `Set operation on key "length" failed: target is readonly.` + // ).toHaveBeenWarned() + + // // mutation methods invoke set/length internally and thus are blocked as well + // wrapped.push(2) + // expect(wrapped.length).toBe(1) + // // push triggers two warnings on [1] and .length + // expect(`target is readonly.`).toHaveBeenWarnedTimes(5) + // }) + + // it('should not trigger effects', () => { + // const wrapped: any = readonly([{ a: 1 }]) + // let dummy + // effect(() => { + // dummy = wrapped[0].a + // }) + // expect(dummy).toBe(1) + // wrapped[0].a = 2 + // expect(wrapped[0].a).toBe(1) + // expect(dummy).toBe(1) + // expect(`target is readonly`).toHaveBeenWarnedTimes(1) + // wrapped[0] = { a: 2 } + // expect(wrapped[0].a).toBe(1) + // expect(dummy).toBe(1) + // expect(`target is readonly`).toHaveBeenWarnedTimes(2) + // }) + // }) + + // const maps = [Map, WeakMap] + // maps.forEach((Collection: any) => { + // describe(Collection.name, () => { + // test('should make nested values readonly', () => { + // const key1 = {} + // const key2 = {} + // const original = new Collection([ + // [key1, {}], + // [key2, {}], + // ]) + // const wrapped = readonly(original) + // expect(wrapped).not.toBe(original) + // expect(isProxy(wrapped)).toBe(true) + // expect(isReactive(wrapped)).toBe(false) + // expect(isReadonly(wrapped)).toBe(true) + // expect(isReactive(original)).toBe(false) + // expect(isReadonly(original)).toBe(false) + // expect(isReactive(wrapped.get(key1))).toBe(false) + // expect(isReadonly(wrapped.get(key1))).toBe(true) + // expect(isReactive(original.get(key1))).toBe(false) + // expect(isReadonly(original.get(key1))).toBe(false) + // }) + + // test('should not allow mutation & not trigger effect', () => { + // const map = readonly(new Collection()) + // const key = {} + // let dummy + // effect(() => { + // dummy = map.get(key) + // }) + // expect(dummy).toBeUndefined() + // map.set(key, 1) + // expect(dummy).toBeUndefined() + // expect(map.has(key)).toBe(false) + // expect( + // `Set operation on key "${key}" failed: target is readonly.` + // ).toHaveBeenWarned() + // }) + + // if (Collection === Map) { + // test('should retrieve readonly values on iteration', () => { + // const key1 = {} + // const key2 = {} + // const original = new Collection([ + // [key1, {}], + // [key2, {}], + // ]) + // const wrapped: any = readonly(original) + // expect(wrapped.size).toBe(2) + // for (const [key, value] of wrapped) { + // expect(isReadonly(key)).toBe(true) + // expect(isReadonly(value)).toBe(true) + // } + // wrapped.forEach((value: any) => { + // expect(isReadonly(value)).toBe(true) + // }) + // for (const value of wrapped.values()) { + // expect(isReadonly(value)).toBe(true) + // } + // }) + // } + // }) + // }) + + // const sets = [Set, WeakSet] + // sets.forEach((Collection: any) => { + // describe(Collection.name, () => { + // test('should make nested values readonly', () => { + // const key1 = {} + // const key2 = {} + // const original = new Collection([key1, key2]) + // const wrapped = readonly(original) + // expect(wrapped).not.toBe(original) + // expect(isProxy(wrapped)).toBe(true) + // expect(isReactive(wrapped)).toBe(false) + // expect(isReadonly(wrapped)).toBe(true) + // expect(isReactive(original)).toBe(false) + // expect(isReadonly(original)).toBe(false) + // expect(wrapped.has(reactive(key1))).toBe(true) + // expect(original.has(reactive(key1))).toBe(false) + // }) + + // test('should not allow mutation & not trigger effect', () => { + // const set = readonly(new Collection()) + // const key = {} + // let dummy + // effect(() => { + // dummy = set.has(key) + // }) + // expect(dummy).toBe(false) + // set.add(key) + // expect(dummy).toBe(false) + // expect(set.has(key)).toBe(false) + // expect( + // `Add operation on key "${key}" failed: target is readonly.` + // ).toHaveBeenWarned() + // }) + + // if (Collection === Set) { + // test('should retrieve readonly values on iteration', () => { + // const original = new Collection([{}, {}]) + // const wrapped: any = readonly(original) + // expect(wrapped.size).toBe(2) + // for (const value of wrapped) { + // expect(isReadonly(value)).toBe(true) + // } + // wrapped.forEach((value: any) => { + // expect(isReadonly(value)).toBe(true) + // }) + // for (const value of wrapped.values()) { + // expect(isReadonly(value)).toBe(true) + // } + // for (const [v1, v2] of wrapped.entries()) { + // expect(isReadonly(v1)).toBe(true) + // expect(isReadonly(v2)).toBe(true) + // } + // }) + // } + // }) + // }) + + // test('calling reactive on an readonly should return readonly', () => { + // const a = readonly({}) + // const b = reactive(a) + // expect(isReadonly(b)).toBe(true) + // // should point to same original + // expect(toRaw(a)).toBe(toRaw(b)) + // }) + + // test('calling readonly on a reactive object should return readonly', () => { + // const a = reactive({}) + // const b = readonly(a) + // expect(isReadonly(b)).toBe(true) + // // should point to same original + // expect(toRaw(a)).toBe(toRaw(b)) + // }) + + // test('readonly should track and trigger if wrapping reactive original', () => { + // const a = reactive({ n: 1 }) + // const b = readonly(a) + // // should return true since it's wrapping a reactive source + // expect(isReactive(b)).toBe(true) + + // let dummy + // effect(() => { + // dummy = b.n + // }) + // expect(dummy).toBe(1) + // a.n++ + // expect(b.n).toBe(2) + // expect(dummy).toBe(2) + // }) + + // test('wrapping already wrapped value should return same Proxy', () => { + // const original = { foo: 1 } + // const wrapped = readonly(original) + // const wrapped2 = readonly(wrapped) + // expect(wrapped2).toBe(wrapped) + // }) + + // test('wrapping the same value multiple times should return same Proxy', () => { + // const original = { foo: 1 } + // const wrapped = readonly(original) + // const wrapped2 = readonly(original) + // expect(wrapped2).toBe(wrapped) + // }) + + // test('markRaw', () => { + // const obj = readonly({ + // foo: { a: 1 }, + // bar: markRaw({ b: 2 }), + // }) + // expect(isReadonly(obj.foo)).toBe(true) + // expect(isReactive(obj.bar)).toBe(false) + // }) + + // test('should make ref readonly', () => { + // const n: any = readonly(ref(1)) + // n.value = 2 + // expect(n.value).toBe(1) + // expect( + // `Set operation on key "value" failed: target is readonly.` + // ).toHaveBeenWarned() + // }) + + describe('shallowReadonly', () => { + test('should not make non-reactive properties reactive', () => { + const props = shallowReadonly({ n: { foo: 1 } }) + expect(isReactive(props.n)).toBe(false) + }) + + test('should make root level properties readonly', () => { + const props = shallowReadonly({ n: 1 }) + // @ts-ignore + props.n = 2 + expect(props.n).toBe(1) + expect( + `Set operation on key "n" failed: target is readonly.` + ).toHaveBeenWarned() + }) + + // to retain 2.x behavior. + test('should NOT make nested properties readonly', () => { + const props = shallowReadonly({ n: { foo: 1 } }) + // @ts-ignore + props.n.foo = 2 + expect(props.n.foo).toBe(2) + expect( + `Set operation on key "foo" failed: target is readonly.` + ).not.toHaveBeenWarned() + }) + }) +})