Skip to content

Instantly share code, notes, and snippets.

@biancadanforth
Last active July 23, 2018 16:18
Show Gist options
  • Save biancadanforth/a86efdb7e9bee085c32dfe38fa1d5f10 to your computer and use it in GitHub Desktop.
Save biancadanforth/a86efdb7e9bee085c32dfe38fa1d5f10 to your computer and use it in GitHub Desktop.
Adding Jest and a simple Jest test to a WebExtension that uses React and Webpack (ignoring package-lock.json), originally from mozilla/webext-commerce
diff --git a/.babelrc b/.babelrc
index 0a69350..c4b45c8 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,5 +1,15 @@
{
- "presets": ["react"],
+ "presets": [
+ "react",
+ [
+ "env",
+ {
+ "targets": {
+ "node": "current"
+ }
+ }
+ ]
+ ],
"plugins": [
"transform-class-properties",
"transform-decorators-legacy"
diff --git a/.eslintrc.json b/.eslintrc.json
index 48f06d7..b197745 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,10 +1,17 @@
{
- "extends": "airbnb",
+ "extends": [
+ "airbnb",
+ "plugin:jest/recommended"
+ ],
"env": {
"webextensions": true,
- "browser": true
+ "browser": true,
+ "jest/globals": true
},
- "plugins": ["react"],
+ "plugins": [
+ "react",
+ "jest"
+ ],
"parser": "babel-eslint",
"rules": {
"object-curly-newline": ["error", {"consistent": true}],
diff --git a/package.json b/package.json
index 0295f4b..d59f901 100644
--- a/package.json
+++ b/package.json
@@ -10,23 +10,31 @@
"build": "webpack --mode=development",
"watch": "webpack --watch --mode=development",
"package": "web-ext build --source-dir build --overwrite-dest",
- "lint": "bin/run_lints.sh"
+ "lint": "bin/run_lints.sh",
+ "test": "jest"
},
"devDependencies": {
"babel-core": "6.26.3",
"babel-eslint": "8.2.6",
+ "babel-jest": "23.4.0",
"babel-loader": "7.1.5",
"babel-plugin-transform-class-properties": "6.24.1",
"babel-plugin-transform-decorators-legacy": "1.3.5",
+ "babel-preset-env": "1.7.0",
"babel-preset-react": "6.24.1",
"copy-webpack-plugin": "4.5.2",
"css-loader": "1.0.0",
+ "enzyme": "3.3.0",
+ "enzyme-adapter-react-16": "1.1.1",
"eslint": "4.19.1",
"eslint-config-airbnb": "17.0.0",
"eslint-import-resolver-webpack": "0.10.1",
"eslint-plugin-import": "2.13.0",
+ "eslint-plugin-jest": "21.18.0",
"eslint-plugin-jsx-a11y": "6.0.3",
"eslint-plugin-react": "7.10.0",
+ "jest": "23.4.1",
+ "react-test-renderer": "16.4.1",
"style-loader": "0.21.0",
"stylelint": "9.3.0",
"stylelint-config-standard": "18.2.0",
@@ -39,5 +47,11 @@
"prop-types": "15.6.2",
"react": "16.4.1",
"react-dom": "16.4.1"
+ },
+ "jest": {
+ "moduleNameMapper": {
+ "\\.css$": "<rootDir>/test/mocks/styleMock.js",
+ "^commerce(.*)$": "<rootDir>/src$1"
+ }
}
}
diff --git a/src/sidebar/components/Accordion.jsx b/src/sidebar/components/Accordion.jsx
index 6e290e9..5987f2d 100644
--- a/src/sidebar/components/Accordion.jsx
+++ b/src/sidebar/components/Accordion.jsx
@@ -13,7 +13,7 @@ import 'commerce/sidebar/components/Accordion.css';
* direct child of an Accordion.
*/
@autobind
-class AccordionSection extends React.Component {
+export class AccordionSection extends React.Component {
static propTypes = {
/** Text displayed in the clickable header of the section */
title: pt.string.isRequired,
@@ -66,7 +66,7 @@ class AccordionSection extends React.Component {
* </Accordion>
*/
@autobind
-export default class Accordion extends React.Component {
+export class Accordion extends React.Component {
static Section = AccordionSection;
static propTypes = {
diff --git a/src/sidebar/index.jsx b/src/sidebar/index.jsx
index 0c9107c..d658263 100644
--- a/src/sidebar/index.jsx
+++ b/src/sidebar/index.jsx
@@ -6,7 +6,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import pt from 'prop-types';
-import Accordion from 'commerce/sidebar/components/Accordion';
+import {Accordion} from 'commerce/sidebar/components/Accordion';
import 'commerce/sidebar/index.css';
diff --git a/test/accordion.test.jsx b/test/accordion.test.jsx
new file mode 100644
index 0000000..80bddc0
--- /dev/null
+++ b/test/accordion.test.jsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import Enzyme from 'enzyme';
+import Adapter from 'enzyme-adapter-react-16';
+
+import {Accordion, AccordionSection} from 'commerce/sidebar/components/Accordion';
+
+Enzyme.configure({ adapter: new Adapter() });
+
+/** create an Accordion with two children, one of which has the
+* initial prop, and test that the child with the initial prop was
+* passed the active prop after rendering */
+describe('<Accordion />', () => {
+ it('renders two <Accordion.Section /> components', () => {
+ const wrapper = Enzyme.shallow(
+ <Accordion>
+ <AccordionSection />
+ <AccordionSection />
+ </Accordion>,
+ );
+ expect(wrapper.find(AccordionSection)).toHaveLength(2);
+ });
+ // it('has one child with the "initial" prop', () => {
+
+ // });
+ // it('and that child was passed the "active" prop after rendering', () => {
+
+ // });
+});
diff --git a/test/mocks/styleMock.js b/test/mocks/styleMock.js
new file mode 100644
index 0000000..f053ebf
--- /dev/null
+++ b/test/mocks/styleMock.js
@@ -0,0 +1 @@
+module.exports = {};

Fix #28: WIP - Add initial Jest test for a React component.

Builds off PR #26.

  • Adds Jest/Enzyme and the first part of a simple React component test, which can be executed via 'npm run test'.
  • Adds eslint rules for Jest
  • Adds new devDeps:
    • babel-jest: Compiles JS code using Babel in Jest tests; this means Babel is run on any files loaded by Jest (and these files are transformed based on .babelrc).
    • babel-preset-env: Compiles ES2015+ down to ES5. Currently this is so we can use 'import' in Node.js for the Jest tests (Jest runs in Node); we add additional rules in .babelrc so that we only perform Babel transforms that are needed for the current Node environment, rather than transforming everything to ES5, which is not needed for Firefox.
    • enzyme: JS testing library for React
    • enzyme-adapter-react-16: Allows Enzyme to be compatible with React 16
    • eslint-plugin-jest: Expose Jest-specific rules for use in ESLint
    • jest: Testing framework for JS including React apps
    • react-test-renderer: A peer dependency for enzyme-adapter-react-16. An experimental React renderer that can be used to render React components to pure JS objects, without depending on the DOM or a native mobile environment.
  • Adds a 'commerce' alias for Jest tests in 'package.json' for React components to match the 'commerce' alias used in Webpack.
  • Adds a style mock for Jest tests, so that we don't fail when we 'import' a stylesheet in a JSX component (this stylesheet 'import' statement is transformed by Webpack via the 'style-loader' and 'css-loader', but we are testing against source files currently)
  • Export both 'Accordion' and 'AccordionSection' classes from 'Accordion.jsx' rather than just 'Accordion', so that 'AccordionSection' can also be used in Jest tests.

Limitations of using Jest:

  • Jest runs in Node. Node's JS implementation is different (how different?) from Firefox's JS implementation. Per this issue: jestjs/jest#848, there is not currently an easy way to run Jest in the browser, nor is it a priority for Facebook.
  • Jest only allows us to perform unit tests on React components. For integration testing, we will still need a solution that lets us interact with the Firefox browser (ex: Mochitests, Marionette).

Remaining Questions:

  • How can I render the original , instead of explicitly passing it two elements in the test? 'Accordion.jsx' renders two AccordionSections already with their own 'this.props.children'.
  • How can I fix the prop-types errors for required props? Do I just have to 'monkey patch' these components and pass dummy props into the components in the test?
@biancadanforth
Copy link
Author

Osmose's feedback to my diff:

Can we store this in a jest.config.js file instead? Leaving tool-specific config out of package.json makes it easier to find.

Formatting for JSDoc comments (example) should look like this:

/**
 * create an Accordion with two children, one of which has the
 * initial prop, and test that the child with the initial prop was
 * passed the active prop after rendering.
 */

Why make this non-default?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment