Merge pull request #2952 from sebmarkbage/typescript

Add Basic TypeScript Class Test
This commit is contained in:
Sebastian Markbåge
2015-01-26 17:19:18 -08:00
6 changed files with 620 additions and 2 deletions

71
jest/jest.d.ts vendored Normal file
View File

@@ -0,0 +1,71 @@
declare var jasmine: any;
declare function afterEach(fn: any): any;
declare function beforeEach(fn: any): any;
declare function describe(name: string, fn: any): void;
declare var it: {
(name: string, fn: any): void;
only: (name: string, fn: any) => void;
}
declare function expect(val: any): Expect;
declare var jest: Jest;
declare function pit(name: string, fn: any): void;
declare function spyOn(obj: any, key: string): any;
declare function xdescribe(name: string, fn: any): void;
declare function xit(name: string, fn: any): void;
interface Expect {
not: Expect
toThrow(message?: string): void
toBe(value: any): void
toEqual(value: any): void
toBeFalsy(): void
toBeTruthy(): void
toBeNull(): void
toBeUndefined(): void
toBeDefined(): void
toMatch(regexp: RegExp): void
toContain(string: string): void
toBeCloseTo(number: number, delta: number): void
toBeGreaterThan(number: number): void
toBeLessThan(number: number): void
toBeCalled(): void
toBeCalledWith(...arguments): void
lastCalledWith(...arguments): void
}
interface Jest {
autoMockOff(): void
autoMockOn(): void
clearAllTimers(): void
dontMock(moduleName: string): void
genMockFromModule(moduleObj: Object): Object
genMockFunction(): MockFunction
genMockFn(): MockFunction
mock(moduleName: string): void
runAllTicks(): void
runAllTimers(): void
runOnlyPendingTimers(): void
setMock(moduleName: string, moduleExports: Object): void
}
interface MockFunction {
(...arguments): any
mock: {
calls: Array<Array<any>>
instances: Array<Object>
}
mockClear(): void
mockImplementation(fn: Function): MockFunction
mockImpl(fn: Function): MockFunction
mockReturnThis(): MockFunction
mockReturnValue(value: any): MockFunction
mockReturnValueOnce(value: any): MockFunction
}
// Allow importing jasmine-check
declare module 'jasmine-check' {
export function install(global?: any): void;
}
declare var check: any;
declare var gen: any;

View File

@@ -3,12 +3,29 @@
var ReactTools = require('../main.js');
var coffee = require('coffee-script');
var ts = require('ts-compiler');
module.exports = {
process: function(src, path) {
if (path.match(/\.coffee$/)) {
return coffee.compile(src, {'bare': true});
}
if (path.match(/\.ts$/) && !path.match(/\.d\.ts$/)) {
ts.compile([path], {
skipWrite: true,
module: 'commonjs'
}, function(err, results) {
if (err) {
throw err;
}
results.forEach(function(file) {
// This is gross, but jest doesn't provide an asynchronous way to
// process a module, and ts currently runs syncronously.
src = file.text;
});
});
return src;
}
return ReactTools.transform(src, {harmony: true});
}
};

View File

