Skip to content

Commit 196a067

Browse files
authoredMar 24, 2023
fix: handle cloning proxied classes w/ enumerable getters (#3026)
1 parent 6efe61a commit 196a067

File tree

2 files changed

+61
-2
lines changed

2 files changed

+61
-2
lines changed
 

‎packages/utils/src/helpers.ts

+20-2
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,26 @@ export function clone<T>(val: T, seen: WeakMap<any, any>): T {
8686
seen.set(val, out)
8787
// we don't need properties from prototype
8888
const props = getOwnProperties(val)
89-
for (const k of props)
90-
out[k] = clone((val as any)[k], seen)
89+
for (const k of props) {
90+
const descriptor = Object.getOwnPropertyDescriptor(val, k)
91+
if (!descriptor)
92+
continue
93+
const cloned = clone((val as any)[k], seen)
94+
if ('get' in descriptor) {
95+
Object.defineProperty(out, k, {
96+
...descriptor,
97+
get() {
98+
return cloned
99+
},
100+
})
101+
}
102+
else {
103+
Object.defineProperty(out, k, {
104+
...descriptor,
105+
value: cloned,
106+
})
107+
}
108+
}
91109
return out
92110
}
93111

‎test/core/test/utils.spec.ts

+41
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,47 @@ describe('deepClone', () => {
149149
objD.ref = objD
150150
expect(deepClone(objD)).toEqual(objD)
151151
})
152+
153+
test('can clone classes with proxied enumerable getters', () => {
154+
const obj = Symbol.for('aClass')
155+
interface TestShape { a: number; b: string }
156+
class A {
157+
[obj]: TestShape
158+
constructor(data: TestShape) {
159+
this[obj] = data
160+
return new Proxy(this, {
161+
ownKeys() {
162+
return Reflect.ownKeys(data)
163+
},
164+
getOwnPropertyDescriptor(target, p) {
165+
return {
166+
...Reflect.getOwnPropertyDescriptor(data, p),
167+
enumerable: true,
168+
}
169+
},
170+
})
171+
}
172+
173+
get a() {
174+
return this[obj].a
175+
}
176+
177+
get b() {
178+
return this[obj].b
179+
}
180+
}
181+
const shape = { a: 1 } as TestShape
182+
Object.defineProperty(shape, 'b', {
183+
configurable: true,
184+
enumerable: true,
185+
get: () => 'B',
186+
})
187+
const aClass = new A(shape)
188+
expect(aClass.a).toEqual(1)
189+
expect(aClass.b).toEqual('B')
190+
expect(Object.keys(aClass)).toEqual(['a', 'b'])
191+
expect(deepClone({ aClass })).toEqual({ aClass: new A({ a: 1, b: 'B' }) })
192+
})
152193
})
153194

154195
describe('resetModules doesn\'t resets only user modules', () => {

0 commit comments

Comments
 (0)
Please sign in to comment.