Starting a React Project

Our project for this chapter, and for the remainder of the book, will be a carousel component. Carousels have become ubiquitous on the web, yet many implementations fall short in various ways. By building your own, you’ll be able to adapt it to your project’s needs.

Create a new directory called test-driven-carousel and opening it in VS Code:

 $ ​​mkdir​​ ​​test-driven-carousel/
 $ ​​code​​ ​​test-driven-carousel/

Use VS Code’s “Git: Initialize Repository” command, or run

 $ ​​git​​ ​​init

on the command line. Once the repo is initialized, add the .gitignore from the previous chapter:

 node_modules/
 .vscode/

Then create a package.json using npm:

 $ ​​npm​​ ​​init​​ ​​-y

As in the previous chapters, add a "private": true entry to the package.json to silence various npm warnings:

 // package.json
 {
 "name"​: ​"test-driven-carousel"​,
»"private"​: ​true​,
 "version"​: ​"1.0.0"​,
 "description"​: ​""​,
 "main"​: ​"index.js"​,
 "scripts"​: {
 "test"​: ​"echo ​​"​​Error: no test specified​​"​​ && exit 1"
  },
 "keywords"​: [],
 "author"​: ​""​,
 "license"​: ​"ISC"
 }

With those two core files in place, make an initial commit:

 :tada: First commit

So far, so familiar. Next, we’ll move into new territory as we add support for a little language called JSX.

Using Babel for JSX

Like any other JavaScript library, React is fully usable through plain, ordinary JavaScript code. But hardly anyone uses it that way. Instead, they compile a language called JSX down to ordinary JavaScript.

JSX allows HTML-like markup to be embedded within JavaScript code. Every tag in JSX is converted to a call to React.createElement(). So for instance, the code

 const​ helloJSX = <h1 className=​"super-big"​>Hello, JSX!</h1>;

would be compiled to

 const​ helloJSX = React.createElement(
 'h1'​,
  { className: ​'super-big'​ },
 'Hello, JSX!'
 );

If you’re new to JSX, this new syntax will take some getting used to. But it makes code much easier to read and write than the equivalent series of React.createElement() calls would be. For more details on JSX syntax, take a look at the React docs.[35]

The compiler that translates JSX into ordinary JavaScript is known as Babel.[36] And it does so much more than just JSX: with Babel, you can write code that uses JavaScript features from the future—those that are only implemented in leading-edge browsers—and run that code everywhere. More precisely, you can run that code in any JavaScript environment that supports the ECMAScript 5 standard (2009). Remember Internet Explorer 9? Yep: thanks to Babel, it can run React code!

To start using Babel, add its core package to the project:

 $ ​​npm​​ ​​install​​ ​​--save-dev​​ ​​@babel/[email protected]
 + @babel/[email protected]

As with ESLint, Babel requires some configuration to make it useful. For this project, you’ll start with two popular Babel presets:

  • The React preset,[37] which provides support for JSX.
  • The env preset,[38] which provides support for all new JavaScript syntax features defined in ES2015 (a.k.a. ES6), ES2016, and ES2017. The name “env” refers to its ability to tailor the transpilation pipeline to a specified set of target environments.

Install the presets with npm:

 $ ​​npm​​ ​​install​​ ​​--save-dev​​ ​​@babel/[email protected]​​ ​​@babel/[email protected]
 + @babel/[email protected]
 + @babel/[email protected]

Then bring in the two presets from a new file called .babelrc.js:

 // .babelrc.js
 module.exports = {
  presets: [​'@babel/preset-react'​, ​'@babel/preset-env'​],
 };

Babel will now apply this configuration to every JS file in the project. To try it out, create a JS file called hello-babel.js:

 // hello-babel.js
 import​ figlet ​from​ ​'figlet'​;
 console.log(figlet.textSync(​'Hello, Babel!'​));

