diff --git a/grunt/tasks/jest.js b/grunt/tasks/jest.js index 679e26c3d0..97e0d38521 100644 --- a/grunt/tasks/jest.js +++ b/grunt/tasks/jest.js @@ -83,7 +83,12 @@ function run(done, configPath) { grunt.util.spawn({ cmd: 'node', args: args, - opts: { stdio: 'inherit', env: { NODE_ENV: 'test' } }, + opts: { + stdio: 'inherit', + env: Object.assign({}, process.env, { + NODE_ENV: 'test', + }), + }, }, function(spawnErr, result, code) { if (spawnErr) { onError(spawnErr); diff --git a/src/isomorphic/modern/class/__tests__/ReactClassEquivalence-test.js b/src/isomorphic/modern/class/__tests__/ReactClassEquivalence-test.js index 36cf5c75ce..0d91c3a144 100644 --- a/src/isomorphic/modern/class/__tests__/ReactClassEquivalence-test.js +++ b/src/isomorphic/modern/class/__tests__/ReactClassEquivalence-test.js @@ -11,24 +11,62 @@ 'use strict'; -var MetaMatchers = require('MetaMatchers'); +var spawnSync = require('child_process').spawnSync; +var path = require('path'); describe('ReactClassEquivalence', function() { - - beforeEach(function() { - this.addMatchers(MetaMatchers); - }); - - var es6 = () => require('./ReactES6Class-test.js'); - var coffee = () => require('./ReactCoffeeScriptClass-test.coffee'); - var ts = () => require('./ReactTypeScriptClass-test.ts'); - it('tests the same thing for es6 classes and CoffeeScript', function() { - expect(coffee).toEqualSpecsIn(es6); + var result1 = runJest('ReactCoffeeScriptClass-test.coffee'); + var result2 = runJest('ReactES6Class-test.js'); + compareResults(result1, result2); }); it('tests the same thing for es6 classes and TypeScript', function() { - expect(ts).toEqualSpecsIn(es6); + var result1 = runJest('ReactTypeScriptClass-test.ts'); + var result2 = runJest('ReactES6Class-test.js'); + compareResults(result1, result2); }); }); + +function runJest(testFile) { + var cwd = process.cwd(); + var jestBin = path.resolve('node_modules', '.bin', 'jest'); + var setupFile = path.resolve(__dirname, 'setupSpecEquivalenceReporter.js'); + var result = spawnSync('node', [ + jestBin, + testFile, + '--setupTestFrameworkScriptFile', + setupFile, + ], {cwd}); + + if (result.error) { + throw result.error; + } + + if (result.status !== 0) { + throw new Error( + 'jest process exited with: ' + + result.status + + '\n' + + 'stdout: ' + + result.stdout.toString() + + 'stderr: ' + + result.stderr.toString() + ); + } + + return result.stdout.toString(); +} + +function compareResults(a, b) { + var regexp = /^EQUIVALENCE.*$/gm; + var aSpecs = (a.match(regexp) || []).sort().join('\n'); + var bSpecs = (b.match(regexp) || []).sort().join('\n'); + + if (aSpecs.length === 0 && bSpecs.length === 0) { + throw new Error('No spec results found in the output'); + } + + expect(aSpecs).toEqual(bSpecs); +} diff --git a/src/isomorphic/modern/class/__tests__/ReactCoffeeScriptClass-test.coffee b/src/isomorphic/modern/class/__tests__/ReactCoffeeScriptClass-test.coffee index 88d12af471..a9871ea6ba 100644 --- a/src/isomorphic/modern/class/__tests__/ReactCoffeeScriptClass-test.coffee +++ b/src/isomorphic/modern/class/__tests__/ReactCoffeeScriptClass-test.coffee @@ -53,6 +53,7 @@ describe 'ReactCoffeeScriptClass', -> ).toThrow() expect(console.error.calls.count()).toBe(1) expect(console.error.calls.argsFor(0)[0]).toContain('No `render` method found on the returned component instance') + undefined it 'renders a simple stateless component with prop', -> class Foo extends React.Component @@ -62,6 +63,7 @@ describe 'ReactCoffeeScriptClass', -> test React.createElement(Foo, bar: 'foo'), 'DIV', 'foo' test React.createElement(Foo, bar: 'bar'), 'DIV', 'bar' + undefined it 'renders based on state using initial values in this.props', -> class Foo extends React.Component @@ -74,6 +76,7 @@ describe 'ReactCoffeeScriptClass', -> className: @state.bar test React.createElement(Foo, initialValue: 'foo'), 'SPAN', 'foo' + undefined it 'renders based on state using props in the constructor', -> class Foo extends React.Component @@ -94,6 +97,7 @@ describe 'ReactCoffeeScriptClass', -> instance = test React.createElement(Foo, initialValue: 'foo'), 'DIV', 'foo' instance.changeState() test React.createElement(Foo), 'SPAN', 'bar' + undefined it 'renders based on context in the constructor', -> class Foo extends React.Component @@ -125,6 +129,7 @@ describe 'ReactCoffeeScriptClass', -> React.createElement Foo test React.createElement(Outer), 'SPAN', 'foo' + undefined it 'renders only once when setting state in componentWillMount', -> renderCount = 0 @@ -141,6 +146,7 @@ describe 'ReactCoffeeScriptClass', -> test React.createElement(Foo, initialValue: 'foo'), 'SPAN', 'bar' expect(renderCount).toBe 1 + undefined it 'should throw with non-object in the initial state property', -> [['an array'], 'a string', 1234].forEach (state) -> @@ -156,6 +162,7 @@ describe 'ReactCoffeeScriptClass', -> ).toThrowError( 'Foo.state: must be set to an object or null' ) + undefined it 'should render with null in the initial state property', -> class Foo extends React.Component @@ -166,6 +173,7 @@ describe 'ReactCoffeeScriptClass', -> span() test React.createElement(Foo), 'SPAN', '' + undefined it 'setState through an event handler', -> class Foo extends React.Component @@ -183,6 +191,7 @@ describe 'ReactCoffeeScriptClass', -> test React.createElement(Foo, initialValue: 'foo'), 'DIV', 'foo' attachedListener() expect(renderedName).toBe 'bar' + undefined it 'should not implicitly bind event handlers', -> class Foo extends React.Component @@ -199,6 +208,7 @@ describe 'ReactCoffeeScriptClass', -> test React.createElement(Foo, initialValue: 'foo'), 'DIV', 'foo' expect(attachedListener).toThrow() + undefined it 'renders using forceUpdate even when there is no state', -> class Foo extends React.Component @@ -217,6 +227,7 @@ describe 'ReactCoffeeScriptClass', -> test React.createElement(Foo, initialValue: 'foo'), 'DIV', 'foo' attachedListener() expect(renderedName).toBe 'bar' + undefined it 'will call all the normal life cycle methods', -> lifeCycles = [] @@ -266,6 +277,7 @@ describe 'ReactCoffeeScriptClass', -> lifeCycles = [] # reset ReactDOM.unmountComponentAtNode container expect(lifeCycles).toEqual ['will-unmount'] + undefined it 'warns when classic properties are defined on the instance, but does not invoke them.', -> @@ -305,6 +317,7 @@ describe 'ReactCoffeeScriptClass', -> expect(console.error.calls.argsFor(3)[0]).toContain( 'contextTypes was defined as an instance property on Foo.' ) + undefined it 'should warn when misspelling shouldComponentUpdate', -> spyOn console, 'error' @@ -323,6 +336,7 @@ describe 'ReactCoffeeScriptClass', -> Did you mean shouldComponentUpdate()? The name is phrased as a question because the function is expected to return a value.' ) + undefined it 'should warn when misspelling componentWillReceiveProps', -> spyOn console, 'error' @@ -340,6 +354,7 @@ describe 'ReactCoffeeScriptClass', -> 'Warning: NamedComponent has a method called componentWillRecieveProps(). Did you mean componentWillReceiveProps()?' ) + undefined it 'should throw AND warn when trying to access classic APIs', -> spyOn console, 'error' @@ -356,6 +371,7 @@ describe 'ReactCoffeeScriptClass', -> expect(console.error.calls.argsFor(1)[0]).toContain( 'isMounted(...) is deprecated in plain JavaScript React classes' ) + undefined it 'supports this.context passed via getChildContext', -> class Bar extends React.Component @@ -373,6 +389,7 @@ describe 'ReactCoffeeScriptClass', -> React.createElement Bar test React.createElement(Foo), 'DIV', 'bar-through-context' + undefined it 'supports classic refs', -> class Foo extends React.Component @@ -383,8 +400,10 @@ describe 'ReactCoffeeScriptClass', -> instance = test(React.createElement(Foo), 'DIV', 'foo') expect(instance.refs.inner.getName()).toBe 'foo' + undefined it 'supports drilling through to the DOM using findDOMNode', -> instance = test Inner(name: 'foo'), 'DIV', 'foo' node = ReactDOM.findDOMNode(instance) expect(node).toBe container.firstChild + undefined diff --git a/src/isomorphic/modern/class/__tests__/setupSpecEquivalenceReporter.js b/src/isomorphic/modern/class/__tests__/setupSpecEquivalenceReporter.js new file mode 100644 index 0000000000..90e430f4c7 --- /dev/null +++ b/src/isomorphic/modern/class/__tests__/setupSpecEquivalenceReporter.js @@ -0,0 +1,31 @@ +/*! + * Copyright 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + +var expect = global.expect; + +var numExpectations = 0; + +global.expect = function() { + numExpectations += 1; + return expect.apply(this, arguments); +}; + +beforeEach(() => numExpectations = 0); + +jasmine.currentEnv_.addReporter({ + specDone: (spec) => { + console.log( + `EQUIVALENCE: ${spec.description}, ` + + `status: ${spec.status}, ` + + `numExpectations: ${numExpectations}` + ); + }, +}); diff --git a/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js b/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js index 3c2ee8fc37..f55475b8c2 100644 --- a/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js +++ b/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js @@ -1324,7 +1324,7 @@ describe('ReactDOMComponent', function() { it('should warn about incorrect casing on properties', function() { spyOn(console, 'error'); ReactTestUtils.renderIntoDocument(React.createElement('input', {type: 'text', tabindex: '1'})); - expect(console.error.calls.count(0)).toBe(1); + expect(console.error.calls.count()).toBe(1); expect(console.error.calls.argsFor(0)[0]).toContain('tabIndex'); }); @@ -1332,7 +1332,7 @@ describe('ReactDOMComponent', function() { spyOn(console, 'error'); ReactTestUtils.renderIntoDocument(React.createElement('input', {type: 'text', onclick: '1'})); ReactTestUtils.renderIntoDocument(React.createElement('input', {type: 'text', onKeydown: '1'})); - expect(console.error.calls.count(0)).toBe(2); + expect(console.error.calls.count()).toBe(2); expect(console.error.calls.argsFor(0)[0]).toContain('onClick'); expect(console.error.calls.argsFor(1)[0]).toContain('onKeyDown'); }); diff --git a/src/test/MetaMatchers.js b/src/test/MetaMatchers.js deleted file mode 100644 index c2cc42fa25..0000000000 --- a/src/test/MetaMatchers.js +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule MetaMatchers - */ - -'use strict'; - -/** - * This modules adds a jasmine matcher toEqualSpecsIn that can be used to - * compare the specs in two different "describe" functions and their result. - * It can be used to test a test. - */ - -function getRunnerWithResults(describeFunction) { - if (describeFunction._cachedRunner) { - // Cached result of execution. This is a convenience way to test against - // the same authoritative function multiple times. - return describeFunction._cachedRunner; - } - // Patch the current global environment. - var env = new jasmine.Env(); - // Execute the tests synchronously. - env.updateInterval = 0; - var outerGetEnv = jasmine.getEnv; - jasmine.getEnv = function() { - return env; - }; - // TODO: Bring over matchers from the existing environment. - console.error(env); - var runner = env.currentRunner(); - try { - env.describe('', describeFunction); - env.execute(); - } finally { - // Restore the environment. - jasmine.getEnv = outerGetEnv; - } - describeFunction._cachedRunner = runner; - return runner; -} - -function compareSpec(actual, expected) { - if (actual.results().totalCount !== expected.results().totalCount) { - return ( - 'Expected ' + expected.results().totalCount + ' expects, ' + - 'but got ' + actual.results().totalCount + ':' + - actual.getFullName() - ); - } - return null; -} - -function includesDescription(specs, description, startIndex) { - for (var i = startIndex; i < specs.length; i++) { - if (specs[i].description === description) { - return true; - } - } - return false; -} - -function compareSpecs(actualSpecs, expectedSpecs) { - for (var i = 0; i < actualSpecs.length && i < expectedSpecs.length; i++) { - var actual = actualSpecs[i]; - var expected = expectedSpecs[i]; - if (actual.description === expected.description) { - var errorMessage = compareSpec(actual, expected); - if (errorMessage) { - return errorMessage; - } - continue; - } else if (includesDescription(actualSpecs, expected.description, i)) { - return 'Did not expect the spec:' + actualSpecs[i].getFullName(); - } else { - return 'Expected an equivalent to:' + expectedSpecs[i].getFullName(); - } - } - if (i < actualSpecs.length) { - return 'Did not expect the spec:' + actualSpecs[i].getFullName(); - } - if (i < expectedSpecs.length) { - return 'Expected an equivalent to:' + expectedSpecs[i].getFullName(); - } - return null; -} - -function compareDescription(a, b) { - if (a.description === b.description) { - return 0; - } - return a.description < b.description ? -1 : 1; -} - -function compareRunners(actual, expected) { - return compareSpecs( - actual.specs().sort(compareDescription), - expected.specs().sort(compareDescription) - ); -} - -var MetaMatchers = { - toEqualSpecsIn(/* util, customEqualityMatcher*/) { - return { - compare(actualDescribeFunction, expectedDescribeFunction) { - if (typeof actualDescribeFunction !== 'function') { - throw Error('toEqualSpecsIn() should be used on a describe function'); - } - if (typeof expectedDescribeFunction !== 'function') { - throw Error('toEqualSpecsIn() should be passed a describe function'); - } - var actual = getRunnerWithResults(actualDescribeFunction); - var expected = getRunnerWithResults(expectedDescribeFunction); - var errorMessage = compareRunners(actual, expected); - - return { - passed: !errorMessage, - message: function() { - return [ - errorMessage, - 'The specs are equal. Expected them to be different.', - ]; - }, - }; - }, - }; - }, -}; - -module.exports = MetaMatchers; diff --git a/src/test/__tests__/MetaMatchers-test.js b/src/test/__tests__/MetaMatchers-test.js deleted file mode 100644 index 365b3aca83..0000000000 --- a/src/test/__tests__/MetaMatchers-test.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @emails react-core - */ - -'use strict'; - -var MetaMatchers = require('MetaMatchers'); - -describe('meta-matchers', function() { - - beforeEach(function() { - jasmine.addMatchers(MetaMatchers); - }); - - function a() { - it('should add 1 and 2', function() { - expect(1 + 2).toBe(3); - }); - } - - function b() { - it('should add 1 and 2', function() { - expect(1 + 2).toBe(3); - }); - } - - function c() { - it('should add 1 and 2', function() { - expect(1 + 2).toBe(3); - }); - it('should mutiply 1 and 2', function() { - expect(1 * 2).toBe(2); - }); - } - - function d() { - it('should add 1 and 2', function() { - expect(1 + 2).toBe(3); - }); - it('should mutiply 1 and 2', function() { - expect(1 * 2).toBe(2); - expect(2 * 1).toBe(2); - }); - } - - it('tests equality of specs', function() { - expect(a).toEqualSpecsIn(b); - }); - - it('tests inequality of specs and expects', function() { - expect(b).not.toEqualSpecsIn(c); - expect(c).not.toEqualSpecsIn(d); - }); - -});