Repository: pmndrs/valtio (ts)

Issue:
DeepResolveType breaks React.ComponentType
Hi,

useSnapshot uses DeepResolveType as the return type which breaks the type if one of the props is a ref to a ComponentType.

```
const store: {Comp: React.ComponentType} = {Comp: () => null}


function MyComponent(){
	const snap = useSnapshot(store) as typeof store;

	return <snap.Comp/>; // this works fine
}


function MyComponent(){
	const snap = useSnapshot(store);

	return <snap.Comp/>; // TS2604: JSX element type 'snap.Comp' does not have any construct or call signatures.
}

```



Pull request:
fix: avoid const enum and deprecate DeepResolveType
ref: https://github.com/pmndrs/valtio/issues/327#issuecomment-1018306353
close #292

`AsRef` const enum was introduced in #146 to fix some typing issue with snapshot. But, this causes some trouble while explicitly typing it, without any workarounds.

This changes `AsRef` to be _hopefully unique_ type. This allows types to be inferred. While we don't recommend, you can define the same `AsRef` on your end. It was our strong hope to hide the internal type as implementation detail and it's finally possible.

`DeepResolveType` was exported previously (which is not considered a public api, but someone might have used it), but it's now deprecated.

## Migration Guide

In case you need to specify snapshot types, there are two options.

### Assert mutable (let's lie)

```ts
type State = { foo?: string }
const state = proxy<State>({})

  const snap = useSnapshot(state) as State
  const handleSnap = (s: State) => {
    // ...
  }
```

### Wrap types with `Readonly<>`

```ts
type State = { foo?: string }
const state = proxy<State>({})

  const snap = useSnapshot(state)
  const handleSnap = (s: Readonly<State>) => {
    // ...
  }
```

If the state is deeply nested, please define `DeepReadonly` type util.
```ts
type DeepReadonly<T> = {
  readonly [P in keyof T]: DeepReadonly<T[P]>;
}

  const handleSnap = (s: DeepReadonly<State>) => {
    // ...
  }
```

### Other hacks

[Extract the return type of `snapshot`](https://github.com/pmndrs/valtio/blob/1034f3f5c36ae6feba7c8dbee3fbf2a96939d4ed/src/react.ts#L18-L23)


Patch:
diff --git a/babel.config.js b/babel.config.js
index 5029a18..fa15c8a 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -22,6 +22,7 @@ module.exports = (api, targets) => {
           runtime: 'automatic',
         },
       ],
+      ['@babel/plugin-transform-typescript', { isTSX: true }],
     ],
   }
 }
diff --git a/package.json b/package.json
index fac034d..722b5ea 100644
--- a/package.json
+++ b/package.json
@@ -135,6 +135,7 @@
     "@babel/core": "^7.16.7",
     "@babel/helper-module-imports": "^7.16.7",
     "@babel/plugin-transform-react-jsx": "^7.16.7",
+    "@babel/plugin-transform-typescript": "^7.16.8",
     "@babel/preset-env": "^7.16.8",
     "@babel/types": "^7.16.8",
     "@rollup/plugin-alias": "^3.1.9",