This file uses the ES2015 modules syntax in the form of the import keyword. If you try running it in Node without Babel, you’ll get a syntax error:

 $ ​​node​​ ​​hello-babel.js
 /Users/tburnham/code/test-driven-carousel/hello-babel.js:1
 (function (exports, require, module, __filename, __dirname) {
  import figlet from 'figlet';
  ^^^^^^
 
 SyntaxError: Unexpected token import
  at createScript (vm.js:80:10)
  at Object.runInThisContext (vm.js:139:10)
  at Module._compile (module.js:616:28)
  at Object.Module._extensions..js (module.js:663:10)
  at Module.load (module.js:565:32)
  at tryModuleLoad (module.js:505:12)
  at Function.Module._load (module.js:497:3)
  at Function.Module.runMain (module.js:693:10)
  at startup (bootstrap_node.js:191:16)
  at bootstrap_node.js:612:3

To run this file through Babel from the command line, you will need the @babel/cli package:

 $ ​​npm​​ ​​install​​ ​​@babel/[email protected]
 + @babel/[email protected]

@babel/cli includes an executable called babel that you can run with npx:

 $ ​​npx​​ ​​babel​​ ​​hello-babel.js
 "use strict";
 
 var _figlet = _interopRequireDefault(require("figlet"));
 
 function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : { default: obj };
 }
 
 console.log(_figlet.default.textSync('Hello, Babel!'));

You don’t need to understand the details of this code. Long story short, Babel has compiled the import syntax into something that’s compatible with Node and other environments that don’t support ES2015 modules.

You can run the compiled script by piping it into node. But first, install that package it’s trying to import:

 $ ​​npm​​ ​​install​​ ​​[email protected]
 + [email protected]

Now check out what happens:

 $ ​​npx​​ ​​babel​​ ​​hello-babel.js​​ ​​|​​ ​​node
  _ _ _ _ ____ _ _ _
  | | | | ___| | | ___ | __ ) __ _| |__ ___| | |
  | |_| |/ _ | |/ _ | _ / _` | '_ / _ | |
  | _ | __/ | | (_) | | |_) | (_| | |_) | __/ |_|
  |_| |_|\___|_|_|\___( ) |____/ \__,_|_.__/ \___|_(_)

Figlet[39] took a plain old string and turned it into ASCII art. Pretty cool!

You can clean up hello-babel.js and its dependencies, as we won’t need them:

 $ ​​rm​​ ​​hello-babel.js
 $ ​​npm​​ ​​uninstall​​ ​​@babel/cli​​ ​​figlet

Then commit the new .babelrc.js and the changes to package.json:

 :wrench: Initial Babel setup

In the next section, we’ll use the power of Babel to bring exciting new syntax features to the world of Jest testing.

Bridging Jest and Babel

Let’s bring Jest to the party. In order to get Jest to run tests through Babel, you’ll need both jest and a package called babel-jest:

 $ ​​npm​​ ​​install​​ ​​--save-dev​​ ​​[email protected]​​ ​​[email protected]
 + [email protected]
 + [email protected]

As of this writing, you’ll also need a special version of the babel-core package. The reason is that the package recently switched names: Babel 6 called it babel-core, but Babel 7 namespaces it as @babel/core. So this package creates a bridge for projects looking for babel-core to direct them to @babel/core:

 $ ​​npm​​ ​​install​​ ​​--save-dev​​ ​​babel-core@^7.0.0-bridge.0
 + babel-core@^7.0.0-bridge.0

Next, update the scripts entry in package.json to make Jest the project’s official test runner:

 // package.json
 {
 ...
 "scripts"​: {
»"test"​: ​"jest"
  },
 ...
 }

And since you will be writing React components, naturally you will need the react package:

 $ ​​npm​​ ​​install​​ ​​[email protected]
 + [email protected]

Notice that this is the first time in the book that we’ve omitted --save-dev when installing a dependency. Since React will be used at runtime in our app (remember, those JSX tags compile to React.createElement() calls!), it’s not just a development dependency.

You’ll also need the react-dom package, a bridge between React elements and the DOM, though only as a dev dependency for testing:

 $ ​​npm​​ ​​install​​ ​​--save-dev​​ ​​[email protected]
 + [email protected]

Create a test file that takes advantage of JSX and the ES2015 import syntax:

 // src/tests/hello.test.js
 import​ React ​from​ ​'react'​;
 
 describe(​'JSX'​, () => {
  it(​'calls React.createElement'​, () => {
const​ createElementSpy = jest.spyOn(React, ​'createElement'​);
  <h1>Hello, JSX!</h1>;
expect(createElementSpy).toHaveBeenCalledWith(​'h1'​, ​null​, ​'Hello, JSX!'​);
  });
 });

jest.spyOn(React, "createElement") replaces the React.createElement() method with a spy that intercepts calls, allowing us to make assertions about how that method is used.

expect(spy).toHaveBeenCalledWith() does just what it says on the tin, failing if the spy wasn’t called or was called with a different set of arguments.

Give it a try:

 $ ​​npx​​ ​​jest
 
  PASS hello.test.js
  JSX
  ✓ calls React.createElement (5ms)
 
 Test Suites: 1 passed, 1 total
 Tests: 1 passed, 1 total
 Snapshots: 0 total
 Time: 1.373s
 Ran all test suites.

Huzzah! Jest automatically recognized Babel and ran our test file through it before running it. Commit the configuration change:

 :wrench: Initial Jest setup

Next, we’ll bring the ever-helpful ESLint and Prettier into the land of Babel.

Adding ESLint and Prettier

To get this new project linted, start by installing some deps that were introduced in the previous chapter:

 $ ​​npm​​ ​​install​​ ​​--save-dev​​ ​​[email protected]​​ ​​
  ​​[email protected]​​ ​​
  ​​[email protected]
 + [email protected]
 + [email protected]
 + [email protected]

Then add the scripts from last chapter’s package.json:

 // package.json
 {
 ...
 "scripts"​: {
 "test"​: ​"jest"​,
»"lint"​: ​"eslint . && prettier-eslint --list-different **/*.js"​,
»"format"​: ​"prettier-eslint --write **/*.js"
  },
 ...
 }

We’ll use three nested ESLint configurations for this project:

  1. The root configuration, .eslintrc.js
  2. The source configuration, src/.eslintrc.js
  3. The test configuration, src/tests/.eslintrc.js

The root and test configurations are borrowed from last chapter’s project:

 // .eslintrc.js
 module.exports = {
  extends: [​'eslint:recommended'​],
  parserOptions: {
  ecmaVersion: 6,
  },
  env: {
  node: ​true​,
  },
  rules: {
  quotes: [​'error'​, ​'single'​, { avoidEscape: ​true​ }],
 'comma-dangle'​: [​'error'​, ​'always-multiline'​],
  },
 };
 module.exports = {
  plugins: [​'jest'​],
  extends: [​'plugin:jest/recommended'​],
 };

The new configuration in src tells ESLint that our project’s code is intended for a browser environment:

 module.exports = {
  env: {
  browser: ​true​,
  },
 };

Then try out the linter on our test file:

 $ ​​npx​​ ​​eslint​​ ​​src/tests/hello.test.js
 ...
 /Users/tburnham/code/test-driven-carousel/src/tests/hello.test.js
  1:1 error Parsing error: 'import' and 'export' may appear only with
  'sourceType: module'
 
 ✖ 1 problem (1 error, 0 warnings)

The error indicates that ESLint is aware of the import syntax but isn’t sure if this JS file is supposed to be an ES2015 module. For this project, we want all JS files to be ES2015 modules. To get ESLint to recognize them as such, modify the parserOptions in the root ESLint config:

 // .eslintrc.js
 module.exports = {
  ...
» parserOptions: {
» ecmaVersion: 6,
» sourceType: ​'module'​,
» },
  ...
 };

sourceType: ’module’ tells ESLint that our code will run in an environment that supports the ES2015 (a.k.a. ES6) import/export syntax.

Try running the linter again:

 $ ​​npx​​ ​​eslint​​ ​​src/tests/hello.test.js
 ...
 /Users/tburnham/code/test-driven-carousel/src/tests/hello.test.js
  6:5 error Parsing error: Unexpected token <
 
 ✖ 1 problem (1 error, 0 warnings)

Now ESLint is saying that it doesn’t recognize JSX syntax. This, too, is best solved through parserOptions:

 // .eslintrc.js
 module.exports = {
  ...
» parserOptions: {
» ecmaVersion: 6,
» sourceType: ​'module'​,
» ecmaFeatures: {
» jsx: ​true​,
» },
» },
  ...
 };

One more attempt:

 $ ​​npx​​ ​​eslint​​ ​​src/tests/hello.test.js

No news from ESLint is good news! Since you’ll be using React in your tests, it’s a good idea to make the linter aware of it. To do that, you’ll need another plugin, eslint-plugin-react:[40]

 $ ​​npm​​ ​​install​​ ​​--save-dev​​ ​​[email protected]
 + [email protected]

Apply the plugin by adding it to the plugins section of your ESLint config, then add its recommended set of rules to our extends list. This plugin is relevant to app code as well as tests, so it belongs in the root ESLint config:

 // .eslintrc.js
 module.exports = {
» plugins: [​'react'​],
» extends: [​'eslint:recommended'​, ​'plugin:react/recommended'​],
  ...
 };

The set of recommended rules is designed to catch a number of common React coding mistakes. Additionally, eslint-plugin-react can make suggestions targeted to the React version you’re using if you specify it in a settings block:

 // .eslintrc.js
 module.exports = {
  ...
» settings: {
» react: {
» version: ​'16.4.2'​,
» },
» },
 };

What about Prettier? If you’re using VS Code, try running the Format Document command on the test file. If you’re using another editor, use the command npx prettier-eslint --write src/tests/hello.test.js. Either way, the file should run through Prettier without complaint. Thanks to the prettier-eslint bridge, it knows all about the special syntax features that we’ve told ESLint to recognize.

If you’re using VS Code and want to run Prettier on save, this would be a good time to enable editor.formatOnSave for this project. In fact, all of the Workspace Settings from last chapter’s project are appropriate for this project:

 {
 "files.exclude"​: {
 "node_modules"​: ​true​,
 "package-lock.json"​: ​true
  },
 "[javascript]"​: {
 "editor.tabSize"​: 2
  },
 "editor.formatOnSave"​: ​true
 }

Now it’s time to make another commit:

 :wrench: Initial ESLint and Prettier config

Add one last supporting technology before we switch into TDD mode: Wallaby.

Configuring Wallaby for Babel

In order to get Wallaby up and running, we need to make some changes to our config from the last chapter. If you’re not using Wallaby, start Jest in watch mode (npx jest --watchAll) and skip ahead to the next section.

Create a new wallaby.js file in the root of the project:

 module.exports = ​function​(wallaby) {
 return​ {
  testFramework: ​'jest'​,
 
  env: {
  type: ​'node'​,
  },
 
  tests: [​'src/tests/**/*.test.js'​],
  files: [​'src/**/*.js'​, ​'!**/*.test.js'​, ​'!**/.*'​],
 
  compilers: {
'**/*.js'​: wallaby.compilers.babel(),
  },
  };
 };

The compilers entry tells Wallaby that all .js files should be compiled with Babel.

Run the Wallaby.js: Start command. Give it a minute to spin up, and watch as hello.test.js is bedecked in beautiful green annotations.

This calls for a commit:

 :wrench: Initial Wallaby setup

Now we’re ready to build our first test-driven React component!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.186.164