ReactClassEquivalence

This commit is contained in:
Dmitrii Abramov
2016-05-25 21:49:31 +01:00
committed by Dan Abramov
parent 517ed859b4
commit 5d332f22b4
7 changed files with 108 additions and 212 deletions
+6 -1
View File
@@ -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);
@@ -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);
}
@@ -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
@@ -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}`
);
},
});
@@ -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');
});
-135
View File
@@ -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;
-62
View File
@@ -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);
});
});