mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
Throw if document is missing by the time invokeGuardedCallbackDev runs (#11677)
* Warn if `document` is missing by the time invokeGuardedCallback runs in DEV * Typo * Add a comment * Use invariant() instead * Create event immediately for clarity
This commit is contained in:
@@ -372,4 +372,43 @@ describe('ReactDOM', () => {
|
||||
delete global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
||||
}
|
||||
});
|
||||
|
||||
it('throws in DEV if jsdom is destroyed by the time setState() is called', () => {
|
||||
spyOnDev(console, 'error');
|
||||
class App extends React.Component {
|
||||
state = {x: 1};
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
const container = document.createElement('div');
|
||||
const instance = ReactDOM.render(<App />, container);
|
||||
const documentDescriptor = Object.getOwnPropertyDescriptor(
|
||||
global,
|
||||
'document',
|
||||
);
|
||||
try {
|
||||
// Emulate jsdom environment cleanup.
|
||||
// This is roughly what happens if the test finished and then
|
||||
// an asynchronous callback tried to setState() after this.
|
||||
delete global.document;
|
||||
const fn = () => instance.setState({x: 2});
|
||||
if (__DEV__) {
|
||||
expect(fn).toThrow(
|
||||
'The `document` global was defined when React was initialized, but is not ' +
|
||||
'defined anymore. This can happen in a test environment if a component ' +
|
||||
'schedules an update from an asynchronous callback, but the test has already ' +
|
||||
'finished running. To solve this, you can either unmount the component at ' +
|
||||
'the end of your test (and ensure that any asynchronous operations get ' +
|
||||
'canceled in `componentWillUnmount`), or you can change the test itself ' +
|
||||
'to be asynchronous.',
|
||||
);
|
||||
} else {
|
||||
expect(fn).not.toThrow();
|
||||
}
|
||||
} finally {
|
||||
// Don't break other tests.
|
||||
Object.defineProperty(global, 'document', documentDescriptor);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -167,6 +167,22 @@ if (__DEV__) {
|
||||
e,
|
||||
f,
|
||||
) {
|
||||
// If document doesn't exist we know for sure we will crash in this method
|
||||
// when we call document.createEvent(). However this can cause confusing
|
||||
// errors: https://github.com/facebookincubator/create-react-app/issues/3482
|
||||
// So we preemptively throw with a better message instead.
|
||||
invariant(
|
||||
typeof document !== 'undefined',
|
||||
'The `document` global was defined when React was initialized, but is not ' +
|
||||
'defined anymore. This can happen in a test environment if a component ' +
|
||||
'schedules an update from an asynchronous callback, but the test has already ' +
|
||||
'finished running. To solve this, you can either unmount the component at ' +
|
||||
'the end of your test (and ensure that any asynchronous operations get ' +
|
||||
'canceled in `componentWillUnmount`), or you can change the test itself ' +
|
||||
'to be asynchronous.',
|
||||
);
|
||||
const evt = document.createEvent('Event');
|
||||
|
||||
// Keeps track of whether the user-provided callback threw an error. We
|
||||
// set this to true at the beginning, then set it to false right after
|
||||
// calling the function. If the function errors, `didError` will never be
|
||||
@@ -222,7 +238,6 @@ if (__DEV__) {
|
||||
|
||||
// Synchronously dispatch our fake event. If the user-provided function
|
||||
// errors, it will trigger our global error handler.
|
||||
const evt = document.createEvent('Event');
|
||||
evt.initEvent(evtType, false, false);
|
||||
fakeNode.dispatchEvent(evt);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user