diff --git a/.circleci/config.yml b/.circleci/config.yml index 6de243b10d9..40fed8faa96 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,7 +6,7 @@ defaults: &defaults version: 2 dependencies: pre: - - npm install -g npm + - yarn global add npm jobs: validate: <<: *defaults @@ -40,7 +40,7 @@ jobs: - run: name: "Bootstrapping" command: | - npm run bootstrap -- --all + yarn bootstrap -- --all - save_cache: key: package-dependencies-{{ checksum "package.json" }} paths: @@ -63,15 +63,19 @@ jobs: - run: name: "Bootstrapping" command: | - npm run bootstrap -- --core + yarn bootstrap -- --core - run: name: "Build react kitchen-sink" command: | - cd examples/cra-kitchen-sink && npm run build-storybook + cd examples/cra-kitchen-sink + yarn build-storybook + yarn storybook -- --smoke-test - run: name: "Build vue kitchen-sink" command: | - cd examples/vue-kitchen-sink && npm run build-storybook + cd examples/vue-kitchen-sink + yarn build-storybook + yarn storybook -- --smoke-test example-react-native: <<: *defaults steps: @@ -87,11 +91,17 @@ jobs: - run: name: "Bootstrapping packages" command: | - npm run bootstrap -- --core --reactnative + yarn bootstrap -- --core --reactnative --reactnativeapp - run: - name: "Running react-native" + name: "Running React-Native example" command: | - echo "TODO" + cd examples/react-native-vanilla + yarn storybook -- --smoke-test + - run: + name: "Running React-Native-App example" + command: | + cd examples/crna-kitchen-sink + yarn storybook -- --smoke-test docs: <<: *defaults steps: @@ -107,11 +117,11 @@ jobs: - run: name: "Bootstrapping" command: | - npm run bootstrap -- --docs + yarn bootstrap -- --docs - run: name: "Running docs" command: | - npm run docs:build + yarn docs:build lint: <<: *defaults steps: @@ -127,7 +137,7 @@ jobs: - run: name: "Linting" command: | - npm run lint + yarn lint unit-test: <<: *defaults steps: @@ -143,12 +153,12 @@ jobs: - run: name: "Bootstrapping" command: | - npm run bootstrap -- --core --reactnative + yarn bootstrap -- --core --reactnative - run: name: "Unit testing" command: | - npm run test -- --coverage -i - npm run coverage + yarn test -- --all --coverage --runInBand + yarn coverage deploy: <<: *defaults steps: diff --git a/.eslintrc.js b/.eslintrc.js index fdb38e0e97c..cffabe84e35 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -72,7 +72,7 @@ module.exports = { 'jsx-a11y/accessible-emoji': ignore, 'jsx-a11y/href-no-hash': ignore, 'jsx-a11y/label-has-for': ignore, - 'jsx-a11y/anchor-is-valid': ['warn', { aspects: ['invalidHref'] }], + 'jsx-a11y/anchor-is-valid': [warn, { aspects: ['invalidHref'] }], 'react/no-unescaped-entities': ignore, }, }; diff --git a/.gitignore b/.gitignore index b95d551d1ca..259157b6874 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,6 @@ coverage/ *.lerna_backup build packages/examples/automated-* -yarn.lock /**/LICENSE docs/public packs/*.tgz diff --git a/.mailmap b/.mailmap new file mode 100644 index 00000000000..1bee5c87040 --- /dev/null +++ b/.mailmap @@ -0,0 +1,50 @@ +# --- instructions --- # + +# Add your account in this format: +Your name here # github:my-github-account, npm:my-npm-account, twitter:my-twitter-handle + +# supported: +# github, npm, twitter, website + +# --- list ----------- # + +Aaron Mc Adam +Aruna Herath +Arunoda Susiripala Arunoda Susiripala +Benedikt D Valdez Benedikt D Valdez +Daniel Duan +Daniel James +Danny Andrews danny@ownlocal.com> +Dustin Kane +Eli Sherer elish +Evgeny Kochetkov Evgeny Kochetkov +Fabien Bernard Fabien BERNARD +Fernando Daciuk +Greenkeeper greenkeeper[bot] +Greenkeeper greenkeeperio-bot +Jason Schloer jschloer +Jean-Michel Francois Jean-Michel FRANCOIS +Jeff Carbonella +Jeff Knaggs +Jordan Gensler +Kanitkorn Sujautra Kanitkorn S +Kent C. Dodds +larry +Madushan Nishantha +Marie-Laure Thuret mthuret +Max Hodges MaxHodges +Michael Shilman +Michael Shilman +Muhammed Thanish +Ned Schwartz Ned Schwartz +Joe Nelson Nelson, Joe +Nikolay Kozhuharenko Nikolay +Norbert de Langen # github:ndelangen, npm:ndelangen, twitter:norbertdelangen +Oleg Proskurin UsulPro +Orta orta +Ritesh Kumar Ritesh Kumar +Sylvain Bannier Sylvain BANNIER +Tom Coleman Tom Coleman +Trevor Eyre # github:TrevorEyre, twitter:trevor_eyre +William Castandet wcastand +Xavier Cazalot xavcz diff --git a/CHANGELOG.md b/CHANGELOG.md index 68ccaca8977..b8c295b2c5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +# 3.2.9 + +2017-August-26 + +#### Bug Fixes + +- Fix getstorybook CLI for React Native projects [#1741](https://github.com/storybooks/storybook/pull/1741) + +#### Documentation + +- Improve `addon-info` README options documentation [#1732](https://github.com/storybooks/storybook/pull/1732) + +#### Maintenance + +- ADD a CLI for bootstrapping [#1216](https://github.com/storybooks/storybook/pull/1216) + +#### Dependency Upgrades + +- Update lerna to the latest version 🚀 [#1727](https://github.com/storybooks/storybook/pull/1727) + # 3.2.8 2017-August-23 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 54fdcbc4c20..9541039df61 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,8 @@ Thanks for your interest in improving Storybook! We are a community-driven proje Please review this document to help to streamline the process and save everyone's precious time. +This guide assumes you're using `yarn` as package manager. You may have some success using `npm` as well, but there are chances you'll get wrong versions of root dependencies in that case (we only commit `yarn.lock` to the repo). + ## Issues No software is bug free. So, if you got an issue, follow these steps: @@ -18,29 +20,57 @@ No software is bug free. So, if you got an issue, follow these steps: ### Testing against `master` -To test your project against the current latest version of storybook, you can clone the repository and link it with `npm`. Try following these steps: +To test your project against the current latest version of storybook, you can clone the repository and link it with `yarn`. Try following these steps: -1. Download the latest version of this project, and build it: +#### 1. Download the latest version of this project, and build it: ```sh git clone https://github.com/storybooks/storybook.git cd storybook -npm install -npm run bootstrap -- --core +yarn install +yarn bootstrap ``` -2. Link `storybook` and any other required dependencies: +The bootstrap command will ask which sections of the codebase you want to bootstrap. Unless you're going to work with ReactNative or the Documentation, you can keep the default. + +You can also pick directly from CLI: + + yarn bootstrap -- --core + +#### 2a. Run unit tests + +You can use one of the example projects in `examples/` to develop on. + +This command will list all the suites and options for running tests. ```sh -cd app/react -npm link - -cd -npm link @storybook/react - -# repeat with whichever other parts of the monorepo you are using. +yarn test ``` +_Note that in order to run the tests fro ReactNative, you must have bootstrapped with ReactNative enabled_ + +You can also pick suites from CLI: + +```sh +yarn test -- --core +``` + +In order to run ALL unit tests, you must have bootstrapped the react-native + +#### 2b. Link `storybook` and any other required dependencies: + +If you want to test your own existing project using the github version of storybook, you need to `link` the packages you use in your project. + + ```sh + cd app/react + yarn link + + cd + yarn link @storybook/react + + # repeat with whichever other parts of the monorepo you are using. + ``` + ### Reproductions The best way to help figure out an issue you are having is to produce a minimal reproduction against the `master` branch. @@ -51,13 +81,12 @@ A good way to do that is using the example `cra-kitchen-sink` app embedded in th # Download and build this repository: git clone https://github.com/storybooks/storybook.git cd storybook -npm install -npm run bootstrap -- --core - -cd examples/cra-kitchen-sink +yarn install +yarn bootstrap # make changes to try and reproduce the problem, such as adding components + stories -npm start storybook +cd examples/cra-kitchen-sink +yarn storybook # see if you can see the problem, if so, commit it: git checkout "branch-describing-issue" @@ -71,7 +100,7 @@ git push -u master If you follow that process, you can then link to the github repository in the issue. See for an example. -**NOTE**: If your issue involves a webpack config, create-react-app will prevent you from modifying the _app's_ webpack config, however you can still modify storybook's to mirror your app's version of storybook. Alternatively, use `npm run eject` in the CRA app to get a modifiable webpack config. +**NOTE**: If your issue involves a webpack config, create-react-app will prevent you from modifying the _app's_ webpack config, however you can still modify storybook's to mirror your app's version of storybook. Alternatively, use `yarn eject` in the CRA app to get a modifiable webpack config. ## Pull Requests (PRs) @@ -82,7 +111,7 @@ We welcome your contributions. There are many ways you can help us. This is few - Work on [API](https://github.com/storybooks/storybook/labels/enhancement%3A%20api), [Addons](https://github.com/storybooks/storybook/labels/enhancement%3A%20addons), [UI](https://github.com/storybooks/storybook/labels/enhancement%3A%20ui) or [Webpack](https://github.com/storybooks/storybook/labels/enhancement%3A%20webpack) use enhancements and new [features](https://github.com/storybooks/storybook/labels/feature%20request). - Add more [tests](https://codecov.io/gh/storybooks/storybook/tree/master/packages) (specially for the [UI](https://codecov.io/gh/storybooks/storybook/tree/master/packages/storybook-ui/src)). -Before you submit a new PR, make you to run `npm test`. Do not submit a PR if tests are failing. If you need any help, create an issue and ask. +Before you submit a new PR, make you to run `yarn test`. Do not submit a PR if tests are failing. If you need any help, create an issue and ask. ### Reviewing PRs @@ -136,7 +165,7 @@ This project written in ES2016+ syntax so, we need to transpile it before use. So run the following command: ```sh -npm run dev +yarn dev ``` This will watch files and transpile in watch mode. @@ -146,14 +175,14 @@ This will watch files and transpile in watch mode. First of all link this repo with: ```sh -npm link +yarn link ``` In order to test features you add, you may need to link the local copy of this repo. For that we need a sample project. Let's create it. ```sh -npm install --global create-react-app getstorybook +yarn global add create-react-app getstorybook create-react-app my-demo-app cd my-demo-app getstorybook @@ -165,12 +194,12 @@ getstorybook Then link storybook inside the sample project with: ```sh -npm link @storybook/react +yarn link @storybook/react ``` ### Getting Changes -After you've done any change, you need to run the `npm run storybook` command every time to see those changes. +After you've done any change, you need to run the `yarn storybook` command every time to see those changes. ## Release Guide @@ -193,12 +222,9 @@ First, build the release: git checkout master git status -# clean out extra files +# clean out extra files & build all the packages # WARNING: destructive if you have extra files lying around! -git clean -fdx && yarn - -# build all the packages -npm run bootstrap -- --all +yarn bootstrap -- --reset --all ``` From here there are different procedures for prerelease (e.g. alpha/beta/rc) and proper release. @@ -209,7 +235,7 @@ From here there are different procedures for prerelease (e.g. alpha/beta/rc) and ```sh # publish and tag the release -npm run publish -- --concurrency 1 --npm-tag=alpha +yarn run publish -- --concurrency 1 --npm-tag=alpha # push the tags git push --tags @@ -219,14 +245,14 @@ git push --tags ```sh # publish but don't commit to git -npm run publish -- --concurrency 1 --skip-git +yarn publish -- --concurrency 1 --skip-git # Update `CHANGELOG.md` # - Edit PR titles/labels on github until output is good # - Optionally, edit a handwritten description in `CHANGELOG.md` -npm run changelog +yarn changelog # tag the release and push `CHANGELOG.md` and tags # FIXME: not end-to-end tested! -npm run github-release +yarn github-release ``` diff --git a/MIGRATION.md b/MIGRATION.md index 7f47186644d..86a2e7d71aa 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -37,7 +37,7 @@ Here's an example of using Notes and Info in 3.2 with the new API. storiesOf('composition', module) .add('new addons api', withInfo('see Notes panel for composition info')( - withNotes({ notes: 'Composition: Info(Notes())' })(context => + withNotes({ text: 'Composition: Info(Notes())' })(context => ) ) diff --git a/README.md b/README.md index 45e01f83d87..d7918c911aa 100644 --- a/README.md +++ b/README.md @@ -92,30 +92,30 @@ We welcome contributions to Storybook! ### Development scripts -#### `npm run bootstrap` +#### `yarn bootstrap` > Installs package dependencies and links packages together - using lerna -#### `npm run publish` +#### `yarn publish` > Push a release to git and npm > will ask for version in interactive mode - using lerna. -#### `npm run lint` +#### `yarn lint` > boolean check if code conforms to linting rules - uses remark & eslint -- `npm run lint:js` - will check js -- `npm run lint:md` - will check markdown + code samples +- `yarn lint:js` - will check js +- `yarn lint:md` - will check markdown + code samples -- `npm run lint:js -- --fix` - will automatically fix js -- `npm run lint:md -- -o` - will automatically fix markdown +- `yarn lint:js -- --fix` - will automatically fix js +- `yarn lint:md -- -o` - will automatically fix markdown -#### `npm run test` +#### `yarn test` > boolean check if unit tests all pass - uses jest -- `npm run test:watch` - will run tests in watch-mode +- `yarn test:watch` - will run tests in watch-mode ### Backers diff --git a/__mocks__/fs.js b/__mocks__/fs.js new file mode 100644 index 00000000000..261aedd7a6a --- /dev/null +++ b/__mocks__/fs.js @@ -0,0 +1,23 @@ +const fs = jest.genMockFromModule('fs'); + +// This is a custom function that our tests can use during setup to specify +// what the files on the "mock" filesystem should look like when any of the +// `fs` APIs are used. +let mockFiles = Object.create(null); + +// eslint-disable-next-line no-underscore-dangle +function __setMockFiles(newMockFiles) { + mockFiles = newMockFiles; +} + +// A custom version of `readdirSync` that reads from the special mocked out +// file list set via __setMockFiles +const readFileSync = (filePath = '') => mockFiles[filePath]; +const existsSync = filePath => !!mockFiles[filePath]; + +// eslint-disable-next-line no-underscore-dangle +fs.__setMockFiles = __setMockFiles; +fs.readFileSync = readFileSync; +fs.existsSync = existsSync; + +module.exports = fs; diff --git a/addons/info/README.md b/addons/info/README.md index 627b2091abb..8e2b16f36b9 100644 --- a/addons/info/README.md +++ b/addons/info/README.md @@ -41,7 +41,7 @@ storiesOf('Component', module) ## Usage with options -`withInfo` can also take an options object in case you want to configure how +`withInfo` can also take an [options object](#global-options) in case you want to configure how the info panel looks on a per-story basis: ```js @@ -50,10 +50,8 @@ import { withInfo } from '@storybook/addon-info'; storiesOf('Component', module) .add('simple info', withInfo({ - text: 'doc string about my component', - maxPropsIntoLine: 1, - maxPropObjectKeys: 10, - maxPropArrayLength: 10, + text: 'String or React Element with docs about my component', // Warning! This option's name will be likely renamed to "summary" in 3.3 release. Follow this PR #1501 for details + // other possible options see in Global options section below )(() => Click the "?" mark at top-right to view the info. ) @@ -78,8 +76,14 @@ import { setDefaults } from '@storybook/addon-info'; // addon-info setDefaults({ - inline: true, - maxPropsIntoLine: 1, + header: false, // Toggles display of header with component name and description + inline: true, // Displays info inline vs click button to view + source: true, // Displays the source of story Component + propTables: [/* Components used in story */], // displays Prop Tables with this components + propTablesExclude: [], // Exclude Components from being shoun in Prop Tables section + styles: {}, // Overrides styles of addon + marksyConf: {}, // Overrides components used to display markdown. Warning! This option's name will be likely deprecated in favor to "components" with the same API in 3.3 release. Follow this PR #1501 for details + maxPropsIntoLine: 1, // Max props to display per line in source code maxPropObjectKeys: 10, maxPropArrayLength: 10, maxPropStringLength: 100, diff --git a/addons/info/package.json b/addons/info/package.json index fc856bec7dd..2c42615e0fe 100644 --- a/addons/info/package.json +++ b/addons/info/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-info", - "version": "3.2.7", + "version": "3.2.9", "description": "A Storybook addon to show additional information for your stories.", "license": "MIT", "main": "dist/index.js", diff --git a/addons/knobs/README.md b/addons/knobs/README.md index a8e66a92070..0940eee9679 100644 --- a/addons/knobs/README.md +++ b/addons/knobs/README.md @@ -158,7 +158,7 @@ const value = color(label, defaultValue); ### object -Allows you to get a JSON object from the user. +Allows you to get a JSON object or array from the user. ```js import { object } from '@storybook/addon-knobs'; @@ -175,7 +175,7 @@ const value = object(label, defaultValue); ### array -Allows you to get an array from the user. +Allows you to get an array of strings from the user. ```js import { array } from '@storybook/addon-knobs'; diff --git a/addons/knobs/src/components/types/Object.js b/addons/knobs/src/components/types/Object.js index 84084144b49..f97eac2b44b 100644 --- a/addons/knobs/src/components/types/Object.js +++ b/addons/knobs/src/components/types/Object.js @@ -88,7 +88,7 @@ ObjectType.defaultProps = { ObjectType.propTypes = { knob: PropTypes.shape({ name: PropTypes.string, - value: PropTypes.object, + value: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), }), onChange: PropTypes.func, }; diff --git a/addons/links/README.md b/addons/links/README.md index 1e22966e1b5..9c34458a53c 100644 --- a/addons/links/README.md +++ b/addons/links/README.md @@ -36,12 +36,14 @@ Have a look at the linkTo function: import { linkTo } from '@storybook/addon-links' linkTo('Toggle', 'off') +linkTo(() => 'Toggle', () => 'off') +linkTo('Toggle') // Links to the first story in the 'Toggle' kind ``` With that, you can link an event in a component to any story in the Storybook. - First parameter is the the story kind name (what you named with `storiesOf`). -- Second parameter is the story name (what you named with `.add`). +-   Second (optional) parameter is the story name (what you named with `.add`). If the second parameter is omitted, the link will point to the first story in the given kind. > You can also pass a function instead for any of above parameter. That function accepts arguments emitted by the event and it should return a string.
> Have a look at [PR86](https://github.com/kadirahq/react-storybook/pull/86) for more information. diff --git a/addons/options/.storybook/config.js b/addons/options/.storybook/config.js index 339b3300e36..6f80581920a 100644 --- a/addons/options/.storybook/config.js +++ b/addons/options/.storybook/config.js @@ -5,10 +5,10 @@ setOptions({ name: 'CUSTOM-OPTIONS', url: 'https://github.com/storybooks/storybook', // goFullScreen: false, - // showLeftPanel: true, - showDownPanel: false, + // showStoriesPanel: true, + showAddonPanel: false, // showSearchBox: false, - // downPanelInRight: false, + // addonPanelInRight: false, }); storybook.configure(() => require('./stories'), module); diff --git a/addons/options/README.md b/addons/options/README.md index b000c47fb8a..3dd473b12a8 100644 --- a/addons/options/README.md +++ b/addons/options/README.md @@ -53,25 +53,25 @@ setOptions({ */ goFullScreen: false, /** - * display left panel that shows a list of stories + * display panel that shows a list of stories * @type {Boolean} */ - showLeftPanel: true, + showStoriesPanel: true, /** - * display horizontal panel that displays addon configurations + * display panel that shows addon configurations * @type {Boolean} */ - showDownPanel: true, + showAddonPanel: true, /** * display floating search box to search through stories * @type {Boolean} */ showSearchBox: false, /** - * show horizontal addons panel as a vertical panel on the right + * show addon panel as a vertical panel on the right * @type {Boolean} */ - downPanelInRight: false, + addonPanelInRight: false, /** * sorts stories * @type {Boolean} @@ -98,7 +98,7 @@ setOptions({ * id to select an addon panel * @type {String} */ - selectedAddonPanel: undefined, // The order of addons in the "Addons Panel" is the same as you import them in 'addons.js'. The first panel will be opened by default as you run Storybook + selectedAddonPanel: undefined, // The order of addons in the "Addon panel" is the same as you import them in 'addons.js'. The first panel will be opened by default as you run Storybook }); storybook.configure(() => require('./stories'), module); diff --git a/addons/storyshots/README.md b/addons/storyshots/README.md index 813db6023f9..6416a4f4bb5 100644 --- a/addons/storyshots/README.md +++ b/addons/storyshots/README.md @@ -145,6 +145,10 @@ Just render the story, don't check the output at all (useful if you just want to Like the default, but allows you to specify a set of options for the test renderer. [See for example here](https://github.com/storybooks/storybook/blob/b915b5439786e0edb17d7f5ab404bba9f7919381/examples/test-cra/src/storyshots.test.js#L14-L16). +### `multiSnapshotWithOptions(options)` + +Like `snapshotWithOptions`, but generate a separate snapshot file for each stories file rather than a single monolithic file (as is the convention in Jest). This makes it dramatically easier to review changes. + ### `shallowSnapshot` Take a snapshot of a shallow-rendered version of the component. diff --git a/addons/storyshots/package.json b/addons/storyshots/package.json index b61abc26770..958c5878e55 100644 --- a/addons/storyshots/package.json +++ b/addons/storyshots/package.json @@ -11,11 +11,14 @@ "scripts": { "build-storybook": "build-storybook", "prepublish": "babel ./src --out-dir ./dist", - "storybook": "start-storybook -p 6006" + "storybook": "start-storybook -p 6006", + "example": "jest storyshot.test" }, "dependencies": { "babel-runtime": "^6.23.0", + "glob": "^7.1.2", "global": "^4.3.2", + "jest-specific-snapshot": "^0.2.0", "prop-types": "^15.5.10", "read-pkg-up": "^2.0.0" }, @@ -23,18 +26,21 @@ "@storybook/addons": "^3.2.6", "@storybook/channels": "^3.2.0", "@storybook/react": "^3.2.8", - "babel-cli": "^6.24.1", + "babel-cli": "^6.26.0", + "babel-jest": "^20.0.3", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-env": "^1.6.0", "babel-preset-react": "^6.24.1", "react": "^15.6.1", - "react-dom": "^15.6.1" + "react-dom": "^15.6.1", + "jest": "^20.0.4", + "jest-cli": "^20.0.4" }, "peerDependencies": { "@storybook/addons": "^3.2.6", "@storybook/channels": "^3.2.0", "@storybook/react": "^3.2.8", - "babel-core": "^6.25.0", + "babel-core": "^6.26.0", "react": "*", "react-test-renderer": "*" } diff --git a/addons/storyshots/src/index.js b/addons/storyshots/src/index.js index 245b28a9b78..3da1bc04e09 100644 --- a/addons/storyshots/src/index.js +++ b/addons/storyshots/src/index.js @@ -1,4 +1,6 @@ import path from 'path'; +import fs from 'fs'; +import glob from 'glob'; import global, { describe, it } from 'global'; import readPkgUp from 'read-pkg-up'; import addons from '@storybook/addons'; @@ -6,8 +8,15 @@ import addons from '@storybook/addons'; import runWithRequireContext from './require_context'; import createChannel from './storybook-channel-mock'; import { snapshot } from './test-bodies'; +import { getPossibleStoriesFiles } from './utils'; -export { snapshotWithOptions, snapshot, shallowSnapshot, renderOnly } from './test-bodies'; +export { + snapshot, + multiSnapshotWithOptions, + snapshotWithOptions, + shallowSnapshot, + renderOnly, +} from './test-bodies'; let storybook; let configPath; @@ -48,6 +57,7 @@ export default function testStorySnapshots(options = {}) { runWithRequireContext(content, contextOpts); } else if (isRNStorybook) { storybook = require.requireActual('@storybook/react-native'); + configPath = path.resolve(options.configPath || 'storybook'); require.requireActual(configPath); } else { @@ -70,13 +80,15 @@ export default function testStorySnapshots(options = {}) { // eslint-disable-next-line for (const group of stories) { - if (options.storyKindRegex && !group.kind.match(options.storyKindRegex)) { + const { fileName, kind } = group; + + if (options.storyKindRegex && !kind.match(options.storyKindRegex)) { // eslint-disable-next-line continue; } describe(suite, () => { - describe(group.kind, () => { + describe(kind, () => { // eslint-disable-next-line for (const story of group.stories) { if (options.storyNameRegex && !story.name.match(options.storyNameRegex)) { @@ -85,7 +97,7 @@ export default function testStorySnapshots(options = {}) { } it(story.name, () => { - const context = { kind: group.kind, story: story.name }; + const context = { fileName, kind, story: story.name }; options.test({ story, context }); }); } @@ -93,3 +105,16 @@ export default function testStorySnapshots(options = {}) { }); } } + +describe('Storyshots Integrity', () => { + describe('Abandoned Storyshots', () => { + const storyshots = glob.sync('**/*.storyshot'); + + const abandonedStoryshots = storyshots.filter(fileName => { + const possibleStoriesFiles = getPossibleStoriesFiles(fileName); + return !possibleStoriesFiles.some(fs.existsSync); + }); + + expect(abandonedStoryshots).toHaveLength(0); + }); +}); diff --git a/addons/storyshots/src/test-bodies.js b/addons/storyshots/src/test-bodies.js index 48362bca93c..bf71ca3f499 100644 --- a/addons/storyshots/src/test-bodies.js +++ b/addons/storyshots/src/test-bodies.js @@ -1,12 +1,40 @@ import renderer from 'react-test-renderer'; import shallow from 'react-test-renderer/shallow'; +import 'jest-specific-snapshot'; +import { getStoryshotFile } from './utils'; + +function getRenderedTree(story, context, options) { + const storyElement = story.render(context); + return renderer.create(storyElement, options).toJSON(); +} + +function getSnapshotFileName(context) { + const fileName = context.fileName; + + if (!fileName) { + return null; + } + + return getStoryshotFile(fileName); +} export const snapshotWithOptions = options => ({ story, context }) => { - const storyElement = story.render(context); - const tree = renderer.create(storyElement, options).toJSON(); + const tree = getRenderedTree(story, context, options); expect(tree).toMatchSnapshot(); }; +export const multiSnapshotWithOptions = options => ({ story, context }) => { + const tree = getRenderedTree(story, context, options); + const snapshotFileName = getSnapshotFileName(context); + + if (!snapshotFileName) { + expect(tree).toMatchSnapshot(); + return; + } + + expect(tree).toMatchSpecificSnapshot(snapshotFileName); +}; + export const snapshot = snapshotWithOptions({}); export function shallowSnapshot({ story, context }) { diff --git a/addons/storyshots/src/utils.js b/addons/storyshots/src/utils.js new file mode 100644 index 00000000000..86f2d3f9294 --- /dev/null +++ b/addons/storyshots/src/utils.js @@ -0,0 +1,15 @@ +import path from 'path'; + +export function getStoryshotFile(fileName) { + const { dir, name } = path.parse(fileName); + return path.format({ dir: path.join(dir, '__snapshots__'), name, ext: '.storyshot' }); +} + +export function getPossibleStoriesFiles(storyshotFile) { + const { dir, name } = path.parse(storyshotFile); + + return [ + path.format({ dir: path.dirname(dir), name, ext: '.js' }), + path.format({ dir: path.dirname(dir), name, ext: '.jsx' }), + ]; +} diff --git a/addons/storyshots/stories/directly_required/__snapshots__/index.storyshot b/addons/storyshots/stories/directly_required/__snapshots__/index.storyshot new file mode 100644 index 00000000000..354841be656 --- /dev/null +++ b/addons/storyshots/stories/directly_required/__snapshots__/index.storyshot @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Another Button with some emoji 1`] = ` + +`; + +exports[`Storyshots Another Button with text 1`] = ` + +`; diff --git a/addons/storyshots/stories/required_with_context/__snapshots__/Button.stories.storyshot b/addons/storyshots/stories/required_with_context/__snapshots__/Button.stories.storyshot new file mode 100644 index 00000000000..56b21f7fdec --- /dev/null +++ b/addons/storyshots/stories/required_with_context/__snapshots__/Button.stories.storyshot @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Button with some emoji 1`] = ` + +`; + +exports[`Storyshots Button with text 1`] = ` + +`; diff --git a/addons/storyshots/stories/required_with_context/__snapshots__/Welcome.stories.storyshot b/addons/storyshots/stories/required_with_context/__snapshots__/Welcome.stories.storyshot new file mode 100644 index 00000000000..bc6abe86a6e --- /dev/null +++ b/addons/storyshots/stories/required_with_context/__snapshots__/Welcome.stories.storyshot @@ -0,0 +1,104 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Welcome to Storybook 1`] = ` +
+

+ Welcome to storybook +

+

+ 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 + + stories located at + + src/stories/index.js + + .) +

