Skip to content

Commit

Permalink
Strengthen nested update counter test coverage (#15166)
Browse files Browse the repository at this point in the history
* Isolate ReactUpdates-test cases

This ensures their behavior is consistent when run in isolation, and that they actually test the cases they're describing.

* Add coverage for cases where we reset nestedUpdateCounter

These cases explicitly verify that we reset the counter in right places.

* Add a mutually recursive test case

* Add test coverage for useLayoutEffect loop
  • Loading branch information
gaearon committed Mar 21, 2019
1 parent 66f280c commit 3151813
Showing 1 changed file with 123 additions and 0 deletions.
123 changes: 123 additions & 0 deletions packages/react-dom/src/__tests__/ReactUpdates-test.js
Expand Up @@ -15,6 +15,7 @@ let ReactTestUtils;

describe('ReactUpdates', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
ReactTestUtils = require('react-dom/test-utils');
Expand Down Expand Up @@ -1311,6 +1312,46 @@ describe('ReactUpdates', () => {
ReactDOM.render(<Foo />, container);
});

it('resets the update counter for unrelated updates', () => {
const container = document.createElement('div');
const ref = React.createRef();

class EventuallyTerminating extends React.Component {
state = {step: 0};
componentDidMount() {
this.setState({step: 1});
}
componentDidUpdate() {
if (this.state.step < limit) {
this.setState({step: this.state.step + 1});
}
}
render() {
return this.state.step;
}
}

let limit = 55;
expect(() => {
ReactDOM.render(<EventuallyTerminating ref={ref} />, container);
}).toThrow('Maximum');

// Verify that we don't go over the limit if these updates are unrelated.
limit -= 10;
ReactDOM.render(<EventuallyTerminating ref={ref} />, container);
expect(container.textContent).toBe(limit.toString());
ref.current.setState({step: 0});
expect(container.textContent).toBe(limit.toString());
ref.current.setState({step: 0});
expect(container.textContent).toBe(limit.toString());

limit += 10;
expect(() => {
ref.current.setState({step: 0});
}).toThrow('Maximum');
expect(ref.current).toBe(null);
});

it('does not fall into an infinite update loop', () => {
class NonTerminating extends React.Component {
state = {step: 0};
Expand All @@ -1336,6 +1377,88 @@ describe('ReactUpdates', () => {
}).toThrow('Maximum');
});

it('does not fall into an infinite update loop with useLayoutEffect', () => {
function NonTerminating() {
const [step, setStep] = React.useState(0);
React.useLayoutEffect(() => {
setStep(x => x + 1);
});
return step;
}

const container = document.createElement('div');
expect(() => {
ReactDOM.render(<NonTerminating />, container);
}).toThrow('Maximum');
});

it('can recover after falling into an infinite update loop', () => {
class NonTerminating extends React.Component {
state = {step: 0};
componentDidMount() {
this.setState({step: 1});
}
componentDidUpdate() {
this.setState({step: 2});
}
render() {
return this.state.step;
}
}

class Terminating extends React.Component {
state = {step: 0};
componentDidMount() {
this.setState({step: 1});
}
render() {
return this.state.step;
}
}

const container = document.createElement('div');
expect(() => {
ReactDOM.render(<NonTerminating />, container);
}).toThrow('Maximum');

ReactDOM.render(<Terminating />, container);
expect(container.textContent).toBe('1');

expect(() => {
ReactDOM.render(<NonTerminating />, container);
}).toThrow('Maximum');

ReactDOM.render(<Terminating />, container);
expect(container.textContent).toBe('1');
});

it('does not fall into mutually recursive infinite update loop with same container', () => {
// Note: this test would fail if there were two or more different roots.

class A extends React.Component {
componentDidMount() {
ReactDOM.render(<B />, container);
}
render() {
return null;
}
}

class B extends React.Component {
componentDidMount() {
ReactDOM.render(<A />, container);
}
render() {
return null;
}
}

const container = document.createElement('div');
expect(() => {
ReactDOM.render(<A />, container);
}).toThrow('Maximum');
});

it('does not fall into an infinite error loop', () => {
function BadRender() {
throw new Error('error');
Expand Down

0 comments on commit 3151813

Please sign in to comment.