Jest Snapshots
The cc-cli package automatically creates snapshot tests for your components based on the already created documentation stories.
3 levels from easiest to maintain, to full control and adding custom tests:
store
-level is one test file for all your stories.document
-level is one test file per documentation file and inside the stories' tests are dynamically created.story
-level is one test file per documentation file and inside the stories' tests are statically imported and created.
Supports a variety of test renderers:
yarn add @component-controls/cc-cli --dev
If you haven't already, you will need to first install jest and related depenencies:
yarn add jest babel-jest --dev
yarn add ts-jest @types/jest --dev
package.json
"scripts": {..."jest": {// you can use a ts-jest preset"preset": "ts-jest",// or transforms for the used files"transform": {"^.+\\.(ts|tsx)?$": "ts-jest","^.+\\.(js|jsx)$": "babel-jest"},"globals": {// Temporary fix for memory leak in ts-jest, if your project is using typescript.// https://github.com/kulshekhar/ts-jest/issues/1967."ts-jest": {"isolatedModules": true}},},},
Depending on your choice for jest rendering, you will need to install one of the following dependecnies:
react-testing-library
yarn add @testing-library/jest-dom @testing-library/react @types/testing-library__jest-dom --dev
enzyme
yarn add enzyme enzyme-to-json @wojtekmaj/enzyme-adapter-react-17 @types/enzyme @types/testing-library__jest-dom --dev
react-test-renderer
yarn add react-test-renderer @types/react-test-renderer --dev
If you are using images, css files etc - you might need to add some custom transforms for your jest tests
example:
package.json
..."jest": {"transform": {...".+\\.(jpg|jpeg|png|gif|svg)$": "jest-url-loader",".+\\.(css|styl|less|sass|scss)$": "jest-transform-css"}}}
The starter projects are set up to demonstrate no-code jest snapshots:
The command-line interface for cc-cli allows for the quickest, zero-config setup. You can add a
test:create
script to your package.json
file and use it to automatically generate tests for your components based on the existing stories.quick start: the following command will generate a test file in
tests/stories.test.js
based on your component-controls configuration files in the .config
folder.package.json
"scripts": {..."test:create": "cc-cli"},
Parameter | Explanation | Input type | Default value |
---|---|---|---|
--config -c | configuration files folder | string | .config |
--generate -g | generate test files for whole store or story/by story files | 'store' | 'doc' | 'story' | store |
--renderer -r | jest framework renderer | 'rtl' | 'rtr' | 'enzyme' | rtl |
--output -o | generated tests output folder | string | tests |
--test -t | generated tests file name | string | `component-controls.test.(js |
--format -f | generated test files format | 'cjs' | 'esm' | 'ts' | cjs |
--overwrite -w | force ovewrite existing test files | boolean | false |
--bundle -b | bundle path, if store loaded from bundle | string | |
--name -n | name of the test group/describe section | string | component-controls generated |
--include -i | array of test file names to include (only for doc/story formats) | string[] | |
--exclude -x | array of test file names to exclude (only for doc/story formats) | string[] | |
--ally -y | whether to include axe accessibility tests | boolean | true |
--data -d | create a data-driven testinf file with x number of rows | number | 0 (disabled) |
--seed -s | random generator seed when generating data | number | undefined (new data on each run) |
custom configuration folder
yarn cc-cli -c .storybook
custom output folder
yarn cc-cli -c .storybook -o testing
custom test renderer
yarn cc-cli -r enzyme
custom test format (typescript)
yarn cc-cli -f ts
yarn cc-cli -g store -t component-controls.test.ts -f ts
This will generate one single test file for all your documentation files.
- This approach is the easiest to maintain.
- The tests are not associated with a specific component (and its documentation file).
- The tests can not be extended with custom test conditions.
test file source...
tests/component-controls.test.ts
/* prettier-ignore-start */import path from 'path';import {loadConfigurations,extractDocuments,} from '@component-controls/config';import { renderExample, renderErr } from '@component-controls/test-renderers';import { render, act } from '@testing-library/react';import { run, AxeResults } from 'axe-core';import { reactRunDOM } from '@component-controls/test-renderers';import '@component-controls/jest-axe-matcher';describe('component-controls generated', () => {const configPath = path.resolve(__dirname, '../.config');const config = loadConfigurations(configPath);const documents = extractDocuments({ config, configPath });if (documents) {documents.forEach((file: string) => {const exports = require(file);const doc = exports.default;const examples = Object.keys(exports).filter(key => key !== 'default').map(key => exports[key]);if (examples.length) {describe(doc.title, () => {examples.forEach(example => {describe(example.name, async () => {let rendered;act(() => {rendered = renderExample({example,doc,config,});});if (!rendered) {renderErr();return;}it('snapshot', () => {const { asFragment } = render(rendered);expect(asFragment()).toMatchSnapshot();});it('accessibility', async () => {const axeResults = await reactRunDOM<AxeResults>(rendered, run);expect(axeResults).toHaveNoAxeViolations();});});});});}});}});/* prettier-ignore-end */
yarn cc-cli -g doc -f ts -w
This will generate one test file per document, and inside will dynamically create tests for each story.
- This approach is a bit harder to maintain - you will need to re-generate the tests when adding a new component or its documentation stories.
- Each test file is associated with its corresponding component (and its documentation file).
- The tests are difficult to extend with custom test conditions.
test file source...
src/Header/Header.test.ts
/* prettier-ignore-start */import path from 'path';import { run, AxeResults } from 'axe-core';import { reactRunDOM } from '@component-controls/test-renderers';import '@component-controls/jest-axe-matcher';import { loadConfigurations } from '@component-controls/config';import { renderDocument, renderErr } from '@component-controls/test-renderers';import { render, act } from '@testing-library/react';import * as examples from './Header.stories';describe('Header', () => {const configPath = path.resolve(__dirname, '../../.config');const config = loadConfigurations(configPath);let renderedExamples: ReturnType<typeof renderDocument> = [];act(() => {renderedExamples = renderDocument(examples, config);});if (!renderedExamples) {renderErr();return;}renderedExamples.forEach(({ name, rendered }) => {describe(name, async () => {it('snapshot', () => {const { asFragment } = render(rendered);expect(asFragment()).toMatchSnapshot();});it('accessibility', async () => {const axeResults = await reactRunDOM<AxeResults>(rendered, run);expect(axeResults).toHaveNoAxeViolations();});});});});/* prettier-ignore-end */
yarn cc-cli -g story -f ts -w
This will generate one test file per document, and inside will create static tests for each story.
-- This approach is the most difficult to maintain - you will need to re-generate the tests when adding a new component (or its documentation stories) and also when adding new stories to an existing documentation file.
- Each test file is associated with its corresponding component (and its documentation file).
- The tests can be extended with custom test conditions.
This approach can lead to outdated test files (for example when you add a new story in an existing document - it will not be included in the tests).
test file source...
src/Header/Header.test.ts
/* prettier-ignore-start */import path from 'path';import { run, AxeResults } from 'axe-core';import { reactRunDOM } from '@component-controls/test-renderers';import '@component-controls/jest-axe-matcher';import { loadConfigurations } from '@component-controls/config';import { renderExample, renderErr } from '@component-controls/test-renderers';import { render, act } from '@testing-library/react';import doc, { overview } from './Header.stories';describe('Header', () => {const configPath = path.resolve(__dirname, '.config');const config = loadConfigurations(configPath);describe('overview', () => {const example = overview;let rendered;act(() => {rendered = renderExample({example,doc,config,});});if (!rendered) {renderErr();return;}it('snapshot', () => {const { asFragment } = render(rendered);expect(asFragment()).toMatchSnapshot();});it('accessibility', async () => {const axeResults = await reactRunDOM<AxeResults>(rendered, run);expect(axeResults).toHaveNoAxeViolations();});});});/* prettier-ignore-end */
Here is an example of extending a test with some custom test conditions (removed the automatically generated snapshot test and replaced with a test for the label content and background style):
test file source...
src/VariantButton/VariantButton.test.ts
/* prettier-ignore-start */import path from 'path';import { run, AxeResults } from 'axe-core';import { reactRunDOM } from '@component-controls/test-renderers';import '@component-controls/jest-axe-matcher';import { loadConfigurations } from '@component-controls/config';import { renderExample, renderErr } from '@component-controls/test-renderers';import { render, act } from '@testing-library/react';import doc, { primary } from './VariantButton.stories';describe('VariantVutton', () => {const configPath = path.resolve(__dirname, '.config');const config = loadConfigurations(configPath);describe('primary', () => {const example = overview;let rendered;act(() => {rendered = renderExample({example,doc,config,});});if (!rendered) {renderErr();return;}it('custom test', () => {const { getByTestId, container } = render(rendered);// expect(asFragment()).toMatchSnapshot(); - remove auto-snapshots// add specific custom tests for a label and a styleexpect(getByTestId('label')).toHaveTextContent('Primary');expect(container.children[0]).toHaveStyle('background: lightgrey');});it('accessibility', async () => {const axeResults = await reactRunDOM<AxeResults>(rendered, run);expect(axeResults).toHaveNoAxeViolations();});});});/* prettier-ignore-end */