The standalone stories compiler @component-controls/webpack-compile makes it possible to create custom test runners for the stories embedded in your documentation.
yarn add @component-controls/webpack-compile
For this example, we will create a file tests/testrunner.js
that we can launch using the shell or from a script in your project's package.json
.
node tests/testrunner.js`.
We will assume your component-controls configuration files are located in the .config
folder of your project. We will also specify a custom distribution folder (by default public
) and bundle name (by default component-controls.js
) for the compiler stories bundle. The last parameter is an array of a few presets that are standard for react
projects.
Before launching the webpack compiler, we need to set the
NODE_ENV
environment variable.
tests/testrunner.js
const path = require('path');const { compile } = require('@component-controls/webpack-compile');(async () => {process.env.NODE_ENV = 'development';const { bundleName} = await compile({configPath: path.resolve(__dirname, '../.config'),distFolder: bundleFolder,bundleName: 'stories.js',presets: ['react', 'react-docgen-typescript'],});//more code below})();
The compiler is an async function that returns the name of the bundle of static stories.
The above-compiled bundle of stores is the static version of the stories before they are loaded into the browser.
Any dynamic document and story properties that are calculated at run-time, as well as the stories render functions are not available from the static store.
To load the run-time stories, we will need to use the @component-controls/store package.
yarn add @component-controls/store
And create a file store_loader.js
that will be used by the individual test files to load the static store at run-time.
tests/store_loader.js
const path = require('path');const { loadStore } = require('@component-controls/store');const bundleName = path.resolve(__dirname,'stories.js');const store = loadStore(require(bundleName));module.exports.store = store;
We will first create a separate folder for the dynamically created test files.
The dynamically created test files can be cleaned (erased) at the end of running the tests.
tests/testrunner.js
const fs = require('fs');...let testFolder = path.resolve(__dirname, 'tests');if (!fs.existsSync(testFolder)) {fs.mkdirSync(testFolder);}...
Next, we will iterate through the static store and create a test file for each store(one per document) that has at least one story.
tests/testrunner.js
const staticStore = require(bundleName);staticStore.stores.forEach(store => {if (store.stories && Object.keys(store.stories).length) {const doc = store.doc;fs.writeFileSync(path.resolve(testFolder, `${doc.title}.test.js`), `//import the 'dynamic' storeconst { store } = require('../store_loader');//we will use react-testing-library to render the storiesconst { render: rtlRender, cleanup } = require('@testing-library/react');//the document title is used for test describedescribe('${doc.title}', () => {//rtl cleanup afdter each testafterEach(cleanup);//gain a reference to the dynamnic version of the documentconst doc = store.docs['${doc.title}'];//iterate through the document storiesdoc.stories.forEach(storyId => {const story = store.stories[storyId];const { renderFn } = story;// will create a test only of the story has a valid render functionif (renderFn) {it(story.name, () => {//create a rendered story nodeconst storyRendered = store.config.renderFn(storyId, store);//mount story on rtl renderedconst { asFragment } = rtlRender(storyRendered);//compare snapshotexpect(asFragment).toMatchSnapshot();});}});});`);}});
We will use the jest-cli
package to run jest using its API.
All jest command-line options can be passed as the first argument to
jest-cli
.
tests/testrunner.js
const { run: runJest } = require('jest-cli');...await runJest([], testFolder);
The full source code for testrunner.js
file:
tests/testrunner.js
const path = require('path');const fs = require('fs');const { run: runJest } = require('jest-cli');const { compile } = require('@component-controls/webpack-compile');(async () => {const bundleFolder = __dirname;process.env.NODE_ENV = 'development';const { bundleName} = await compile({configPath: path.resolve(__dirname, '../.config'),distFolder: bundleFolder,bundleName: 'stories.js',presets: ['react', 'react-docgen-typescript'],});let testFolder = path.resolve(__dirname, 'tests');if (!fs.existsSync(testFolder)) {fs.mkdirSync(testFolder);}const staticStore = require(bundleName);staticStore.stores.forEach(store => {if (store.stories && Object.keys(store.stories).length) {const doc = store.doc;fs.writeFileSync(path.resolve(testFolder, `${doc.title}.test.js`), `const { store } = require('../store_loader');const { render: rtlRender, cleanup } = require('@testing-library/react');describe('${doc.title}', () => {afterEach(cleanup);const doc = store.docs['${doc.title}'];doc.stories.forEach(storyId => {const story = store.stories[storyId];const { renderFn } = story;if (renderFn) {it(story.name, () => {const storyRendered = store.config.renderFn(storyId, store);const { asFragment } = rtlRender(storyRendered);expect(asFragment).toMatchSnapshot();});}});});`);}});await runJest([], testFolder);})();