@@ -58,6 +58,7 @@
"populist": "~0.1.6",
"recast": "^0.9.11",
"sauce-tunnel": "~1.1.0",
"ts-compiler": "^2.0.0",
"tmp": "~0.0.18",
"uglify-js": "~2.4.0",
"uglifyify": "^2.4.0",
@@ -82,7 +83,8 @@
"setupEnvScriptFile": "jest/environment.js",
"testFileExtensions": [
"js",
"coffee"
"coffee",
"ts"
],
"modulePathIgnorePatterns": [
"/build/",

View File

@@ -19,9 +19,14 @@ describe('ReactClassEquivalence', function() {
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 coffee script', function() {
it('tests the same thing for es6 classes and CoffeeScript', function() {
expect(coffee).toEqualSpecsIn(es6);
});
it('tests the same thing for es6 classes and TypeScript', function() {
expect(ts).toEqualSpecsIn(es6);
});
});

View File

@@ -0,0 +1,491 @@
/*!
* Copyright 2015, 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.
*/
///<reference path='../../../../jest/jest.d.ts'/>
///<reference path='./react.d.ts'/>
import React = require('React');
// Before Each
var container;
var attachedListener = null;
var renderedName = null;
class Inner extends React.Component {
getName() {
return this.props.name;
}
render() {
attachedListener = this.props.onClick;
renderedName = this.props.name;
return React.createElement('div', { className: this.props.name });
}
};
function test(element, expectedTag, expectedClassName) {
var instance = React.render(element, container);
expect(container.firstChild).not.toBeNull();
expect(container.firstChild.tagName).toBe(expectedTag);
expect(container.firstChild.className).toBe(expectedClassName);
return instance;
}
// Classes need to be declared at the top level scope, so we declare all the
// classes that will be used by the tests below, instead of inlining them.
// TODO: Consider redesigning this using modules so that we can use non-unique
// names of classes and bundle them with the test code.
// it preserves the name of the class for use in error messages
// it throws if no render function is defined
class Empty extends React.Component { }
// it renders a simple stateless component with prop
class SimpleStateless {
props : any;
render() {
return React.createElement(Inner, {name: this.props.bar});
}
}
// it renders based on state using initial values in this.props
class InitialState extends React.Component {
state = {
bar: this.props.initialValue
};
render() {
return React.createElement('span', {className: this.state.bar});
}
}
// it renders based on state using props in the constructor
class StateBasedOnProps extends React.Component {
constructor(props) {
super(props);
this.state = { bar: props.initialValue };
}
changeState() {
this.setState({ bar: 'bar' });
}
render() {
if (this.state.bar === 'foo') {
return React.createElement('div', {className: 'foo'});
}
return React.createElement('span', {className: this.state.bar});
}
}
// it renders based on context in the constructor
class StateBasedOnContext extends React.Component {
static contextTypes = {
tag: React.PropTypes.string,
className: React.PropTypes.string
}
state = {
tag: this.context.tag,
className: this.context.className
}
render() {
var Tag = this.state.tag;
return React.createElement(Tag, {className: this.state.className});
}
}
class ProvideChildContextTypes extends React.Component {
static childContextTypes = {
tag: React.PropTypes.string,
className: React.PropTypes.string
}
getChildContext() {
return { tag: 'span', className: 'foo' };
}
render() {
return React.createElement(StateBasedOnContext);
}
}
// it renders only once when setting state in componentWillMount
var renderCount = 0;
class RenderOnce extends React.Component {
state = {
bar: this.props.initialValue
}
componentWillMount() {
this.setState({ bar: 'bar' });
}
render() {
renderCount++;
return React.createElement('span', {className: this.state.bar});
}
}
// it should throw with non-object in the initial state property
class ArrayState {
state = ['an array']
render() {
return React.createElement('span');
}
}
class StringState {
state = 'a string'
render() {
return React.createElement('span');
}
}
class NumberState {
state = 1234
render() {
return React.createElement('span');
}
}
// it should render with null in the initial state property
class NullState extends React.Component {
state = null
render() {
return React.createElement('span');
}
}
// it setState through an event handler
class BoundEventHandler extends React.Component {
state = {
bar: this.props.initialValue
}
handleClick = () => {
this.setState({ bar: 'bar' });
}
render() {
return (
React.createElement(Inner, {
name: this.state.bar,
onClick: this.handleClick
})
);
}
}
// it should not implicitly bind event handlers
class UnboundEventHandler extends React.Component {
state = {
bar: this.props.initialValue
}
handleClick() {
this.setState({ bar: 'bar' });
}
render() {
return React.createElement(
Inner, { name: this.state.bar, onClick: this.handleClick }
);
}
}
// it renders using forceUpdate even when there is no state
class ForceUpdateWithNoState extends React.Component {
mutativeValue : string = this.props.initialValue
handleClick() {
this.mutativeValue = 'bar';
this.forceUpdate();
}
render() {
return (
React.createElement(Inner, {
name: this.mutativeValue,
onClick: this.handleClick.bind(this)}
)
);
}
}
// it will call all the normal life cycle methods
var lifeCycles = [];
class NormalLifeCycles {
props : any
state = {}
componentWillMount() {
lifeCycles.push('will-mount');
}
componentDidMount() {
lifeCycles.push('did-mount');
}
componentWillReceiveProps(nextProps) {
lifeCycles.push('receive-props', nextProps);
}
shouldComponentUpdate(nextProps, nextState) {
lifeCycles.push('should-update', nextProps, nextState);
return true;
}
componentWillUpdate(nextProps, nextState) {
lifeCycles.push('will-update', nextProps, nextState);
}
componentDidUpdate(prevProps, prevState) {
lifeCycles.push('did-update', prevProps, prevState);
}
componentWillUnmount() {
lifeCycles.push('will-unmount');
}
render() {
return React.createElement('span', {className: this.props.value});
}
}
// warns when classic properties are defined on the instance,
// but does not invoke them.
var getInitialStateWasCalled = false;
class ClassicProperties extends React.Component {
contextTypes = {};
propTypes = {};
getInitialState() {
getInitialStateWasCalled = true;
return {};
}
render() {
return React.createElement('span', {className: 'foo'});
}
}
// it should warn when mispelling shouldComponentUpdate
class NamedComponent {
componentShouldUpdate() {
return false;
}
render() {
return React.createElement('span', {className: 'foo'});
}
}
// it supports this.context passed via getChildContext
class ReadContext extends React.Component {
static contextTypes = { bar: React.PropTypes.string };
render() {
return React.createElement('div', { className: this.context.bar });
}
}
class ProvideContext {
static childContextTypes = { bar: React.PropTypes.string };
getChildContext() {
return { bar: 'bar-through-context' };
}
render() {
return React.createElement(ReadContext);
}
}
// it supports classic refs
class ClassicRefs {
render() {
return React.createElement(Inner, {name: 'foo', ref: 'inner'});
}
}
// Describe the actual test cases.
describe('ReactTypeScriptClass', function() {
beforeEach(function() {
container = document.createElement('div');
attachedListener = null;
renderedName = null;
});
it('preserves the name of the class for use in error messages', function() {
expect(Empty.name).toBe('Empty');
});
it('throws if no render function is defined', function() {
expect(() => React.render(React.createElement(Empty), container)).toThrow();
});
it('renders a simple stateless component with prop', function() {
test(React.createElement(SimpleStateless, {bar: 'foo'}), 'DIV', 'foo');
test(React.createElement(SimpleStateless, {bar: 'bar'}), 'DIV', 'bar');
});
it('renders based on state using initial values in this.props', function() {
test(
React.createElement(InitialState, {initialValue: 'foo'}),
'SPAN',
'foo'
);
});
it('renders based on state using props in the constructor', function() {
var instance = test(
React.createElement(StateBasedOnProps, {initialValue: 'foo'}),
'DIV',
'foo'
);
instance.changeState();
test(React.createElement(StateBasedOnProps), 'SPAN', 'bar');
});
it('renders based on context in the constructor', function() {
test(React.createElement(ProvideChildContextTypes), 'SPAN', 'foo');
});
it('renders only once when setting state in componentWillMount', function() {
renderCount = 0;
test(React.createElement(RenderOnce, {initialValue: 'foo'}), 'SPAN', 'bar');
expect(renderCount).toBe(1);
});
it('should throw with non-object in the initial state property', function() {
expect(() => test(React.createElement(ArrayState), 'span', ''))
.toThrow(
'Invariant Violation: ArrayState.state: ' +
'must be set to an object or null'
);
expect(() => test(React.createElement(StringState), 'span', ''))
.toThrow(
'Invariant Violation: StringState.state: ' +
'must be set to an object or null'
);
expect(() => test(React.createElement(NumberState), 'span', ''))
.toThrow(
'Invariant Violation: NumberState.state: ' +
'must be set to an object or null'
);
});
it('should render with null in the initial state property', function() {
test(React.createElement(NullState), 'SPAN', '');
});
it('setState through an event handler', function() {
test(
React.createElement(BoundEventHandler, {initialValue: 'foo'}),
'DIV',
'foo'
);
attachedListener();
expect(renderedName).toBe('bar');
});
it('should not implicitly bind event handlers', function() {
test(
React.createElement(UnboundEventHandler, {initialValue: 'foo'}),
'DIV',
'foo'
);
expect(attachedListener).toThrow();
});
it('renders using forceUpdate even when there is no state', function() {
test(
React.createElement(ForceUpdateWithNoState, {initialValue: 'foo'}),
'DIV',
'foo'
);
attachedListener();
expect(renderedName).toBe('bar');
});
it('will call all the normal life cycle methods', function() {
lifeCycles = [];
test(React.createElement(NormalLifeCycles, {value: 'foo'}), 'SPAN', 'foo');
expect(lifeCycles).toEqual([
'will-mount',
'did-mount'
]);
lifeCycles = []; // reset
test(React.createElement(NormalLifeCycles, {value: 'bar'}), 'SPAN', 'bar');
expect(lifeCycles).toEqual([
'receive-props', { value: 'bar' },
'should-update', { value: 'bar' }, {},
'will-update', { value: 'bar' }, {},
'did-update', { value: 'foo' }, {}
]);
lifeCycles = []; // reset
React.unmountComponentAtNode(container);
expect(lifeCycles).toEqual([
'will-unmount'
]);
});
it('warns when classic properties are defined on the instance, ' +
'but does not invoke them.', function() {
var warn = jest.genMockFn();
console.warn = warn;
getInitialStateWasCalled = false;
test(React.createElement(ClassicProperties), 'SPAN', 'foo');
expect(getInitialStateWasCalled).toBe(false);
expect(warn.mock.calls.length).toBe(3);
expect(warn.mock.calls[0][0]).toContain(
'getInitialState was defined on ClassicProperties, ' +
'a plain JavaScript class.'
);
expect(warn.mock.calls[1][0]).toContain(
'propTypes was defined as an instance property on ClassicProperties.'
);
expect(warn.mock.calls[2][0]).toContain(
'contextTypes was defined as an instance property on ClassicProperties.'
);
});
it('should warn when mispelling shouldComponentUpdate', function() {
var warn = jest.genMockFn();
console.warn = warn;
test(React.createElement(NamedComponent), 'SPAN', 'foo');
expect(warn.mock.calls.length).toBe(1);
expect(warn.mock.calls[0][0]).toBe(
'Warning: ' +
'NamedComponent has a method called componentShouldUpdate(). Did you ' +
'mean shouldComponentUpdate()? The name is phrased as a question ' +
'because the function is expected to return a value.'
);
});
it('should throw AND warn when trying to access classic APIs', function() {
var warn = jest.genMockFn();
console.warn = warn;
var instance = test(
React.createElement(Inner, {name: 'foo'}),
'DIV','foo'
);
expect(() => instance.getDOMNode()).toThrow();
expect(() => instance.replaceState({})).toThrow();
expect(() => instance.isMounted()).toThrow();
expect(() => instance.setProps({ name: 'bar' })).toThrow();
expect(warn.mock.calls.length).toBe(4);
expect(warn.mock.calls[0][0]).toContain(
'getDOMNode(...) is deprecated in plain JavaScript React classes'
);
expect(warn.mock.calls[1][0]).toContain(
'replaceState(...) is deprecated in plain JavaScript React classes'
);
expect(warn.mock.calls[2][0]).toContain(
'isMounted(...) is deprecated in plain JavaScript React classes'
);
expect(warn.mock.calls[3][0]).toContain(
'setProps(...) is deprecated in plain JavaScript React classes'
);
});
it('supports this.context passed via getChildContext', function() {
test(React.createElement(ProvideContext), 'DIV', 'bar-through-context');
});
it('supports classic refs', function() {
var instance = test(React.createElement(ClassicRefs), 'DIV', 'foo');
expect(instance.refs.inner.getName()).toBe('foo');
});
it('supports drilling through to the DOM using findDOMNode', function() {
var instance = test(
React.createElement(Inner, {name: 'foo'}),
'DIV',
'foo'
);
var node = React.findDOMNode(instance);
expect(node).toBe(container.firstChild);
});
});

32
src/modern/class/__tests__/react.d.ts vendored Normal file
View File

@@ -0,0 +1,32 @@
/*!
* Copyright 2015, 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.
*/
/**
* TypeScript Definition File for React.
*
* Full type definitions are not yet officially supported. These are mostly
* just helpers for the unit test.
*/
declare module 'React' {
export class Component {
props: any;
state: any;
context: any;
static name: string;
constructor(props?, context?);
setState(partial : any, callback ?: any) : void;
forceUpdate(callback ?: any) : void;
}
export var PropTypes : any;
export function createElement(tag : any, props ?: any, ...children : any[]) : any
export function render(element : any, container : any) : any
export function unmountComponentAtNode(container : any) : void
export function findDOMNode(instance : any) : any
}