Thank you for helping us make this library more robust and stable.
- Tests reduce bugs in new features and existing features
- Tests are good documentation
- Tests reduce the cost of change and refactoring
- Tests improve code design
Testing is done using Mocha, Jest, and Storybook. Roughly speaking: Jest tests DOM structure and visual regression with images, Mocha tests user interaction and events, and Storybook allows you to visually inspect and interact with a component.
- Run lint, Karma/PhantomJS environment, and snapshot tests with
npm test
. - Test Mocha tests interactively in your browser.
- Start server from terminal with
npm start
- Browse to http://localhost:8001
- Start server from terminal with
- Run snapshot tests with
npm run test:snapshot
or, for just a specific file:npm run test:snapshot components/button/
. - The entire test suite may take up to 10 minutes to run. To update Jest snapshots for a single component, use
npm run test:snapshot:update -- -t=popover
where the test name containspopover
.
Add DOM snapshot, image snapshot, and documentation site examples to Storybook for manual testing.
npm start
and browse to http://localhost:9001 to view Storybook stories.
There are two parts to code linting: style and quality. Prettier formats JavaScript, markdown and JSON to a consistent style for increased readability. ESLint checks for code quality. Many editors have prettier
format-on-save options. Use eslint-disable-line [RULE]
within tests for necessary exceptions.
npm run lint
will check style and quality.npm run lint:fix
will fix most style issues.npm run lint:quality
will runeslint
.npm run lint:style
will runprettier
.
Story-based tests use Jest, React Storybook, and Storyshots to automatically create DOM and image snapshots of each story example. Snapshot testing uses the Jest framework to take a snapshot of the state of the DOM when the component is rendered and save it as a string for future comparison. StoryShots utilizes Jest Image Snapshot to test the visual rendering of pages against previously correct versions for visual regression testing. These tests are run without a DOM. Most props that don't involve the user can be tested here.
To create tests automatically, import examples in /components/storybook-stories.js
into /components/story-based-tests.js
also. Then, run npm run test:snapshot
. Markup and image snapshots will be generated for each Storybook story. To update existing snapshots, please use npm run test:snapshot:update
.
Use Jest to test the presence of:
- DOM/markup nodes
- CSS classes
- Styles
- Accessiblity features of the DOM
Do not use Jest for:
- Mouse/keyboard user interaction (event callbacks)
Suites such as DOM snanpshot tests or accessibility tests should be added to all stories and the whole library at once. Stories that do not pass, should be exluded from continuous integration tests and an issue should be created to remove the component from exlusion. In short, add types of testing to all new components by excluding existing components that fail instead of adding existing components to a list of components to test. This forces new components to meet the requirements of the new tests and creates a list of components that need to be worked on instead of a list of components that currently pass.
Snapshot test suite source files that run stories present in /components/story-based-tests.js
:
/tests/story-based.accessibility-test.js
/tests/story-based.image-test.js
/tests/story-based.snapshot-test.js
If a Storybook story should not be tested by Storyshots, please add the suffix NoTest
to the story's name.
- Test if the markup of every state conforms to the SLDS site.
- Always pass HTML IDs in - Many components have the optional
id
property but will generate a random id to use if not passed in. These randomly generated IDs will cause your snapshot tests to fail. The markup text diff may be easier to debug if you change one prop per snapshot and have many snapshots instead of changing many props in one snapshot. - Reuse code examples in
examples
folder for tests. This allows confirmation of the alignment of the documentation site examples with SLDS markup.
- Mocha - Test framework ran in Puppeteer
- Chai w/Expect Syntax - Test assertion library
- Karma - Command line test runner for Mocha
- Sinon - Stub/mock generator for callbacks and human interactions
- Enzyme - manipulate and traverse the DOM with syntax similar to jQuery
- Chai Enzyme - Chai assertions and convenience functions which eliminate asynchronous render complexities
- Istanbul - Measures code coverage for Mocha framework
- react-docgen - Extracts information from React components and generates JSON used by the documentation site
- Tests need to simulate user interactions as closely as possible.
- Tests must work in both PhantomJS via the CLI and in your local browser at http://localhost:8001.
- All pull requests must contain unit testing of:
- All components not in a
private
folder - All props. This includes
children
, but only to check ifchildren
rendered. - Correct parameters for all event callbacks
- Keyboard interactions specified at SLDS site and possibly additional test for
tab
if DOM focus is involved - Mouse interactions. This includes testing if the component gained focus or lost focus when another element is clicked.
- Correct DOM focus manipulation (if applicable)
- Jest snapshots for each SLDS state and variant implemented and all documentation site example.
- All components not in a
- Tests must unmount and clean up the test fixture after each test or grouping of related tests. Do not allow unrelated tests to "bleed" into each other.
- Add to the
PropType
description comments_Tested with snapshot testing._
or_Tested with Mocha testing._
to state to consumers how the prop is tested. This will also give you starting list of what needs testing.assistiveText
,className
,labels
,id
,isOpen
,options
,variant
are examples of props to test with snapshots. eg:
const propTypes = {
/**
* CSS class names to be added to the accordion component. _Tested with snapshot testing._
*/
className: PropTypes.oneOfType([PropTypes.array, PropTypes.object, PropTypes.string]),
/**
* HTML id for accordion component. _Tested with snapshot testing._
*/
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
}
HTML Snapshots are a great way to compare markup with the SLDS site examples.
- Copy markup from design system site
- Convert to JSX. SVGs may require extra attention and hand-conversion.
- Copy JSX into the new component's
render
function to feed the markup into the Jest snapshot npm run test:snapshot
(ornpm run test:snapshot -- -u
to overwrite the existing snapshot)- Return to the component and
npm run test:snapshot -- --watch
- Modify your component until you get the markup correct.
Files ending in .browser-test.jsx
will be run by CI server and in browser.
- ARIA attribute states should be tested with in-browser Mocha tests.
- All mouse/keyboard interactions and events must have Mocha tests.
- For components with user interactions events, real DOM testing is preferred. It is not recommended to use shallow rendering or to modify component prototypes with mock functions for these tests.
- Because they are often easier to debug in the browser, mouse/keyboard user interaction testing should be done using Mocha.
Mocha test suite source file:
/tests/browser-tests.js
Here is a well-commented sample test file which you can copy/paste into a new file to get started:
// Import your external dependencies
import React from 'react';
import PropTypes from 'prop-types';
import chai, { expect } from 'chai';
import chaiEnzyme from 'chai-enzyme';
/* Enzyme Helpers that can mount and unmount React component instances to
* the DOM and set `this.wrapper` and `this.dom` within Mocha's `this`
* context [full source here](tests/enzyme-helpers.js). `this` can
* only be referenced if inside `function () {}`.
*/
import { mountComponent, unmountComponent } from '../../../tests/enzyme-helpers';
// Import your internal dependencies (for example):
import Tree from '../../components/tree';
/* Set Chai to use chaiEnzyme for enzyme compatible assertions:
* https://github.com/producthunt/chai-enzyme
*/
chai.use(chaiEnzyme());
/* A re-usable demo component fixture outside of `describe` sections
* can accept props within each test and be unmounted after each tests.
* This wrapping component will be similar to your wrapping component
* you will create in the React Storybook for manual testing.
*/
const propTypes = {
sampleProp: PropTypes.string
};
class DemoComponent extends React.Component {
constructor (props) {
super(props);
this.state = {};
}
// event handlers
render () {
return (
<Tree />
);
}
});
DemoComponent.displayName = 'DemoComponent';
DemoComponent.propTypes = propTypes;
DemoComponent.defaultProps = defaultProps;
/* All tests for component being tested should be wrapped in a root `describe`,
* which should be named after the component being tested.
* When read aloud, the cumulative `describe` and `it` names should form a coherent
* sentence, eg "Date Picker default structure and css is present with expected
* attributes set". If you are having trouble constructing a cumulative short
* sentence, this may be an indicator that your test is poorly structured.
* String provided as first parameter names the `describe` section. Limit to nouns
* as much as possible/appropriate.`
*/
describe('Component Name here', () => {
/* Below you will find some examples of minimum areas to be tested.
* This should not be considered an exhaustive list. Please ensure
* thorough testing of your code.
*/
describe('Assistive technology and keyboard interactions', () => {
/* Detect if presence of accessibility features such as ARIA
* roles and screen reader text is present in the DOM.
* If your component has an ARIA role in application, and
* does not use `tab-index`, test that the correct keyboard
* navigation is present. Test event callback functions using
* Simulate. For more information, view
* https://github.com/airbnb/enzyme/blob/master/docs/api/ReactWrapper/simulate.md
*/
// String provided as first parameter names the `it` section.
// Use short declarative sentences (sentence with "it").
describe('onClick', () => {
const itemClicked = sinon.spy();
beforeEach(mountComponent(
<DemoComponent itemClicked={itemClicked} />
));
afterEach(unmountComponent);
/* Please notice the of `function () {}` and not () => {}.
* It allows access to the Mocha test context via `this`.
*/
it('calls event handler', function () {
const item = this.wrapper.find('#example-tree-1').find('.slds-tree__item');
// If applicable, use second parameter to pass the data object
item.simulate('click', {});
expect(itemClicked.callCount).to.equal(1);
// If applicable, also test callback's data object for correct contents.
});
});
});
});