diff --git a/docs/README.md b/docs/README.md
index 4433eb9f2..21fd17df4 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -76,6 +76,8 @@
* [setProps(nextProps)](/docs/api/ReactWrapper/setProps.md)
* [setContext(context)](/docs/api/ReactWrapper/setContext.md)
* [instance()](/docs/api/ReactWrapper/instance.md)
+ * [unmount()](/docs/api/ReactWrapper/unmount.md)
+ * [mount()](/docs/api/ReactWrapper/mount.md)
* [update()](/docs/api/ReactWrapper/update.md)
* [type()](/docs/api/ReactWrapper/type.md)
* [forEach(fn)](/docs/api/ReactWrapper/forEach.md)
diff --git a/docs/api/ReactWrapper/mount.md b/docs/api/ReactWrapper/mount.md
new file mode 100644
index 000000000..55a493964
--- /dev/null
+++ b/docs/api/ReactWrapper/mount.md
@@ -0,0 +1,51 @@
+# `.mount() => Self`
+
+A method that re-mounts the component. This can be used to simulate a component going through
+an unmount/mount lifecycle.
+
+#### Returns
+
+`ReactWrapper`: Returns itself.
+
+
+
+#### Example
+
+```jsx
+const willMount = sinon.spy();
+const didMount = sinon.spy();
+const willUnmount = sinon.spy();
+
+class Foo extends React.Component {
+ constructor(props) {
+ super(props);
+ this.componentWillUnmount = willUnmount;
+ this.componentWillMount = willMount;
+ this.componentDidMount = didMount;
+ }
+ render() {
+ return (
+
+ {this.props.id}
+
+ );
+ }
+}
+const wrapper = mount();
+expect(willMount.callCount).to.equal(1);
+expect(didMount.callCount).to.equal(1);
+expect(willUnmount.callCount).to.equal(0);
+wrapper.unmount();
+expect(willMount.callCount).to.equal(1);
+expect(didMount.callCount).to.equal(1);
+expect(willUnmount.callCount).to.equal(1);
+wrapper.mount();
+expect(willMount.callCount).to.equal(2);
+expect(didMount.callCount).to.equal(2);
+expect(willUnmount.callCount).to.equal(1);
+```
+
+
+#### Related Methods
+
+- [`.unmount() => Self`](unmount.md)
diff --git a/docs/api/ReactWrapper/unmount.md b/docs/api/ReactWrapper/unmount.md
new file mode 100644
index 000000000..67e4fa10b
--- /dev/null
+++ b/docs/api/ReactWrapper/unmount.md
@@ -0,0 +1,47 @@
+# `.unmount() => Self`
+
+A method that re-mounts the component. This can be used to simulate a component going through
+an unmount/mount lifecycle.
+
+#### Returns
+
+`ReactWrapper`: Returns itself.
+
+
+
+#### Example
+
+```jsx
+const willMount = sinon.spy();
+const didMount = sinon.spy();
+const willUnmount = sinon.spy();
+
+class Foo extends React.Component {
+ constructor(props) {
+ super(props);
+ this.componentWillUnmount = willUnmount;
+ this.componentWillMount = willMount;
+ this.componentDidMount = didMount;
+ }
+ render() {
+ return (
+
+ {this.props.id}
+
+ );
+ }
+}
+const wrapper = mount();
+expect(willMount.callCount).to.equal(1);
+expect(didMount.callCount).to.equal(1);
+expect(willUnmount.callCount).to.equal(0);
+wrapper.unmount();
+expect(willMount.callCount).to.equal(1);
+expect(didMount.callCount).to.equal(1);
+expect(willUnmount.callCount).to.equal(1);
+```
+
+
+#### Related Methods
+
+- [`.mount() => Self`](mount.md)
diff --git a/docs/api/mount.md b/docs/api/mount.md
index cb14bbb02..5aeb61eb1 100644
--- a/docs/api/mount.md
+++ b/docs/api/mount.md
@@ -133,6 +133,12 @@ Manually sets context of the root component.
#### [`.instance() => ReactComponent`](ReactWrapper/instance.md)
Returns the instance of the root component.
+#### [`.unmount() => ReactWrapper`](ReactWrapper/unmount.md)
+A method that un-mounts the component.
+
+#### [`.mount() => ReactWrapper`](ReactWrapper/mount.md)
+A method that re-mounts the component.
+
#### [`.update() => ReactWrapper`](ReactWrapper/update.md)
Calls `.forceUpdate()` on the root component instance.
diff --git a/package.json b/package.json
index a7ae60dd3..09051cb98 100644
--- a/package.json
+++ b/package.json
@@ -10,11 +10,11 @@
"version": "npm run build",
"clean": "rimraf build",
"lint": "eslint src/**",
- "test": "mocha --compilers js:babel-core/register --recursive src/**/__tests__/*.js",
+ "test": "mocha --compilers js:babel-core/register --recursive withDom.js src/**/__tests__/*.js",
"check": "npm run lint && npm run test:all",
"build": "babel src --out-dir build",
- "test:watch": "mocha --compilers js:babel-core/register --recursive src/**/__tests__/*.js --watch",
- "test:only": "mocha --compilers js:babel-core/register --watch",
+ "test:only": "mocha --compilers js:babel-core/register --watch withDom.js",
+ "test:watch": "mocha --compilers js:babel-core/register --recursive withDom.js src/**/__tests__/*.js --watch",
"test:describeWithDOMOnly": "mocha --compilers js:babel-core/register --recursive src/**/__tests__/describeWithDOM/describeWithDOMOnly-spec.js",
"test:describeWithDOMSkip": "mocha --compilers js:babel-core/register --recursive src/**/__tests__/describeWithDOM/describeWithDOMSkip-spec.js",
"test:all": "npm run react:13 && npm test && npm run test:describeWithDOMOnly && npm run test:describeWithDOMSkip && npm run react:14 && npm test && npm run test:describeWithDOMOnly && npm run test:describeWithDOMSkip",
diff --git a/src/ReactWrapper.js b/src/ReactWrapper.js
index 071edc814..2e6868cf8 100644
--- a/src/ReactWrapper.js
+++ b/src/ReactWrapper.js
@@ -137,6 +137,38 @@ export default class ReactWrapper {
return this;
}
+ /**
+ * A method that unmounts the component. This can be used to simulate a component going through
+ * and unmount/mount lifecycle.
+ *
+ * @returns {ReactWrapper}
+ */
+ unmount() {
+ if (this.root !== this) {
+ throw new Error('ReactWrapper::unmount() can only be called on the root');
+ }
+ this.single(() => {
+ this.component.setState({ mount: false });
+ });
+ return this;
+ }
+
+ /**
+ * A method that re-mounts the component. This can be used to simulate a component going through
+ * an unmount/mount lifecycle.
+ *
+ * @returns {ReactWrapper}
+ */
+ mount() {
+ if (this.root !== this) {
+ throw new Error('ReactWrapper::mount() can only be called on the root');
+ }
+ this.single(() => {
+ this.component.setState({ mount: true });
+ });
+ return this;
+ }
+
/**
* A method that sets the props of the root component, and re-renders. Useful for when you are
* wanting to test how the component behaves over time with changing props. Calling this, for
diff --git a/src/ReactWrapperComponent.jsx b/src/ReactWrapperComponent.jsx
index 590689eb6..520f5201d 100644
--- a/src/ReactWrapperComponent.jsx
+++ b/src/ReactWrapperComponent.jsx
@@ -26,6 +26,7 @@ export default function createWrapperComponent(node, options = {}) {
getInitialState() {
return {
+ mount: true,
props: this.props.props,
context: this.props.context,
};
@@ -62,8 +63,10 @@ export default function createWrapperComponent(node, options = {}) {
render() {
const { Component } = this.props;
+ const { mount, props } = this.state;
+ if (!mount) return null;
return (
-
+
);
},
};
diff --git a/src/__tests__/ReactWrapper-spec.js b/src/__tests__/ReactWrapper-spec.js
index 0ff8df3d4..af70bf000 100644
--- a/src/__tests__/ReactWrapper-spec.js
+++ b/src/__tests__/ReactWrapper-spec.js
@@ -1,3 +1,4 @@
+import { describeWithDOM, describeIf } from './_helpers';
import React from 'react';
import { expect } from 'chai';
import sinon from 'sinon';
@@ -5,9 +6,7 @@ import {
mount,
render,
ReactWrapper,
- describeWithDOM,
} from '../';
-import { describeIf } from './_helpers';
import { REACT013 } from '../version';
describeWithDOM('mount', () => {
@@ -490,6 +489,68 @@ describeWithDOM('mount', () => {
});
});
+
+ describe('.mount()', () => {
+ it('should call componentWillUnmount()', () => {
+ const willMount = sinon.spy();
+ const didMount = sinon.spy();
+ const willUnmount = sinon.spy();
+
+ class Foo extends React.Component {
+ constructor(props) {
+ super(props);
+ this.componentWillUnmount = willUnmount;
+ this.componentWillMount = willMount;
+ this.componentDidMount = didMount;
+ }
+ render() {
+ return (
+
+ {this.props.id}
+
+ );
+ }
+ }
+ const wrapper = mount();
+ expect(willMount.callCount).to.equal(1);
+ expect(didMount.callCount).to.equal(1);
+ expect(willUnmount.callCount).to.equal(0);
+ wrapper.unmount();
+ expect(willMount.callCount).to.equal(1);
+ expect(didMount.callCount).to.equal(1);
+ expect(willUnmount.callCount).to.equal(1);
+ wrapper.mount();
+ expect(willMount.callCount).to.equal(2);
+ expect(didMount.callCount).to.equal(2);
+ expect(willUnmount.callCount).to.equal(1);
+ });
+ });
+
+ describe('.unmount()', () => {
+ it('should call componentWillUnmount()', () => {
+ const spy = sinon.spy();
+
+ class Foo extends React.Component {
+ constructor(props) {
+ super(props);
+ this.componentWillUnmount = spy;
+ }
+ render() {
+ return (
+
+ {this.props.id}
+
+ );
+ }
+ }
+ const wrapper = mount();
+ expect(spy.calledOnce).to.equal(false);
+ wrapper.unmount();
+ expect(spy.calledOnce).to.equal(true);
+ });
+
+ });
+
describe('.simulate(eventName, data)', () => {
it('should simulate events', () => {
diff --git a/src/__tests__/Utils-spec.js b/src/__tests__/Utils-spec.js
index 229829b92..06f45ea66 100644
--- a/src/__tests__/Utils-spec.js
+++ b/src/__tests__/Utils-spec.js
@@ -1,4 +1,5 @@
-import React from 'react/addons';
+import { describeWithDOM } from './_helpers.js';
+import React from 'react';
import { expect } from 'chai';
import sinon from 'sinon';
import {
@@ -12,10 +13,7 @@ import {
selectorType,
mapNativeEventNames,
} from '../Utils';
-import {
- describeWithDOM,
- mount,
-} from '../';
+import { mount } from '../';
describe('Utils', () => {
diff --git a/src/__tests__/_helpers.js b/src/__tests__/_helpers.js
index 91d619dcb..12f00361b 100644
--- a/src/__tests__/_helpers.js
+++ b/src/__tests__/_helpers.js
@@ -1,3 +1,14 @@
+export function describeWithDOM(a, b) {
+ describe('(uses jsdom)', () => {
+ if (global.document) {
+ describe(a, b);
+ } else {
+ // if jsdom isn't available, skip every test in this describe context
+ describe.skip(a, b);
+ }
+ });
+}
+
/**
* Simple wrapper around mocha describe which allows a boolean to be passed in first which
* determines whether or not the test will be run
diff --git a/withDom.js b/withDom.js
new file mode 100644
index 000000000..19b49d36b
--- /dev/null
+++ b/withDom.js
@@ -0,0 +1,22 @@
+if (!global.document) {
+ try {
+ const jsdom = require('jsdom').jsdom; // could throw
+
+ const exposedProperties = ['window', 'navigator', 'document'];
+
+ global.document = jsdom('');
+ global.window = document.defaultView;
+ Object.keys(document.defaultView).forEach((property) => {
+ if (typeof global[property] === 'undefined') {
+ exposedProperties.push(property);
+ global[property] = document.defaultView[property];
+ }
+ });
+
+ global.navigator = {
+ userAgent: 'node.js',
+ };
+ } catch (e) {
+ // jsdom is not supported...
+ }
+}