From a84dcd08edf8ddf578694c60b78550e335de6017 Mon Sep 17 00:00:00 2001 From: Anton Korzunov Date: Fri, 20 Apr 2018 14:22:00 +1000 Subject: [PATCH] fix: Proxy should keep methods own props. #918 This includes name, arity, toString and everything else possible stored among original method. --- src/proxy/createClassProxy.js | 34 ++++++++++++++++++++++++++---- test/AppContainer.dev.test.js | 24 +++++++++++++++++++++ test/proxy/instance-method.test.js | 29 +++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/src/proxy/createClassProxy.js b/src/proxy/createClassProxy.js index a2bc66fe1..037710967 100644 --- a/src/proxy/createClassProxy.js +++ b/src/proxy/createClassProxy.js @@ -65,6 +65,32 @@ const setSFPFlag = (component, flag) => value: flag, }) +const copyMethodDescriptors = (target, source) => { + if (source) { + // it is possible to use `function-double` to construct an ideal clone, but does not make a sence + const keys = Object.getOwnPropertyNames(source) + + keys.forEach(key => + safeDefineProperty( + target, + key, + Object.getOwnPropertyDescriptor(source, key), + ), + ) + + safeDefineProperty(target, 'toString', { + configurable: true, + writable: false, + enumerable: false, + value: function toString() { + return String(source) + }, + }) + } + + return target +} + function createClassProxy(InitialComponent, proxyKey, options) { const renderOptions = { ...defaultRenderOptions, @@ -103,7 +129,7 @@ function createClassProxy(InitialComponent, proxyKey, options) { } function lifeCycleWrapperFactory(wrapperName, sideEffect = identity) { - return function wrappedMethod(...rest) { + return copyMethodDescriptors(function wrappedMethod(...rest) { proxiedUpdate.call(this) sideEffect(this) return ( @@ -111,13 +137,13 @@ function createClassProxy(InitialComponent, proxyKey, options) { CurrentComponent.prototype[wrapperName] && CurrentComponent.prototype[wrapperName].apply(this, rest) ) - } + }, InitialComponent.prototype && InitialComponent.prototype[wrapperName]) } function methodWrapperFactory(wrapperName, realMethod) { - return function wrappedMethod(...rest) { + return copyMethodDescriptors(function wrappedMethod(...rest) { return realMethod.apply(this, rest) - } + }, realMethod) } const fakeBasePrototype = Base => diff --git a/test/AppContainer.dev.test.js b/test/AppContainer.dev.test.js index 44cf6f9f7..26ccd9f2a 100644 --- a/test/AppContainer.dev.test.js +++ b/test/AppContainer.dev.test.js @@ -3,6 +3,7 @@ import React, { Component } from 'react' import createReactClass from 'create-react-class' import { mount } from 'enzyme' import { mapProps } from 'recompose' +import { polyfill } from 'react-lifecycles-compat' import { AppContainer } from '../src/index.dev' import RHL from '../src/reactHotLoader' import { increment as incrementGeneration } from '../src/global/generation' @@ -1398,6 +1399,7 @@ describe(`AppContainer (dev)`, () => { return
I AM NEW CHILD
} } + RHL.register(App, 'App3', 'test.js') wrapper.setProps({ children: }) expect(spy).not.toHaveBeenCalled() @@ -1946,5 +1948,27 @@ describe(`AppContainer (dev)`, () => { expect(wrapper.text()).toBe('new render + old state + 20') }) + + it('should work with react-lifecycle-compact', () => { + class Component extends React.Component { + static getDerivedStateFromProps() { + return {} + } + } + + /* eslint-disable no-underscore-dangle */ + polyfill(Component) + expect( + Component.prototype.componentWillReceiveProps + .__suppressDeprecationWarning, + ).toBe(true) + + const Proxy = .type + expect( + Proxy.prototype.componentWillReceiveProps.__suppressDeprecationWarning, + ).toBe(true) + + /* eslint-enable */ + }) }) }) diff --git a/test/proxy/instance-method.test.js b/test/proxy/instance-method.test.js index 00e237674..a0b02aa80 100644 --- a/test/proxy/instance-method.test.js +++ b/test/proxy/instance-method.test.js @@ -138,4 +138,33 @@ describe('instance method', () => { }) }) }) + + it('passes methods props thought', () => { + const injectedMethod = (a, b) => this[24 + a + b] + + injectedMethod.staticProp = 'magic' + + class App extends React.Component { + method() { + return 42 + } + } + + App.prototype.injectedMethod = injectedMethod + + const app1 = new App() + + expect(app1.injectedMethod).toBe(injectedMethod) + expect(app1.injectedMethod.staticProp).toBe('magic') + expect(String(app1.injectedMethod)).toBe(String(injectedMethod)) + + const Proxy = createProxy(App).get() + + const app2 = new Proxy() + + expect(app2.injectedMethod).not.toBe(injectedMethod) + expect(app2.injectedMethod.staticProp).toBe('magic') + expect(app2.injectedMethod.length).toBe(2) + expect(String(app2.injectedMethod)).toBe(String(injectedMethod)) + }) })