From 196a067a429d0ea892ac66ac03ee2462104036f7 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Fri, 24 Mar 2023 06:05:44 -0400 Subject: [PATCH] fix: handle cloning proxied classes w/ enumerable getters (#3026) --- packages/utils/src/helpers.ts | 22 +++++++++++++++++-- test/core/test/utils.spec.ts | 41 +++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/packages/utils/src/helpers.ts b/packages/utils/src/helpers.ts index ed1636fc148c..eaa68d03b3af 100644 --- a/packages/utils/src/helpers.ts +++ b/packages/utils/src/helpers.ts @@ -86,8 +86,26 @@ export function clone(val: T, seen: WeakMap): T { seen.set(val, out) // we don't need properties from prototype const props = getOwnProperties(val) - for (const k of props) - out[k] = clone((val as any)[k], seen) + for (const k of props) { + const descriptor = Object.getOwnPropertyDescriptor(val, k) + if (!descriptor) + continue + const cloned = clone((val as any)[k], seen) + if ('get' in descriptor) { + Object.defineProperty(out, k, { + ...descriptor, + get() { + return cloned + }, + }) + } + else { + Object.defineProperty(out, k, { + ...descriptor, + value: cloned, + }) + } + } return out } diff --git a/test/core/test/utils.spec.ts b/test/core/test/utils.spec.ts index 5e9fdc5a6251..49ae5b3317d4 100644 --- a/test/core/test/utils.spec.ts +++ b/test/core/test/utils.spec.ts @@ -149,6 +149,47 @@ describe('deepClone', () => { objD.ref = objD expect(deepClone(objD)).toEqual(objD) }) + + test('can clone classes with proxied enumerable getters', () => { + const obj = Symbol.for('aClass') + interface TestShape { a: number; b: string } + class A { + [obj]: TestShape + constructor(data: TestShape) { + this[obj] = data + return new Proxy(this, { + ownKeys() { + return Reflect.ownKeys(data) + }, + getOwnPropertyDescriptor(target, p) { + return { + ...Reflect.getOwnPropertyDescriptor(data, p), + enumerable: true, + } + }, + }) + } + + get a() { + return this[obj].a + } + + get b() { + return this[obj].b + } + } + const shape = { a: 1 } as TestShape + Object.defineProperty(shape, 'b', { + configurable: true, + enumerable: true, + get: () => 'B', + }) + const aClass = new A(shape) + expect(aClass.a).toEqual(1) + expect(aClass.b).toEqual('B') + expect(Object.keys(aClass)).toEqual(['a', 'b']) + expect(deepClone({ aClass })).toEqual({ aClass: new A({ a: 1, b: 'B' }) }) + }) }) describe('resetModules doesn\'t resets only user modules', () => {