Skip to content
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

Adds Mount tracking #1730

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -59,6 +59,8 @@ import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });
```

See [the installation docs](/docs/installation/README.md) for more details on configuring `enzyme`.

3rd Party Adapters
=============

Expand Down
2 changes: 1 addition & 1 deletion docs/api/mount.md
Expand Up @@ -8,7 +8,7 @@ want to run your tests inside of a browser, the recommended approach to using `m
on a library called [jsdom](https://github.com/tmpvar/jsdom) which is essentially a headless browser
implemented completely in JS.

**Note**: unlike shallow or static rendering, full rendering actually mounts the component in the DOM, which means that tests can affect each other if they are all using the same DOM. Keep that in mind while writing your tests and, if necessary, use [`.unmount()`](ReactWrapper/unmount.md) or something similar as cleanup.
**Note**: unlike shallow or static rendering, full rendering actually mounts the component in the DOM, which means that tests can affect each other if they are all using the same DOM. Keep that in mind while writing your tests and, if necessary, use [`.unmount()`](ReactWrapper/unmount.md) or something similar as cleanup. You can also automate this via a lifecycle hook with [wrapper sandboxing](../installation/wrapper-sandboxing.md).

```jsx
import { mount } from 'enzyme';
Expand Down
2 changes: 2 additions & 0 deletions docs/installation/README.md
Expand Up @@ -17,3 +17,5 @@ although neither library is a dependency of enzyme.
{% include "./react-014.md" %}

{% include "./react-013.md" %}

{% include "./wrapper-sandboxing.md" %}
60 changes: 60 additions & 0 deletions docs/installation/wrapper-sandboxing.md
@@ -0,0 +1,60 @@
# Wrapper sandboxing

If you are wanting to use enzyme in a large-scale application, you may want to automate
calls to `unmount()` via a lifecycle hook in your test environment, rather than at the
individual test level. Enzyme allows for this as part of its configuration:

ES6:
```js
// setup file
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({
adapter: new Adapter(),
enableSandbox: true,
});
```

```js
// test file
import { shallow, mount } from 'enzyme';

const wrapper = mount(<Foo />);
```

```js
// test file (in a lifecycle hook)
import { unmountAllWrappers } from 'enzyme';

unmountAllWrappers();
```

ES5:
<!-- eslint no-var: 0 -->
```js
// setup file
var enzyme = require('enzyme');
var Adapter = require('enzyme-adapter-react-16');

enzyme.configure({
adapter: new Adapter(),
enableSandbox: true,
});
```

<!-- eslint no-var: 0 -->
```js
// test file
var enzyme = require('enzyme');

var wrapper = enzyme.mount(<Foo />);
```

<!-- eslint no-var: 0 -->
```js
// test file (in a lifecycle hook)
var enzyme = require('enzyme');

enzyme.unmountAllWrappers();
```
10 changes: 10 additions & 0 deletions packages/enzyme-test-suite/test/ReactWrapper-spec.jsx
Expand Up @@ -15,6 +15,7 @@ import {
sym,
} from 'enzyme/build/Utils';
import getAdapter from 'enzyme/build/getAdapter';
import * as wrapperSandbox from 'enzyme/build/wrapperSandbox';

import './_helpers/setupAdapters';
import {
Expand Down Expand Up @@ -79,6 +80,15 @@ describeWithDOM('mount', () => {
mount(<div ref={spy} />);
expect(spy).to.have.property('callCount', 1);
});

it('should call trackWrapper', () => {
const spy = sinon.spy();
const originalTrackWrapper = wrapperSandbox.trackWrapper;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eesh, this is a really brittle test that will definitely break in babel 7 and when node ships native ESM support. is this necessary? can we instead test the actual behavior?

wrapperSandbox.trackWrapper = spy;
mount(<p>foo</p>);
expect(spy).to.have.property('callCount', 1);
wrapperSandbox.trackWrapper = originalTrackWrapper;
});
});

describe('context', () => {
Expand Down
10 changes: 10 additions & 0 deletions packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx
Expand Up @@ -15,6 +15,7 @@ import {
sym,
} from 'enzyme/build/Utils';
import getAdapter from 'enzyme/build/getAdapter';
import * as wrapperSandbox from 'enzyme/build/wrapperSandbox';

import './_helpers/setupAdapters';
import {
Expand Down Expand Up @@ -75,6 +76,15 @@ describe('shallow', () => {
expect(wrapper.children().type()).to.equal('div');
expect(wrapper.children().props().bam).to.equal(undefined);
});

it('should call trackWrapper', () => {
const spy = sinon.spy();
const originalTrackWrapper = wrapperSandbox.trackWrapper;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

wrapperSandbox.trackWrapper = spy;
shallow(<p>foo</p>);
expect(spy).to.have.property('callCount', 1);
wrapperSandbox.trackWrapper = originalTrackWrapper;
});
});

describe('context', () => {
Expand Down
35 changes: 35 additions & 0 deletions packages/enzyme-test-suite/test/wrapperSandbox-spec.jsx
@@ -0,0 +1,35 @@
import React from 'react';
import { expect } from 'chai';
import sinon from 'sinon';
import { ReactWrapper, configure } from 'enzyme';
import { get } from 'enzyme/build/configuration';

import {
trackWrapper,
unmountAllWrappers,
} from 'enzyme/build/wrapperSandbox';

const originalConfig = get();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should probably be populated before or beforeEach rather than at require time


describe('wrapperSandbox', () => {
afterEach(() => {
configure(originalConfig);
});

it('does what i expect', () => {
const wrapper = new ReactWrapper(<p>foo</p>);
const spy = sinon.spy();
wrapper.unmount = spy;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const spy = sinon.spy(wrapper, 'unmount');


trackWrapper(wrapper);
unmountAllWrappers();
// default configuration is not to track wrappers
expect(spy).to.have.property('callCount', 0);

configure({ enableSandbox: true });
trackWrapper(wrapper);
unmountAllWrappers();
// default configuration is not to track wrappers
expect(spy).to.have.property('callCount', 1);
});
});
2 changes: 2 additions & 0 deletions packages/enzyme/src/index.js
Expand Up @@ -6,6 +6,7 @@ import mount from './mount';
import shallow from './shallow';
import render from './render';
import { merge as configure } from './configuration';
import { unmountAllWrappers } from './wrapperSandbox';

module.exports = {
render,
Expand All @@ -15,4 +16,5 @@ module.exports = {
ReactWrapper,
configure,
EnzymeAdapter,
unmountAllWrappers,
};
5 changes: 4 additions & 1 deletion packages/enzyme/src/mount.js
@@ -1,4 +1,5 @@
import ReactWrapper from './ReactWrapper';
import { trackWrapper } from './wrapperSandbox';

/**
* Mounts and renders a react component into the document and provides a testing wrapper around it.
Expand All @@ -7,5 +8,7 @@ import ReactWrapper from './ReactWrapper';
* @returns {ReactWrapper}
*/
export default function mount(node, options) {
return new ReactWrapper(node, null, options);
const wrapper = new ReactWrapper(node, null, options);
trackWrapper(wrapper);
return wrapper;
}
5 changes: 4 additions & 1 deletion packages/enzyme/src/shallow.js
@@ -1,4 +1,5 @@
import ShallowWrapper from './ShallowWrapper';
import { trackWrapper } from './wrapperSandbox';

/**
* Shallow renders a react component and provides a testing wrapper around it.
Expand All @@ -7,5 +8,7 @@ import ShallowWrapper from './ShallowWrapper';
* @returns {ShallowWrapper}
*/
export default function shallow(node, options) {
return new ShallowWrapper(node, null, options);
const wrapper = new ShallowWrapper(node, null, options);
trackWrapper(wrapper);
return wrapper;
}
31 changes: 31 additions & 0 deletions packages/enzyme/src/wrapperSandbox.js
@@ -0,0 +1,31 @@
import { get } from './configuration';

const wrappers = new Set();

/**
* Stores a reference to a testing wrapper for later unmounting
* via unmountAllWrappers()
*
* @param {ReactWrapper|ShallowWrapper} wrapper
*/
export function trackWrapper(wrapper) {
const { enableSandbox } = get();
if (enableSandbox) {
wrappers.add(wrapper);
}
}

/**
* Unmounts all sandboxed Enzyme wrappers.
*
* Usually, this can be run once for an entire test suite after all each test
* (and its nested hooks)have been run. However, in some cases this may need
* to be run this manually.This is most commonly needed when a component uses
* timeouts/ animation frames that are mocked for tests; in that case, waiting
* until after you have restored those globals will lead to their stored
* identifiers being invalid.
*/
export function unmountAllWrappers() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i kind of feel like if enableSandbox is false, this function should throw

wrappers.forEach(wrapper => wrapper.unmount());
wrappers.clear();
}