Skip to content

Commit e18bb65

Browse files
committed
[enzyme-adapter-react-{16,16.1,16.2}] [Fix] ensure that this.state starts out null when unspecified on a custom component
Fixes #1849. This bug in react's shallow renderer was fixed in facebook/react#11965, and only exists in React v16.0 - v16.2.
1 parent 6d1a498 commit e18bb65

File tree

5 files changed

+166
-0
lines changed

5 files changed

+166
-0
lines changed

packages/enzyme-adapter-react-16.1/src/ReactSixteenOneAdapter.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,22 @@ function nodeToHostNode(_node) {
210210

211211
const eventOptions = { animation: true };
212212

213+
function getEmptyStateValue() {
214+
// this handles a bug in React 16.0 - 16.2
215+
// see https://github.com/facebook/react/commit/39be83565c65f9c522150e52375167568a2a1459
216+
// also see https://github.com/facebook/react/pull/11965
217+
218+
// eslint-disable-next-line react/prefer-stateless-function
219+
class EmptyState extends React.Component {
220+
render() {
221+
return null;
222+
}
223+
}
224+
const testRenderer = new ShallowRenderer();
225+
testRenderer.render(React.createElement(EmptyState));
226+
return testRenderer._instance.state;
227+
}
228+
213229
class ReactSixteenOneAdapter extends EnzymeAdapter {
214230
constructor() {
215231
super();
@@ -319,6 +335,30 @@ class ReactSixteenOneAdapter extends EnzymeAdapter {
319335
);
320336
return withSetStateAllowed(() => renderer.render({ ...el, type: wrappedEl }, context));
321337
}
338+
if (isStateful) {
339+
// fix react bug; see implementation of `getEmptyStateValue`
340+
const emptyStateValue = getEmptyStateValue();
341+
if (emptyStateValue) {
342+
Object.defineProperty(Component.prototype, 'state', {
343+
configurable: true,
344+
enumerable: true,
345+
get() {
346+
return null;
347+
},
348+
set(value) {
349+
if (value !== emptyStateValue) {
350+
Object.defineProperty(this, 'state', {
351+
configurable: true,
352+
enumerable: true,
353+
value,
354+
writable: true,
355+
});
356+
}
357+
return true;
358+
},
359+
});
360+
}
361+
}
322362
return withSetStateAllowed(() => renderer.render(el, context));
323363
}
324364
},

packages/enzyme-adapter-react-16.2/src/ReactSixteenTwoAdapter.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,22 @@ function nodeToHostNode(_node) {
211211

212212
const eventOptions = { animation: true };
213213

214+
function getEmptyStateValue() {
215+
// this handles a bug in React 16.0 - 16.2
216+
// see https://github.com/facebook/react/commit/39be83565c65f9c522150e52375167568a2a1459
217+
// also see https://github.com/facebook/react/pull/11965
218+
219+
// eslint-disable-next-line react/prefer-stateless-function
220+
class EmptyState extends React.Component {
221+
render() {
222+
return null;
223+
}
224+
}
225+
const testRenderer = new ShallowRenderer();
226+
testRenderer.render(React.createElement(EmptyState));
227+
return testRenderer._instance.state;
228+
}
229+
214230
class ReactSixteenTwoAdapter extends EnzymeAdapter {
215231
constructor() {
216232
super();
@@ -321,6 +337,30 @@ class ReactSixteenTwoAdapter extends EnzymeAdapter {
321337
);
322338
return withSetStateAllowed(() => renderer.render({ ...el, type: wrappedEl }, context));
323339
}
340+
if (isStateful) {
341+
// fix react bug; see implementation of `getEmptyStateValue`
342+
const emptyStateValue = getEmptyStateValue();
343+
if (emptyStateValue) {
344+
Object.defineProperty(Component.prototype, 'state', {
345+
configurable: true,
346+
enumerable: true,
347+
get() {
348+
return null;
349+
},
350+
set(value) {
351+
if (value !== emptyStateValue) {
352+
Object.defineProperty(this, 'state', {
353+
configurable: true,
354+
enumerable: true,
355+
value,
356+
writable: true,
357+
});
358+
}
359+
return true;
360+
},
361+
});
362+
}
363+
}
324364
return withSetStateAllowed(() => renderer.render(el, context));
325365
}
326366
},

packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,22 @@ const eventOptions = {
226226
auxClick: !!TestUtils.Simulate.auxClick, // 16.5+
227227
};
228228

229+
function getEmptyStateValue() {
230+
// this handles a bug in React 16.0 - 16.2
231+
// see https://github.com/facebook/react/commit/39be83565c65f9c522150e52375167568a2a1459
232+
// also see https://github.com/facebook/react/pull/11965
233+
234+
// eslint-disable-next-line react/prefer-stateless-function
235+
class EmptyState extends React.Component {
236+
render() {
237+
return null;
238+
}
239+
}
240+
const testRenderer = new ShallowRenderer();
241+
testRenderer.render(React.createElement(EmptyState));
242+
return testRenderer._instance.state;
243+
}
244+
229245
class ReactSixteenAdapter extends EnzymeAdapter {
230246
constructor() {
231247
super();
@@ -341,6 +357,30 @@ class ReactSixteenAdapter extends EnzymeAdapter {
341357
);
342358
return withSetStateAllowed(() => renderer.render({ ...el, type: wrappedEl }, context));
343359
}
360+
if (isStateful) {
361+
// fix react bug; see implementation of `getEmptyStateValue`
362+
const emptyStateValue = getEmptyStateValue();
363+
if (emptyStateValue) {
364+
Object.defineProperty(Component.prototype, 'state', {
365+
configurable: true,
366+
enumerable: true,
367+
get() {
368+
return null;
369+
},
370+
set(value) {
371+
if (value !== emptyStateValue) {
372+
Object.defineProperty(this, 'state', {
373+
configurable: true,
374+
enumerable: true,
375+
value,
376+
writable: true,
377+
});
378+
}
379+
return true;
380+
},
381+
});
382+
}
383+
}
344384
return withSetStateAllowed(() => renderer.render(el, context));
345385
}
346386
},

packages/enzyme-test-suite/test/ReactWrapper-spec.jsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,30 @@ describeWithDOM('mount', () => {
139139
expect(() => mount(<Foo />)).not.to.throw();
140140
});
141141

142+
it('starts out with undefined state', () => {
143+
class Foo extends React.Component {
144+
render() {
145+
return (
146+
<div>
147+
{typeof this.state}
148+
{JSON.stringify(this.state)}
149+
</div>
150+
);
151+
}
152+
}
153+
154+
const wrapper = mount(<Foo />);
155+
expect(wrapper.state()).to.equal(null);
156+
expect(wrapper.debug()).to.equal(`
157+
<Foo>
158+
<div>
159+
object
160+
null
161+
</div>
162+
</Foo>
163+
`.trim());
164+
});
165+
142166
describeIf(is('>= 16.3'), 'uses the isValidElementType from the Adapter to validate the prop type of Component', () => {
143167
const Foo = () => null;
144168
const Bar = () => null;

packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,28 @@ describe('shallow', () => {
132132

133133
expect(() => shallow(<Foo />)).not.to.throw();
134134
});
135+
136+
it('starts out with undefined state', () => {
137+
class Foo extends React.Component {
138+
render() {
139+
return (
140+
<div>
141+
{typeof this.state}
142+
{JSON.stringify(this.state)}
143+
</div>
144+
);
145+
}
146+
}
147+
148+
const wrapper = shallow(<Foo />);
149+
expect(wrapper.state()).to.equal(null);
150+
expect(wrapper.debug()).to.equal(`
151+
<div>
152+
object
153+
null
154+
</div>
155+
`.trim());
156+
});
135157
});
136158

137159
describe('context', () => {

0 commit comments

Comments
 (0)