Last active
January 4, 2021 10:01
-
-
Save tmeasday/c0de43edf8f91171fd7ec8bd5c90c064 to your computer and use it in GitHub Desktop.
Using Jest transformer to test CSF stories
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module.exports = { | |
// Make sure you match CSF files! | |
testMatch: ['**/__tests__/**/*.js', '**/?(*.)test.js', '**/?(*.)stories.js'], | |
// You'll probably want to add some code to mock out things for stories | |
setupFilesAfterEnv: ['<rootDir>/.storybook/setupFilesAfterEnv'], | |
transform: { | |
// This is the key bit! | |
'^.+\\.stories\\.js$': '<rootDir>/.storybook/transformer', | |
'\\.js$': 'babel-jest', | |
}, | |
// ... | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// add whatever you need to mock out stuff for stories, this would normally be at the top-level in your storyshots.test.js |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is what we use (Chromatic does the rest!) but you can include any test body you like! | |
import React from 'react'; | |
const renderer = require('react-test-renderer'); | |
// eslint-disable-next-line no-console | |
console.warn = msg => { | |
throw new Error('console.warn: "' + msg + '"'); | |
}; | |
// eslint-disable-next-line no-console | |
console.error = msg => { | |
throw new Error('console.error: "' + msg + '"'); | |
}; | |
export function smokeTestStory(Component, args) { | |
const storyElement = <Component {...args} />; | |
renderer.create(storyElement, { | |
createNodeMock(element) { | |
if (element.type === 'iframe') { | |
return { | |
addEventListener: jest.fn(), | |
contentWindow: { | |
postMessage: jest.fn(), | |
}, | |
}; | |
} | |
if (element.type === 'code') { | |
return { noHighlight: true }; | |
} | |
return null; | |
}, | |
}); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { defaultDecorateStory } from '@storybook/client-api'; | |
// Global args don't currently exist but they might one day I guess | |
import { args as globalArgs, decorators as globalDecorators } from './preview.js'; | |
import { smokeTestStory } from './smokeTestStory'; | |
function matches(storyKey, arrayOrRegex) { | |
if (Array.isArray(arrayOrRegex)) { | |
return arrayOrRegex.includes(storyKey); | |
} | |
return storyKey.match(arrayOrRegex); | |
} | |
export function testCsf({ default: defaultExport, ...otherExports }) { | |
if (!defaultExport) { | |
throw new Error('Story file not in CSF, please fix!'); | |
} | |
const { | |
args: componentArgs, | |
decorators: componentDecorators = [], | |
excludeStories = [], | |
includeStories, | |
} = defaultExport; | |
let storyEntries = Object.entries(otherExports); | |
if (includeStories) { | |
storyEntries = storyEntries.filter(([storyKey]) => matches(storyKey, includeStories)); | |
} | |
if (excludeStories) { | |
storyEntries = storyEntries.filter(([storyKey]) => !matches(storyKey, excludeStories)); | |
} | |
storyEntries | |
.filter( | |
([exportName]) => | |
!(Array.isArray(excludeStories) | |
? excludeStories.includes(exportName) | |
: exportName.matches(excludeStories)) | |
) | |
.forEach(([exportName, exported]) => { | |
it(exported.story?.name || exported.storyName || exportName, () => { | |
const Component = defaultDecorateStory(exported, [ | |
...globalDecorators, | |
...componentDecorators, | |
]); | |
smokeTestStory(Component, { ...globalArgs, ...componentArgs, ...exported.args }); | |
}); | |
}); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { transform } = require('@babel/core'); | |
module.exports = { | |
process(src, filename) { | |
const hackedSrc = `${src} | |
if (!require.main) { | |
require('${__dirname}/testCsf').testCsf(module.exports); | |
}`; | |
const result = transform(hackedSrc, { | |
filename, | |
}); | |
return result ? result.code : src; | |
}, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey @leipert,
Firstly, whoa, what happened to the formatting here? Let me try and fix it.
Thanks, good pick up!
This is just a basic CSF feature I am implementing. (The include/exclude stories is defined in the CSF file). It's not a "feature" of storyshots because SS uses the Storybook's story loading machinery behind the scenes which already does this.
I don't need to add a feature like storyKindRegex or storyNameRegex (or your filter function) because one of the key benefits of doing this the way I've sketched here is that we can lean on jest directly -- each file is treated a separate test suite and you can use the standard jest tools to filter them. In fact at Chromatic we use this code above in combination with CircleCI's test splitting feature to spread these tests over several test runs already.
Although another thing to note is that the tests run significantly faster in any case and splitting may not be required at all!