diff --git a/readme.md b/readme.md
index 1a9ed43..e5c2eb9 100644
--- a/readme.md
+++ b/readme.md
@@ -295,7 +295,7 @@ derive({
 #### `proxyWithComputed` util
 
 You can have computed values with dependency tracking with property access.
-Dependency tracking in `proxyWithComputed` conflicts with the work in `useSnapshot`.
+Dependency tracking in `proxyWithComputed` overlaps the work in `useSnapshot`.
 React users should prefer using `derive`.
 `proxyWithComputed` works well for some edge cases and for vanilla-js users.
 
@@ -368,6 +368,21 @@ state.delete(1)
 state.forEach(v => console.log(v)) // 2,3,4
 ```
 
+#### `proxyMap` util
+
+This is to create a proxy which emulate the native Map behavior. The API is the same as Map API
+
+```js
+
+import { proxyMap } from 'valtio/utils'
+ 
+const state = proxyMap([["key", "value"], ["key2", "value2"]])
+state.set("key", "value")
+state.delete("key")
+state.get("key") // ---> value
+state.forEach((value, key) => console.log(key, value)) // ---> "key", "value", "key2", "value2"
+```
+
 #### Plugins
 
 - [eslint-plugin-valtio](https://github.com/pmndrs/eslint-plugin-valtio)
diff --git a/rollup.config.js b/rollup.config.js
index b302833..450dbfa 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -81,7 +81,6 @@ function createCommonJSConfig(input, output) {
         },
       }),
       resolve({ extensions }),
-      typescript(),
       babelPlugin(getBabelOptions({ ie: 11 })),
     ],
   }
@@ -105,7 +104,6 @@ function createUMDConfig(input, output) {
         },
       }),
       resolve({ extensions }),
-      typescript(),
       babelPlugin(getBabelOptions({ ie: 11 })),
     ],
   }
@@ -128,7 +126,6 @@ function createSystemConfig(input, output) {
         },
       }),
       resolve({ extensions }),
-      typescript(),
       getEsbuild('node12'),
     ],
   }
diff --git a/src/react.ts b/src/react.ts
index 2cc4762..34a9043 100644
--- a/src/react.ts
+++ b/src/react.ts
@@ -14,7 +14,13 @@ import {
 } from 'proxy-compare'
 import { createMutableSource, useMutableSource } from './useMutableSource'
 import { getVersion, snapshot, subscribe } from './vanilla'
-import type { DeepResolveType } from './vanilla'
+
+class SnapshotWrapper<T extends object> {
+  fn(p: T) {
+    return snapshot(p)
+  }
+}
+type Snapshot<T extends object> = ReturnType<SnapshotWrapper<T>['fn']>
 
 const isSSR =
   typeof window === 'undefined' ||
@@ -127,12 +133,12 @@ type Options = {
 export const useSnapshot = <T extends object>(
   proxyObject: T,
   options?: Options
-): DeepResolveType<T> => {
+): Snapshot<T> => {
   const forceUpdate = useReducer((c) => c + 1, 0)[1]
   const affected = new WeakMap()
   const lastAffected = useRef<typeof affected>()
-  const prevSnapshot = useRef<DeepResolveType<T>>()
-  const lastSnapshot = useRef<DeepResolveType<T>>()
+  const prevSnapshot = useRef<Snapshot<T>>()
+  const lastSnapshot = useRef<Snapshot<T>>()
   useIsomorphicLayoutEffect(() => {
     lastSnapshot.current = prevSnapshot.current = snapshot(proxyObject)
   }, [proxyObject])
diff --git a/src/utils.ts b/src/utils.ts
index f7c1f3a..14c8614 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -10,3 +10,4 @@ export { addComputed_DEPRECATED as addComputed } from './utils/addComputed'
 export { proxyWithComputed } from './utils/proxyWithComputed'
 export { proxyWithHistory } from './utils/proxyWithHistory'
 export { proxySet } from './utils/proxySet'
+export { proxyMap } from './utils/proxyMap'
diff --git a/src/utils/proxyMap.ts b/src/utils/proxyMap.ts
new file mode 100644
index 0000000..75f6ea7
--- /dev/null
+++ b/src/utils/proxyMap.ts
@@ -0,0 +1,111 @@
+import { proxy } from 'valtio'
+
+type KeyValRecord<K, V> = [key: K, value: V]
+
+type InternalProxyMap<K, V> = Map<K, V> & {
+  data: KeyValRecord<K, V>[]
+  toJSON: object
+}
+
+/**
+ * proxyMap
+ *
+ * This is to create a proxy which mimic the native Map behavior.
+ * The API is the same as Map API
+ *
+ * @example
+ * import { proxyMap } from 'valtio/utils'
+ * const state = proxyMap([["key", "value"]])
+ *
+ * //can be used inside a proxy as well
+ * const state = proxy({
+ *   count: 1,
+ *   map: proxyMap()
+ * })
+ *
+ * // When using an object as a key, you can wrap it with `ref` so it's not proxied
+ * // this is useful if you want to preserve the key equality
+ * import { ref } from 'valtio'
+ *
+ * const key = ref({})
+ * state.set(key, "value")
+ * state.get(key) //value
+ *
+ * const key = {}
+ * state.set(key, "value")
+ * state.get(key) //undefined
+ */
+export const proxyMap = <K, V>(
+  entries?: Iterable<readonly [K, V]> | null
+): Map<K, V> => {
+  const map: InternalProxyMap<K, V> = proxy({
+    data: Array.from(entries || []) as KeyValRecord<K, V>[],
+    has(key) {
+      return this.data.some((p) => p[0] === key)
+    },
+    set(key, value) {
+      const record = this.data.find((p) => p[0] === key)
+      if (record) {
+        record[1] = value
+      } else {
+        this.data.push([key, value])
+      }
+      return this
+    },
+    get(key) {
+      return this.data.find((p) => p[0] === key)?.[1]
+    },
+    delete(key) {
+      const index = this.data.findIndex((p) => p[0] === key)
+      if (index === -1) {
+        return false
+      }
+      this.data.splice(index, 1)
+      return true
+    },
+    clear() {
+      this.data.splice(0)
+    },
+    get size() {
+      return this.data.length
+    },
+    toJSON() {
+      return {}
+    },
+    forEach(cb) {
+      this.data.forEach((p) => {
+        cb(p[1], p[0], this)
+      })
+    },
+    keys() {
+      return this.data.map((p) => p[0]).values()
+    },
+    values() {
+      return this.data.map((p) => p[1]).values()
+    },
+    entries() {
+      return new Map(this.data).entries()
+    },
+    get [Symbol.toStringTag]() {
+      return 'Map'
+    },
+    [Symbol.iterator]() {
+      return this.entries()
+    },
+  })
+
+  Object.defineProperties(map, {
+    data: {
+      enumerable: false,
+    },
+    size: {
+      enumerable: false,
+    },
+    toJSON: {
+      enumerable: false,
+    },
+  })
+  Object.seal(map)
+
+  return map as Map<K, V>
+}
diff --git a/src/utils/proxySet.ts b/src/utils/proxySet.ts
index c45eae6..2f0ac17 100644
--- a/src/utils/proxySet.ts
+++ b/src/utils/proxySet.ts
@@ -21,7 +21,7 @@ type InternalProxySet<T> = Set<T> & {
  *   set: proxySet()
  * })
  */
-export const proxySet = <T>(initialValues: Iterable<T> | null = []): Set<T> => {
+export const proxySet = <T>(initialValues?: Iterable<T> | null): Set<T> => {
   const set: InternalProxySet<T> = proxy({
     data: Array.from(new Set(initialValues)),
     has(value) {
diff --git a/src/utils/proxyWithComputed.ts b/src/utils/proxyWithComputed.ts
index 9308f4e..8cfa39d 100644
--- a/src/utils/proxyWithComputed.ts
+++ b/src/utils/proxyWithComputed.ts
@@ -1,6 +1,12 @@
 import { createProxy as createProxyToCompare, isChanged } from 'proxy-compare'
 import { proxy, snapshot } from '../vanilla'
-import type { DeepResolveType } from '../vanilla'
+
+class SnapshotWrapper<T extends object> {
+  fn(p: T) {
+    return snapshot(p)
+  }
+}
+type Snapshot<T extends object> = ReturnType<SnapshotWrapper<T>['fn']>
 
 /**
  * proxyWithComputed
@@ -30,9 +36,9 @@ export const proxyWithComputed = <T extends object, U extends object>(
   initialObject: T,
   computedFns: {
     [K in keyof U]:
-      | ((snap: DeepResolveType<T>) => U[K])
+      | ((snap: Snapshot<T>) => U[K])
       | {
-          get: (snap: DeepResolveType<T>) => U[K]
+          get: (snap: Snapshot<T>) => U[K]
           set?: (state: T, newValue: U[K]) => void
         }
   }
@@ -45,11 +51,11 @@ export const proxyWithComputed = <T extends object, U extends object>(
     const { get, set } = (
       typeof computedFn === 'function' ? { get: computedFn } : computedFn
     ) as {
-      get: (snap: DeepResolveType<T>) => U[typeof key]
+      get: (snap: Snapshot<T>) => U[typeof key]
       set?: (state: T, newValue: U[typeof key]) => void
     }
     let computedValue: U[typeof key]
-    let prevSnapshot: DeepResolveType<T> | undefined
+    let prevSnapshot: Snapshot<T> | undefined
     let affected = new WeakMap()
     const desc: PropertyDescriptor = {}
     desc.get = () => {
diff --git a/src/vanilla.ts b/src/vanilla.ts
index 05e8954..e21e9e0 100644
--- a/src/vanilla.ts
+++ b/src/vanilla.ts
@@ -7,7 +7,7 @@ const HANDLER = Symbol()
 const PROMISE_RESULT = Symbol()
 const PROMISE_ERROR = Symbol()
 
-const enum AsRef {}
+type AsRef = { $$valtioRef: true }
 const refSet = new WeakSet()
 export const ref = <T extends object>(o: T): T & AsRef => {
   refSet.add(o)
@@ -247,6 +247,9 @@ export const subscribe = <T extends object>(
   }
 }
 
+/**
+ * @deprecated this will be removed in next versions
+ */
 export type DeepResolveType<T> = T extends (...args: any[]) => any
   ? T
   : T extends AsRef
@@ -259,9 +262,18 @@ export type DeepResolveType<T> = T extends (...args: any[]) => any
     }
   : T
 
-export const snapshot = <T extends object>(
-  proxyObject: T
-): DeepResolveType<T> => {
+type AnyFunction = (...args: any[]) => any
+type Snapshot<T> = T extends AnyFunction
+  ? T
+  : T extends AsRef
+  ? T
+  : T extends Promise<infer V>
+  ? Snapshot<V>
+  : {
+      readonly [K in keyof T]: Snapshot<T[K]>
+    }
+
+export const snapshot = <T extends object>(proxyObject: T): Snapshot<T> => {
   if (
     typeof process === 'object' &&
     process.env.NODE_ENV !== 'production' &&
diff --git a/yarn.lock b/yarn.lock
index b1d0987..f01f686 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -528,7 +528,7 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.14.5"
 
-"@babel/plugin-syntax-typescript@^7.7.2":
+"@babel/plugin-syntax-typescript@^7.16.7", "@babel/plugin-syntax-typescript@^7.7.2":
   version "7.16.7"
   resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz#39c9b55ee153151990fb038651d58d3fd03f98f8"
   integrity sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==
@@ -781,6 +781,15 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.16.7"
 
+"@babel/plugin-transform-typescript@^7.16.8":
+  version "7.16.8"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz#591ce9b6b83504903fa9dd3652c357c2ba7a1ee0"
+  integrity sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ==
+  dependencies:
+    "@babel/helper-create-class-features-plugin" "^7.16.7"
+    "@babel/helper-plugin-utils" "^7.16.7"
+    "@babel/plugin-syntax-typescript" "^7.16.7"
+
 "@babel/plugin-transform-unicode-escapes@^7.16.7":
   version "7.16.7"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz#da8717de7b3287a2c6d659750c964f302b31ece3"


Test patch:
diff --git a/tests/proxyMap.test.tsx b/tests/proxyMap.test.tsx
new file mode 100644
index 0000000..23717f4
--- /dev/null
+++ b/tests/proxyMap.test.tsx
@@ -0,0 +1,331 @@
+import { StrictMode } from 'react'
+import { fireEvent, render, waitFor } from '@testing-library/react'
+import { proxy, useSnapshot } from 'valtio'
+import { proxyMap, proxySet } from 'valtio/utils'
+
+const consoleError = console.error
+beforeEach(() => {
+  console.error = jest.fn((message) => {
+    if (
+      process.env.NODE_ENV === 'production' &&
+      message.startsWith('act(...) is not supported in production')
+    ) {
+      return
+    }
+
+    consoleError(message)
+  })
+})
+afterEach(() => {
+  console.error = consoleError
+})
+
+const initialValues = [
+  {
+    name: 'number',
+    value: [[-10, 1]],
+  },
+  {
+    name: 'string',
+    value: [['hello', 'world']],
+  },
+  {
+    name: 'Symbol',
+    value: [[Symbol(), Symbol()]],
+  },
+  {
+    name: 'boolean',
+    value: [[false, true]],
+  },
+  {
+    name: 'array',
+    value: [
+      [
+        [10, 'hello'],
+        [1, 2, 3, 'x', 'w'],
+      ],
+    ],
+  },
+  {
+    name: 'object',
+    value: [[{}, { id: 'somehting', field: null }]],
+  },
+  {
+    name: 'null',
+    value: [[null, [null, 1]]],
+  },
+  {
+    name: 'function',
+    value: [[() => {}, () => {}]],
+  },
+  {
+    name: 'array buffer',
+    value: [[new ArrayBuffer(8), new ArrayBuffer(8)]],
+  },
+  {
+    name: 'Set',
+    value: [[new Set(), new Set(['x', 'y', 'z'])]],
+  },
+  {
+    name: 'Map',
+    value: [[new Map(), new Map([['key', 'value']])]],
+  },
+  {
+    name: 'proxySet',
+    value: [[proxySet([{}]), proxySet([{}, Symbol()])]],
+  },
+]
+
+const inputValues = [
+  {
+    name: 'array',
+    value: [1, 'hello'],
+  },
+  {
+    name: 'nested array',
+    value: [[1, 'hello']],
+  },
+  {
+    name: 'Map',
+    value: new Map<any, any>([
+      ['key1', 'value1'],
+      [{}, 'value2'],
+    ]),
+  },
+  {
+    name: 'boolean',
+    value: false,
+  },
+  {
+    name: 'number',
+    value: 123,
+  },
+  {
+    name: 'string',
+    value: 'hello',
+  },
+  {
+    name: 'Set',
+    value: new Set([1, 2, 3]),
+  },
+  {
+    name: 'proxySet',
+    value: proxySet([1, {}, null, 'xyz', Symbol()]),
+  },
+  {
+    name: 'object',
+    value: { id: Symbol(), field: 'field', bool: true, null: null },
+  },
+]
+
+describe('features parity with native Map', () => {
+  initialValues.forEach(({ name, value }) => {
+    it(`Support Map operations on ${name}`, () => {
+      const map = proxyMap(value as any)
+      const nativeMap = new Map(value as any)
+
+      // check for Symbol.toStringTag / toString
+      expect(`${map}`).toBe(`${nativeMap}`)
+
+      const expectOutputToMatch = () => {
+        expect(map.size).toStrictEqual(nativeMap.size)
+        expect(Array.from(map.values())).toStrictEqual(
+          Array.from(nativeMap.values())
+        )
+        expect(Array.from(map.keys())).toStrictEqual(
+          Array.from(nativeMap.keys())
+        )
+        expect(Array.from(map.entries())).toStrictEqual(
+          Array.from(nativeMap.entries())
+        )
+        expect(JSON.stringify(map)).toStrictEqual(JSON.stringify(nativeMap))
+
+        // cover loops
+        const handleForEach = jest.fn()
+        const handleForOf = jest.fn()
+
+        map.forEach(handleForEach)
+        expect(handleForEach).toHaveBeenCalledTimes(map.size)
+
+        for (const _ of map) {
+          handleForOf()
+        }
+
+        expect(handleForOf).toHaveBeenCalledTimes(map.size)
+      }
+
+      expectOutputToMatch()
+
+      const [firstElementFromMap] = map
+      const [firstElementFromNativeMap] = nativeMap
+
+      // Bypass Forbidden non-null assertion
+      const keyFromMap = firstElementFromMap && firstElementFromMap[0]
+      const keyFromNativeMap =
+        firstElementFromNativeMap && firstElementFromNativeMap[0]
+
+      expect(map.has(keyFromMap)).toBe(nativeMap.has(keyFromNativeMap))
+
+      map.delete(keyFromMap)
+      nativeMap.delete(keyFromNativeMap)
+      expectOutputToMatch()
+
+      map.set('newKey', {})
+      nativeMap.set('newKey', {})
+      expectOutputToMatch()
+
+      // test value replacement
+      map.set('newKey', 'newValue')
+      nativeMap.set('newKey', 'newValue')
+      expectOutputToMatch()
+
+      // test getter
+      expect(map.get('newKey')).toBe(nativeMap.get('newKey'))
+    })
+  })
+
+  it('support initialization with null', () => {
+    const map = proxyMap(null)
+    const nativeMap = new Map(null)
+
+    expect(map.size).toStrictEqual(nativeMap.size)
+    expect(Array.from(map.values())).toStrictEqual(
+      Array.from(nativeMap.values())
+    )
+    expect(Array.from(map.keys())).toStrictEqual(Array.from(nativeMap.keys()))
+    expect(Array.from(map.entries())).toStrictEqual(
+      Array.from(nativeMap.entries())
+    )
+  })
+})
+
+describe('clear map', () => {
+  initialValues.forEach(({ name, value }) => {
+    it(`clear proxyMap of ${name}`, async () => {
+      const state = proxyMap(value as any)
+
+      const TestComponent = () => {
+        const snap = useSnapshot(state)
+
+        return (
+          <>
+            <div>size: {snap.size}</div>
+            <button onClick={() => state.clear()}>button</button>
+          </>
+        )
+      }
+
+      const { getByText } = render(
+        <StrictMode>
+          <TestComponent />
+        </StrictMode>
+      )
+
+      expect(state.size).toBeGreaterThan(0)
+      getByText(`size: ${state.size}`)
+
+      fireEvent.click(getByText('button'))
+      await waitFor(() => {
+        getByText('size: 0')
+      })
+    })
+  })
+})
+
+describe('add value', () => {
+  inputValues.forEach(({ name, value }) => {
+    it(`update size when adding ${name}`, async () => {
+      const state = proxy({
+        map: proxyMap(),
+      })
+
+      const TestComponent = () => {
+        const snap = useSnapshot(state)
+
+        return (
+          <>
+            <div>size: {snap.map.size}</div>
+            <button onClick={() => state.map.set(value, value)}>button</button>
+          </>
+        )
+      }
+
+      const { getByText } = render(
+        <StrictMode>
+          <TestComponent />
+        </StrictMode>
+      )
+
+      getByText('size: 0')
+      fireEvent.click(getByText('button'))
+
+      await waitFor(() => {
+        getByText('size: 1')
+      })
+    })
+  })
+})
+
+describe('delete', () => {
+  inputValues.forEach(({ name, value }) => {
+    it(`return false when trying to delete non-existing value of type ${name}`, () => {
+      const set = proxyMap()
+
+      expect(set.delete(value)).toBe(false)
+    })
+  })
+
+  initialValues.forEach(({ name, value }) => {
+    it(`support delete on key of type ${name}`, async () => {
+      const state = proxy({
+        map: proxyMap(value as any),
+      })
+
+      // pick a random value from the set
+      const [firstValue] = state.map
+
+      // Bypass Forbidden non-null assertion
+      const firstKey = firstValue && firstValue[0]
+
+      const TestComponent = () => {
+        const snap = useSnapshot(state)
+
+        return (
+          <>
+            <div>size: {snap.map.size}</div>
+            <button onClick={() => state.map.delete(firstKey)}>button</button>
+          </>
+        )
+      }
+
+      const { getByText } = render(
+        <StrictMode>
+          <TestComponent />
+        </StrictMode>
+      )
+
+      getByText(`size: ${state.map.size}`)
+
+      const expectedSizeAfterDelete =
+        state.map.size > 1 ? state.map.size - 1 : 0
+
+      fireEvent.click(getByText('button'))
+      await waitFor(() => {
+        getByText(`size: ${expectedSizeAfterDelete}`)
+      })
+    })
+  })
+})
+
+describe('proxyMap internal', () => {
+  it('should be sealed', () => {
+    expect(Object.isSealed(proxySet())).toBe(true)
+  })
+
+  it('should list only enumerable properties', () => {
+    const notEnumerableProps = ['data', 'size', 'toJSON']
+    expect(
+      Object.keys(proxyMap()).some((k) => notEnumerableProps.includes(k))
+    ).toBe(false)
+  })
+})