diff --git a/.babelrc b/.babelrc index cbd3ac60a229..9b84f85f78e7 100644 --- a/.babelrc +++ b/.babelrc @@ -1,6 +1,6 @@ { "presets": [ - "es2015" + "es2015", "react" ], "plugins": [ "transform-runtime" diff --git a/.storybook/config.js b/.storybook/config.js new file mode 100644 index 000000000000..425739ff73fa --- /dev/null +++ b/.storybook/config.js @@ -0,0 +1,10 @@ +import { configure } from '@kadira/storybook'; + +const req = require.context('../stories/required_with_context', true, /.stories.js$/) + +function loadStories() { + req.keys().forEach((filename) => req(filename)) + require('../stories/directly_required') +} + +configure(loadStories, module); \ No newline at end of file diff --git a/package.json b/package.json index 98be5c92eade..2cc6d035708d 100644 --- a/package.json +++ b/package.json @@ -11,15 +11,23 @@ "scripts": { "prepublish": "babel ./src --out-dir ./dist", "lint": "standard", - "test": "npm run lint" + "test": "npm run lint", + "jest": "jest", + "storybook": "start-storybook -p 6006", + "build-storybook": "build-storybook" }, "devDependencies": { + "@kadira/storybook": "^2.21.0", "babel-cli": "^6.14.0", + "babel-jest": "^18.0.0", "babel-plugin-transform-runtime": "^6.15.0", "babel-preset-es2015": "^6.18.0", + "babel-preset-react": "^6.16.0", + "jest": "^18.0.0", "standard": "^8.6.0" }, "dependencies": { + "react": "^15.4.1", "babel-runtime": "^6.20.0", "react-test-renderer": "^15.3.1", "read-pkg-up": "^2.0.0" diff --git a/src/index.js b/src/index.js index c5073f5aa15b..fefda44d2bed 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,14 @@ import renderer from 'react-test-renderer' import path from 'path' import readPkgUp from 'read-pkg-up' - +import runWithRequireContext from './require_context' const { describe, it, expect } = global let storybook let configPath +const babel = require('babel-core') + const pkg = readPkgUp.sync().pkg const isStorybook = (pkg.devDependencies && pkg.devDependencies['@kadira/storybook']) || @@ -18,21 +20,26 @@ const isRNStorybook = export default function testStorySnapshots (options = {}) { if (isStorybook) { storybook = require.requireActual('@kadira/storybook') + const loadBabelConfig = require('@kadira/storybook/dist/server/babel_config').default const configDirPath = path.resolve(options.configPath || '.storybook') configPath = path.join(configDirPath, 'config.js') + + const content = babel.transformFileSync(configPath, babelConfig).code + const contextOpts = { + filename: configPath, + dirname: configDirPath + } + const babelConfig = loadBabelConfig(configDirPath) + + runWithRequireContext(content, contextOpts) } else if (isRNStorybook) { storybook = require.requireActual('@kadira/react-native-storybook') configPath = path.resolve(options.configPath || 'storybook') + require.requireActual(configPath) } else { throw new Error('\'storyshots\' is intended only to be used with react storybook or react native storybook') } - try { - require.requireActual(configPath) - } catch (e) { - throw new Error(`Could not load stories from ${configPath}. Check 'configPath' option`) - } - if (typeof describe !== 'function') { throw new Error('\'testStorySnapshots\' is intended only to be used inside jest') } diff --git a/src/require_context.js b/src/require_context.js new file mode 100644 index 000000000000..b67537f657da --- /dev/null +++ b/src/require_context.js @@ -0,0 +1,72 @@ +import vm from 'vm' +import fs from 'fs' +import path from 'path' +import moduleSystem from 'module' + +function requireModules (keys, root, directory, regExp, recursive) { + const files = fs.readdirSync(path.join(root, directory)) + + files.forEach((filename) => { + // webpack adds a './' to the begining of the key + // TODO: Check this in windows + const entryKey = `./${path.join(directory, filename)}` + if (regExp.test(entryKey)) { + // eslint-disable-next-line no-param-reassign, global-require, import/no-dynamic-require + keys[entryKey] = require(path.join(root, directory, filename)) + return + } + + if (!recursive) { + return + } + + if (fs.statSync(path.join(root, directory, filename)).isDirectory()) { + requireModules(keys, root, path.join(directory, filename), regExp, recursive) + } + }) +} + +function isRelativeRequest (request) { + if (request.charCodeAt(0) !== 46/* . */) { + return false + } + + if (request === '.' || '..') { + return true + } + + return request.charCodeAt(1) === 47/* / */ || ( + request.charCodeAt(1) === 46/* . */ && request.charCodeAt(2) === 47/* / */) +} + +export default function runWithRequireContext (content, options) { + const { filename, dirname } = options + + const newRequire = (request) => { + if (isRelativeRequest(request)) { + // eslint-disable-next-line global-require, import/no-dynamic-require + return require(path.resolve(dirname, request)) + } + + // eslint-disable-next-line global-require, import/no-dynamic-require + return require(request) + } + + newRequire.resolve = require.resolve + newRequire.extensions = require.extensions + newRequire.main = require.main + newRequire.cache = require.cache + + newRequire.context = (directory, useSubdirectories = false, regExp = /^\.\//) => { + const fullPath = path.resolve(dirname, directory) + const keys = {} + requireModules(keys, fullPath, '.', regExp, useSubdirectories) + + const req = f => (keys[f]) + req.keys = () => (Object.keys(keys)) + return req + } + + const compiledModule = vm.runInThisContext(moduleSystem.wrap(content)) + compiledModule(module.exports, newRequire, module, filename, dirname) +} diff --git a/stories/__test__/__snapshots__/storyshots.test.js.snap b/stories/__test__/__snapshots__/storyshots.test.js.snap new file mode 100644 index 000000000000..c02481b10f6e --- /dev/null +++ b/stories/__test__/__snapshots__/storyshots.test.js.snap @@ -0,0 +1,200 @@ +exports[`Storyshots Another Button with some emoji 1`] = ` + +`; + +exports[`Storyshots Another Button with text 1`] = ` + +`; + +exports[`Storyshots Button with some emoji 1`] = ` + +`; + +exports[`Storyshots Button with text 1`] = ` + +`; + +exports[`Storyshots Welcome to Storybook 1`] = ` +
+ This is a UI component dev environment for your app. +
+
+ We\'ve added some basic stories inside the
+
+ src/stories
+
+ directory.
+
+ A story is a single state of one or more UI components. You can have as many stories as you want.
+
+ (Basically a story is like a visual test case.)
+
+ See these sample
+
+ stories
+
+ for a component called
+
+ Button
+
+ .
+
+ Just like that, you can add your own components as stories.
+
+ You can also edit those components and see changes right away.
+
+ (Try editing the
+
+ Button
+
+ component located at
+
+ src/stories/Button.js
+
+ .)
+
+ This is just one thing you can do with Storybook.
+
+ Have a look at the
+
+ React Storybook
+
+ repo for more information.
+
+ This is a UI component dev environment for your app. +
+
+ We've added some basic stories inside the src/stories
directory.
+
+ A story is a single state of one or more UI components. You can have as many stories as you want.
+
+ (Basically a story is like a visual test case.)
+
+ See these sample stories for a component called Button
.
+
+ Just like that, you can add your own components as stories.
+
+ You can also edit those components and see changes right away.
+
+ (Try editing the Button
component
+ located at src/stories/Button.js
.)
+
+ This is just one thing you can do with Storybook.
+
+ Have a look at the React Storybook repo for more information.
+