Skip to content

Commit

Permalink
Rip out sinon & mocha into separate modules
Browse files Browse the repository at this point in the history
This refactor addresses several issues regarding enzyme working in a variety of environments, and
the library just generally not making any assumptions about the environment that tests will be
run in.

For the most part, this amounts to:

  - remove direct dependency on jsdom
- remove direct dependency on sinon
- remove assumed dependency on mocha

Additionally, this change moves the code that was removed into separate modules that are
defined in the packages folder. In this case we have added two modules: "enzyme-mocha" and
"enzyme-sinon".

  This design will allow us to have one repo where all issues, code, and documentation
regarding enzyme are centralized, but modules are still separated at the NPM level. This
will allow us to make atomic releases across the modules more easily, as well as keep
all of the discussion in one place.

  Left to do for this to be mergable is:

  [ ] Add a "guides" section in the docs explaining how to use enzyme in different environments
  [ ] Build a proper npm script to run an npm publish in the proper order for each module
  [ ] Add more meaningful tests for packages
  • Loading branch information
lelandrichardson committed Dec 8, 2015
1 parent 2db7c67 commit 7708643
Show file tree
Hide file tree
Showing 29 changed files with 285 additions and 197 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ node_modules
.idea

/build
packages/*/build
_book
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ node_js:
before_install:
- 'if [ "${TRAVIS_NODE_VERSION}" != "0.9" ]; then case "$(npm --version)" in 1.*) npm install -g npm@1.4.28 ;; 2.*) npm install -g npm@2 ;; esac ; fi'
- 'if [ "${TRAVIS_NODE_VERSION}" != "0.6" ] && [ "${TRAVIS_NODE_VERSION}" != "0.9" ]; then npm install -g npm; fi'
before_script: "sh install-relevant-react.sh"
before_script: "sh scripts/install-relevant-react.sh"
script:
- 'if [ "${TRAVIS_NODE_VERSION}" = "4.2" ]; then npm run lint && npm run travis ; elif [ "${TRAVIS_NODE_VERSION}" = "0.12" ]; then npm run travis ; else npm test ; fi'
after_script:
Expand Down
31 changes: 18 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ compatible with all major test runners and assertion libraries out there. The do
examples for enzyme use [mocha](https://mochajs.org/) and [chai](http://chaijs.com/), but you
should be able to extrapolate to your framework of choice.

[Using Enzyme with Mocha](/docs/guides/mocha.md)
[Using Enzyme with Karma](/docs/guides/karma.md)
[Using Enzyme with Jasmine](/docs/guides/jasmine.md)



### [Installation](/docs/installation/README.md)
Expand Down Expand Up @@ -42,6 +46,7 @@ Basic Usage
## [Shallow Rendering](/docs/api/shallow.md)

```jsx
import React from 'react';
import { shallow } from 'enzyme';

describe('<MyComponent />', () => {
Expand Down Expand Up @@ -81,22 +86,14 @@ Read the full [API Documentation](/docs/api/shallow.md)



## [JSDOM Full Rendering](/docs/api/mount.md)
## [Full DOM Rendering](/docs/api/mount.md)

```jsx
import {
describeWithDOM,
mount,
spyLifecycle,
} from 'enzyme';

describeWithDOM('<Foo />', () => {
import React from 'react';
import sinon from 'sinon';
import { mount } from 'enzyme';

it('calls componentDidMount', () => {
spyLifecycle(Foo);
const wrapper = mount(<Foo />);
expect(Foo.prototype.componentDidMount.calledOnce).to.be.true;
});
describe('<Foo />', () => {

it('allows us to set props', () => {
const wrapper = mount(<Foo bar="baz" />);
Expand All @@ -113,6 +110,13 @@ describeWithDOM('<Foo />', () => {
wrapper.find('button').simulate('click');
expect(onButtonClick.calledOnce).to.be.true;
});

it('calls componentDidMount', () => {
sinon.spy(Foo.prototype, 'componentDidMount');
const wrapper = mount(<Foo />);
expect(Foo.prototype.componentDidMount.calledOnce).to.be.true;
Foo.prototype.componentDidMount.restore();
});

});
```
Expand All @@ -123,6 +127,7 @@ Read the full [API Documentation](/docs/api/mount.md)
## [Static Rendered Markup](/docs/api/render.md)

```jsx
import React from 'react';
import { render } from 'enzyme';

describe('<Foo />', () => {
Expand Down
7 changes: 5 additions & 2 deletions docs/api/mount.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ Full DOM rendering is ideal for use cases where you have components that may int
or may require the full lifecycle in order to fully test the component (ie, `componentDidMount`
etc.)

Full DOM rendering depends on a library called [jsdom](https://github.com/tmpvar/jsdom) which is
essentially a headless browser implemented completely in JS.
Full DOM rendering requires that a full DOM API be available at the global scope. This means that
it must be run in an environment that at least "looks like" a browser environment. If you do not
want to run your tests inside of a browswer, the recommended approach to using `mount` is to depend
on a library called [jsdom](https://github.com/tmpvar/jsdom) which is essentially a headless browser
implemented completely in JS.

```jsx
import { mount } from 'enzyme';
Expand Down
21 changes: 21 additions & 0 deletions docs/guides/mocha.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Using Enzyme with Mocha

```bash
npm i --save-dev enzyme-mocha
```

```jsx
import { mount } from 'enzyme';
import { describeWithDOM } from 'enzyme-mocha';

describe('<Foo />', () => {

it('calls componentDidMount', () => {
spy(Foo.prototype, 'componentDidMount');
const wrapper = mount(<Foo />);
expect(Foo.prototype.componentDidMount.calledOnce).to.be.true;
});

});

```
29 changes: 0 additions & 29 deletions docs/installation/jsdom.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,32 +26,3 @@ nvm use 0.12
```bash
nvm use 4
```

### Preventing tests from failing on old versions

If you are worried about tests not passing on versions of node that don't support jsdom, Enzyme
comes with a helper function to wrap your tests in a safety layer such that any tests written
inside of that function will be skipped if jsdom is not available. (Note that this is for mocha
only).

```jsx
import { mount, shallow } from 'enzyme';

describe('MyComponent', () => {
describeWithDOM('interaction', () => {
// these tests will get skipped if jsdom is not available...
it('should do something', () => {
const wrapper = mount(<MyComponent />);
// ...
});
});
describe('non-interaction', () => {
// these tests will always run
it('should do something', () => {
const wrapper = shallow(<MyComponent />);
// ...
});
});
});

```
24 changes: 13 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@
"postversion": "git push && git push --tags && npm run clean && npm run docs:publish",
"version": "npm run build",
"clean": "rimraf build",
"lint": "eslint src/**",
"test": "mocha --compilers js:babel/register --recursive src/**/__tests__/*.js",
"lint": "eslint src/** packages/**/src/**",
"check": "npm run lint && npm run test:all",
"build": "babel src --out-dir build",
"test:watch": "mocha --compilers js:babel/register --recursive src/**/__tests__/*.js --watch",
"build": "npm run build:core && npm run build:packages",
"build:core": "babel src --out-dir build",
"build:packages": "bash ./scripts/build-packages.sh",
"test": "npm run test:core && npm run test:packages",
"test:core": "mocha --compilers js:babel/register test/*.js",
"test:packages": "bash ./scripts/test-packages.sh",
"test:watch": "mocha --compilers js:babel/register test/*.js packages/**/test/*.js --watch",
"test:all": "npm run react:13 && npm test && npm run react:14 && npm test",
"react:clean": "rimraf node_modules/react node_modules/react-dom node_modules/react-addons-test-utils",
"react:13": "npm run react:clean && npm i react@0.13",
Expand All @@ -23,7 +27,7 @@
"docs:build": "npm run docs:prepare && gitbook build",
"docs:watch": "npm run docs:prepare && gitbook serve",
"docs:publish": "npm run docs:clean && npm run docs:build && cd _book && git init && git commit --allow-empty -m 'update book' && git fetch https://github.com/airbnb/enzyme.git gh-pages && git checkout -b gh-pages && git add . && git commit -am 'update book' && git push https://github.com/airbnb/enzyme.git gh-pages --force",
"travis": "istanbul cover _mocha -- --compilers js:babel/register --recursive src/**/__tests__/*.js"
"travis": "istanbul cover _mocha -- --compilers js:babel/register test/*.js packages/**/test/*.js"
},
"repository": {
"type": "git",
Expand All @@ -47,7 +51,6 @@
"license": "MIT",
"dependencies": {
"cheerio": "^0.19.0",
"sinon": "^1.15.4",
"underscore": "^1.8.3"
},
"devDependencies": {
Expand All @@ -60,14 +63,13 @@
"eslint-plugin-react": "^3.8.0",
"gitbook-cli": "^1.0.0",
"istanbul": "^0.4.0",
"jsdom": "^6.1.0",
"mocha": "^2.2.5",
"rimraf": "^2.4.3"
"mocha-jsdom": "^1.0.0",
"rimraf": "^2.4.3",
"sinon": "^1.15.4"
},
"peerDependencies": {
"react": "0.13.x || 0.14.x"
},
"optionalDependencies": {
"jsdom": "^6.1.0",
"mocha-jsdom": "^1.0.0"
}
}
1 change: 1 addition & 0 deletions packages/enzyme-mocha/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Left empty in order to include the build directory for npm publish
24 changes: 24 additions & 0 deletions packages/enzyme-mocha/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "enzyme-mocha",
"version": "1.1.0",
"description": "Make Mocha and Enzyme play nice",
"main": "build/index.js",
"scripts": {
"build": "babel src --out-dir build",
"test": "mocha --compilers js:babel/register"
},
"repository": "https://github.com/airbnb/enzyme/tree/master/packages/enzyme-mocha",
"keywords": [
"mocha",
"enzyme"
],
"author": "Leland Richardson <leland.richardson@airbnb.com>",
"license": "MIT",
"dependencies": {
"jsdomify": "^1.0.2"
},
"peerDependencies": {
"react": "0.13.x || 0.14.x",
"mocha": ">= 2.0.0"
}
}
33 changes: 33 additions & 0 deletions packages/enzyme-mocha/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
let jsdomify;

try {
jsdomify = require('jsdomify');
jsdomify.create();
require('react'); // require react explicitly after jsdomify.create() has been called
jsdomify.destroy();
} catch (e) {
// jsdom is not supported
}

export function jsdomSetup() {
if (!jsdomify) return;
jsdomify.create();
}

export function jsdomTeardown() {
if (!jsdomify) return;
jsdomify.destroy();
}

export function describeWithDOM(a, b) {
describe('(uses jsdom)', () => {
if (typeof jsdom === 'function') {
beforeEach(jsdomSetup);
afterEach(jsdomTeardown);
describe(a, b);
} else {
// if jsdom isn't available, skip every test in this describe context
describe.skip(a, b);
}
});
}
3 changes: 3 additions & 0 deletions packages/enzyme-mocha/test/jsdom-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
describe('jsdom', () => {
it.skip('TODO: set up tests');
});
1 change: 1 addition & 0 deletions packages/enzyme-sinon/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Left empty in order to include the build directory for npm publish
24 changes: 24 additions & 0 deletions packages/enzyme-sinon/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "enzyme-sinon",
"version": "1.1.0",
"description": "Make Sinon and Enzyme play nice",
"main": "build/index.js",
"scripts": {
"build": "babel src --out-dir build",
"test": "mocha --compilers js:babel/register"
},
"repository": "https://github.com/airbnb/enzyme/tree/master/packages/enzyme-mocha",
"keywords": [
"sinon",
"enzyme"
],
"author": "Leland Richardson <leland.richardson@airbnb.com>",
"license": "MIT",
"dependencies": {
"sinon": "^1.17.2"
},
"peerDependencies": {
"react": "0.13.x || 0.14.x",
"mocha": ">= 2.0.0"
}
}
20 changes: 20 additions & 0 deletions packages/enzyme-sinon/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Sinon from 'sinon';
import onPrototype from './onPrototype';

export let sinon = Sinon.sandbox.create();

export function spySetup() {
sinon = Sinon.sandbox.create();
}

export function spyTearDown() {
sinon.restore();
}

export function spyLifecycle(Component, sinonInstance = sinon) {
onPrototype(Component, (proto, name) => sinonInstance.spy(proto, name));
}

export function spyMethods(Component, sinonInstance = sinon) {
onPrototype(Component, null, (proto, name) => sinonInstance.spy(proto, name));
}
25 changes: 25 additions & 0 deletions packages/enzyme-sinon/src/onPrototype.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export default function onPrototype(Component, lifecycle, method) {
const proto = Component.prototype;
Object.getOwnPropertyNames(proto).forEach((name) => {
if (typeof proto[name] !== 'function') return;
switch (name) {
case 'componentDidMount':
case 'componentWillMount':
case 'componentDidUnmount':
case 'componentWillUnmount':
case 'componentWillReceiveProps':
case 'componentDidUpdate':
case 'componentWillUpdate':
case 'shouldComponentUpdate':
case 'render':
if (lifecycle) lifecycle(proto, name);
break;
case 'constructor':
// don't spy on the constructor, even though it shows up in the prototype
break;
default:
if (method) method(proto, name);
break;
}
});
}
31 changes: 31 additions & 0 deletions packages/enzyme-sinon/test/onPrototype-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { expect } from 'chai';
import sinon from 'sinon';
import onPrototype from '../src/onPrototype';

describe('onPrototype', () => {

it('makes the expected calls', () => {

class Foo {
a() {}
b() {}
componentDidUpdate() {}
}

const lifecycleSpy = sinon.spy();
const methodSpy = sinon.spy();

onPrototype(Foo, lifecycleSpy, methodSpy);

expect(lifecycleSpy.callCount).to.equal(1);
expect(lifecycleSpy.args[0][0]).to.equal(Foo.prototype);
expect(lifecycleSpy.args[0][1]).to.equal('componentDidUpdate');

expect(methodSpy.callCount).to.equal(2);
expect(methodSpy.args[0][0]).to.equal(Foo.prototype);
expect(methodSpy.args[0][1]).to.equal('a');
expect(methodSpy.args[1][1]).to.equal('b');

});

});
10 changes: 10 additions & 0 deletions scripts/build-packages.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env zsh

# die on error
setopt -e

for pkg in packages/*/ ; do
pushd ${pkg} > /dev/null
npm run build
popd > /dev/null
done
File renamed without changes.
Loading

0 comments on commit 7708643

Please sign in to comment.