+

+ Usually we create stories with smaller UI components in the app. +
+ Have a look at the + + + Writing Stories + + + section in our documentation. +

+

+ + NOTE: + +
+ Have a look at the + + + .storybook/webpack.config.js + + + to add webpack loaders and plugins you are using in this project. +

+
+`; diff --git a/addons/storyshots/stories/storyshot.test.js b/addons/storyshots/stories/storyshot.test.js new file mode 100644 index 00000000000..6e5a0fea82c --- /dev/null +++ b/addons/storyshots/stories/storyshot.test.js @@ -0,0 +1,8 @@ +import path from 'path'; +import initStoryshots, { multiSnapshotWithOptions } from '../src'; + +initStoryshots({ + framework: 'react', + configPath: path.join(__dirname, '..', '.storybook'), + test: multiSnapshotWithOptions({}), +}); diff --git a/app/react-native/package.json b/app/react-native/package.json index 087e5be2859..d2d78b903fb 100644 --- a/app/react-native/package.json +++ b/app/react-native/package.json @@ -30,7 +30,7 @@ "@storybook/channel-websocket": "^3.2.0", "@storybook/ui": "^3.2.7", "autoprefixer": "^7.1.1", - "babel-core": "^6.25.0", + "babel-core": "^6.26.0", "babel-loader": "^7.0.0", "babel-plugin-syntax-async-functions": "^6.13.0", "babel-plugin-syntax-trailing-function-commas": "^6.22.0", @@ -39,14 +39,14 @@ "babel-plugin-transform-react-constant-elements": "^6.23.0", "babel-plugin-transform-regenerator": "^6.24.1", "babel-plugin-transform-runtime": "^6.23.0", - "babel-polyfill": "^6.23.0", + "babel-polyfill": "^6.26.0", "babel-preset-env": "^1.6.0", "babel-preset-minify": "^0.2.0", "babel-preset-react": "^6.24.1", "babel-preset-stage-0": "^6.24.1", "babel-runtime": "^6.23.0", "case-sensitive-paths-webpack-plugin": "^2.0.0", - "commander": "^2.9.0", + "commander": "^2.11.0", "css-loader": "^0.28.1", "events": "^1.1.1", "express": "^4.15.3", @@ -70,7 +70,7 @@ "ws": "^3.0.0" }, "devDependencies": { - "babel-cli": "^6.24.1", + "babel-cli": "^6.26.0", "react": "^15.6.1", "react-dom": "^15.6.1", "react-native": "^0.43.3" diff --git a/app/react-native/src/bin/storybook-start.js b/app/react-native/src/bin/storybook-start.js index ec80c34b706..66b7802a8bd 100644 --- a/app/react-native/src/bin/storybook-start.js +++ b/app/react-native/src/bin/storybook-start.js @@ -15,6 +15,7 @@ program .option('-r, --reset-cache', 'reset react native packager') .option('--skip-packager', 'run only storybook server') .option('-i, --manual-id', 'allow multiple users to work with same storybook') + .option('--smoke-test', 'Exit after successful start') .parse(process.argv); const projectDir = path.resolve(); @@ -38,6 +39,9 @@ server.listen(...listenAddr, err => { } const address = `http://${program.host || 'localhost'}:${program.port}/`; console.info(`\nReact Native Storybook started on => ${address}\n`); // eslint-disable-line no-console + if (program.smokeTest) { + process.exit(0); + } }); if (!program.skipPackager) { diff --git a/app/react-native/src/preview/index.js b/app/react-native/src/preview/index.js index 04edb17cbe1..ece4bd2319c 100644 --- a/app/react-native/src/preview/index.js +++ b/app/react-native/src/preview/index.js @@ -23,7 +23,10 @@ export default class Preview { if (module && module.hot) { // TODO remove the kind on dispose } - return new StoryKindApi(this._stories, this._addons, this._decorators, kind); + + const fileName = module ? module.filename : null; + + return new StoryKindApi(this._stories, this._addons, this._decorators, kind, fileName); } setAddon(addon) { @@ -44,11 +47,14 @@ export default class Preview { getStorybook() { return this._stories.getStoryKinds().map(kind => { + const fileName = this._stories.getStoryFileName(kind); + const stories = this._stories.getStories(kind).map(name => { const render = this._stories.getStory(kind, name); return { name, render }; }); - return { kind, stories }; + + return { kind, fileName, stories }; }); } diff --git a/app/react-native/src/preview/story_kind.js b/app/react-native/src/preview/story_kind.js index edec71a5d48..01024277cac 100644 --- a/app/react-native/src/preview/story_kind.js +++ b/app/react-native/src/preview/story_kind.js @@ -1,10 +1,11 @@ /* eslint no-underscore-dangle: 0 */ export default class StoryKindApi { - constructor(stories, addons, decorators, kind) { + constructor(stories, addons, decorators, kind, fileName) { this.kind = kind; this._stories = stories; this._decorators = decorators.slice(); + this._fileName = fileName; Object.assign(this, addons); } @@ -15,7 +16,7 @@ export default class StoryKindApi { add(story, fn) { const decorated = this._decorate(fn); - this._stories.addStory(this.kind, story, decorated); + this._stories.addStory(this.kind, story, decorated, this._fileName); return this; } diff --git a/app/react-native/src/preview/story_store.js b/app/react-native/src/preview/story_store.js index 07e91876b12..f202ab8c6b2 100644 --- a/app/react-native/src/preview/story_store.js +++ b/app/react-native/src/preview/story_store.js @@ -9,11 +9,12 @@ export default class StoryStore extends EventEmitter { this._data = {}; } - addStory(kind, name, fn) { + addStory(kind, name, fn, fileName) { count += 1; if (!this._data[kind]) { this._data[kind] = { kind, + fileName, index: count, stories: {}, }; @@ -46,6 +47,15 @@ export default class StoryStore extends EventEmitter { .map(info => info.name); } + getStoryFileName(kind) { + const storiesKind = this._data[kind]; + if (!storiesKind) { + return null; + } + + return storiesKind.fileName; + } + getStory(kind, name) { const storiesKind = this._data[kind]; if (!storiesKind) { diff --git a/app/react-native/src/server/index.html.js b/app/react-native/src/server/index.html.js index 1ab3ca5d59b..e3bc622c4e2 100644 --- a/app/react-native/src/server/index.html.js +++ b/app/react-native/src/server/index.html.js @@ -9,15 +9,6 @@ export default function(publicPath, options) { Storybook for React