-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[New] add componentDidCatch
support, and simulateError
#1797
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
# `.simulateError(error) => Self` | ||
|
||
Simulate a component throwing an error as part of its rendering lifecycle. | ||
|
||
This is particularly useful in combination with React 16 error boundaries (ie, the `componentDidCatch` lifecycle method). | ||
|
||
|
||
#### Arguments | ||
|
||
1. `error` (`Any`): The error to throw. | ||
|
||
|
||
|
||
#### Returns | ||
|
||
`ReactWrapper`: Returns itself. | ||
|
||
|
||
|
||
#### Example | ||
|
||
```jsx | ||
function Something() { | ||
// this is just a placeholder | ||
return null; | ||
} | ||
|
||
class ErrorBoundary extends React.Component { | ||
componentDidCatch(error, info) { | ||
const { spy } = this.props; | ||
spy(error, info); | ||
} | ||
|
||
render() { | ||
const { children } = this.props; | ||
return ( | ||
<React.Fragment> | ||
{children} | ||
</React.Fragment> | ||
); | ||
} | ||
} | ||
ErrorBoundary.propTypes = { | ||
children: PropTypes.node.isRequired, | ||
spy: PropTypes.func.isRequired, | ||
}; | ||
|
||
const spy = sinon.spy(); | ||
const wrapper = mount(<ErrorBoundary spy={spy}><Something /></ErrorBoundary>); | ||
const error = new Error('hi!'); | ||
wrapper.find(Something).simulateError(error); | ||
|
||
expect(spy).to.have.property('callCount', 1); | ||
expect(spy.args).to.deep.equal([ | ||
error, | ||
{ | ||
componentStack: ` | ||
in Something (created by ErrorBoundary) | ||
in ErrorBoundary (created by WrapperComponent) | ||
in WrapperComponent`, | ||
}, | ||
]); | ||
``` | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
# `.simulateError(error) => Self` | ||
|
||
Simulate a component throwing an error as part of its rendering lifecycle. | ||
|
||
This is particularly useful in combination with React 16 error boundaries (ie, the `componentDidCatch` lifecycle method). | ||
|
||
|
||
#### Arguments | ||
|
||
1. `error` (`Any`): The error to throw. | ||
|
||
|
||
|
||
#### Returns | ||
|
||
`ShallowWrapper`: Returns itself. | ||
|
||
|
||
|
||
#### Example | ||
|
||
```jsx | ||
function Something() { | ||
// this is just a placeholder | ||
return null; | ||
} | ||
|
||
class ErrorBoundary extends React.Component { | ||
componentDidCatch(error, info) { | ||
const { spy } = this.props; | ||
spy(error, info); | ||
} | ||
|
||
render() { | ||
const { children } = this.props; | ||
return ( | ||
<React.Fragment> | ||
{children} | ||
</React.Fragment> | ||
); | ||
} | ||
} | ||
ErrorBoundary.propTypes = { | ||
children: PropTypes.node.isRequired, | ||
spy: PropTypes.func.isRequired, | ||
}; | ||
|
||
const spy = sinon.spy(); | ||
const wrapper = shallow(<ErrorBoundary spy={spy}><Something /></ErrorBoundary>); | ||
const error = new Error('hi!'); | ||
wrapper.find(Something).simulateError(error); | ||
|
||
expect(spy).to.have.property('callCount', 1); | ||
expect(spy.args).to.deep.equal([ | ||
error, | ||
{ | ||
componentStack: ` | ||
in Something (created by ErrorBoundary) | ||
in ErrorBoundary (created by WrapperComponent) | ||
in WrapperComponent`, | ||
}, | ||
]); | ||
``` | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,7 @@ import { | |
createMountWrapper, | ||
propsWithKeysAndRef, | ||
ensureKeyOrUndefined, | ||
simulateError, | ||
} from 'enzyme-adapter-utils'; | ||
import { findCurrentFiberUsingSlowPath } from 'react-reconciler/reflection'; | ||
|
||
|
@@ -262,6 +263,19 @@ class ReactSixteenOneAdapter extends EnzymeAdapter { | |
getNode() { | ||
return instance ? toTree(instance._reactInternalFiber).rendered : null; | ||
}, | ||
simulateError(nodeHierarchy, rootNode, error) { | ||
const { instance: catchingInstance } = nodeHierarchy | ||
.find(x => x.instance && x.instance.componentDidCatch) || {}; | ||
|
||
simulateError( | ||
error, | ||
catchingInstance, | ||
rootNode, | ||
nodeHierarchy, | ||
nodeTypeFromType, | ||
adapter.displayNameOfNode, | ||
); | ||
}, | ||
simulateEvent(node, event, mock) { | ||
const mappedEvent = mapNativeEventNames(event, eventOptions); | ||
const eventFn = TestUtils.Simulate[mappedEvent]; | ||
|
@@ -279,6 +293,7 @@ class ReactSixteenOneAdapter extends EnzymeAdapter { | |
} | ||
|
||
createShallowRenderer(/* options */) { | ||
const adapter = this; | ||
const renderer = new ShallowRenderer(); | ||
let isDOM = false; | ||
let cachedNode = null; | ||
|
@@ -327,6 +342,16 @@ class ReactSixteenOneAdapter extends EnzymeAdapter { | |
: elementToTree(output), | ||
}; | ||
}, | ||
simulateError(nodeHierarchy, rootNode, error) { | ||
simulateError( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not check the // ErrorBoundary is a component with a componentDidCatch method that renders null
// if an error is encountered.
function MyComponent({children}) {
return (
<div>
<h3>My component is cool!</h3>
<ErrorBoundary>
{children}
</ErrorBoundary>
</div>
);
}
const BadChild = () => null;
const wrapper = shallow(
<MyComponent>
<BadChild />
</MyComponent>
);
wrapper.find(BadChild).simulateError(new Error('That was bad'));
expect(wrapper.find('h3')).toExist(); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In a shallow render, there can't ever be any other error boundaries, since only the root node is actually rendered. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, but if This actually seems to work: const wrapper = shallow(
<MyComponent>
<BadChild />
</MyComponent>
);
const errorBoundary = wrapper.find('ErrorBoundary').shallow();
errorBoundary.find(BadChild).simulateError(new Error('That was bad'));
expect(wrapper.find('h3')).toExist(); ...but it doesn't really feel intuitive. I can see people trying my previous example and being confused when it doesn't work, especially since I did just that :\ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not reasonable or possible, because of how shallow rendering works. In shallow rendering, everything that's not the root node is treated as if it's a div - ie, as if the component implementation is |
||
error, | ||
renderer._instance, | ||
cachedNode, | ||
nodeHierarchy.concat(cachedNode), | ||
nodeTypeFromType, | ||
adapter.displayNameOfNode, | ||
); | ||
}, | ||
simulateEvent(node, event, ...args) { | ||
const handler = node.props[propFromEvent(event, eventOptions)]; | ||
if (handler) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I have multiple error boundary components in my tree, will this throw on the closest one to the current node?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should throw on the first one it finds as it traverses upwards.