mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
ReactClassEquivalence
This commit is contained in:
committed by
Dan Abramov
parent
517ed859b4
commit
5d332f22b4
+6
-1
@@ -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');
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user