Skip to content

Commit

Permalink
[enzyme-adapter-react-{16,16.1,16.2}] [Fix] ensure that this.state
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
ljharb committed Oct 2, 2018
1 parent 6d1a498 commit e18bb65
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 0 deletions.
40 changes: 40 additions & 0 deletions packages/enzyme-adapter-react-16.1/src/ReactSixteenOneAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,22 @@ function nodeToHostNode(_node) {

const eventOptions = { animation: true };

function getEmptyStateValue() {
// this handles a bug in React 16.0 - 16.2
// see https://github.com/facebook/react/commit/39be83565c65f9c522150e52375167568a2a1459
// also see https://github.com/facebook/react/pull/11965

// eslint-disable-next-line react/prefer-stateless-function
class EmptyState extends React.Component {
render() {
return null;
}
}
const testRenderer = new ShallowRenderer();
testRenderer.render(React.createElement(EmptyState));
return testRenderer._instance.state;
}

class ReactSixteenOneAdapter extends EnzymeAdapter {
constructor() {
super();
Expand Down Expand Up @@ -319,6 +335,30 @@ class ReactSixteenOneAdapter extends EnzymeAdapter {
);
return withSetStateAllowed(() => renderer.render({ ...el, type: wrappedEl }, context));
}
if (isStateful) {
// fix react bug; see implementation of `getEmptyStateValue`
const emptyStateValue = getEmptyStateValue();
if (emptyStateValue) {
Object.defineProperty(Component.prototype, 'state', {
configurable: true,
enumerable: true,
get() {
return null;
},
set(value) {
if (value !== emptyStateValue) {
Object.defineProperty(this, 'state', {
configurable: true,
enumerable: true,
value,
writable: true,
});
}
return true;
},
});
}
}
return withSetStateAllowed(() => renderer.render(el, context));
}
},
Expand Down
40 changes: 40 additions & 0 deletions packages/enzyme-adapter-react-16.2/src/ReactSixteenTwoAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,22 @@ function nodeToHostNode(_node) {

const eventOptions = { animation: true };

function getEmptyStateValue() {
// this handles a bug in React 16.0 - 16.2
// see https://github.com/facebook/react/commit/39be83565c65f9c522150e52375167568a2a1459
// also see https://github.com/facebook/react/pull/11965

// eslint-disable-next-line react/prefer-stateless-function
class EmptyState extends React.Component {
render() {
return null;
}
}
const testRenderer = new ShallowRenderer();
testRenderer.render(React.createElement(EmptyState));
return testRenderer._instance.state;
}

class ReactSixteenTwoAdapter extends EnzymeAdapter {
constructor() {
super();
Expand Down Expand Up @@ -321,6 +337,30 @@ class ReactSixteenTwoAdapter extends EnzymeAdapter {
);
return withSetStateAllowed(() => renderer.render({ ...el, type: wrappedEl }, context));
}
if (isStateful) {
// fix react bug; see implementation of `getEmptyStateValue`
const emptyStateValue = getEmptyStateValue();
if (emptyStateValue) {
Object.defineProperty(Component.prototype, 'state', {
configurable: true,
enumerable: true,
get() {
return null;
},
set(value) {
if (value !== emptyStateValue) {
Object.defineProperty(this, 'state', {
configurable: true,
enumerable: true,
value,
writable: true,
});
}
return true;
},
});
}
}
return withSetStateAllowed(() => renderer.render(el, context));
}
},
Expand Down
40 changes: 40 additions & 0 deletions packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,22 @@ const eventOptions = {
auxClick: !!TestUtils.Simulate.auxClick, // 16.5+
};

function getEmptyStateValue() {
// this handles a bug in React 16.0 - 16.2
// see https://github.com/facebook/react/commit/39be83565c65f9c522150e52375167568a2a1459
// also see https://github.com/facebook/react/pull/11965

// eslint-disable-next-line react/prefer-stateless-function
class EmptyState extends React.Component {
render() {
return null;
}
}
const testRenderer = new ShallowRenderer();
testRenderer.render(React.createElement(EmptyState));
return testRenderer._instance.state;
}

class ReactSixteenAdapter extends EnzymeAdapter {
constructor() {
super();
Expand Down Expand Up @@ -341,6 +357,30 @@ class ReactSixteenAdapter extends EnzymeAdapter {
);
return withSetStateAllowed(() => renderer.render({ ...el, type: wrappedEl }, context));
}
if (isStateful) {
// fix react bug; see implementation of `getEmptyStateValue`
const emptyStateValue = getEmptyStateValue();
if (emptyStateValue) {
Object.defineProperty(Component.prototype, 'state', {
configurable: true,
enumerable: true,
get() {
return null;
},
set(value) {
if (value !== emptyStateValue) {
Object.defineProperty(this, 'state', {
configurable: true,
enumerable: true,
value,
writable: true,
});
}
return true;
},
});
}
}
return withSetStateAllowed(() => renderer.render(el, context));
}
},
Expand Down
24 changes: 24 additions & 0 deletions packages/enzyme-test-suite/test/ReactWrapper-spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,30 @@ describeWithDOM('mount', () => {
expect(() => mount(<Foo />)).not.to.throw();
});

it('starts out with undefined state', () => {
class Foo extends React.Component {
render() {
return (
<div>
{typeof this.state}
{JSON.stringify(this.state)}
</div>
);
}
}

const wrapper = mount(<Foo />);
expect(wrapper.state()).to.equal(null);
expect(wrapper.debug()).to.equal(`
<Foo>
<div>
object
null
</div>
</Foo>
`.trim());
});

describeIf(is('>= 16.3'), 'uses the isValidElementType from the Adapter to validate the prop type of Component', () => {
const Foo = () => null;
const Bar = () => null;
Expand Down
22 changes: 22 additions & 0 deletions packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,28 @@ describe('shallow', () => {

expect(() => shallow(<Foo />)).not.to.throw();
});

it('starts out with undefined state', () => {
class Foo extends React.Component {
render() {
return (
<div>
{typeof this.state}
{JSON.stringify(this.state)}
</div>
);
}
}

const wrapper = shallow(<Foo />);
expect(wrapper.state()).to.equal(null);
expect(wrapper.debug()).to.equal(`
<div>
object
null
</div>
`.trim());
});
});

describe('context', () => {
Expand Down

0 comments on commit e18bb65

Please sign in to comment.