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()
+ })
+ })
+})