diff --git a/.travis.yml b/.travis.yml index 87cc3f0cd..356457cad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,6 +35,8 @@ matrix: env: KARMA=true REACT=0.13 - node_js: "6" env: KARMA=true REACT=0.14 + - node_js: "6" + env: KARMA=true REACT=15.4 - node_js: "6" env: KARMA=true REACT=15 allow_failures: @@ -43,4 +45,5 @@ matrix: env: - REACT=0.13 - REACT=0.14 + - REACT=15.4 - REACT=15 diff --git a/README.md b/README.md index 408e0598a..135392a77 100644 --- a/README.md +++ b/README.md @@ -60,12 +60,18 @@ npm i --save-dev enzyme Enzyme is currently compatible with `React 15.x`, `React 0.14.x` and `React 0.13.x`. In order to achieve this compatibility, some dependencies cannot be explicitly listed in our `package.json`. -If you are using `React 0.14` or `React 15.x`, in addition to `enzyme`, you will have to ensure that +If you are using `React 0.14` or `React <15.5`, in addition to `enzyme`, you will have to ensure that you also have the following npm modules installed if they were not already: ```bash -npm i --save-dev react-addons-test-utils -npm i --save-dev react-dom +npm i --save-dev react-addons-test-utils react-dom +``` + +If you are using `React >=15.5`, in addition to `enzyme`, you will have to ensure that you also have +the following npm modules installed if they were not already: + +```bash +npm i --save-dev react-test-renderer react-dom ``` diff --git a/install-relevant-react.sh b/install-relevant-react.sh index 6619dda6b..6303d0ffe 100644 --- a/install-relevant-react.sh +++ b/install-relevant-react.sh @@ -12,6 +12,10 @@ if [ "$REACT" = "0.14" ]; then npm run react:14 fi +if [ "$REACT" = "15.4" ]; then + npm run react:15.4 +fi + if [ "$REACT" = "15" ]; then npm run react:15 fi diff --git a/karma.conf.js b/karma.conf.js index 8bbdc4da0..c2387224b 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -4,6 +4,36 @@ require('babel-register'); var IgnorePlugin = require('webpack').IgnorePlugin; var REACT013 = require('./src/version').REACT013; +var REACT155 = require('./src/version').REACT155; + +function getPlugins() { + var plugins = []; + + /* + this list of conditional IgnorePlugins mirrors the conditional + requires in src/react-compat.js and exists to avoid error + output from the webpack compilation + */ + + if (!REACT013) { + plugins.push(new IgnorePlugin(/react\/lib\/ExecutionEnvironment/)); + plugins.push(new IgnorePlugin(/react\/lib\/ReactContext/)); + plugins.push(new IgnorePlugin(/react\/addons/)); + } + if (REACT013) { + plugins.push(new IgnorePlugin(/react-dom/)); + } + if (REACT013 || REACT155) { + plugins.push(new IgnorePlugin(/react-addons-test-utils/)); + } + if (!REACT155) { + plugins.push(new IgnorePlugin(/react-test-renderer/)); + plugins.push(new IgnorePlugin(/react-dom\/test-utils/)); + plugins.push(new IgnorePlugin(/create-react-class/)); + } + + return plugins; +} module.exports = function karma(config) { config.set({ @@ -33,7 +63,7 @@ module.exports = function karma(config) { ], exclude: [ - 'test/_*.{jsx,js}', + 'test/_helpers/index.jsx', ], browsers: [ @@ -71,18 +101,7 @@ module.exports = function karma(config) { }, ], }, - plugins: [ - /* - this list of conditional IgnorePlugins mirrors the conditional - requires in src/react-compat.js and exists to avoid error - output from the webpack compilation - */ - !REACT013 && new IgnorePlugin(/react\/lib\/ExecutionEnvironment/), - !REACT013 && new IgnorePlugin(/react\/lib\/ReactContext/), - !REACT013 && new IgnorePlugin(/react\/addons/), - REACT013 && new IgnorePlugin(/react-dom/), - REACT013 && new IgnorePlugin(/react-addons-test-utils/), - ].filter(function filterPlugins(plugin) { return plugin !== false; }), + plugins: getPlugins(), }, webpackServer: { diff --git a/package.json b/package.json index bfa21393f..04bb62646 100644 --- a/package.json +++ b/package.json @@ -19,17 +19,18 @@ "test:watch": "mocha --recursive --watch test", "test:karma": "karma start", "test:env": "sh ./example-test.sh", - "test:all": "npm run react:13 && npm run test:only && npm run react:14 && npm run test:only && npm run react:15 && npm run test:only", - "react:clean": "rimraf node_modules/react node_modules/react-dom node_modules/react-addons-test-utils", + "test:all": "npm run react:13 && npm run test:only && npm run react:14 && npm run test:only && npm run react:15.4 && npm run test:only && npm run react:15 && npm run test:only", + "react:clean": "rimraf node_modules/react node_modules/react-dom node_modules/react-addons-test-utils node_modules/react-test-renderer", "react:13": "rimraf node_modules/.bin/npm && npm run react:clean && npm i react@0.13 && npm install", "react:14": "rimraf node_modules/.bin/npm && npm run react:clean && npm i react@0.14 react-dom@0.14 react-addons-test-utils@0.14 && npm install", - "react:15": "rimraf node_modules/.bin/npm && npm run react:clean && npm i react@15 react-dom@15 react-addons-test-utils@15 && npm install", + "react:15.4": "rimraf node_modules/.bin/npm && npm run react:clean && npm i react@15.4 react-dom@15.4 react-addons-test-utils@15.4 && npm install", + "react:15": "rimraf node_modules/.bin/npm && npm run react:clean && npm i react@15 react-dom@15 create-react-class@15 react-test-renderer@^15.5.4 && npm install", "docs:clean": "rimraf _book", "docs:prepare": "gitbook install", "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 git@github.com:airbnb/enzyme.git gh-pages && git checkout -b gh-pages && git add . && git commit -am 'update book' && git push git@github.com:airbnb/enzyme.git gh-pages --force", - "travis": "babel-node ./node_modules/.bin/istanbul cover --report html _mocha -- test --recursive" + "travis": "babel-node \"$(which istanbul)\" cover --report html _mocha -- test --recursive" }, "repository": { "type": "git", @@ -60,6 +61,7 @@ "object.assign": "^4.0.4", "object.entries": "^1.0.3", "object.values": "^1.0.3", + "prop-types": "^15.5.4", "uuid": "^2.0.3" }, "devDependencies": { @@ -71,6 +73,7 @@ "babel-register": "^6.24.1", "chai": "^3.5.0", "coveralls": "^2.13.0", + "create-react-class": "^15.5.2", "enzyme-example-jest": "^0.1.0", "enzyme-example-karma": "^0.1.1", "enzyme-example-karma-webpack": "^0.1.4", diff --git a/src/ReactWrapperComponent.jsx b/src/ReactWrapperComponent.jsx index 4bba0d081..19a187d52 100644 --- a/src/ReactWrapperComponent.jsx +++ b/src/ReactWrapperComponent.jsx @@ -1,4 +1,5 @@ -import React, { PropTypes } from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import objectAssign from 'object.assign'; /* eslint react/forbid-prop-types: 0 */ diff --git a/src/ShallowWrapper.js b/src/ShallowWrapper.js index 1567c689c..9d7b74ba9 100644 --- a/src/ShallowWrapper.js +++ b/src/ShallowWrapper.js @@ -37,6 +37,7 @@ import { batchedUpdates, isDOMComponentElement, } from './react-compat'; +import { REACT155 } from './version'; /** * Finds all nodes in the current wrapper nodes' render trees that match the provided predicate @@ -98,6 +99,17 @@ function validateOptions(options) { } } + +function performBatchedUpdates(wrapper, fn) { + const renderer = wrapper.root.renderer; + if (REACT155) { + // React 15.5+ exposes batching on shallow renderer itself + return renderer.unstable_batchedUpdates(fn); + } + // React <15.5: Fallback to ReactDOM + return batchedUpdates(fn); +} + /** * @class ShallowWrapper */ @@ -110,7 +122,7 @@ class ShallowWrapper { this.unrendered = nodes; this.renderer = createShallowRenderer(); withSetStateAllowed(() => { - batchedUpdates(() => { + performBatchedUpdates(this, () => { this.renderer.render(nodes, options.context); const instance = this.instance(); if ( @@ -223,7 +235,7 @@ class ShallowWrapper { const prevContext = instance.context; const nextProps = props || prevProps; const nextContext = context || prevContext; - batchedUpdates(() => { + performBatchedUpdates(this, () => { let shouldRender = true; // dirty hack: // make sure that componentWillReceiveProps is called before shouldComponentUpdate @@ -611,7 +623,7 @@ class ShallowWrapper { withSetStateAllowed(() => { // TODO(lmr): create/use synthetic events // TODO(lmr): emulate React's event propagation - batchedUpdates(() => { + performBatchedUpdates(this, () => { handler(...args); }); this.root.update(); diff --git a/src/react-compat.js b/src/react-compat.js index 2d108bd30..204deab53 100644 --- a/src/react-compat.js +++ b/src/react-compat.js @@ -7,7 +7,7 @@ */ import objectAssign from 'object.assign'; -import { REACT013 } from './version'; +import { REACT013, REACT155 } from './version'; let TestUtils; let createShallowRenderer; @@ -18,6 +18,7 @@ let childrenToArray; let renderWithOptions; let unmountComponentAtNode; let batchedUpdates; +let shallowRendererFactory; const React = require('react'); @@ -95,12 +96,44 @@ if (REACT013) { // to list this as a dependency in package.json and have 0.13 work properly. // As a result, right now this is basically an implicit dependency. try { - // eslint-disable-next-line import/no-extraneous-dependencies - TestUtils = require('react-addons-test-utils'); + if (REACT155) { + // eslint-disable-next-line import/no-extraneous-dependencies + TestUtils = require('react-dom/test-utils'); + } else { + // eslint-disable-next-line import/no-extraneous-dependencies + TestUtils = require('react-addons-test-utils'); + } + } catch (e) { + if (REACT155) { + console.error( // eslint-disable-line no-console + 'react-dom@15.5+ is an implicit dependency when using react@15.5+ with enzyme. ' + + 'Please add the appropriate version to your devDependencies. ' + + 'See https://github.com/airbnb/enzyme#installation', + ); + } else { + console.error( // eslint-disable-line no-console + 'react-addons-test-utils is an implicit dependency in order to support react@0.13-14. ' + + 'Please add the appropriate version to your devDependencies. ' + + 'See https://github.com/airbnb/enzyme#installation', + ); + } + throw e; + } + + // Shallow renderer is accessible via the react-test-renderer package for React 15.5+. + // This is a separate package though and may not be installed. + try { + if (REACT155) { + // eslint-disable-next-line import/no-extraneous-dependencies + shallowRendererFactory = require('react-test-renderer/shallow').createRenderer; + } else { + // eslint-disable-next-line import/no-extraneous-dependencies + shallowRendererFactory = TestUtils.createRenderer; + } } catch (e) { // eslint-disable-next-line no-console console.error( - 'react-addons-test-utils is an implicit dependency in order to support react@0.13-14. ' + + 'react-test-renderer is an implicit dependency in order to support react@15.5+. ' + 'Please add the appropriate version to your devDependencies. ' + 'See https://github.com/airbnb/enzyme#installation', ); @@ -115,7 +148,7 @@ if (REACT013) { // is essentially a replacement for `TestUtils.createRenderer` that doesn't use // shallow rendering when it's just a DOM element. createShallowRenderer = function createRendererCompatible() { - const renderer = TestUtils.createRenderer(); + const renderer = shallowRendererFactory(); const originalRender = renderer.render; const originalRenderOutput = renderer.getRenderOutput; let isDOM = false; diff --git a/src/version.js b/src/version.js index 8d64d2611..d55f7f3df 100644 --- a/src/version.js +++ b/src/version.js @@ -1,6 +1,10 @@ import React from 'react'; export const VERSION = React.version; + +const [major, minor] = VERSION.split('.'); + export const REACT013 = VERSION.slice(0, 4) === '0.13'; export const REACT014 = VERSION.slice(0, 4) === '0.14'; -export const REACT15 = VERSION.slice(0, 3) === '15.'; +export const REACT15 = major === '15'; +export const REACT155 = REACT15 && minor >= 5; diff --git a/test/ReactWrapper-spec.jsx b/test/ReactWrapper-spec.jsx index d80529b6a..0233beff7 100644 --- a/test/ReactWrapper-spec.jsx +++ b/test/ReactWrapper-spec.jsx @@ -1,9 +1,11 @@ /* globals document */ import React from 'react'; +import PropTypes from 'prop-types'; import { expect } from 'chai'; import sinon from 'sinon'; import { batchedUpdates } from '../src/react-compat'; +import { createClass } from './_helpers/react-compat'; import { describeWithDOM, @@ -23,9 +25,9 @@ import { REACT013, REACT014, REACT15 } from '../src/version'; describeWithDOM('mount', () => { describe('context', () => { it('can pass in context', () => { - const SimpleComponent = React.createClass({ + const SimpleComponent = createClass({ contextTypes: { - name: React.PropTypes.string, + name: PropTypes.string, }, render() { return