├── .circleci
└── config.yml
├── .eslintrc.json
├── .gitignore
├── .nvmrc
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── karma.conf.js
├── package.json
├── rollup.config.js
├── src
├── context.js
├── index.js
├── mount.js
├── setup-app.js
└── visit.js
├── tests
├── .eslintrc.json
├── index.js
├── mount-test.js
├── setup-app-test.js
└── visit-test.js
└── yarn.lock
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | references:
2 | defaults: &defaults
3 | working_directory: ~/react
4 | docker:
5 | - image: circleci/node:8-browsers
6 |
7 | cache_key: &cache_key
8 | react-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}
9 |
10 | attach_workspace: &attach_workspace
11 | attach_workspace:
12 | at: ~/
13 |
14 | version: 2.0
15 | jobs:
16 | install:
17 | <<: *defaults
18 | steps:
19 | - checkout
20 | - restore_cache:
21 | name: Restore cache
22 | key: *cache_key
23 | - run:
24 | name: Install dependencies
25 | command: yarn
26 | - save_cache:
27 | name: Save cache
28 | key: *cache_key
29 | paths:
30 | - node_modules
31 | - persist_to_workspace:
32 | root: ~/
33 | paths:
34 | - react
35 | - .ssh
36 |
37 | lint:
38 | <<: *defaults
39 | steps:
40 | - *attach_workspace
41 | - run:
42 | name: Lint package
43 | command: yarn lint
44 |
45 | build:
46 | <<: *defaults
47 | steps:
48 | - *attach_workspace
49 | - run:
50 | name: Build documentation
51 | command: yarn docs
52 | - run:
53 | name: Build package
54 | command: yarn build
55 | - persist_to_workspace:
56 | root: ~/
57 | paths:
58 | - react/dist
59 | - react/docs
60 |
61 | test:
62 | <<: *defaults
63 | steps:
64 | - *attach_workspace
65 | - run:
66 | name: Run tests
67 | command: yarn test
68 |
69 | deploy:
70 | <<: *defaults
71 | steps:
72 | - *attach_workspace
73 | - run:
74 | name: Publish to NPM
75 | command: |
76 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
77 | npx bigtest-release --access=public
78 | - run:
79 | name: Push Tags
80 | command: git push --tags
81 |
82 | workflows:
83 | version: 2
84 | default:
85 | jobs:
86 | - install
87 | - lint:
88 | requires:
89 | - install
90 | - build:
91 | requires:
92 | - install
93 | - test:
94 | requires:
95 | - install
96 | - deploy:
97 | requires:
98 | - lint
99 | - build
100 | - test
101 | filters:
102 | branches:
103 | only: master
104 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": [
4 | "standard",
5 | "plugin:react/recommended"
6 | ],
7 | "rules": {
8 | "semi": ["error", "always"],
9 | "space-before-function-paren": ["error", {
10 | "anonymous": "never",
11 | "named": "never",
12 | "asyncArrow": "always"
13 | }],
14 | "indent": ["error", 2]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /node_modules
3 | /dist
4 | /docs
5 | *.log
6 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 8
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5 |
6 | ## [Unreleased]
7 |
8 | ## [0.1.2] - 2018-06-20
9 |
10 | ### Fixed
11 |
12 | - exception documentation
13 |
14 | ## [0.1.1] - 2018-05-30
15 |
16 | ### Fixed
17 |
18 | - README typos
19 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at oss@frontside.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 BigTest
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # :warning: DEPRECATED :warning:
2 | In order to make BigTest development faster and friction free, we've consolidated all of our individual projects into a [single repository on the Frontside Organization](https://github.com/thefrontside/bigtest). We'd love to see you there!
3 |
4 | _note: the last release from this repository was 0.1.2_
5 |
6 | ## @bigtest/react
7 |
8 | React DOM helpers for testing big
9 |
10 | ## What is this?
11 |
12 | This package aims to provide a set of helpers to make it easier to
13 | acceptance test your React applications.
14 |
15 | The `mount` helper will resolve after mounting a component in a
16 | freshly inserted DOM node. Subsequent uses will clean up any previously
17 | mounted component.
18 |
19 | ``` javascript
20 | import { mount } from '@bigtest/react';
21 | import Button from '../src/components/button';
22 |
23 | describe('My Button', () => {
24 | // `mount` returns a promise that resolves after rendering
25 | beforeEach(() => mount(() => ));
26 |
27 | it('renders', () => {
28 | expect(document.querySelector('.button')).to.exist;
29 | });
30 | });
31 | ```
32 |
33 | The `setupAppForTesting` helper not only mounts the application
34 | component, but will resolve with an instance of the component, and
35 | provide an in-memory history object for [React
36 | Router](https://github.com/ReactTraining/react-router) to use during
37 | testing.
38 |
39 | ``` javascript
40 | import { setupAppForTesting } from '@bigtest/react';
41 | import App from '../src/app';
42 |
43 | describe('My Application', () => {
44 | let app;
45 |
46 | beforeEach(async () => {
47 | app = await setupAppForTesting(App);
48 | });
49 |
50 | it('renders', () => {
51 | expect(document.querySelector('#app')).to.exist;
52 | });
53 |
54 | it('has a history prop', () => {
55 | // this prop is only provided if defined in `propTypes`
56 | expect(app.props).to.have.property('history');
57 | });
58 | });
59 | ```
60 |
61 | The `visit` helpers (`visit`, `goBack`, `goForward`, and `location`)
62 | only work after using `setupAppForTesting` and interface with the
63 | application's `history` prop to navigate between routes.
64 |
65 | ``` javascript
66 | import { setupAppForTesting, visit, location } from '@bigtest/react';
67 | import App from '../src/app';
68 |
69 | describe('My Application', () => {
70 | beforeEach(() => setupAppForTesting(App));
71 |
72 | describe('navigating', () => {
73 | beforeEach(() => visit('/some-route'));
74 |
75 | it('is at the new route', () => {
76 | expect(location()).to.have.property('pathname', '/some-route');
77 | });
78 | });
79 | });
80 | ```
81 |
82 | The `cleanup` helper is called at the start of every `mount` and
83 | `setupAppForTesting` call to clean up any previously mounted component
84 | or application. It can be also used on it's own if you need to clean
85 | up previously mounted components yourself.
86 |
87 | ``` javascript
88 | import { mount, cleanup } from '@bigtest/react';
89 | import Button from '../src/components/button';
90 |
91 | describe('My Button', () => {
92 | beforeEach(() => mount(() => ));
93 |
94 | // it's best not to do this so that you can investigate and play
95 | // with your component for debugging purposes after a test runs
96 | afterEach(() => cleanup());
97 |
98 | // ...
99 | });
100 | ```
101 |
102 | ### Options
103 |
104 | Both `mount` and `setupAppForTesting` accept a hash of `options` as
105 | the second argument.
106 |
107 | ``` javascript
108 | mount(Component, {
109 | // used as the ID of the newly inserted DOM node
110 | mountId: 'testing-root',
111 |
112 | // where to insert the new DOM node
113 | rootElement: document.body,
114 |
115 | // called before the component is mounted; if a promise is returned,
116 | // the component will mount after it resolves
117 | setup: () => {},
118 |
119 | // called during the next `cleanup` invocation, either at the
120 | // beginning of the next `mount` or `setupAppForTesting` call, or
121 | // when invoking `cleanup` directly; like `setup`, any returned
122 | // promise will cause it to wait until resolving
123 | teardown: () => {}
124 | })
125 | ```
126 |
127 | Additionally, `setupAppForTesting` accepts a `props` option which will
128 | pass along any user-defined props to the application component.
129 |
130 | ``` javascript
131 | setupAppForTesting(App, {
132 | props: {
133 | store: createStore(),
134 | // you can provide your own history object as well
135 | history: createHistory(historyOptions)
136 | }
137 | })
138 | ```
139 |
140 | ## Reusability
141 |
142 | For the most optimal experience when testing your application, any
143 | required application logic that does not live in a component's
144 | life-cycle hooks should be made reusable. Either by providing the
145 | necessary `setup` and `teardown` options, or by moving the logic into
146 | component life-cycle hooks.
147 |
148 | In addition to this, it will probably make sense to make use of
149 | `setupAppForTesting` inside of your own test helper where any other
150 | necessary setup is taken care of. This also allows us to not have to
151 | import our app and duplicate setup logic in every test file.
152 |
153 | ``` javascript
154 | // tests/helpers.js
155 | import { setupAppForTesting } from '@bigtest/react';
156 | export { visit, goBack, goForward, location } from '@bigtest/react';
157 |
158 | import App from '../src/app';
159 | import createServer from './mocks/server';
160 |
161 | export function setupApplicationForTesting() {
162 | beforeEach(async function () => {
163 | this.app = await setupAppForTesting(App, {
164 | setup: () => this.server = createServer(),
165 | teardown () => this.server.shutdown()
166 | });
167 | });
168 | }
169 | ```
170 |
171 | ## Even Better Testing
172 |
173 | While these helpers allow you to repeatedly mount your application for
174 | testing, interacting with your application can also be troublesome.
175 |
176 | [`@bigtest/interactor`](https://github.com/bigtestjs/interactor)
177 | provides an easy way to interact with the various parts of your app
178 | via any browser, just like a user would interact with your app. In
179 | fact, composable interactors are the perfect companion for testing
180 | an app made with composable components.
181 |
182 | ``` javascript
183 | import { setupAppForTesting } from '@bigtest/react';
184 |
185 | import App from '../src/app';
186 | import HomePageInteractor from './interactors/home';
187 |
188 | describe('My Application', () => {
189 | const home = new HomePageInteractor();
190 | beforeEach(() => setupAppForTesting(App))
191 |
192 | it('has a fancy button', () => {
193 | expect(home.button.isPresent).to.be.true;
194 | expect(home.button.isFancy).to.be.true;
195 | });
196 | });
197 | ```
198 |
199 | Check out the
200 | [`@bigtest/interactor`](https://github.com/bigtestjs/interactor) repo
201 | for more information about interactors and how they work.
202 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = (config) => {
2 | config.set({
3 | frameworks: ['mocha'],
4 | reporters: ['mocha'],
5 | browsers: ['Chrome'],
6 |
7 | files: [
8 | { pattern: 'tests/index.js', watched: false }
9 | ],
10 |
11 | preprocessors: {
12 | 'tests/index.js': ['webpack']
13 | },
14 |
15 | mochaReporter: {
16 | showDiff: true
17 | },
18 |
19 | webpack: {
20 | module: {
21 | rules: [{
22 | test: /\.js$/,
23 | exclude: /node_modules/,
24 | loader: 'babel-loader',
25 | options: {
26 | babelrc: false,
27 | presets: [
28 | ['@babel/env', {
29 | modules: false
30 | }],
31 | '@babel/stage-3',
32 | '@babel/react'
33 | ]
34 | }
35 | }]
36 | }
37 | },
38 |
39 | webpackMiddleware: {
40 | stats: 'minimal'
41 | },
42 |
43 | plugins: [
44 | 'karma-chrome-launcher',
45 | 'karma-mocha',
46 | 'karma-mocha-reporter',
47 | 'karma-webpack'
48 | ]
49 | });
50 | };
51 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@bigtest/react",
3 | "description": "React DOM helpers for testing big",
4 | "version": "0.1.2",
5 | "license": "MIT",
6 | "repository": "https://github.com/bigtestjs/react",
7 | "main": "dist/umd/index.js",
8 | "module": "dist/esm/index.js",
9 | "files": [
10 | "dist",
11 | "docs",
12 | "src"
13 | ],
14 | "scripts": {
15 | "build": "rollup --config",
16 | "docs": "bigtest-docs",
17 | "lint": "eslint --ignore-path .gitignore ./",
18 | "postpublish": "bigtest-tag-version",
19 | "start": "karma start",
20 | "test": "karma start --single-run"
21 | },
22 | "peerDependencies": {
23 | "react": "^16.0.0",
24 | "react-dom": "^16.0.0"
25 | },
26 | "devDependencies": {
27 | "@babel/core": "^7.0.0-beta.0",
28 | "@babel/polyfill": "^7.0.0-beta.0",
29 | "@babel/preset-env": "^7.0.0-beta.0",
30 | "@babel/preset-react": "^7.0.0-beta.0",
31 | "@babel/preset-stage-3": "^7.0.0-beta.0",
32 | "@babel/register": "^7.0.0-beta.0",
33 | "@bigtest/meta": "bigtestjs/meta#v0.0.1",
34 | "babel-eslint": "^8.2.3",
35 | "babel-loader": "^8.0.0-beta.0",
36 | "chai": "^4.1.2",
37 | "chai-as-promised": "^7.1.1",
38 | "eslint": "^4.19.1",
39 | "eslint-config-standard": "^11.0.0",
40 | "eslint-plugin-import": "^2.12.0",
41 | "eslint-plugin-node": "^6.0.1",
42 | "eslint-plugin-promise": "^3.8.0",
43 | "eslint-plugin-react": "^7.8.2",
44 | "eslint-plugin-standard": "^3.1.0",
45 | "karma": "^2.0.2",
46 | "karma-chrome-launcher": "^2.2.0",
47 | "karma-mocha": "^1.3.0",
48 | "karma-mocha-reporter": "^2.2.5",
49 | "karma-webpack": "^3.0.0",
50 | "mocha": "^5.2.0",
51 | "prop-types": "^15.6.1",
52 | "react": "^16.0.0",
53 | "react-dom": "^16.0.0",
54 | "rollup": "^0.59.3",
55 | "rollup-plugin-babel": "^4.0.0-beta.0",
56 | "rollup-plugin-commonjs": "^9.1.3",
57 | "rollup-plugin-node-resolve": "^3.3.0",
58 | "webpack": "^3.10.0"
59 | },
60 | "dependencies": {
61 | "history": "^4.7.2"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 | import resolve from 'rollup-plugin-node-resolve';
3 | import commonjs from 'rollup-plugin-commonjs';
4 | import pkg from './package.json';
5 |
6 | export default {
7 | input: 'src/index.js',
8 | output: [{
9 | format: 'umd',
10 | name: 'BigTest.ReactHelpers',
11 | file: pkg.main,
12 | globals: {
13 | 'react': 'React',
14 | 'react-dom': 'ReactDOM'
15 | }
16 | }, {
17 | format: 'es',
18 | file: pkg.module
19 | }],
20 | external: [
21 | 'react',
22 | 'react-dom'
23 | ],
24 | plugins: [
25 | resolve(),
26 | commonjs({
27 | exclude: 'src/**'
28 | }),
29 | babel({
30 | babelrc: false,
31 | comments: false,
32 | presets: [
33 | ['@babel/env', {
34 | modules: false
35 | }],
36 | '@babel/stage-3',
37 | '@babel/react'
38 | ]
39 | })
40 | ]
41 | };
42 |
--------------------------------------------------------------------------------
/src/context.js:
--------------------------------------------------------------------------------
1 | // local context where various things can be persisted using the
2 | // helper functions below
3 | let context = Object.create(null);
4 |
5 | /**
6 | * Clears everything from the current context by creating a brand new
7 | * empty context.
8 | *
9 | * @private
10 | */
11 | export function clearContext() {
12 | context = Object.create(null);
13 | }
14 |
15 | /**
16 | * Adds things to the context by key, value.
17 | *
18 | * @private
19 | * @param {Object} newContexts - Hash of things to store in the
20 | * current context
21 | */
22 | export function setContext(newContext) {
23 | Object.assign(context, newContext);
24 | }
25 |
26 | /**
27 | * Retrieves the value of a specific key in the current context.
28 | *
29 | * @private
30 | * @param {String} key - The key of the value to retrieve
31 | * @param {String|Boolean} [error] - Specific error message to throw
32 | * @returns Value of `key` within the current context
33 | * @throws {Error} when `key` is not fonud in the current context
34 | */
35 | export function getContext(key, error) {
36 | if (typeof error === 'undefined') {
37 | error = `no ${key} context, make sure \`setupAppForTesting\` was called`;
38 | }
39 |
40 | if (key in context) {
41 | return context[key];
42 | } else if (error) {
43 | throw new Error(error);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export { default as mount, cleanup } from './mount';
2 | export { default as setupAppForTesting } from './setup-app';
3 | export { visit, goBack, goForward, location } from './visit';
4 |
--------------------------------------------------------------------------------
/src/mount.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, unmountComponentAtNode } from 'react-dom';
3 | import { clearContext, setContext, getContext } from './context';
4 |
5 | /**
6 | * Creates a div with an ID and appends it into the `$root` element.
7 | *
8 | * @private
9 | * @param {String} id - Div ID
10 | * @param {Node} $root - Element to append the div into
11 | * @returns {Node} The appended div element
12 | */
13 | function insertNode(id, $root) {
14 | let $node = document.createElement('div');
15 | $node.id = id;
16 | $root.appendChild($node);
17 | return $node;
18 | }
19 |
20 | /**
21 | * Cleans up any component that was mounted by previously calling
22 | * `mount`, and clears the current context used by other helpers.
23 | *
24 | * The `teardown` option provided to the previous `mount` function
25 | * will be called when using this helper. If a promise is returned,
26 | * the cleanup will not happen until after that promise resovles.
27 | *
28 | * @function cleanup
29 | * @returns {Promise} Resolves after unmounting the component and
30 | * clearing the current context
31 | */
32 | export function cleanup() {
33 | let {
34 | node,
35 | teardown = () => {}
36 | } = getContext('mountOptions', false) || {};
37 |
38 | // maybe teardown
39 | return Promise.resolve().then(teardown)
40 | // unmount any existing node and clear the context
41 | .then(() => {
42 | if (node) {
43 | unmountComponentAtNode(node);
44 | node.parentNode.removeChild(node);
45 | }
46 |
47 | clearContext();
48 | });
49 | }
50 |
51 | /**
52 | * Mounts the component within a freshly inserted dom node. If there
53 | * was a component previously mounted by this function, the `cleanup`
54 | * helper is automatically used to safely unmount it and clear any
55 | * existing context.
56 | *
57 | * ``` javascript
58 | * await mount(() => (
59 | *