Skip to content

Commit a84dcd0

Browse files
committed
fix: Proxy should keep methods own props. #918
This includes name, arity, toString and everything else possible stored among original method.
1 parent 1ec9ec2 commit a84dcd0

File tree

3 files changed

+83
-4
lines changed

3 files changed

+83
-4
lines changed

src/proxy/createClassProxy.js

+30-4
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,32 @@ const setSFPFlag = (component, flag) =>
6565
value: flag,
6666
})
6767

68+
const copyMethodDescriptors = (target, source) => {
69+
if (source) {
70+
// it is possible to use `function-double` to construct an ideal clone, but does not make a sence
71+
const keys = Object.getOwnPropertyNames(source)
72+
73+
keys.forEach(key =>
74+
safeDefineProperty(
75+
target,
76+
key,
77+
Object.getOwnPropertyDescriptor(source, key),
78+
),
79+
)
80+
81+
safeDefineProperty(target, 'toString', {
82+
configurable: true,
83+
writable: false,
84+
enumerable: false,
85+
value: function toString() {
86+
return String(source)
87+
},
88+
})
89+
}
90+
91+
return target
92+
}
93+
6894
function createClassProxy(InitialComponent, proxyKey, options) {
6995
const renderOptions = {
7096
...defaultRenderOptions,
@@ -103,21 +129,21 @@ function createClassProxy(InitialComponent, proxyKey, options) {
103129
}
104130

105131
function lifeCycleWrapperFactory(wrapperName, sideEffect = identity) {
106-
return function wrappedMethod(...rest) {
132+
return copyMethodDescriptors(function wrappedMethod(...rest) {
107133
proxiedUpdate.call(this)
108134
sideEffect(this)
109135
return (
110136
!isFunctionalComponent &&
111137
CurrentComponent.prototype[wrapperName] &&
112138
CurrentComponent.prototype[wrapperName].apply(this, rest)
113139
)
114-
}
140+
}, InitialComponent.prototype && InitialComponent.prototype[wrapperName])
115141
}
116142

117143
function methodWrapperFactory(wrapperName, realMethod) {
118-
return function wrappedMethod(...rest) {
144+
return copyMethodDescriptors(function wrappedMethod(...rest) {
119145
return realMethod.apply(this, rest)
120-
}
146+
}, realMethod)
121147
}
122148

123149
const fakeBasePrototype = Base =>

test/AppContainer.dev.test.js

+24
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React, { Component } from 'react'
33
import createReactClass from 'create-react-class'
44
import { mount } from 'enzyme'
55
import { mapProps } from 'recompose'
6+
import { polyfill } from 'react-lifecycles-compat'
67
import { AppContainer } from '../src/index.dev'
78
import RHL from '../src/reactHotLoader'
89
import { increment as incrementGeneration } from '../src/global/generation'
@@ -1398,6 +1399,7 @@ describe(`AppContainer (dev)`, () => {
13981399
return <div>I AM NEW CHILD</div>
13991400
}
14001401
}
1402+
14011403
RHL.register(App, 'App3', 'test.js')
14021404
wrapper.setProps({ children: <App /> })
14031405
expect(spy).not.toHaveBeenCalled()
@@ -1946,5 +1948,27 @@ describe(`AppContainer (dev)`, () => {
19461948

19471949
expect(wrapper.text()).toBe('new render + old state + 20')
19481950
})
1951+
1952+
it('should work with react-lifecycle-compact', () => {
1953+
class Component extends React.Component {
1954+
static getDerivedStateFromProps() {
1955+
return {}
1956+
}
1957+
}
1958+
1959+
/* eslint-disable no-underscore-dangle */
1960+
polyfill(Component)
1961+
expect(
1962+
Component.prototype.componentWillReceiveProps
1963+
.__suppressDeprecationWarning,
1964+
).toBe(true)
1965+
1966+
const Proxy = <Component />.type
1967+
expect(
1968+
Proxy.prototype.componentWillReceiveProps.__suppressDeprecationWarning,
1969+
).toBe(true)
1970+
1971+
/* eslint-enable */
1972+
})
19491973
})
19501974
})

test/proxy/instance-method.test.js

+29
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,33 @@ describe('instance method', () => {
138138
})
139139
})
140140
})
141+
142+
it('passes methods props thought', () => {
143+
const injectedMethod = (a, b) => this[24 + a + b]
144+
145+
injectedMethod.staticProp = 'magic'
146+
147+
class App extends React.Component {
148+
method() {
149+
return 42
150+
}
151+
}
152+
153+
App.prototype.injectedMethod = injectedMethod
154+
155+
const app1 = new App()
156+
157+
expect(app1.injectedMethod).toBe(injectedMethod)
158+
expect(app1.injectedMethod.staticProp).toBe('magic')
159+
expect(String(app1.injectedMethod)).toBe(String(injectedMethod))
160+
161+
const Proxy = createProxy(App).get()
162+
163+
const app2 = new Proxy()
164+
165+
expect(app2.injectedMethod).not.toBe(injectedMethod)
166+
expect(app2.injectedMethod.staticProp).toBe('magic')
167+
expect(app2.injectedMethod.length).toBe(2)
168+
expect(String(app2.injectedMethod)).toBe(String(injectedMethod))
169+
})
141170
})

0 commit comments

Comments
 (0)