1. Introduction
- Virtual DOM
- Support among browsers
2. Node.js and Node Package Manager
- Node
- NPM
- New NPM project
- Starting project
- Installing a package locally
- installing a package globally
- Installing specific version of package
- Configuration
3. Visual Studio
- Visual Studio Code
- Visual Studio Code Extensions
- Node.js tools for Visual Studio
4. Webpack
- Installation
- Configuration and new project
- Running
- Creating bundle.js for deployment
- Creating packages
5. React
- Resources
- Hello World
- Hello World with Node.js
6. JSX
7. Build a react component
- ES6
- Prebuild components
8. Managing state
9. Routing
- Simple routing
- Router
10. Display a list
11. PropTypes
12. Redux
- Store
- Actions (events)
- Reducers (event handlers)
- Enrich page
- Responding to events
13. MobX
- Installation
- Refreshing data
14. TypeScript
- Resources
- The idea
- Installing
- Compiling and running in Visual Studio
- TypScript in Node.js
- Debugging
15. Using external libraries with TypeScript
- jQuery
- React
16. Typescript - Advanced features
17. Testing
- Test runner - Mocha
- Test setup/tear down
- Custom test suite
- Setup environment
- Simple Test
- Running test upon save
- Assertion library - Expect
- Assertion library - Chai
- Mocking (with expect)
- Assertion library comparison
- Testing React Code (React Utils)
- Testing React Code (React Utils + Enzyme)
- Adding TypeScript support1
- JEST test framework
1. Introduction
React is a JavaScript framework for building responsive (single page) applications.
React web
React web - Tic-tac-toe game
Code pen to play with React
Why React.js?
node.js tutorial on guru99.com
Virtual DOM
Support among browsers
2. Node.js and Node Package Manager
Node.js tutorial at w3resource
Node
Having index.js
JavaScript
console.log('Hello from Basics');
Then
CMD node index.jswill write "Hello from Basics" to console output
NPM
New NPM project
CMD npm initThis will create package.json project file (similar to .csproj in C#)
JavaScript
{
"name": "tutorial",
"version": "1.0.0",
"description": "tutorial",
"scripts": {
"start": "node src/index.js"
},
"author": "Zbynek",
}
Starting project
CMD npm startNPM will look in package.json for scripts/start
JavaScript
"scripts": {
"start": "node server.js",
}
and start "node server.js" in in the example above
Installing a package locally
CMD npm install <package>will create local folder node_modules\<package> and add will dependency to package.json
JavaScript
"dependencies": {
"jquery": "2.2.3",
"node_module": "0.0.0"
},
"devDependencies": {
"mocha": "2.4.5"
}
CMD npm installwill read the list of dependencies from package.json and check if they are already downloaded
Note: if an error "cannot rename file" coccurs, close all apps that can lock projects folders (especially Free Commander or browser). Worst case, restart computer.
installing a package globally
CMD npm install -g <package>will copy the package to C:\Users\<user>\AppData\Roaming\npm\<package>
Downloaded packages are held in npm cache under C:\Users\<user>\AppData\Roaming\npm-cache
Installing specific version of package
CMD npm install myPackage@1.0.1
Configuration
CMD npm config editConfig file is stored as C:\windows\<user>\.npmrc
List config
CMD npm config ls -l
3. Visual Studio
Visual Studio Code
Visual Studio Code Extensions
Debugger for Chrome
Code coverage Code coverage - github
Read here how to write Create custom extensions
Node.js tools for Visual Studio
4. Webpack
Helps with:
- bundling all .js files into one .js file - runs as dev server (with the bundled .js file in memory)
Installation
CMD npm install -g webpack
Configuration and new project
JSON
{
"name": "basics1",
"description": "Run simple script with webpack",
"scripts": {
"start": "node devServer.js"
},
"dependencies": {
"jquery": "2.2.3"
},
"devDependencies": {
"extract-text-webpack-plugin": "^1.0.1",
"transfer-webpack-plugin": "^0.1.4",
"webpack": "^1.13.3",
"webpack": "1.13.0",
"webpack-dev-server": "^1.16.2",
"write-file-webpack-plugin": "^3.4.2"
}
}
Create webpack.config.dev.js in root of your project
JSON
var webpack = require('webpack');
var TransferWebpackPlugin = require('transfer-webpack-plugin');
var WriteFilePlugin = require('write-file-webpack-plugin');
module.exports = {
entry: [
'webpack-dev-server/client?http://localhost:3000',
'./index.js'
],
output: {
filename: "bundle.js",
path: __dirname + "/dist",
publicPath: '/'
},
resolve: {
extensions: ["", ".js"]
}
};
Create devserver.js in root of your project
JavaScript
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var config = require('./webpack.config.dev');
var port=3000;
new WebpackDevServer(webpack(config), {
publicPath: config.output.publicPath,
// hot: true, // enables hot reloading
historyApiFallback: true
}).listen(port, 'localhost', function (err, result) {
if (err) {
console.log(err);
}
console.log('Listening at localhost:'+port);
});
Create index.html as starting point
HTML <html> <body> <div id="root"></div> <script src="bundle.js"></script> <!-- bundle.je gets created webpack (all .js in one file) --> </body> </html>Create index.js as starting point
JavaScript
document.write("hello from Basics1/index.js");
Running
Bundle.js can be seen by typing "localhost:3000/bundle.js"
Creating bundle.js for deployment
JSON
webpack.publish.config.json
...
module.exports = {
entry: [
'./index.tsx'
],
output: {
filename: "bundle.js",
path: __dirname + "/dist",
publicPath: ''
},
..
the entry elements doesn ot have to have any dev-server infoCreate new command in package.json to use new config
JSON
package.json
"scripts": {
"package": "webpack --config webpack.publish.config.json"
...
},
and run
CMD npm run packageto create bundle.js under dist
Creating packages
Add following to package.json
JSON
package.json
"scripts": {
"build": "gulp pack",
...
},
"devDependencies": {
"gulp": "^3.9.1",
"gulp-clean": "^0.3.2",
"gulp-shell": "^0.5.2",
...
}
and run "npm install" to install gulpCreate new gulpfile.js in root
JSON
package.json
var gulp = require('gulp'),
shell = require('gulp-shell'),
clean = require('gulp-clean');
gulp.task('clean', function () {
return gulp.src(['./dist'], { read: false }).pipe(clean());
});
gulp.task('compile-tsx', ['clean'], shell.task(['tsc']))
gulp.task('copy-files', ['compile-tsx'], function() {
return gulp.src(['src/**/!(*.tsx)', 'package.json']).pipe(gulp.dest('./dist'));
});
gulp.task('pack', ['copy-files'], shell.task(['npm pack dist/']));
gulp.task('publish', ['copy-files'], shell.task(['npm publish dist/']));
and run
CMD npm run buildto create my-package-1.0.0.tgz in root
To consume the file from other project add the package to package.json
JSON
package.json
"dependencies": {
"my-package": "1.0.0",
...
}
and either copy the my-package-1.0.0.tgz to the root of the project that should consume it
or publish the package to repository and run "npm install" to install the package in under "node_module"
5. React
Resources
Great tutorials at tutorialspoint.com https://www.tutorialspoint.com/reactjs/
Hello World
HTML
<html>
<body onload="myFunction()" />
<h1>React.js basics</h1>
<div id="demo" >This will be replaced by pure JavaScript</div>
<div id="react-app" >This will be replaced by React.js</div>
<div id="react-app1" >This will be replaced by React.js 1</div>
<script src="https://unpkg.com/react@15/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15/dist/react-dom.js"></script>
<script>
function myFunction() {
document.getElementById("demo").innerHTML = "Hello Wrold - Replaced by pure JavaScript";
var rootElement =
React.createElement('div', {},
React.createElement('h1', {}, "People"),
React.createElement('ul', {},
React.createElement('li', {},
React.createElement('h2', {}, "John First"),
React.createElement('a', {href: 'mailto:john@first.com'}, 'john@first.com')
),
React.createElement('li', {},
React.createElement('h2', {}, "Paul Second"),
React.createElement('a', {href: 'mailto:paul@second.com'}, 'paul@second.com')
)
)
)
ReactDOM.render(rootElement, document.getElementById('react-app'))
ReactDOM.render(rootElement, document.getElementById('react-app1'))
}
</script>
</body>
</html>
Hello World with Node.js
HTML
import * as React from 'react';
import * as ReactDOM from 'react-dom';
var rootElement =
React.createElement('div', {},
React.createElement('h1', {}, "People"),
React.createElement('ul', {},
React.createElement('li', {},
React.createElement('h2', {}, "John First"),
React.createElement('a', {href: 'mailto:john@first.com'}, 'john@first.com')
),
React.createElement('li', {},
React.createElement('h2', {}, "Paul Second"),
React.createElement('a', {href: 'mailto:paul@second.com'}, 'paul@second.com')
)
)
)
ReactDOM.render(rootElement, document.getElementById('root'))
and add following to package.json
JSON
"dependencies": {
...
"@types/react": "^0.14.47",
"@types/react-dom": "^0.14.18",
"@types/react-redux": "^4.4.35",
"@types/redux-logger": "^2.6.32",
"@types/redux-thunk": "^2.1.32",
"react": "15.3.2",
"react-dom": "15.3.2"
},
"devDependencies": {
...
"react-hot-loader": "^1.3.0",
"react-logger": "^1.1.0",
"react-redux": "^4.4.6",
"react-router": "^3.0.0",
"react-thunk": "^1.0.0",
"redux": "^3.6.0",
"redux-logger": "^2.7.4",
"redux-thunk": "^2.1.0",
}
and run "nmp install" or install the lastest by "nmp install <package> --save"
6. JSX
Babel preprocessor
JSX on typescriptlang.org
JSX aloows embedding hmlt tag into JavaScrip.
JavaScript
var AboutPage = React.createClass({
render: funtion() {
return (
<div>
<h1 color="blue">About</h1>
<p>Demo page</p>
</div>
);
}
});
The JSX code with nested elements above compiles to JavaScript with React
JavaScript
React.createElement("div", null,
React.createElement("h1", {color:"blue"}, "About" ),
React.createElement("p", null, "Demo page"},
)
7. Build a react component
JavaScript
var React = require('react'); // includes React package
var AboutPage = React.createClass({
render: function() {
return (
<div>
<h1 color="blue">About</h1>
<p>Demo page</p>
</div>
);
}
});
module.Export = AboutPage; // makes the page available
Include that page in main page
JavaScript
var React = require('react');
var AboutPage = require('./components/aboutPage'); // includes AbouPage component
React.render(<AboutPage>, document.getElementById('root')); // renders AboutPage in root element
Create index.html that uses the app built above
HTML <html> <body> <div id="root"></div> <script src="scripts/bundle.js"></script> // gets created by gulp or webpack (all .js in one file) </body> </html>
ES6
JavaScript
<div onClick={this.handleClick}/>
ES6 requires bind
JavaScript
<div onClick={this.handleClick.bind(this)}/>
It can be handled in class constructor
JavaScript
class People extends React.Component{
constructor(props) {
super(props);
this.handleClick=this.handleClick.bind(this);
}
ES5 Stateless component (since React 1.4)
JavaScript
var ContactPage = function(props) {
return (
<h1> Main Street 1, Small City</h1>
);
});
ES6 Stateless component
JavaScript
const ContactPage = (props) => {
return (
<h1> Main Street 1, Small City</h1>
);
});
Stateless components do not have Lifecycle methods
Prebuild components
8. Managing state
Assuming there is a simple React component that renders first name from a Person class.
1. using forceUpdate
In constructor
JavaScriptrender()
JavaScript this.Person.firstNameonClick()
JavaScript
this.person = new Person('Paul');
this.forceUpdate()
2. using State
In constructor
JavaScript
this.state = new Person('John');
render()
JavaScript this.state.person.firstNameonClick()
JavaScript
var newPerson = new Person('Paul');
this.setState(newPerson);
Note: if you use an array, you cannot assign it directly to state, you have to use
JavaScript
this.state = { "people" : people }
Note: Redux is using the same principle, just requires more code.
3. Using @observable in MobX, please refer to the MobX section
9. Routing
Simple routing
You have to display contact page is user types http://mypages.com/#contact
JavaScript
var React = require('react');
var AboutPage = require('./components/aboutPage');
var Contact = require('./components/contact');
var AboutPage = React.createClass({
render: function() {
var Page;
switch (this.props.route) {
case 'contact' : Page = Contact; break;
default: Page = AboutPage;
}
return (
<div>
<Page/>
</div>
);
}
});
function render() {
var myRoute = window.location.hash.substr(1);
React.render(<App route = {myRoute} /> document.getElementById('root'));
}
window.addEventListener('hashchange', render); // listens to URL changes and renders new content
render(); // renders page first time
Router
JavaScript
var React = require('react'); // includes React package
var myRoutes = (
<Route name="app" path="/" handler={require('./main')}>
<DefaultRoute handler=={require('./components/aboutPage'}>
// if path is not set, name is used
<Route name="people" path="allPeople" handler=={require('./components/people'}>
</Route>
);
}
});
module.Export = myRoutes;
Update main.js to use myRoutes
JavaScript
var React = require('react');
var RouteHandler = require('react-router');
var myRoutes = require('./myRoutes');
var App = React.createClass({
render: function() {
return (
<div>
<RouteHandler/>
</div>
);
}
});
Router.run(myRoutes, function(Handler) {
React.render(<handler>, document.getElementById('root'));
});
10. Display a list
Lifecycle: componentWillMount - runs before initial renderning componentDidMount - runs after render (DOM exists) componentWillReceiveProps - run before new properties are received. Does not run on initial render. shouldComponentUpdate - runs before render. If returns falls, render will be ignored. componentWillUpdate - after rendering (when new props are states changed). SetState cannot be called here componentDidUpdate - after componts updates propageated to the DOM componentWillUnmount - before component is unmounted from the DOM
JavaScript
var People = React.createClass({
// initial state is an empty array
getInitialState: function() {
return {
people: []
};
},
// loads data in json format and assigns them to people
componentWillMount: function() {
this.setState({ people: peopleService.getPeople() });
},
render: funtion() {
var createPersonText = function(person) {
return (
// id is necessary for the framework
<li key={author.id}>{person.firstName} {person.lastName}</li>
);
}
return (
<div>
<ul>
{this.state.people.map(createPersonText, this)}
</ul>
</div>
);
}
});
module.exports = People;
11. PropTypes
React.PropTypes.object.isRequired React.PropTypes.func.isRequired React.PropTypes.bool React.PropTypes.number React.PropTypes.string React.PropTypes.array React.PropTypes.funcDo not run in minified version!
JavaScript
var PeopleList = React.createClass({
propTypes: {
people: ReactPropTypes.array.isRequired
},
render: funtion() {
...
If people array has not been passed then browser will show a warning.
12. Redux
Dank Abramov - Twitter
Dan Abramov - You might not need redux
10 Steps tutorial in (full)VS
React-redux environment setup
Single immutable store The only way to change a state is to emit an action States are changed by pure functions (called reducers) Onedirectional data flow Flux - has mutliple stores - stores owns the logic to change the data Action Creators export function setName(name) { return [ type: SET_NAME, name: name } }
Store
let store = createStore(reducer)
store.dispatch(action)
store.subscribe(listener)
replaceReducer(nextReducer)
Immutability: changing a state means returning new object
Reasons for immutable state:
Clarity - change can occur only in one place and it is reducer
Performance - redux does not need to iterate over all propeties to figure out if any of them changed. If reference is different, it changed.
Time-travel debugging
Already in JS:
Number, String, Boolean, Undefined, Null
Mutable:
Objects.Arrays, Functions
Changing a state by creating new object:
in ES6 Object.assign({}, state, {name: 'John'}); // {} empty object
ES5 - Lodash
Ways to enforce immutability
redux-immutable-state-invariant
Immutable.js
Create store
Configure store
configureStore.js
JavaScript
import {createStore} from 'redux';
import rootReducer from './myReducers';
export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState
}
Instantiate store for the app in src/index.js
JavaScript
import configureStore from './store/configureStore'
import {Provider} from 'react-redux'
const store = configureStore();
render(
<Provider store={store}>
<Router history={browseHistory routes={routes} />
</Provider>,
document.getElementById('root')
);
Actions (events)
JavaScript
export function createPerson(person) {
return { type: 'CREATE_PERSON', person};
}
if the action should be reused, definig the action as a const helps avoiding type error
JavaScript // consts defined in an independent file export const CREATE_PERSON = 'CREATE_PERSON';
JavaScript
import * as types from './actiontypes';
export function createPerson(person) {
return { type: types.CREATE_PERSON, person};
}
Reducers (event handlers)
JavaScript
function myReducer(state, action) {
switch (action.type) {
case 'SET_NAME':
return (Object.assign({}, state, { name: 'John' });
default:
return state;
}
}
reducers/index.js
function arrayReducer(state =[], action) {
switch (action.type) {
case types.CREATE_PERSON:
return [...state, Object.assign({}, action.person)];
default:
return state;
}
}
Using Root reducer simplifies multi case statements
JavaScript
import {combineReducers} from 'redux';
import myReducer from './myReducerFile';
const rootReducer = combineReducers({
myReducer : myReducer
});
export default rootReducer;
All reducers are called when an action is dispatched containers - logic - are aware of Redux - subscribe to redux state - dispatch Redux actions - generated from react-redux Presentational - view - read data from Props - invoke callbacks on props - written by hand
Enrich page
JavaScript
import React,{PropTypes} from 'redux';
class People extends React.Component{
constructor(props) {
super(props, contect);
this.state = {
person:{ name:""}
};
this.OnNameChanged = this.OnNameChanged.bind(this);
this.OnClickCreate = this.OnClickCreate.bind(this);
}
onNameChanged(event) {
const person = this.state.person;
person.name = event.target.value;
setState({person:person});
}
onClickCreate() {
alert('${this.state.person.name} would be created here');
}
render() {
return (
<div>
<input type="text" onChange={this.onNameChanged} value{this.state.course.title} />
<input type="submit" onClick={this.onClickCreate} value="Create" />
</div>
);
}
}
export default PeoplePage;
If you start using Redux it will change like this:
JavaScript
import React,{PropTypes} from 'redux';
import {connect} from 'react-redux';
import * as personAction from './personActions';
class PeoplePage extends React.Component{
constructor(props) {
...
}
onClickCreate() {
// props.dispatch is injected by connect(), if connect uses only 1 argument
this.props.dispatch(personAction.createPerson(this.state.person));
}
personDetail(person, index) {
return <div key={index}>{person.name}</div>
}
render() {
return (
<div>
{this.props.people.map(this.personDetail)} //map() iterates over list - this line displays list
...
</div>
);
}
}
function mapStateToProps(state, ownProps) {
return {
people: state.people
};
}
export default connect(mapStateToProps)(PeoplePage);
Flow:
REACT
- user clicks "Create" button
- personPage.js onClickCreate() calls this.props.dispatch(personAction.createPerson(this.state.person))
ACTION
- personAction.js createPerson(aPerson) returns { type='CREATE_PERSON', person=aPerson }
- dispatch() calls all reducers
REDUCERS
- personReducer.js personReducer() returns new array with the person passed addedd into the array
REACT
- personPage.js mapStateToProps(state. ownProps) returns { people: state.people}
- personPage.js render() draws array in this.props.people
Simplify the code by using mapDispatchToProps()
JavaScript
import React,{PropTypes} from 'redux';
import {connect} from 'react-redux';
import * as personAction from './personActions';
class PeoplePage extends React.Component{
constructor(props) {
...
}
PeoplePage.PropTypes = {
people: PropTypes.array.isRequired,
createPerson: PropTypes.func.isRequired
}
onClickCreate() {
this.props.createPerson(this.state.person);
// before was: this.props.dispatch(personAction.createPerson(this.state.person));
}
personDetail(person, index) {
return <div key={index}>{person.name}</div>
}
render() {
return (
<div>
{this.props.people.map(this.personDetail)} //map() iterates over list - this line displays list
...
</div>
);
}
}
function mapStateToProps(state, ownProps) {
return {
people: state.people
};
}
function mapDispatchToProps(dispatch) { // "dispatch" gets injected by the "connect()" function
return {
createPerson: person => dispatch(personActions.createPerson(person))
};
}
export default connect(mapStateToProps, mapDispatchToProps)(PeoplePage);
Simplify the code by using bindActionCreators
JavaScript
import React,{PropTypes} from 'redux';
import {connect} from 'react-redux';
import * as personAction from './personActions';
import {bindActionCreators} from 'redux';
class PeoplePage extends React.Component{
constructor(props) {
}
PeoplePage.PropTypes = {
people: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired,
}
onClickCreate() {
this.props.actions.createPerson(this.state.person);
}
render() {
return (
...
);
}
}
function mapStateToProps(state, ownProps) {
return {
people: state.people
};
}
function mapDispatchToProps(dispatch) { // "dispatch" gets injected by the "connect()" function
return {
actions: bindActionCreators(courseActions, dispatch); mapping all actions in personActions.js
// before was: createPerson: person => dispatch(personActions.createPerson(person));
};
}
export default connect(mapStateToProps, mapDispatchToProps)(PeoplePage);
Responding to events
13. MobX
Installation
CMD npm install mobx --save npm install mobx-react --saveif using TypeScript, set experimentalDecorators to true
JSON
tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true
...
Refreshing data
JavaScript
@observer
class PersonPanel {
@observable person = new Person('John')
...
}
render()
JavaScript this.Person.firstName
JavaScript
onClick() {
this.person = new Person('Paul')
}
14. TypeScript
Resources
Quick Start
Typescript in 30 minutes
The idea
TypeScript
// http://www.typescriptlang.org/docs/handbook/classes.html
export class Person {
lastName: string;
firstName: string;
age: number;
member : boolean;
constructor(aFirstName : string) {
this.firstName = aFirstName;
}
};
will be compiled to JavaScript
JavaScript
var Person = (function () {
function Person(aFirstName) {
this.firstName = aFirstName;
}
return Person;
}());
exports.Person = Person;
that will be deployed to browser.Importing code from other file
TypeScript
// import a function or a class
import {add} from './src/mylib';
import {Person} from './src/mylib';
// import all exported objects
import * as myLib from './src/mylib';
Installing
or
it can installed (globally) by NPM as:
CMD npm install -g typescript
Compiling and running in Visual Studio
In VS 2015 File > Create new project > Other languages > TypeScript. Hit F5.
Compiling from command line
CMD tsc myApp.tstsc.exe is usually located under C:\Program Files (x86)\Microsoft SDKs\TypeScript\1.5\ or C:\Users\<userName>\AppData\Roaming\npm\
TypeScript vs. ES6
typescript vs ecmascript-6/
es5 es2015 typescript/
TypScript in Node.js
Add following to packages.json
JSON
{
"devDependencies": {
...
"typescript": "2.1.1",
"ts-loader": "^1.2.0"
}
}
and run
CMD npm installAdd following to webpack.config.dev.js
JSON
module: {
loaders: [
{ test: /\.ts?$/, loaders: ['ts']} // 'ts' means use 'ts-loader'
],
}
Run
CMD tsc --initto create tsconfig.json. The file contains tsc options then can listed by
CMD tsc -hbe aware that there are different sets of options for Micrososft tsc and Node.js tsc.
Update the file appropriatelly
JSON
{
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"module": "commonjs",
"target": "es5"
},
"include": [
"./src/**/*",
"./index.ts"
],
"exclude": [
"node_modules"
]
}
Create index.ts with some TypeScript code
TypeScript
document.getElementById("root").innerHTML = "Replaced in TypeScript - pure JavaScript still works";
class Person {
firstName: string;
age: number;
constructor(firstName : string) {
this.firstName = firstName;
}
}
var p = new PersonF("John");
document.write("FirtsName=" + p.firstName);
run
CMD npm startto see the code on the website
Debugging
JSON
package.json
"source-map-loader": "^0.1.5"
JSON
tsconfig.json
"compilerOptions": {
"sourceMap": true,
JSON
webpack.config.dev.js
// Enable sourcemaps for debugging webpack's output.
devtool: "source-map",
module: {
preLoaders: [
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{ test: /\.js$/, loader: "source-map-loader" }
]
},
15. Using external libraries with TypeScript
jQuery
Codeplex
Github
Add to the top of your .ts file
TypeScript /// <reference path="jquery.d.ts" />use jQuery in code
TypeScript
var el1 = document.getElementById('content1');
el1.innerHTML = $("<div>ABC</div>").html();
JavaScript 2 does not require to reference .d.ts in the code file. It uses a reference in package.jsonTo search if a types are available for a package go to npmjs.com or http://microsoft.github.io/TypeSearch/
If yes, then run
JSON
npm install --save @types/<package>
or update package.json"
TypeScript
"devDependencies": {
"@types/<package>": "<version>"
}
and run
JSON
npm install
to download the types. The types will be downloaded under node_module\@types\<package>
React
Rect .ts
Tutorial how to install and compile react component with NPM and Webpack
Rect and Webpack
Download by npm
CMD npm install --save react react-dom @types/react @types/react-domTutorial TODO app in react
16. Typescript - Advanced features
17. Testing
Test runner - Mocha
JSON
package.json
{
"devDependencies": {
...
"@types/mocha": "^2.2.34",
"mocha": "2.4.5",
...
}
}
In order to run Mocha, update package.json
JSON
package.json
{
"scripts": {
"pretest": "tsc",
"test": "mocha \"dist/**/*.test.js\"",
...
},
The "pretest" step compiles all typescript files (based on tsconfig.js) into dist folder and the "test" steps will execute the test based on *.test.js file patternLet's write a test. A dummy test to start to show the basics. 1 pass, 1 fail
TypeScript
export class TestException {
message: string;
constructor(aMessage : string) {
this.message = aMessage;
}
};
describe('Dummy test demo', () => {
it('should PASS', () => {
// does nothing
});
it('should FAIL', () => {
throw new TestException("My failed test message");
});
});
Run the tests from command line by executing
CMD npm testNow something more real. Assuming there is a method add that needs to be tested
TypeScript
import * as myLib from './myLib';
describe('MyLib', () => {
it('WHEN add() called THEN correct value returned', () => {
// act
var result = myLib.add(1, 2);
// assert
var expected = 3;
if (result != expected) throw new TestException(result + " != " + expected);
});
});
Test setup/tear down
TypeScript
export class TestException {
message: string;
constructor(aMessage : string) {
this.message = aMessage;
}
};
// Setup / Tear down
before(function(){
console.log(' Runs one time before the first test in the file')
})
after(function(){
console.log(' Runs one time after all tests in the file were executed')
})
beforeEach(function(){
console.log(' Runs before each test in the file')
})
afterEach(function(){
console.log(' Runs after each test in the file')
})
describe('Dummy test demo with setup/tear down', () => {
it('should PASS', () => {
// does nothing
});
it('should FAIL', () => {
throw new TestException("My failed test message");
});
});
Custom test suite
TypeScript
function makeTestSuite(name:string, tests:any) {
describe(name, function () {
before(function () {
console.log(" executed before each test in the tst suite");
});
tests();
after(function () {
console.log(" executed before each test in the test suite");
});
});
}
makeTestSuite('Suite 1 - with setup', function(){
it('Test 1', function(done){
done();
});
it('Test 2', function(done){
done();
});
});
describe('Test Suite2 - without setup', function(){
it('Test 3', function(done){
done();
});
});
Setup environment
and all tests under src ending *.test.js
Create testSetup.js as configuration for Mocha you can use "--reporter progress" to get just limited output (how many tests passed/failed).
JSON
process.env.NODE_ENV = 'test'; // disables hot reloading
require('babel-register')(); // will transpile tests so that they could be written in TypeScript
require.extensions['.css'] = function () {return null;}; // disable features the Mocha does not understand
var jsdom = require('jsdom').jsdom; // simulate browser
var exposedProperties = ['window', 'navigator', 'document']; // helps to simulate browser environment
global.window = ...
if you use TypeScript, you need to compile the ts(x) files to js and copy .css or .less files that are imported in compiled .js files
JSON
package.json
"scripts": {
"pretest": "tsc & xcopy .\\src\\*.less .\\dist\\src\\ /Y /S /D /R",
"test": "mocha --reporter progress testSetup.js \"dist/test/**/*.test.js\"",
...
}
Simple Test
Include "expect" library that provides assert (expect) functionality.
JavaScript
// import expect from 'expect'; ES6 - requires babel
var expect = require('expect');
describe('test group', () => {
it('dummy test - pass', () => {
expect(true).toEqual(true);
});
});
Start tests with
CMD npm test
Running test upon save
JSON
"scripts:"{
"start": "npm-run-all --parallel test:watch open:src",
"open:src": "node devServer.js",
"test:watch": "npm run test -- --watch"
...
}
When multiple nodes are found(e.g. 2 buttons)
Error: This method is only meant to be run on single node. 2 found instead.
Testing components:
export class
Import in {}
Video Testing Connected React Compinents 5:00-6:00
Assertion library - Expect
To install Expect, modify package.json
JSON
package.json
"devDependencies": {
"@types/expect": "^1.13.31",
"expect": "1.19.0",
...
}
TypeScript
import * as myLib from './myLib';
import * as expect from 'expect';
describe('MyLib:', () => {
it('WHEN add() called THEN correct value returned', () => {
// act
var result = myLib.add(1, 2);
// assert
expect(result).toEqual(3);
});
});
Assertion library - Chai
To install Expect, modify package.json
JSON
package.json
"devDependencies": {
"@types/chai": "3.4.30",
"chai": "^3.5.0"
...
}
TypeScript
import * as myLib from './myLib';
import * as chai from 'chai';
chai.should();
var expect = chai.expect;
var assert = chai.assert;
describe('MyLib:', () => {
it('WHEN add() called THEN correct value returned', () => {
// act
var result = myLib.add(1, 2);
// assert
// Option 1
expect(result).to.equals(3);
// expect(result).toEqual(3); 'expect' library style
// Option 2
result.should.equal(3);
// Option 3
assert.equal(result, 3);
});
});
Mocking (with expect)
TypeScript
import * as expect from 'expect';
// method to be mocked
var myService = {
getValue: function () {}
}
describe('MyService:', () => {
// bypass call and return value
it('WHEN getValue() called THEN 3 returned', () => {
// arrange
var spy = expect.spyOn(myService, 'getValue')
var dice = spy.andReturn(3)
// act
var result = myService.getValue();
// assert
spy.restore()
expect(result).toEqual(3);
});
// fail if not called
it('WHEN test runs THEN FAIL if getValue() was not called', () => {
// arrange
var spy = expect.spyOn(myService, 'getValue')
// assert
expect(spy).toHaveBeenCalled('not called')
spy.restore()
});
});
Assertion library comparison
Feature Chai Expect should() syntax x expect() syntax x x assert syntax x integrated mocking x extensions ? xRecommendation to use Expect because:
- It integrates mocking and stubbing functionality, which limits configuration problem issues if these were in 2 separated libraries
- It limits syntax options how to write asserts, which will lean to cleaner
- Recommended by Cory House (author of Pluralsite React-Redux course - assessed with 5 stars)
Testing React Code (React Utils)
JSON
package.json
{
"devDependencies": {
"react-addons-test-utils": "15.0.2",
...
}
and write a test like this
TypeScript
import * as React from 'react';
import * as TestUtils from 'react-addons-test-utils';
import * as expect from 'expect';
import {HelloMessage} from './HelloMessage';
describe('Testing with React utils', () => {
it('WHEN page gets rendered THEN elements are correct', () => {
// arrange
let renderer = TestUtils.createRenderer();
renderer.render('<HelloMessage name="John" />');
let output = renderer.getRenderOutput();
// assert
const h1Element = output.props.children[0];
expect(h1Element.props.value).toBe('Hello John');
});
});
Testing React Code (React Utils + Enzyme)
JSON
package.json
{
"devDependencies": {
"@types/enzyme": "2.4.30",
"enzyme": "2.2.0",
"jsdom": "8.5.0",
...
}
which allows to write markup test like this
TypeScript
...
import * as Enzyme from 'enzyme';
describe('Testing with Enzyme', () => {
it('WHEN page gets rendered THEN elements are correct', () => {
// act
var wrapper = Enzyme.shallow(<HelloMessage name="John" />);
// assert
expect(wrapper.find('h1').text()).toEqual('Counter:');
// if you have e.g. 2 buttons, you can finsd the first one like this
// var incrementButton = wrapper.find('button').first();
// expect(incrementButton.text()).toEqual('INCREMENT'); // multiple control types - use the first
// expect(incrementButton.prop('id')).toEqual('3'); // type of the control
});
});
Adding TypeScript support1
/// <reference path="../mocha.d.ts" />
Expect
https://www.npmjs.com/package/expect
https://github.com/mjackson/expect
npm
http://stackoverflow.com/questions/26977722/how-to-run-mocha-tests-written-in-typescript
$ npm install -g ts-node
$ mocha test.ts --require ts-node/register src/**/*.spec.ts
In test code: http://stackoverflow.com/questions/35812221/typescript-default-import-failing
import * as _expect from 'expect';
const expect = _expect as any as typeof _expect.default;
babel-core
babel-preset-es2015
babel-register
add .babelrc
{
"preset": ["es2015"]
}
"test": "mocha --compilers js:babel-register"
JEST test framework
Jest is a test framework used by Facebook. It integrates test runner, assertion library, mocking and code coverage.
The runner syntax is the same as for Mocha, syntax for expect() is similar, but not the same.
Code coverage can be executed as "npm test -- --coverage", watch as "npm test -- --watch"
To configure Jest for project, use the package.json below.
JSON
package.json
"scripts": {
"test": "jest",
...
},
"jest": {
"transform": {
".(ts|tsx)": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "(/tests/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
],
"testResultsProcessor": "<rootDir>/node_modules/ts-jest/coverageprocessor.js",
"moduleNameMapper": {
"^.+\\.(css|less)$": "<rootDir>/src/tools/styleMock.js",
"^.+\\.(gif|ttf|eot|svg)$": "<rootDir>/src/tools/fileMock.js"
}
},
"devDependencies": {
"@types/enzyme": "^2.7.1",
"@types/jest": "^16.0.3",
"enzyme": "^2.7.0",
"jest": "^18.1.0",
"jest-cli": "^18.1.0",
"react-addons-test-utils": "15.4.2",
"ts-jest": "^18.0.1",
...
}
To write a React test, you can use the sample below
TypeScript
import * as React from 'react'
import { mount, shallow } from 'enzyme'
import { MyComponent } from './MyComponent'
describe('<MyComponent />', () => {
it('should render component', () => {
var store = { subscribe: jest.fn(), dispatch: jest.fn(), getState: jest.fn(), errors: [] }
var props = { headerText : 'My header', store: store }
var wrapper = mount(<MyComponent headerText='My header' store={ store } errors={ [] } />)
expect(wrapper.find('.header').text()).toBe(props.headerText)
});
});
If your code imports .less file, you need update configuration
JSON
package.json
"jest": {
...
"moduleNameMapper": {
"^.+\\.(css|less)$": "<rootDir>/src/fakeStyle.js",
"^.+\\.(gif|ttf|eot|svg)$": "<rootDir>/src/fakeFile.js"
},
...
}
JavaScript // test/fileMock.js module.exports = '';
JavaScript
// test/styleMock.js
module.exports = {};