mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-03 05:04:51 +08:00
Merge branch 'master' into patch-1
This commit is contained in:
commit
a4f27689e1
@ -58,6 +58,14 @@ jobs:
|
||||
name: "Link packages"
|
||||
command: |
|
||||
yarn install
|
||||
- run:
|
||||
name: Workaround for https://github.com/GoogleChrome/puppeteer/issues/290
|
||||
command: sh ./scripts/workaround-puppeteer-issue-290.sh
|
||||
- run:
|
||||
name: "Build official-storybook"
|
||||
command: |
|
||||
cd examples/official-storybook
|
||||
yarn build-storybook
|
||||
- run:
|
||||
name: "Build react kitchen-sink"
|
||||
command: |
|
||||
@ -73,34 +81,33 @@ jobs:
|
||||
command: |
|
||||
cd examples/angular-cli
|
||||
yarn build-storybook
|
||||
|
||||
- run:
|
||||
name: "Run react kitchen-sink"
|
||||
name: "Run react kitchen-sink (smoke test)"
|
||||
command: |
|
||||
cd examples/cra-kitchen-sink
|
||||
yarn storybook
|
||||
background: true
|
||||
yarn storybook --smoke-test
|
||||
- run:
|
||||
name: "Run vue kitchen-sink"
|
||||
name: "Run vue kitchen-sink (smoke test)"
|
||||
command: |
|
||||
cd examples/vue-kitchen-sink
|
||||
yarn storybook
|
||||
background: true
|
||||
yarn storybook --smoke-test
|
||||
- run:
|
||||
name: "Run angular-cli"
|
||||
name: "Run angular-cli (smoke test)"
|
||||
command: |
|
||||
cd examples/angular-cli
|
||||
yarn storybook
|
||||
background: true
|
||||
yarn storybook --smoke-test
|
||||
- run:
|
||||
name: Workaround for https://github.com/GoogleChrome/puppeteer/issues/290
|
||||
command: sh ./scripts/workaround-puppeteer-issue-290.sh
|
||||
name: "Run image snapshots"
|
||||
command: yarn test --image
|
||||
- run:
|
||||
name: Integration Test - Kichen sinks
|
||||
command: yarn test --integration
|
||||
- store_artifacts:
|
||||
path: integration/__image_snapshots__
|
||||
destination: integration_image_snapshots
|
||||
- store_artifacts:
|
||||
path: examples/official-storybook/image-snapshots/__image_snapshots__
|
||||
destination: official_storybook_image_snapshots
|
||||
react-native:
|
||||
<<: *defaults
|
||||
steps:
|
||||
|
@ -3,6 +3,6 @@ root = true
|
||||
[*]
|
||||
end_of_line = lf
|
||||
|
||||
[*.{js,json,ts}]
|
||||
[*.{js,json,ts,vue,html}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -21,3 +21,4 @@ package-lock.json
|
||||
storybook-static
|
||||
integration/__image_snapshots__/__diff_output__
|
||||
.jest-test-results.json
|
||||
/examples/cra-kitchen-sink/src/__image_snapshots__/__diff_output__/
|
||||
|
252
CHANGELOG.md
252
CHANGELOG.md
@ -1,3 +1,255 @@
|
||||
# 3.4.0-alpha.5
|
||||
|
||||
2018-January-19
|
||||
|
||||
#### Features
|
||||
|
||||
- Angular and Vue storyshots [#2564](https://github.com/storybooks/storybook/pull/2564)
|
||||
- Addon-info: Added "Copy button" for code example [#2713](https://github.com/storybooks/storybook/pull/2713)
|
||||
- Angular: Serve styles and assets using .angular-cli webpack configuration [#2735](https://github.com/storybooks/storybook/pull/2735)
|
||||
- API: Added an event that is emitted when a channel is created. [#2711](https://github.com/storybooks/storybook/pull/2711)
|
||||
- Addon-a11y: Handle components with delayed rendering [#2651](https://github.com/storybooks/storybook/pull/2651)
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
- Angular: knobs with template [#2766](https://github.com/storybooks/storybook/pull/2766)
|
||||
|
||||
#### Documentation
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
4 PRs
|
||||
</summary>
|
||||
|
||||
- Updating css-style testing docs to mention StoryShots image snapshots feature [#2767](https://github.com/storybooks/storybook/pull/2767)
|
||||
- Fix introduction page header [#2757](https://github.com/storybooks/storybook/pull/2757)
|
||||
- fix Storyshots README, add missing export for image snapshots [#2759](https://github.com/storybooks/storybook/pull/2759)
|
||||
- Update MIGRATION.md [#2709](https://github.com/storybooks/storybook/pull/2709)
|
||||
|
||||
</details>
|
||||
|
||||
#### Dependency Upgrades
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
91 upgrades
|
||||
</summary>
|
||||
|
||||
- Upgraded `@types/lodash` in `/` from "4.14.92" to "4.14.93" [#2783](https://github.com/storybooks/storybook/pull/2783)
|
||||
- Upgraded `jest` in `/` from "22.1.2" to "22.1.3" [#2783](https://github.com/storybooks/storybook/pull/2783)
|
||||
- Upgraded `jest-cli` in `/` from "22.1.2" to "22.1.3" [#2783](https://github.com/storybooks/storybook/pull/2783)
|
||||
- Upgraded `jest-config` in `/` from "22.1.2" to "22.1.3" [#2783](https://github.com/storybooks/storybook/pull/2783)
|
||||
- Upgraded `jest-jasmine2` in `/` from "22.1.2" to "22.1.3" [#2783](https://github.com/storybooks/storybook/pull/2783)
|
||||
- Upgraded `postcss-flexbugs-fixes` in `app/angular` from "3.2.0" to "3.3.0" [#2783](https://github.com/storybooks/storybook/pull/2783)
|
||||
- Upgraded `postcss-flexbugs-fixes` in `app/polymer` from "3.2.0" to "3.3.0" [#2783](https://github.com/storybooks/storybook/pull/2783)
|
||||
- Upgraded `postcss-flexbugs-fixes` in `app/react` from "3.2.0" to "3.3.0" [#2783](https://github.com/storybooks/storybook/pull/2783)
|
||||
- Upgraded `postcss-flexbugs-fixes` in `app/vue` from "3.2.0" to "3.3.0" [#2783](https://github.com/storybooks/storybook/pull/2783)
|
||||
- Upgraded `@angular/cli` in `examples/angular-cli` from "1.6.4" to "1.6.5" [#2783](https://github.com/storybooks/storybook/pull/2783)
|
||||
- Upgraded `jest` in `examples/cra-kitchen-sink` from "22.1.2" to "22.1.3" [#2783](https://github.com/storybooks/storybook/pull/2783)
|
||||
- Update gatsby in /docs from 1.9.157 to 1.9.158 [#2779](https://github.com/storybooks/storybook/pull/2779)
|
||||
- Upgraded `jasmine-core` in `examples/angular-cli` from "2.8.0" to "2.9.0" [#2780](https://github.com/storybooks/storybook/pull/2780)
|
||||
- Upgraded `react-chromatic` in `examples/official-storybook` from "0.7.7" to "0.7.8" [#2780](https://github.com/storybooks/storybook/pull/2780)
|
||||
- Upgraded `eslint-plugin-jest` in `/` from "21.6.2" to "21.7.0" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `jest` in `/` from "22.1.1" to "22.1.2" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `jest-cli` in `/` from "22.1.1" to "22.1.2" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `jest-config` in `/` from "22.1.1" to "22.1.2" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `jest-environment-jsdom` in `/` from "22.1.0" to "22.1.2" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `jest-jasmine2` in `/` from "22.1.1" to "22.1.2" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `uuid` in `addons/actions` from "3.2.0" to "3.2.1" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `uuid` in `addons/events` from "3.2.0" to "3.2.1" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `glamorous` in `lib/components` from "4.11.2" to "4.11.3" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `glamorous` in `addons/a11y` from "4.11.2" to "4.11.3" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `glamorous` in `addons/info` from "4.11.2" to "4.11.3" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `glamorous` in `addons/jest` from "4.11.2" to "4.11.3" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `css-loader` in `app/angular` from "0.28.8" to "0.28.9" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `html-loader` in `app/angular` from "0.5.4" to "0.5.5" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `uuid` in `app/angular` from "3.2.0" to "3.2.1" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `css-loader` in `app/polymer` from "0.28.8" to "0.28.9" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `uuid` in `app/polymer` from "3.2.0" to "3.2.1" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `css-loader` in `app/react-native` from "0.28.8" to "0.28.9" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `uuid` in `app/react-native` from "3.2.0" to "3.2.1" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `css-loader` in `app/react` from "0.28.8" to "0.28.9" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `glamorous` in `app/react` from "4.11.2" to "4.11.3" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `html-loader` in `app/react` from "0.5.4" to "0.5.5" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `uuid` in `app/react` from "3.2.0" to "3.2.1" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `css-loader` in `app/vue` from "0.28.8" to "0.28.9" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `html-loader` in `app/vue` from "0.5.4" to "0.5.5" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `uuid` in `app/vue` from "3.2.0" to "3.2.1" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `semver` in `lib/cli` from "5.4.1" to "5.5.0" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `@angular/animations` in `examples/angular-cli` from "5.2.0" to "5.2.1" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `@angular/common` in `examples/angular-cli` from "5.2.0" to "5.2.1" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `@angular/compiler` in `examples/angular-cli` from "5.2.0" to "5.2.1" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `@angular/core` in `examples/angular-cli` from "5.2.0" to "5.2.1" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `@angular/forms` in `examples/angular-cli` from "5.2.0" to "5.2.1" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `@angular/http` in `examples/angular-cli` from "5.2.0" to "5.2.1" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `@angular/platform-browser` in `examples/angular-cli` from "5.2.0" to "5.2.1" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `@angular/platform-browser-dynamic` in `examples/angular-cli` from "5.2.0" to "5.2.1" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `@angular/router` in `examples/angular-cli` from "5.2.0" to "5.2.1" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `@angular/compiler-cli` in `examples/angular-cli` from "5.2.0" to "5.2.1" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `@angular/language-service` in `examples/angular-cli` from "5.2.0" to "5.2.1" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `glamorous` in `examples/cra-kitchen-sink` from "4.11.2" to "4.11.3" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `jest` in `examples/cra-kitchen-sink` from "22.1.1" to "22.1.2" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `css-loader` in `examples/vue-kitchen-sink` from "0.28.8" to "0.28.9" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `uuid` in `examples/official-storybook` from "3.2.0" to "3.2.1" [#2770](https://github.com/storybooks/storybook/pull/2770)
|
||||
- Upgraded `gatsby-plugin-sharp` in `/docs` from "1.6.24" to "1.6.25" [#2768](https://github.com/storybooks/storybook/pull/2768)
|
||||
- Upgraded `gatsby-remark-images` in `/docs` from "1.5.36" to "1.5.37" [#2768](https://github.com/storybooks/storybook/pull/2768)
|
||||
- Upgraded `eslint-plugin-prettier` in `/` from "2.4.0" to "2.5.0" [#2764](https://github.com/storybooks/storybook/pull/2764)
|
||||
- Upgraded `lerna` in `/` from "2.7.0" to "2.7.1" [#2764](https://github.com/storybooks/storybook/pull/2764)
|
||||
- Upgraded `uuid` in `addons/actions` from "3.1.0" to "3.2.0" [#2764](https://github.com/storybooks/storybook/pull/2764)
|
||||
- Upgraded `uuid` in `addons/events` from "3.1.0" to "3.2.0" [#2764](https://github.com/storybooks/storybook/pull/2764)
|
||||
- Upgraded `react-modal` in `lib/ui` from "3.1.10" to "3.1.11" [#2764](https://github.com/storybooks/storybook/pull/2764)
|
||||
- Upgraded `uuid` in `app/angular` from "3.1.0" to "3.2.0" [#2764](https://github.com/storybooks/storybook/pull/2764)
|
||||
- Upgraded `uuid` in `app/polymer` from "3.1.0" to "3.2.0" [#2764](https://github.com/storybooks/storybook/pull/2764)
|
||||
- Upgraded `uuid` in `app/react-native` from "3.1.0" to "3.2.0" [#2764](https://github.com/storybooks/storybook/pull/2764)
|
||||
- Upgraded `uuid` in `app/react` from "3.1.0" to "3.2.0" [#2764](https://github.com/storybooks/storybook/pull/2764)
|
||||
- Upgraded `uuid` in `app/vue` from "3.1.0" to "3.2.0" [#2764](https://github.com/storybooks/storybook/pull/2764)
|
||||
- Upgraded `uuid` in `examples/official-storybook` from "3.1.0" to "3.2.0" [#2764](https://github.com/storybooks/storybook/pull/2764)
|
||||
- Update gatsby in /docs from 1.9.155 to 1.9.157 [#2763](https://github.com/storybooks/storybook/pull/2763)
|
||||
- Upgraded `jest` in `/` from "22.0.6" to "22.1.1" [#2754](https://github.com/storybooks/storybook/pull/2754)
|
||||
- Upgraded `jest-cli` in `/` from "22.0.6" to "22.1.1" [#2754](https://github.com/storybooks/storybook/pull/2754)
|
||||
- Upgraded `jest-config` in `/` from "22.0.6" to "22.1.1" [#2754](https://github.com/storybooks/storybook/pull/2754)
|
||||
- Upgraded `jest-diff` in `/` from "22.0.6" to "22.1.0" [#2754](https://github.com/storybooks/storybook/pull/2754)
|
||||
- Upgraded `jest-environment-jsdom` in `/` from "22.0.6" to "22.1.0" [#2754](https://github.com/storybooks/storybook/pull/2754)
|
||||
- Upgraded `jest-jasmine2` in `/` from "22.0.6" to "22.1.1" [#2754](https://github.com/storybooks/storybook/pull/2754)
|
||||
- Upgraded `babel-plugin-react-docgen` in `app/angular` from "1.8.1" to "1.8.2" [#2754](https://github.com/storybooks/storybook/pull/2754)
|
||||
- Upgraded `babel-preset-react-app` in `app/angular` from "3.1.0" to "3.1.1" [#2754](https://github.com/storybooks/storybook/pull/2754)
|
||||
- Upgraded `babel-plugin-react-docgen` in `app/polymer` from "1.8.1" to "1.8.2" [#2754](https://github.com/storybooks/storybook/pull/2754)
|
||||
- Upgraded `babel-preset-react-app` in `app/polymer` from "3.1.0" to "3.1.1" [#2754](https://github.com/storybooks/storybook/pull/2754)
|
||||
- Upgraded `babel-plugin-react-docgen` in `app/react` from "1.8.1" to "1.8.2" [#2754](https://github.com/storybooks/storybook/pull/2754)
|
||||
- Upgraded `babel-preset-react-app` in `app/react` from "3.1.0" to "3.1.1" [#2754](https://github.com/storybooks/storybook/pull/2754)
|
||||
- Upgraded `babel-plugin-react-docgen` in `app/vue` from "1.8.1" to "1.8.2" [#2754](https://github.com/storybooks/storybook/pull/2754)
|
||||
- Upgraded `babel-preset-react-app` in `app/vue` from "3.1.0" to "3.1.1" [#2754](https://github.com/storybooks/storybook/pull/2754)
|
||||
- Upgraded `babel-jest` in `examples/cra-kitchen-sink` from "22.0.6" to "22.1.0" [#2754](https://github.com/storybooks/storybook/pull/2754)
|
||||
- Upgraded `jest` in `examples/cra-kitchen-sink` from "22.0.6" to "22.1.1" [#2754](https://github.com/storybooks/storybook/pull/2754)
|
||||
- Upgraded `react-scripts` in `examples/cra-kitchen-sink` from "1.0.17" to "1.1.0" [#2754](https://github.com/storybooks/storybook/pull/2754)
|
||||
- Upgraded `eslint-plugin-jest` in `/` from "21.6.1" to "21.6.2" [#2746](https://github.com/storybooks/storybook/pull/2746)
|
||||
- Upgraded `webpack-dev-server` in `examples/polymer-cli` from "2.10.1" to "2.11.0" [#2746](https://github.com/storybooks/storybook/pull/2746)
|
||||
- Upgraded `webpack-dev-server` in `examples/vue-kitchen-sink` from "2.10.1" to "2.11.0" [#2746](https://github.com/storybooks/storybook/pull/2746)
|
||||
- Upgraded `jest-image-snapshot` in `/` from "2.2.1" to "2.3.0" [#2740](https://github.com/storybooks/storybook/pull/2740)
|
||||
- Upgraded `polymer-webpack-loader` in `app/polymer` from "2.0.0" to "2.0.1"
|
||||
|
||||
</details>
|
||||
|
||||
# 3.3.10
|
||||
|
||||
2018-January-19
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
- Addon-backgrounds: remove redundant scrollbars [#2744](https://github.com/storybooks/storybook/pull/2744)
|
||||
- Addon-info: add keys for separator spans in OneOfType [#2743](https://github.com/storybooks/storybook/pull/2743)
|
||||
|
||||
# 3.4.0-alpha.4
|
||||
|
||||
2018-January-13
|
||||
|
||||
Republish `3.4.0-alpha.3` due to potential publishing errors
|
||||
|
||||
# 3.4.0-alpha.3
|
||||
|
||||
2018-January-13
|
||||
|
||||
#### Features
|
||||
|
||||
- Polymer 2 support [#2225](https://github.com/storybooks/storybook/pull/2225)
|
||||
- Add image snapshots to addon-storyshots [#2413](https://github.com/storybooks/storybook/pull/2413)
|
||||
- Angular template support for Storybook [#2690](https://github.com/storybooks/storybook/pull/2690)
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
- Remove polymer-cli dependency [#2741](https://github.com/storybooks/storybook/pull/2741)
|
||||
- Add scss for components in angular apps by default. [#2703](https://github.com/storybooks/storybook/pull/2703)
|
||||
|
||||
#### Maintenance
|
||||
|
||||
- Change ng stories dir [#2672](https://github.com/storybooks/storybook/pull/2672)
|
||||
|
||||
#### Dependency Upgrades
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
54 Upgrades
|
||||
</summary>
|
||||
|
||||
- Upgraded `jest-image-snapshot` in `/` from "2.2.1" to "2.3.0" [#2740](https://github.com/storybooks/storybook/pull/2740)
|
||||
- Upgraded `polymer-webpack-loader` in `app/polymer` from "2.0.0" to "2.0.1" [#2740](https://github.com/storybooks/storybook/pull/2740)
|
||||
- Upgraded `jest-image-snapshot` in `addons/storyshots` from "2.2.1" to "2.3.0" [#2740](https://github.com/storybooks/storybook/pull/2740)
|
||||
- Upgraded `shelljs` in `/` from "0.7.8" to "0.8.0" [#2734](https://github.com/storybooks/storybook/pull/2734)
|
||||
- Upgraded `shelljs` in `addons/links` from "0.7.8" to "0.8.0" [#2734](https://github.com/storybooks/storybook/pull/2734)
|
||||
- Upgraded `autoprefixer` in `app/react` from "7.2.4" to "7.2.5" [#2734](https://github.com/storybooks/storybook/pull/2734)
|
||||
- Upgraded `shelljs` in `app/react` from "0.7.8" to "0.8.0" [#2734](https://github.com/storybooks/storybook/pull/2734)
|
||||
- Upgraded `autoprefixer` in `app/angular` from "7.2.4" to "7.2.5" [#2734](https://github.com/storybooks/storybook/pull/2734)
|
||||
- Upgraded `shelljs` in `app/angular` from "0.7.8" to "0.8.0" [#2734](https://github.com/storybooks/storybook/pull/2734)
|
||||
- Upgraded `autoprefixer` in `app/react-native` from "7.2.4" to "7.2.5" [#2734](https://github.com/storybooks/storybook/pull/2734)
|
||||
- Upgraded `shelljs` in `app/react-native` from "0.7.8" to "0.8.0" [#2734](https://github.com/storybooks/storybook/pull/2734)
|
||||
- Upgraded `autoprefixer` in `app/vue` from "7.2.4" to "7.2.5" [#2734](https://github.com/storybooks/storybook/pull/2734)
|
||||
- Upgraded `shelljs` in `app/vue` from "0.7.8" to "0.8.0" [#2734](https://github.com/storybooks/storybook/pull/2734)
|
||||
- Upgraded `shelljs` in `lib/cli` from "0.7.8" to "0.8.0" [#2734](https://github.com/storybooks/storybook/pull/2734)
|
||||
- Update gatsby in /docs from 1.9.153 to 1.9.154 [#2733](https://github.com/storybooks/storybook/pull/2733)
|
||||
- Update @angular/cli in examples/angular-cli from 1.6.3 to 1.6.4 [#2726](https://github.com/storybooks/storybook/pull/2726)
|
||||
- Upgraded `commander` in `/` from "2.12.2" to "2.13.0" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `jest` in `/` from "22.0.5" to "22.0.6" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `jest-cli` in `/` from "22.0.5" to "22.0.6" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `jest-config` in `/` from "22.0.5" to "22.0.6" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `jest-diff` in `/` from "22.0.5" to "22.0.6" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `jest-environment-jsdom` in `/` from "22.0.5" to "22.0.6" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `jest-jasmine2` in `/` from "22.0.5" to "22.0.6" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `nodemon` in `/` from "1.14.10" to "1.14.11" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `prettier` in `/` from "1.9.2" to "1.10.2" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `tslint` in `/` from "5.8.0" to "5.9.1" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `commander` in `app/react` from "2.12.2" to "2.13.0" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `common-tags` in `app/react` from "1.7.0" to "1.7.2" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `nodemon` in `app/react` from "1.14.10" to "1.14.11" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `commander` in `app/angular` from "2.12.2" to "2.13.0" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `common-tags` in `app/angular` from "1.7.0" to "1.7.2" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `zone.js` in `app/angular` from "0.8.19" to "0.8.20" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `nodemon` in `app/angular` from "1.14.10" to "1.14.11" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `commander` in `app/react-native` from "2.12.2" to "2.13.0" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `commander` in `app/vue` from "2.12.2" to "2.13.0" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `common-tags` in `app/vue` from "1.7.0" to "1.7.2" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `nodemon` in `app/vue` from "1.14.10" to "1.14.11" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `babel-jest` in `examples/cra-kitchen-sink` from "22.0.4" to "22.0.6" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `jest` in `examples/cra-kitchen-sink` from "22.0.5" to "22.0.6" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `commander` in `lib/cli` from "2.12.2" to "2.13.0" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `zone.js` in `examples/angular-cli` from "0.8.19" to "0.8.20" [#2724](https://github.com/storybooks/storybook/pull/2724)
|
||||
- Upgraded `@storybook/addon-actions` in `/docs` from "3.3.7" to "3.3.8" [#2722](https://github.com/storybooks/storybook/pull/2722)
|
||||
- Upgraded `@storybook/addon-links` in `/docs` from "3.3.7" to "3.3.8" [#2722](https://github.com/storybooks/storybook/pull/2722)
|
||||
- Upgraded `@storybook/addons` in `/docs` from "3.3.7" to "3.3.8" [#2722](https://github.com/storybooks/storybook/pull/2722)
|
||||
- Upgraded `@storybook/react` in `/docs` from "3.3.7" to "3.3.8" [#2722](https://github.com/storybooks/storybook/pull/2722)
|
||||
- Upgraded `gatsby-transformer-remark` in `/docs` from "1.7.27" to "1.7.28" [#2722](https://github.com/storybooks/storybook/pull/2722)
|
||||
- Upgraded `gatsby` in `/docs` from "1.9.151" to "1.9.153" [#2722](https://github.com/storybooks/storybook/pull/2722)
|
||||
- Upgraded `@storybook/addon-actions` in `/docs` from "3.3.6" to "3.3.7" [#2710](https://github.com/storybooks/storybook/pull/2710)
|
||||
- Upgraded `@storybook/addon-links` in `/docs` from "3.3.6" to "3.3.7" [#2710](https://github.com/storybooks/storybook/pull/2710)
|
||||
- Upgraded `@storybook/addons` in `/docs` from "3.3.6" to "3.3.7" [#2710](https://github.com/storybooks/storybook/pull/2710)
|
||||
- Upgraded `@storybook/react` in `/docs` from "3.3.6" to "3.3.7" [#2710](https://github.com/storybooks/storybook/pull/2710)
|
||||
- Upgraded `gatsby-link` in `/docs` from "1.6.33" to "1.6.34" [#2710](https://github.com/storybooks/storybook/pull/2710)
|
||||
- Upgraded `gatsby-transformer-remark` in `/docs` from "1.7.26" to "1.7.27" [#2710](https://github.com/storybooks/storybook/pull/2710)
|
||||
- Upgraded `gatsby` in `/docs` from "1.9.150" to "1.9.151" [#2710](https://github.com/storybooks/storybook/pull/2710)
|
||||
|
||||
</details>
|
||||
|
||||
# 3.3.9
|
||||
|
||||
2018-January-13
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
- Start haul/react-native using named binaries instead of cli.js [#2715](https://github.com/storybooks/storybook/pull/2715)
|
||||
- Reflect the new peer dependencies in docs and CLI templates [#2714](https://github.com/storybooks/storybook/pull/2714)
|
||||
- Don't mangle function names for production builds [#2705](https://github.com/storybooks/storybook/pull/2705)
|
||||
|
||||
# 3.4.0-alpha.2
|
||||
|
||||
2018-January-11
|
||||
|
||||
This is a duplicate of `3.4.0-alpha.1`, re-published because I accidentally published `3.4.0-alpha.1` on the `latest` NPM tag.
|
||||
|
||||
# 3.3.8
|
||||
|
||||
2018-January-11
|
||||
|
||||
This is a duplicate of `3.3.7`, re-published because I accidentally published `3.4.0-alpha.1` on the `latest` NPM tag.
|
||||
|
||||
# 3.4.0-alpha.1
|
||||
|
||||
2018-January-10
|
||||
|
@ -76,6 +76,14 @@ In order for the snapshot-integration tests to be executed properly, examples be
|
||||
|
||||
Puppeteer is used to launch and grab screenshots of example pages, while jest is used to assert matching images.
|
||||
|
||||
##### CRA-kitchen-sink - Image snapshots using Storyshots
|
||||
|
||||
`yarn test --image`
|
||||
|
||||
This option executes tests from `<rootdir>/examples/cra-kitchen-sink`
|
||||
In order for the image snapshots to be correctly generated, you must have static build of the storybook up-to-date.
|
||||
|
||||
Puppeteer is used to launch and grab screenshots of example pages, while jest is used to assert matching images. (just like integration tests)
|
||||
|
||||
#### 2b. Run e2e tests for CLI
|
||||
|
||||
|
15
MIGRATION.md
15
MIGRATION.md
@ -18,7 +18,20 @@
|
||||
|
||||
## From version 3.2.x to 3.3.x
|
||||
|
||||
There should be no breaking changes in this release, but read on if you're using `addon-knobs`: we advise an update to your code for efficiency's sake.
|
||||
There wasn't expected be any breaking changes in this release, but unfortunately it turned out that there are some. We're revisiting our [release strategy](https://github.com/storybooks/storybook/blob/master/RELEASES.md) to follow semver more strictly.
|
||||
Also read on if you're using `addon-knobs`: we advise an update to your code for efficiency's sake.
|
||||
|
||||
### `babel-core` is now a peer dependency ([#2494](https://github.com/storybooks/storybook/pull/2494))
|
||||
|
||||
This affects you if you don't use babel in your project. You may need to add `babel-core` as dev dependency:
|
||||
```
|
||||
npm install --save-dev babel-core
|
||||
```
|
||||
This was done to support different major versions of babel.
|
||||
|
||||
### Base webpack config now contains vital plugins ([#1775](https://github.com/storybooks/storybook/pull/1775))
|
||||
|
||||
This affects you if you use custom webpack config in [Full Control Mode](https://storybook.js.org/configurations/custom-webpack-config/#full-control-mode) while not preserving the plugins from `storybookBaseConfig`. Before `3.3`, preserving them was just a reccomendation, but now it [became](https://github.com/storybooks/storybook/pull/2578) a requirement.
|
||||
|
||||
### Refactored Knobs
|
||||
|
||||
|
@ -116,3 +116,4 @@ in a patch release.
|
||||
|
||||
- For PATCH PR's, any maintainer can review, test, approve, and merge it.
|
||||
- For MINOR/MAJOR PR's, once a maintainer reviews, tests, and approves it, s/he should clear it with the other maintainers before merging it into the release branch.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-a11y",
|
||||
"version": "3.4.0-alpha.1",
|
||||
"version": "3.4.0-alpha.5",
|
||||
"description": "a11y addon for storybook",
|
||||
"keywords": [
|
||||
"a11y",
|
||||
@ -25,8 +25,9 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/components": "^3.4.0-alpha.1",
|
||||
"@storybook/components": "^3.4.0-alpha.5",
|
||||
"axe-core": "^2.6.1",
|
||||
"glamorous": "^4.11.3",
|
||||
"prop-types": "^15.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@ -6,10 +6,10 @@ import Report from './Report';
|
||||
|
||||
const styles = {
|
||||
passes: {
|
||||
color: '#2ecc71',
|
||||
color: '#0D6731',
|
||||
},
|
||||
violations: {
|
||||
color: '#e74c3c',
|
||||
color: '#AC2300',
|
||||
},
|
||||
};
|
||||
|
||||
@ -47,11 +47,11 @@ class Panel extends Component {
|
||||
<Tabs
|
||||
tabs={[
|
||||
{
|
||||
label: <span style={styles.violations}>Violations</span>,
|
||||
label: <span style={styles.violations}>{violations.length} Violations</span>,
|
||||
panel: <Report passes={false} items={violations} empty="No a11y violations found." />,
|
||||
},
|
||||
{
|
||||
label: <span style={styles.passes}>Passes</span>,
|
||||
label: <span style={styles.passes}>{passes.length} Passes</span>,
|
||||
panel: <Report passes items={passes} empty="No a11y check passed" />,
|
||||
},
|
||||
]}
|
||||
|
17
addons/a11y/src/components/Report/RerunButton.js
Normal file
17
addons/a11y/src/components/Report/RerunButton.js
Normal file
@ -0,0 +1,17 @@
|
||||
import glamorous from 'glamorous';
|
||||
|
||||
const RerunButton = glamorous.button({
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
border: 'none',
|
||||
borderTop: 'solid 1px rgba(0, 0, 0, 0.2)',
|
||||
borderLeft: 'solid 1px rgba(0, 0, 0, 0.2)',
|
||||
background: 'rgba(255, 255, 255, 0.5)',
|
||||
padding: '5px 10px',
|
||||
borderRadius: '4px 0 0 0',
|
||||
color: 'rgba(0, 0, 0, 0.5)',
|
||||
textTransform: 'uppercase',
|
||||
});
|
||||
|
||||
export default RerunButton;
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import addons from '@storybook/addons';
|
||||
import RerunButton from './RerunButton';
|
||||
import Item from './Item';
|
||||
|
||||
const styles = {
|
||||
@ -17,17 +18,23 @@ const styles = {
|
||||
},
|
||||
};
|
||||
|
||||
function Report({ items, empty, passes }) {
|
||||
if (items.length) {
|
||||
return (
|
||||
function onRerunClick() {
|
||||
const channel = addons.getChannel();
|
||||
channel.emit('addon:a11y:rerun');
|
||||
}
|
||||
|
||||
const Report = ({ items, empty, passes }) => (
|
||||
<Fragment>
|
||||
{items.length ? (
|
||||
<div style={styles.container}>
|
||||
{items.map(item => <Item passes={passes} item={item} key={item.id} />)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <span style={styles.empty}>{empty}</span>;
|
||||
}
|
||||
) : (
|
||||
<span style={styles.empty}>{empty}</span>
|
||||
)}
|
||||
<RerunButton onClick={onRerunClick}>Re-run tests</RerunButton>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
Report.propTypes = {
|
||||
items: PropTypes.arrayOf(
|
||||
|
@ -5,6 +5,7 @@ import { baseFonts } from '@storybook/components';
|
||||
const styles = {
|
||||
container: {
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
...baseFonts,
|
||||
},
|
||||
tabs: {
|
||||
|
@ -15,8 +15,24 @@ class WrapStory extends Component {
|
||||
channel: {},
|
||||
};
|
||||
|
||||
/* eslint-disable react/no-find-dom-node */
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.runA11yCheck = this.runA11yCheck.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { channel } = this.props;
|
||||
channel.on('addon:a11y:rerun', this.runA11yCheck);
|
||||
this.runA11yCheck();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { channel } = this.props;
|
||||
channel.removeListener('addon:a11y:rerun', this.runA11yCheck);
|
||||
}
|
||||
|
||||
/* eslint-disable react/no-find-dom-node */
|
||||
runA11yCheck() {
|
||||
const { channel } = this.props;
|
||||
const wrapper = findDOMNode(this);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-actions",
|
||||
"version": "3.4.0-alpha.1",
|
||||
"version": "3.4.0-alpha.5",
|
||||
"description": "Action Logger addon for storybook",
|
||||
"keywords": [
|
||||
"storybook"
|
||||
@ -25,7 +25,7 @@
|
||||
"make-error": "^1.3.2",
|
||||
"prop-types": "^15.6.0",
|
||||
"react-inspector": "^2.2.2",
|
||||
"uuid": "^3.1.0"
|
||||
"uuid": "^3.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@storybook/addons": "^3.3.0",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-backgrounds",
|
||||
"version": "3.4.0-alpha.1",
|
||||
"version": "3.4.0-alpha.5",
|
||||
"description": "A storybook addon to show different backgrounds for your preview",
|
||||
"keywords": [
|
||||
"addon",
|
||||
|
@ -5,7 +5,7 @@ import addons from '@storybook/addons';
|
||||
|
||||
const style = {
|
||||
wrapper: {
|
||||
overflow: 'scroll',
|
||||
overflow: 'auto',
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-centered",
|
||||
"version": "3.4.0-alpha.1",
|
||||
"version": "3.4.0-alpha.5",
|
||||
"description": "Storybook decorator to center components",
|
||||
"license": "MIT",
|
||||
"author": "Muhammed Thanish <mnmtanish@gmail.com>",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-events",
|
||||
"version": "3.4.0-alpha.1",
|
||||
"version": "3.4.0-alpha.5",
|
||||
"description": "Add events to your Storybook stories.",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -25,7 +25,7 @@
|
||||
"format-json": "^1.0.3",
|
||||
"prop-types": "^15.6.0",
|
||||
"react-textarea-autosize": "^5.2.1",
|
||||
"uuid": "^3.1.0"
|
||||
"uuid": "^3.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@storybook/addons": "^3.3.0",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-graphql",
|
||||
"version": "3.4.0-alpha.1",
|
||||
"version": "3.4.0-alpha.5",
|
||||
"description": "Storybook addon to display the GraphiQL IDE",
|
||||
"keywords": [
|
||||
"storybook"
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 160 KiB |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-info",
|
||||
"version": "3.4.0-alpha.1",
|
||||
"version": "3.4.0-alpha.5",
|
||||
"description": "A Storybook addon to show additional information for your stories.",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
@ -15,9 +15,10 @@
|
||||
"storybook": "start-storybook -p 9010"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/client-logger": "^3.4.0-alpha.1",
|
||||
"@storybook/components": "^3.4.0-alpha.1",
|
||||
"@storybook/client-logger": "^3.4.0-alpha.5",
|
||||
"@storybook/components": "^3.4.0-alpha.5",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"glamorous": "^4.11.3",
|
||||
"global": "^4.3.2",
|
||||
"marksy": "^6.0.3",
|
||||
"nested-object-assign": "^1.0.1",
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,46 +1,47 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withTheme } from 'glamorous';
|
||||
import createFragment from 'react-addons-create-fragment';
|
||||
|
||||
const valueStyles = {
|
||||
const getValueStyles = (codeColors = {}) => ({
|
||||
func: {
|
||||
color: '#170',
|
||||
color: codeColors.func || '#170',
|
||||
},
|
||||
|
||||
attr: {
|
||||
color: '#666',
|
||||
color: codeColors.attr || '#666',
|
||||
},
|
||||
|
||||
object: {
|
||||
color: '#666',
|
||||
color: codeColors.object || '#666',
|
||||
},
|
||||
|
||||
array: {
|
||||
color: '#666',
|
||||
color: codeColors.array || '#666',
|
||||
},
|
||||
|
||||
number: {
|
||||
color: '#a11',
|
||||
color: codeColors.number || '#a11',
|
||||
},
|
||||
|
||||
string: {
|
||||
color: '#22a',
|
||||
color: codeColors.string || '#22a',
|
||||
wordBreak: 'break-word',
|
||||
},
|
||||
|
||||
bool: {
|
||||
color: '#a11',
|
||||
color: codeColors.bool || '#a11',
|
||||
},
|
||||
|
||||
empty: {
|
||||
color: '#777',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
function previewArray(val, maxPropArrayLength) {
|
||||
function previewArray(val, maxPropArrayLength, valueStyles) {
|
||||
const items = {};
|
||||
val.slice(0, maxPropArrayLength).forEach((item, i) => {
|
||||
items[`n${i}`] = <PropVal val={item} />;
|
||||
items[`n${i}`] = <PropVal val={item} valueStyles={valueStyles} />;
|
||||
items[`c${i}`] = ', ';
|
||||
});
|
||||
if (val.length > maxPropArrayLength) {
|
||||
@ -51,13 +52,13 @@ function previewArray(val, maxPropArrayLength) {
|
||||
return <span style={valueStyles.array}>[{createFragment(items)}]</span>;
|
||||
}
|
||||
|
||||
function previewObject(val, maxPropObjectKeys) {
|
||||
function previewObject(val, maxPropObjectKeys, valueStyles) {
|
||||
const names = Object.keys(val);
|
||||
const items = {};
|
||||
names.slice(0, maxPropObjectKeys).forEach((name, i) => {
|
||||
items[`k${i}`] = <span style={valueStyles.attr}>{name}</span>;
|
||||
items[`c${i}`] = ': ';
|
||||
items[`v${i}`] = <PropVal val={val[name]} />;
|
||||
items[`v${i}`] = <PropVal val={val[name]} valueStyles={valueStyles} />;
|
||||
items[`m${i}`] = ', ';
|
||||
});
|
||||
if (names.length > maxPropObjectKeys) {
|
||||
@ -74,11 +75,13 @@ function previewObject(val, maxPropObjectKeys) {
|
||||
);
|
||||
}
|
||||
|
||||
export default function PropVal(props) {
|
||||
const { maxPropObjectKeys, maxPropArrayLength, maxPropStringLength } = props;
|
||||
function PropVal(props) {
|
||||
const { maxPropObjectKeys, maxPropArrayLength, maxPropStringLength, theme } = props;
|
||||
let { val } = props;
|
||||
const { codeColors } = theme || {};
|
||||
let braceWrap = true;
|
||||
let content = null;
|
||||
const valueStyles = props.valueStyles || getValueStyles(codeColors);
|
||||
|
||||
if (typeof val === 'number') {
|
||||
content = <span style={valueStyles.number}>{val}</span>;
|
||||
@ -91,7 +94,7 @@ export default function PropVal(props) {
|
||||
} else if (typeof val === 'boolean') {
|
||||
content = <span style={valueStyles.bool}>{`${val}`}</span>;
|
||||
} else if (Array.isArray(val)) {
|
||||
content = previewArray(val, maxPropArrayLength);
|
||||
content = previewArray(val, maxPropArrayLength, valueStyles);
|
||||
} else if (typeof val === 'function') {
|
||||
content = <span style={valueStyles.func}>{val.name ? `${val.name}()` : 'anonymous()'}</span>;
|
||||
} else if (!val) {
|
||||
@ -105,7 +108,7 @@ export default function PropVal(props) {
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
content = previewObject(val, maxPropObjectKeys);
|
||||
content = previewObject(val, maxPropObjectKeys, valueStyles);
|
||||
}
|
||||
|
||||
if (!braceWrap) return content;
|
||||
@ -118,6 +121,8 @@ PropVal.defaultProps = {
|
||||
maxPropObjectKeys: 3,
|
||||
maxPropArrayLength: 3,
|
||||
maxPropStringLength: 50,
|
||||
theme: {},
|
||||
valueStyles: null,
|
||||
};
|
||||
|
||||
PropVal.propTypes = {
|
||||
@ -125,4 +130,19 @@ PropVal.propTypes = {
|
||||
maxPropObjectKeys: PropTypes.number,
|
||||
maxPropArrayLength: PropTypes.number,
|
||||
maxPropStringLength: PropTypes.number,
|
||||
theme: PropTypes.shape({
|
||||
codeColors: PropTypes.object,
|
||||
}),
|
||||
valueStyles: PropTypes.shape({
|
||||
func: PropTypes.object,
|
||||
attr: PropTypes.object,
|
||||
object: PropTypes.object,
|
||||
array: PropTypes.object,
|
||||
number: PropTypes.object,
|
||||
string: PropTypes.object,
|
||||
bool: PropTypes.object,
|
||||
empty: PropTypes.object,
|
||||
}),
|
||||
};
|
||||
|
||||
export default withTheme(PropVal);
|
||||
|
@ -4,6 +4,7 @@ import React, { createElement } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import global from 'global';
|
||||
import { baseFonts } from '@storybook/components';
|
||||
import { ThemeProvider } from 'glamorous';
|
||||
|
||||
import marksy from 'marksy';
|
||||
|
||||
@ -364,11 +365,11 @@ export default class Story extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.showInline) {
|
||||
return this._renderInline();
|
||||
}
|
||||
|
||||
return this._renderOverlay();
|
||||
return (
|
||||
<ThemeProvider theme={this.state.stylesheet}>
|
||||
{this.props.showInline ? this._renderInline() : this._renderOverlay()}
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,21 +52,6 @@ Code.defaultProps = {
|
||||
code: null,
|
||||
};
|
||||
|
||||
export function Pre(props) {
|
||||
const style = {
|
||||
fontSize: '.88em',
|
||||
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
||||
backgroundColor: '#fafafa',
|
||||
padding: '.5rem',
|
||||
lineHeight: 1.5,
|
||||
overflowX: 'scroll',
|
||||
};
|
||||
return <pre style={style}>{props.children}</pre>;
|
||||
}
|
||||
|
||||
Pre.propTypes = { children: PropTypes.node };
|
||||
Pre.defaultProps = { children: null };
|
||||
|
||||
export function Blockquote(props) {
|
||||
const style = {
|
||||
fontSize: '1.88em',
|
||||
@ -79,3 +64,5 @@ export function Blockquote(props) {
|
||||
|
||||
Blockquote.propTypes = { children: PropTypes.node };
|
||||
Blockquote.defaultProps = { children: null };
|
||||
|
||||
export { default as Pre } from './pre/pre';
|
||||
|
13
addons/info/src/components/markdown/pre/copy.js
Normal file
13
addons/info/src/components/markdown/pre/copy.js
Normal file
@ -0,0 +1,13 @@
|
||||
/* eslint-disable no-undef */
|
||||
export default function copy(str) {
|
||||
const tmp = document.createElement('TEXTAREA');
|
||||
const focus = document.activeElement;
|
||||
|
||||
tmp.value = str;
|
||||
|
||||
document.body.appendChild(tmp);
|
||||
tmp.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(tmp);
|
||||
focus.focus();
|
||||
}
|
68
addons/info/src/components/markdown/pre/copyButton.js
Normal file
68
addons/info/src/components/markdown/pre/copyButton.js
Normal file
@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import glamorous, { withTheme } from 'glamorous';
|
||||
|
||||
const Button = glamorous.button(
|
||||
{
|
||||
overflow: 'hidden',
|
||||
border: '1px solid #eee',
|
||||
borderRadius: 3,
|
||||
backgroundColor: '#FFFFFF',
|
||||
cursor: 'pointer',
|
||||
fontSize: 13,
|
||||
padding: '3px 10px',
|
||||
alignSelf: 'flex-start',
|
||||
|
||||
':hover': {
|
||||
backgroundColor: '#f4f7fa',
|
||||
borderColor: '#ddd',
|
||||
},
|
||||
|
||||
':active': {
|
||||
backgroundColor: '#e9ecef',
|
||||
borderColor: '#ccc',
|
||||
},
|
||||
},
|
||||
props => props.styles
|
||||
);
|
||||
|
||||
const ContentWrapper = glamorous.div(
|
||||
{
|
||||
transition: 'transform .2s ease',
|
||||
height: 16,
|
||||
},
|
||||
props => ({
|
||||
...props.styles,
|
||||
transform: props.toggled ? 'translateY(0px)' : 'translateY(-100%) translateY(-6px)',
|
||||
})
|
||||
);
|
||||
|
||||
function CopyButton(props) {
|
||||
const { copyButton = {}, copyButtonContent } = props.theme;
|
||||
const { toggleText = 'Copied!', text = 'Copy', ...copyButtonStyles } = copyButton;
|
||||
|
||||
return (
|
||||
<Button onClick={props.onClick} styles={copyButtonStyles}>
|
||||
<ContentWrapper styles={copyButtonContent} toggled={props.toggled}>
|
||||
<div style={{ marginBottom: 6 }}>{toggleText}</div>
|
||||
<div>{text}</div>
|
||||
</ContentWrapper>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
CopyButton.propTypes = {
|
||||
onClick: PropTypes.func,
|
||||
toggled: PropTypes.bool,
|
||||
theme: PropTypes.shape({
|
||||
copyButton: PropTypes.object,
|
||||
}),
|
||||
};
|
||||
|
||||
CopyButton.defaultProps = {
|
||||
onClick: () => {},
|
||||
toggled: false,
|
||||
theme: {},
|
||||
};
|
||||
|
||||
export default withTheme(CopyButton);
|
75
addons/info/src/components/markdown/pre/pre.js
Normal file
75
addons/info/src/components/markdown/pre/pre.js
Normal file
@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import glamorous, { withTheme } from 'glamorous';
|
||||
|
||||
import CopyButton from './copyButton';
|
||||
import copy from './copy';
|
||||
|
||||
const TOGGLE_TIMEOUT = 1800;
|
||||
|
||||
const StyledPre = glamorous.pre(
|
||||
{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
fontSize: '.88em',
|
||||
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
||||
backgroundColor: '#fafafa',
|
||||
padding: '.5rem',
|
||||
lineHeight: 1.5,
|
||||
overflowX: 'scroll',
|
||||
},
|
||||
props => props.styles
|
||||
);
|
||||
|
||||
class Pre extends React.Component {
|
||||
state = {
|
||||
copied: false,
|
||||
};
|
||||
|
||||
setRef = elem => {
|
||||
this.pre = elem;
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
const text = this.pre && this.pre.innerText;
|
||||
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
|
||||
copy(text);
|
||||
this.setState({ copied: true });
|
||||
|
||||
clearTimeout(this.timeout);
|
||||
|
||||
this.timeout = setTimeout(() => {
|
||||
this.setState({ copied: false });
|
||||
}, TOGGLE_TIMEOUT);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { pre } = this.props.theme;
|
||||
|
||||
return (
|
||||
<StyledPre styles={pre}>
|
||||
<div ref={this.setRef}>{this.props.children}</div>
|
||||
<CopyButton onClick={this.handleClick} toggled={this.state.copied} />
|
||||
</StyledPre>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Pre.propTypes = {
|
||||
children: PropTypes.node,
|
||||
theme: PropTypes.shape({
|
||||
pre: PropTypes.object,
|
||||
}),
|
||||
};
|
||||
|
||||
Pre.defaultProps = {
|
||||
children: null,
|
||||
theme: {},
|
||||
};
|
||||
|
||||
export default withTheme(Pre);
|
@ -8,13 +8,13 @@ const OneOfType = ({ propType }) => {
|
||||
return (
|
||||
<span>
|
||||
{propType.value
|
||||
.map((value, i) => [
|
||||
<PrettyPropType
|
||||
key={`${value.name}${value.value ? `-${value.value}` : ''}`}
|
||||
propType={value}
|
||||
/>,
|
||||
i < length - 1 ? <span> | </span> : null,
|
||||
])
|
||||
.map((value, i) => {
|
||||
const key = `${value.name}${value.value ? `-${value.value}` : ''}`;
|
||||
return [
|
||||
<PrettyPropType key={key} propType={value} />,
|
||||
i < length - 1 ? <span key={`${key}-separator`}> | </span> : null,
|
||||
];
|
||||
})
|
||||
.reduce((acc, tuple) => acc.concat(tuple), [])}
|
||||
</span>
|
||||
);
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-jest",
|
||||
"version": "3.4.0-alpha.1",
|
||||
"version": "3.4.0-alpha.5",
|
||||
"description": "React storybook addon that show component jest report",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -25,9 +25,9 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/components": "^3.4.0-alpha.1",
|
||||
"@storybook/components": "^3.4.0-alpha.5",
|
||||
"glamor": "^2.20.40",
|
||||
"glamorous": "^4.11.2",
|
||||
"glamorous": "^4.11.3",
|
||||
"global": "^4.3.2",
|
||||
"prop-types": "^15.6.0"
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-knobs",
|
||||
"version": "3.4.0-alpha.1",
|
||||
"version": "3.4.0-alpha.5",
|
||||
"description": "Storybook Addon Prop Editor Component",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
|
1
addons/knobs/polymer.js
Normal file
1
addons/knobs/polymer.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/polymer');
|
38
addons/knobs/src/angular/helpers.js
vendored
38
addons/knobs/src/angular/helpers.js
vendored
@ -1,22 +1,19 @@
|
||||
/* eslint no-underscore-dangle: 0 */
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { Component, SimpleChange, ChangeDetectorRef } from '@angular/core';
|
||||
import { getParameters, getAnnotations, getPropMetadata } from './utils';
|
||||
import { getParameters, getAnnotations } from './utils';
|
||||
|
||||
const getComponentMetadata = ({ component, props = {}, moduleMetadata = {}, template = '' }) => {
|
||||
const getComponentMetadata = ({ component, props = {}, moduleMetadata = {} }) => {
|
||||
if (!component || typeof component !== 'function') throw new Error('No valid component provided');
|
||||
|
||||
const componentMeta = getAnnotations(component)[0] || {};
|
||||
const propsMeta = getPropMetadata(component);
|
||||
const paramsMetadata = getParameters(component);
|
||||
|
||||
return {
|
||||
component,
|
||||
props,
|
||||
componentMeta,
|
||||
propsMeta,
|
||||
moduleMetadata,
|
||||
template,
|
||||
params: paramsMetadata,
|
||||
};
|
||||
};
|
||||
@ -89,6 +86,14 @@ const getAnnotatedComponent = ({ componentMeta, component, params, knobStore, ch
|
||||
return KnobWrapperComponent;
|
||||
};
|
||||
|
||||
const createComponentFromTemplate = template => {
|
||||
const componentClass = class DynamicComponent {};
|
||||
|
||||
return Component({
|
||||
template,
|
||||
})(componentClass);
|
||||
};
|
||||
|
||||
const resetKnobs = (knobStore, channel) => {
|
||||
knobStore.reset();
|
||||
channel.emit('addon:knobs:setKnobs', {
|
||||
@ -99,16 +104,20 @@ const resetKnobs = (knobStore, channel) => {
|
||||
|
||||
export function prepareComponent({ getStory, context, channel, knobStore }) {
|
||||
resetKnobs(knobStore, channel);
|
||||
const {
|
||||
component,
|
||||
componentMeta,
|
||||
props,
|
||||
propsMeta,
|
||||
params,
|
||||
moduleMetadata,
|
||||
} = getComponentMetadata(getStory(context));
|
||||
const story = getStory(context);
|
||||
let { component } = story;
|
||||
const { template } = story;
|
||||
|
||||
if (!componentMeta) throw new Error('No component metadata available');
|
||||
if (!component) {
|
||||
component = createComponentFromTemplate(template);
|
||||
}
|
||||
|
||||
const { componentMeta, props, params, moduleMetadata } = getComponentMetadata({
|
||||
...story,
|
||||
component,
|
||||
});
|
||||
|
||||
if (!componentMeta && component) throw new Error('No component metadata available');
|
||||
|
||||
const AnnotatedComponent = getAnnotatedComponent({
|
||||
componentMeta,
|
||||
@ -121,7 +130,6 @@ export function prepareComponent({ getStory, context, channel, knobStore }) {
|
||||
return {
|
||||
component: AnnotatedComponent,
|
||||
props,
|
||||
propsMeta,
|
||||
moduleMetadata,
|
||||
};
|
||||
}
|
||||
|
4
addons/knobs/src/angular/utils.js
vendored
4
addons/knobs/src/angular/utils.js
vendored
@ -27,10 +27,6 @@ export function getAnnotations(component) {
|
||||
return getMeta(component, ['annotations'], []);
|
||||
}
|
||||
|
||||
export function getPropMetadata(component) {
|
||||
return getMeta(component, ['__prop__metadata__', 'propMetadata'], {});
|
||||
}
|
||||
|
||||
export function getParameters(component) {
|
||||
const params = reflectionCapabilities.parameters(component);
|
||||
|
||||
|
87
addons/knobs/src/polymer/WrapStory.html
Normal file
87
addons/knobs/src/polymer/WrapStory.html
Normal file
@ -0,0 +1,87 @@
|
||||
<dom-module id="wrap-story">
|
||||
<script>
|
||||
class WrapStory extends HTMLElement {
|
||||
static get is() {
|
||||
return 'wrap-story';
|
||||
}
|
||||
|
||||
constructor(component, channel, context, storyFn, knobStore) {
|
||||
super();
|
||||
|
||||
this.attachShadow({ mode: 'open' });
|
||||
this.shadowRoot.innerHTML = '<div id="wrapper"></div>';
|
||||
|
||||
this.channel = channel;
|
||||
this.context = context;
|
||||
this.storyFn = storyFn;
|
||||
this.knobStore = knobStore;
|
||||
|
||||
this.knobChanged = this.knobChanged.bind(this);
|
||||
this.knobClicked = this.knobClicked.bind(this);
|
||||
this.resetKnobs = this.resetKnobs.bind(this);
|
||||
this.setPaneKnobs = this.setPaneKnobs.bind(this);
|
||||
|
||||
this.connectChannel(this.channel);
|
||||
this.knobStore.subscribe(this.setPaneKnobs);
|
||||
this.render(component);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.disconnectChannel(this.channel);
|
||||
this.knobStore.unsubscribe(this.setPaneKnobs);
|
||||
}
|
||||
|
||||
connectChannel(channel) {
|
||||
channel.on('addon:knobs:knobChange', this.knobChanged);
|
||||
channel.on('addon:knobs:knobClick', this.knobClicked);
|
||||
channel.on('addon:knobs:reset', this.resetKnobs);
|
||||
}
|
||||
|
||||
disconnectChannel(channel) {
|
||||
channel.removeListener('addon:knobs:knobChange', this.knobChanged);
|
||||
channel.removeListener('addon:knobs:knobClick', this.knobClicked);
|
||||
channel.removeListener('addon:knobs:reset', this.resetKnobs);
|
||||
}
|
||||
|
||||
knobChanged(change) {
|
||||
const { name, value } = change;
|
||||
const { knobStore, storyFn, context } = this;
|
||||
// Update the related knob and it's value.
|
||||
const knobOptions = knobStore.get(name);
|
||||
|
||||
knobOptions.value = value;
|
||||
knobStore.markAllUnused();
|
||||
this.render(storyFn(context));
|
||||
}
|
||||
|
||||
knobClicked(clicked) {
|
||||
const knobOptions = this.knobStore.get(clicked.name);
|
||||
knobOptions.callback();
|
||||
}
|
||||
|
||||
resetKnobs() {
|
||||
const { knobStore, storyFn, context } = this;
|
||||
knobStore.reset();
|
||||
this.render(storyFn(context));
|
||||
this.setPaneKnobs(this.channel, this.knobStore, false);
|
||||
}
|
||||
|
||||
setPaneKnobs(timestamp = +new Date()) {
|
||||
const { channel, knobStore } = this;
|
||||
channel.emit('addon:knobs:setKnobs', { knobs: knobStore.getAll(), timestamp });
|
||||
}
|
||||
|
||||
render(component) {
|
||||
const wrapper = this.shadowRoot.querySelector('div#wrapper');
|
||||
if (typeof component === 'string') {
|
||||
wrapper.innerHTML = component;
|
||||
} else {
|
||||
wrapper.innerHTML = '';
|
||||
wrapper.appendChild(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(WrapStory.is, WrapStory);
|
||||
</script>
|
||||
</dom-module>
|
36
addons/knobs/src/polymer/index.js
Normal file
36
addons/knobs/src/polymer/index.js
Normal file
@ -0,0 +1,36 @@
|
||||
import addons from '@storybook/addons';
|
||||
import window from 'global';
|
||||
import './WrapStory.html';
|
||||
|
||||
import { knob, text, boolean, number, color, object, array, date, select, manager } from '../base';
|
||||
|
||||
export { knob, text, boolean, number, color, object, array, date, select };
|
||||
|
||||
export function button(name, callback) {
|
||||
return manager.knob(name, { type: 'button', value: Date.now(), callback, hideLabel: true });
|
||||
}
|
||||
|
||||
function prepareComponent({ getStory, context, channel, knobStore }) {
|
||||
const WrapStory = window.customElements.get('wrap-story');
|
||||
return new WrapStory(getStory(context), channel, context, getStory, knobStore);
|
||||
}
|
||||
|
||||
export const polymerHandler = (channel, knobStore) => getStory => context =>
|
||||
prepareComponent({ getStory, context, channel, knobStore });
|
||||
|
||||
function wrapperKnobs(options) {
|
||||
const channel = addons.getChannel();
|
||||
manager.setChannel(channel);
|
||||
|
||||
if (options) channel.emit('addon:knobs:setOptions', options);
|
||||
|
||||
return polymerHandler(channel, manager.knobStore);
|
||||
}
|
||||
|
||||
export function withKnobs(storyFn, context) {
|
||||
return wrapperKnobs()(storyFn)(context);
|
||||
}
|
||||
|
||||
export function withKnobsOptions(options = {}) {
|
||||
return (storyFn, context) => wrapperKnobs(options)(storyFn)(context);
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-links",
|
||||
"version": "3.4.0-alpha.1",
|
||||
"version": "3.4.0-alpha.5",
|
||||
"description": "Story Links addon for storybook",
|
||||
"keywords": [
|
||||
"storybook"
|
||||
@ -22,7 +22,7 @@
|
||||
"storybook": "start-storybook -p 9001"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/components": "^3.4.0-alpha.1",
|
||||
"@storybook/components": "^3.4.0-alpha.5",
|
||||
"global": "^4.3.2",
|
||||
"prop-types": "^15.5.10"
|
||||
},
|
||||
@ -30,7 +30,7 @@
|
||||
"enzyme": "^3.3.0",
|
||||
"react": "^16.1.0",
|
||||
"react-dom": "^16.1.0",
|
||||
"shelljs": "^0.7.8"
|
||||
"shelljs": "^0.8.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@storybook/addons": "^3.3.0",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-notes",
|
||||
"version": "3.4.0-alpha.1",
|
||||
"version": "3.4.0-alpha.5",
|
||||
"description": "Write notes for your Storybook stories.",
|
||||
"keywords": [
|
||||
"addon",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-options",
|
||||
"version": "3.4.0-alpha.1",
|
||||
"version": "3.4.0-alpha.5",
|
||||
"description": "Options addon for storybook",
|
||||
"keywords": [
|
||||
"storybook"
|
||||
|
@ -14,6 +14,8 @@ StoryShots adds automatic Jest Snapshot Testing for [Storybook](https://storyboo
|
||||
This addon works with Storybook for:
|
||||
- [React](https://github.com/storybooks/storybook/tree/master/app/react)
|
||||
- [React Native](https://github.com/storybooks/storybook/tree/master/app/react-native)
|
||||
- [Angular](https://github.com/storybooks/storybook/tree/master/app/angular)
|
||||
- [Vue](https://github.com/storybooks/storybook/tree/master/app/vue)
|
||||
|
||||

|
||||
|
||||
@ -36,7 +38,70 @@ Usually, you might already have completed this step. If not, here are some resou
|
||||
|
||||
> Note: If you use React 16, you'll need to follow [these additional instructions](https://github.com/facebook/react/issues/9102#issuecomment-283873039).
|
||||
|
||||
## Configure Storyshots
|
||||
### Configure Jest for React
|
||||
StoryShots addon for React is dependent on [react-test-renderer](https://github.com/facebook/react/tree/master/packages/react-test-renderer), but
|
||||
[doesn't](#deps-issue) install it, so you need to install it separately.
|
||||
|
||||
```sh
|
||||
npm install --save-dev react-test-renderer
|
||||
```
|
||||
|
||||
### Configure Jest for Angular
|
||||
StoryShots addon for Angular is dependent on [jest-preset-angular](https://github.com/thymikee/jest-preset-angular), but
|
||||
[doesn't](#deps-issue) install it, so you need to install it separately.
|
||||
|
||||
```sh
|
||||
npm install --save-dev jest-preset-angular
|
||||
```
|
||||
|
||||
If you already use Jest for testing your angular app - probably you already have the needed jest configuration.
|
||||
Anyway you can add these lines to your jest config:
|
||||
```js
|
||||
module.exports = {
|
||||
globals: {
|
||||
__TRANSFORM_HTML__: true,
|
||||
},
|
||||
transform: {
|
||||
'^.+\\.jsx?$': 'babel-jest',
|
||||
'^.+\\.(ts|html)$': '<rootDir>/node_modules/jest-preset-angular/preprocessor.js',
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node', '.html'],
|
||||
};
|
||||
```
|
||||
### Configure Jest for Vue
|
||||
StoryShots addon for Vue is dependent on [jest-vue-preprocessor](https://github.com/vire/jest-vue-preprocessor), but
|
||||
[doesn't](#deps-issue) install it, so you need to install it separately.
|
||||
|
||||
```sh
|
||||
npm install --save-dev jest-vue-preprocessor
|
||||
```
|
||||
|
||||
If you already use Jest for testing your vue app - probably you already have the needed jest configuration.
|
||||
Anyway you can add these lines to your jest config:
|
||||
```js
|
||||
module.exports = {
|
||||
transform: {
|
||||
'^.+\\.jsx?$': 'babel-jest',
|
||||
'.*\\.(vue)$': '<rootDir>/node_modules/jest-vue-preprocessor',
|
||||
},
|
||||
moduleFileExtensions: ['vue', 'js', 'jsx', 'json', 'node'],
|
||||
};
|
||||
```
|
||||
|
||||
### <a name="deps-issue"></a>Why don't we install dependencies of each framework ?
|
||||
Storyshots addon is currently supporting React, Angular and Vue. Each framework needs its own packages to be integrated with Jest. We don't want people that use only React will need to bring other dependencies that do not make sense for them.
|
||||
|
||||
`dependencies` - will installed an exact version of the particular dep - Storyshots can work with different versions of the same framework (let's say React v16 and React v15), that have to be compatible with a version of its plugin (react-test-renderer).
|
||||
|
||||
`optionalDependencies` - behaves like a regular dependency, but do not fail the installation in case there is a problem to bring the dep.
|
||||
|
||||
`peerDependencies` - listing all the deps in peer will trigger warnings during the installation - we don't want users to install unneeded deps by hand.
|
||||
|
||||
`optionalPeerDependencies` - unfortunately there is nothing like this =(
|
||||
|
||||
For more information read npm [docs](https://docs.npmjs.com/files/package.json#dependencies)
|
||||
|
||||
## Configure Storyshots for HTML snapshots
|
||||
|
||||
Create a new test file with the name `Storyshots.test.js`. (Or whatever the name you prefer, as long as it matches Jest's config [`testMatch`](http://facebook.github.io/jest/docs/en/configuration.html#testmatch-array-string)).
|
||||
Then add following content to it:
|
||||
@ -53,6 +118,106 @@ Now run your Jest test command. (Usually, `npm test`.) Then you can see all of y
|
||||
|
||||

|
||||
|
||||
|
||||
## Configure Storyshots for image snapshots
|
||||
|
||||
/*\ **React-native** is **not supported** by this test function.
|
||||
|
||||
Internally, it uses [jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot).
|
||||
|
||||
When willing to generate and compare image snapshots for your stories, you have two options:
|
||||
- Have a storybook running (ie. accessible via http(s), for instance using `yarn run storybook`)
|
||||
- Have a static build of the storybook (for instance, using `yarn run build-storybook`)
|
||||
|
||||
Then you will need to reference the storybook URL (`file://...` if local, `http(s)://...` if served)
|
||||
|
||||
### Using default values for _imageSnapshots_
|
||||
|
||||
Then you can either create a new Storyshots instance or edit the one you previously used:
|
||||
```js
|
||||
import initStoryshots, { imageSnapshot } from '@storybook/addon-storyshots';
|
||||
|
||||
initStoryshots({suite: 'Image storyshots', test: imageSnapshot});
|
||||
```
|
||||
This will assume you have a storybook running on at _http://localhost:6006_.
|
||||
Internally here are the steps:
|
||||
- Launches a Chrome headless using [puppeteer](https://github.com/GoogleChrome/puppeteer)
|
||||
- Browses each stories (calling _http://localhost:6006/iframe.html?..._ URL),
|
||||
- Take screenshots & save all images under _\_image_snapshots\__ folder.
|
||||
|
||||
### Specifying the storybook URL
|
||||
|
||||
If you want to set specific storybook URL, you can specify via the `storybookUrl` parameter, see below:
|
||||
```js
|
||||
import initStoryshots, { imageSnapshot } from '@storybook/addon-storyshots';
|
||||
|
||||
initStoryshots({suite: 'Image storyshots', test: imageSnapshot({storybookUrl: 'http://my-specific-domain.com:9010'})});
|
||||
```
|
||||
The above config will use _https://my-specific-domain.com:9010_ for screenshots.
|
||||
|
||||
|
||||
You may also use a local static build of storybook if you do not want to run the webpack dev-server:
|
||||
```js
|
||||
import initStoryshots, { imageSnapshot } from '@storybook/addon-storyshots';
|
||||
|
||||
initStoryshots({suite: 'Image storyshots', test: imageSnapshot({storybookUrl: 'file:///path/to/my/storybook-static'})});
|
||||
```
|
||||
|
||||
### Specifying options to _jest-image-snapshots_
|
||||
|
||||
If you wish to customize [jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot), then you can provide a `getMatchOptions` parameter that should return the options config object.
|
||||
```js
|
||||
import initStoryshots, { imageSnapshot } from '@storybook/addon-storyshots';
|
||||
const getMatchOptions = ({context : {kind, story}, url}) => {
|
||||
return {
|
||||
failureThreshold: 0.2,
|
||||
failureThresholdType: 'percent',
|
||||
}
|
||||
}
|
||||
initStoryshots({suite: 'Image storyshots', test: imageSnapshot({storybookUrl: 'http://localhost:6006', getMatchOptions})});
|
||||
```
|
||||
`getMatchOptions` receives an object: `{ context: {kind, story}, url}`. _kind_ is the kind of the story and the _story_ its name. _url_ is the URL the browser will use to screenshot.
|
||||
|
||||
|
||||
### Integrate image storyshots with regular app
|
||||
|
||||
You may want to use another Jest project to run your image snapshots as they require more resources: Chrome and Storybook built/served.
|
||||
You can find a working example of this in the [official-storybook](https://github.com/storybooks/storybook/tree/master/examples/official-storybook) example.
|
||||
|
||||
### Integrate image storyshots with [Create React App](https://github.com/facebookincubator/create-react-app)
|
||||
|
||||
You have two options here, you can either:
|
||||
|
||||
- Simply add the storyshots configuration inside any of your `test.js` file. You must ensure you have either a running storybook or a static build available.
|
||||
|
||||
- Create a custom test file using Jest outside of the CRA scope:
|
||||
|
||||
A more robust approach would be to separate existing test files ran by create-react-app (anything `(test|spec).js` suffixed files) from the test files to run storyshots with image snapshots.
|
||||
This use case can be achieved by using a custom name for the test file, ie something like `image-storyshots.runner.js`. This file will contains the `initStoryshots` call with image snapshots configuration.
|
||||
Then you will create a separate script entry in your package.json, for instance
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"image-snapshots" : "jest image-storyshots.runner.js --config path/to/custom/jest.config.json"
|
||||
}
|
||||
}
|
||||
```
|
||||
Note that you will certainly need a custom config file for Jest as you run it outside of the CRA scope and thus you do not have the built-in config.
|
||||
|
||||
Once that's setup, you can run `yarn run image-snapshots` (or `npm run image-snapshots`).
|
||||
|
||||
### Reminder
|
||||
|
||||
An image snapshot is simply a screenshot taken by a web browser (in our case, Chrome).
|
||||
|
||||
The browser opens a page (either using the static build of storybook or a running instance of Storybook)
|
||||
|
||||
If you run your test without either the static build or a running instance, this wont work.
|
||||
|
||||
To make sure your screenshots are taken from latest changes of your Storybook, you must keep your static build or running Storybook up-to-date.
|
||||
This can be achieved by adding a step before running the test ie: `yarn run build-storybook && yarn run image-snapshots`.
|
||||
If you run the image snapshots against a running Storybook in dev mode, you don't have to care about being up-to-date because the dev-server is watching changes and rebuilds automatically.
|
||||
|
||||
## Options
|
||||
|
||||
### `configPath`
|
||||
@ -203,6 +368,10 @@ Take a snapshot of a shallow-rendered version of the component. Note that this o
|
||||
|
||||
Utility function used in `multiSnapshotWithOptions`. This is made available for users who implement custom test functions that also want to take advantage of multi-file storyshots.
|
||||
|
||||
### `imageSnapshot`
|
||||
|
||||
Render the story and take Jest snapshots as images. see [Configure image snapshots](#configure-storyshots-for-image-snapshots)
|
||||
|
||||
###### Example:
|
||||
|
||||
Let's say we wanted to create a test function for shallow && multi-file snapshots:
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-storyshots",
|
||||
"version": "3.4.0-alpha.1",
|
||||
"version": "3.4.0-alpha.5",
|
||||
"description": "StoryShots is a Jest Snapshot Testing Addon for Storybook.",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
@ -11,22 +11,24 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build-storybook": "build-storybook",
|
||||
"prepare": "babel ./src --out-dir ./dist",
|
||||
"prepare": "node ../../scripts/prepare.js",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"example": "jest storyshot.test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/channels": "^3.4.0-alpha.1",
|
||||
"@storybook/channels": "^3.4.0-alpha.5",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"glob": "^7.1.2",
|
||||
"global": "^4.3.2",
|
||||
"jest-image-snapshot": "^2.3.0",
|
||||
"jest-specific-snapshot": "^0.3.0",
|
||||
"prop-types": "^15.6.0",
|
||||
"puppeteer": "^0.13.0",
|
||||
"read-pkg-up": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addons": "^3.4.0-alpha.1",
|
||||
"@storybook/react": "^3.4.0-alpha.1",
|
||||
"@storybook/addons": "^3.4.0-alpha.5",
|
||||
"@storybook/react": "^3.4.0-alpha.5",
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-jest": "^20.0.3",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
@ -40,9 +42,7 @@
|
||||
"react-dom": "^16.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@storybook/addons": "^3.4.0-alpha.1",
|
||||
"babel-core": "^6.26.0 || ^7.0.0-0",
|
||||
"react": "*",
|
||||
"react-test-renderer": "*"
|
||||
"@storybook/addons": "^3.4.0-alpha.5",
|
||||
"babel-core": "^6.26.0 || ^7.0.0-0"
|
||||
}
|
||||
}
|
||||
|
92
addons/storyshots/src/angular/app.component.ts
Normal file
92
addons/storyshots/src/angular/app.component.ts
Normal file
@ -0,0 +1,92 @@
|
||||
// We could use NgComponentOutlet here but there's currently no easy way
|
||||
// to provide @Inputs and subscribe to @Outputs, see
|
||||
// https://github.com/angular/angular/issues/15360
|
||||
// For the time being, the ViewContainerRef approach works pretty well.
|
||||
import {
|
||||
Component,
|
||||
Inject,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
ComponentFactoryResolver,
|
||||
OnDestroy,
|
||||
EventEmitter,
|
||||
SimpleChanges,
|
||||
SimpleChange,
|
||||
} from '@angular/core';
|
||||
import { STORY } from './app.token';
|
||||
import { NgStory, ICollection } from './types';
|
||||
|
||||
@Component({
|
||||
selector: 'storybook-dynamic-app-root',
|
||||
template: '<ng-template #target></ng-template>',
|
||||
})
|
||||
export class AppComponent implements OnInit, OnDestroy {
|
||||
@ViewChild('target', { read: ViewContainerRef })
|
||||
target: ViewContainerRef;
|
||||
constructor(private cfr: ComponentFactoryResolver, @Inject(STORY) private data: NgStory) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.putInMyHtml();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.target.clear();
|
||||
}
|
||||
|
||||
private putInMyHtml(): void {
|
||||
this.target.clear();
|
||||
const compFactory = this.cfr.resolveComponentFactory(this.data.component);
|
||||
const instance = this.target.createComponent(compFactory).instance;
|
||||
|
||||
this.setProps(instance, this.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set inputs and outputs
|
||||
*/
|
||||
private setProps(instance: any, { props = {} }: NgStory): void {
|
||||
const changes: SimpleChanges = {};
|
||||
const hasNgOnChangesHook = !!instance['ngOnChanges'];
|
||||
|
||||
Object.keys(props).map((key: string) => {
|
||||
const value = props[key];
|
||||
const instanceProperty = instance[key];
|
||||
|
||||
if (!(instanceProperty instanceof EventEmitter) && !!value) {
|
||||
instance[key] = value;
|
||||
if (hasNgOnChangesHook) {
|
||||
changes[key] = new SimpleChange(undefined, value, instanceProperty === undefined);
|
||||
}
|
||||
} else if (typeof value === 'function' && key !== 'ngModelChange') {
|
||||
instanceProperty.subscribe(value);
|
||||
}
|
||||
});
|
||||
|
||||
this.callNgOnChangesHook(instance, changes);
|
||||
this.setNgModel(instance, props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually call 'ngOnChanges' hook because angular doesn't do that for dynamic components
|
||||
* Issue: [https://github.com/angular/angular/issues/8903]
|
||||
*/
|
||||
private callNgOnChangesHook(instance: any, changes: SimpleChanges): void {
|
||||
if (!!Object.keys(changes).length) {
|
||||
instance.ngOnChanges(changes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If component implements ControlValueAccessor interface try to set ngModel
|
||||
*/
|
||||
private setNgModel(instance: any, props: ICollection): void {
|
||||
if (!!props['ngModel']) {
|
||||
instance.writeValue(props.ngModel);
|
||||
}
|
||||
|
||||
if (typeof props.ngModelChange === 'function') {
|
||||
instance.registerOnChange(props.ngModelChange);
|
||||
}
|
||||
}
|
||||
}
|
4
addons/storyshots/src/angular/app.token.ts
Normal file
4
addons/storyshots/src/angular/app.token.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { InjectionToken } from '@angular/core';
|
||||
import { NgStory } from './types';
|
||||
|
||||
export const STORY = new InjectionToken<NgStory>('story');
|
64
addons/storyshots/src/angular/helpers.ts
Normal file
64
addons/storyshots/src/angular/helpers.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { Component, Type } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { AppComponent } from './app.component';
|
||||
import { STORY } from './app.token';
|
||||
import { NgStory } from './types';
|
||||
|
||||
const getModuleMeta = (
|
||||
declarations: Array<Type<any> | any[]>,
|
||||
entryComponents: Array<Type<any> | any[]>,
|
||||
bootstrap: Array<Type<any> | any[]>,
|
||||
data: NgStory,
|
||||
moduleMetadata: any
|
||||
) => {
|
||||
return {
|
||||
declarations: [...declarations, ...(moduleMetadata.declarations || [])],
|
||||
imports: [BrowserModule, FormsModule, ...(moduleMetadata.imports || [])],
|
||||
providers: [
|
||||
{ provide: STORY, useValue: Object.assign({}, data) },
|
||||
...(moduleMetadata.providers || []),
|
||||
],
|
||||
entryComponents: [...entryComponents, ...(moduleMetadata.entryComponents || [])],
|
||||
schemas: [...(moduleMetadata.schemas || [])],
|
||||
bootstrap: [...bootstrap],
|
||||
};
|
||||
};
|
||||
|
||||
const createComponentFromTemplate = (template: string): Function => {
|
||||
const componentClass = class DynamicComponent {};
|
||||
|
||||
return Component({
|
||||
template: template,
|
||||
})(componentClass);
|
||||
};
|
||||
|
||||
export const initModuleData = (storyObj: NgStory): any => {
|
||||
const { component, template, props, moduleMetadata = {} } = storyObj;
|
||||
|
||||
let AnnotatedComponent;
|
||||
|
||||
if (template) {
|
||||
AnnotatedComponent = createComponentFromTemplate(template);
|
||||
} else {
|
||||
AnnotatedComponent = component;
|
||||
}
|
||||
|
||||
const story = {
|
||||
component: AnnotatedComponent,
|
||||
props,
|
||||
};
|
||||
|
||||
const moduleMeta = getModuleMeta(
|
||||
[AppComponent, AnnotatedComponent],
|
||||
[AnnotatedComponent],
|
||||
[AppComponent],
|
||||
story,
|
||||
moduleMetadata
|
||||
);
|
||||
|
||||
return {
|
||||
AppComponent,
|
||||
moduleMeta,
|
||||
};
|
||||
};
|
43
addons/storyshots/src/angular/loader.js
vendored
Normal file
43
addons/storyshots/src/angular/loader.js
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
import runWithRequireContext from '../require_context';
|
||||
import hasDependency from '../hasDependency';
|
||||
import loadConfig from '../config-loader';
|
||||
|
||||
function setupAngularJestPreset() {
|
||||
// Angular + Jest + Storyshots = Crazy Shit:
|
||||
// We need to require 'jest-preset-angular/setupJest' before any storybook code
|
||||
// is running inside jest - one of the things that `jest-preset-angular/setupJest` does is
|
||||
// extending the `window.Reflect` with all the needed metadata functions, that are required
|
||||
// for emission of the TS decorations like 'design:paramtypes'
|
||||
require.requireActual('jest-preset-angular/setupJest');
|
||||
}
|
||||
|
||||
function test(options) {
|
||||
return (
|
||||
options.framework === 'angular' || (!options.framework && hasDependency('@storybook/angular'))
|
||||
);
|
||||
}
|
||||
|
||||
function load(options) {
|
||||
setupAngularJestPreset();
|
||||
|
||||
const { content, contextOpts } = loadConfig({
|
||||
configDirPath: options.configPath,
|
||||
babelConfigPath: '@storybook/angular/dist/server/babel_config',
|
||||
});
|
||||
|
||||
runWithRequireContext(content, contextOpts);
|
||||
|
||||
return {
|
||||
framework: 'angular',
|
||||
renderTree: require.requireActual('./renderTree').default,
|
||||
renderShallowTree: () => {
|
||||
throw new Error('Shallow renderer is not supported for angular');
|
||||
},
|
||||
storybook: require.requireActual('@storybook/angular'),
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
load,
|
||||
test,
|
||||
};
|
44
addons/storyshots/src/angular/renderTree.js
vendored
Normal file
44
addons/storyshots/src/angular/renderTree.js
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import AngularSnapshotSerializer from 'jest-preset-angular/AngularSnapshotSerializer';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import HTMLCommentSerializer from 'jest-preset-angular/HTMLCommentSerializer';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { addSerializer } from 'jest-specific-snapshot';
|
||||
import { initModuleData } from './helpers.ts';
|
||||
|
||||
addSerializer(HTMLCommentSerializer);
|
||||
addSerializer(AngularSnapshotSerializer);
|
||||
|
||||
function getRenderedTree(story, context) {
|
||||
const currentStory = story.render(context);
|
||||
|
||||
const { moduleMeta, AppComponent } = initModuleData(currentStory);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [...moduleMeta.imports],
|
||||
declarations: [...moduleMeta.declarations],
|
||||
providers: [...moduleMeta.providers],
|
||||
schemas: [NO_ERRORS_SCHEMA, ...moduleMeta.schemas],
|
||||
bootstrap: [...moduleMeta.bootstrap],
|
||||
});
|
||||
|
||||
TestBed.overrideModule(BrowserDynamicTestingModule, {
|
||||
set: {
|
||||
entryComponents: [...moduleMeta.entryComponents],
|
||||
},
|
||||
});
|
||||
|
||||
return TestBed.compileComponents().then(() => {
|
||||
const tree = TestBed.createComponent(AppComponent);
|
||||
tree.detectChanges();
|
||||
|
||||
return tree;
|
||||
});
|
||||
}
|
||||
|
||||
export default getRenderedTree;
|
19
addons/storyshots/src/angular/types.ts
Normal file
19
addons/storyshots/src/angular/types.ts
Normal file
@ -0,0 +1,19 @@
|
||||
export interface NgModuleMetadata {
|
||||
declarations?: Array<any>;
|
||||
entryComponents?: Array<any>;
|
||||
imports?: Array<any>;
|
||||
schemas?: Array<any>;
|
||||
providers?: Array<any>;
|
||||
}
|
||||
|
||||
export interface ICollection {
|
||||
[p: string]: any;
|
||||
}
|
||||
|
||||
export interface NgStory {
|
||||
component?: any;
|
||||
props: ICollection;
|
||||
propsMeta?: ICollection;
|
||||
moduleMetadata?: NgModuleMetadata;
|
||||
template?: string;
|
||||
}
|
24
addons/storyshots/src/config-loader.js
Normal file
24
addons/storyshots/src/config-loader.js
Normal file
@ -0,0 +1,24 @@
|
||||
import path from 'path';
|
||||
|
||||
const babel = require('babel-core');
|
||||
|
||||
function getConfigContent({ resolvedConfigDirPath, configPath, babelConfigPath }) {
|
||||
const loadBabelConfig = require.requireActual(babelConfigPath).default;
|
||||
const babelConfig = loadBabelConfig(resolvedConfigDirPath);
|
||||
return babel.transformFileSync(configPath, babelConfig).code;
|
||||
}
|
||||
|
||||
function load({ configDirPath, babelConfigPath }) {
|
||||
const resolvedConfigDirPath = path.resolve(configDirPath || '.storybook');
|
||||
const configPath = path.join(resolvedConfigDirPath, 'config.js');
|
||||
|
||||
const content = getConfigContent({ resolvedConfigDirPath, configPath, babelConfigPath });
|
||||
const contextOpts = { filename: configPath, dirname: resolvedConfigDirPath };
|
||||
|
||||
return {
|
||||
content,
|
||||
contextOpts,
|
||||
};
|
||||
}
|
||||
|
||||
export default load;
|
18
addons/storyshots/src/frameworkLoader.js
Normal file
18
addons/storyshots/src/frameworkLoader.js
Normal file
@ -0,0 +1,18 @@
|
||||
import loaderReact from './react/loader';
|
||||
import loaderRn from './rn/loader';
|
||||
import loaderAngular from './angular/loader';
|
||||
import loaderVue from './vue/loader';
|
||||
|
||||
const loaders = [loaderReact, loaderAngular, loaderRn, loaderVue];
|
||||
|
||||
function loadFramework(options) {
|
||||
const loader = loaders.find(frameworkLoader => frameworkLoader.test(options));
|
||||
|
||||
if (!loader) {
|
||||
throw new Error('storyshots is intended only to be used with storybook');
|
||||
}
|
||||
|
||||
return loader.load(options);
|
||||
}
|
||||
|
||||
export default loadFramework;
|
13
addons/storyshots/src/hasDependency.js
Normal file
13
addons/storyshots/src/hasDependency.js
Normal file
@ -0,0 +1,13 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import readPkgUp from 'read-pkg-up';
|
||||
|
||||
const { pkg } = readPkgUp.sync();
|
||||
|
||||
export default function hasDependency(name) {
|
||||
return (
|
||||
(pkg.devDependencies && pkg.devDependencies[name]) ||
|
||||
(pkg.dependencies && pkg.dependencies[name]) ||
|
||||
fs.existsSync(path.join('node_modules', name, 'package.json'))
|
||||
);
|
||||
}
|
@ -1,94 +1,58 @@
|
||||
import path from 'path';
|
||||
/* eslint-disable no-loop-func */
|
||||
import fs from 'fs';
|
||||
import glob from 'glob';
|
||||
import global, { describe, it } from 'global';
|
||||
import readPkgUp from 'read-pkg-up';
|
||||
import global, { describe, it, beforeEach, afterEach } from 'global';
|
||||
import addons from '@storybook/addons';
|
||||
|
||||
import runWithRequireContext from './require_context';
|
||||
import loadFramework from './frameworkLoader';
|
||||
import createChannel from './storybook-channel-mock';
|
||||
import { snapshotWithOptions } from './test-bodies';
|
||||
import { getPossibleStoriesFiles, getSnapshotFileName } from './utils';
|
||||
import { imageSnapshot } from './test-body-image-snapshot';
|
||||
|
||||
import {
|
||||
multiSnapshotWithOptions,
|
||||
snapshotWithOptions,
|
||||
snapshot,
|
||||
shallowSnapshot,
|
||||
renderOnly,
|
||||
} from './test-bodies';
|
||||
|
||||
global.STORYBOOK_REACT_CLASSES = global.STORYBOOK_REACT_CLASSES || {};
|
||||
|
||||
export {
|
||||
getSnapshotFileName,
|
||||
snapshot,
|
||||
multiSnapshotWithOptions,
|
||||
snapshotWithOptions,
|
||||
shallowSnapshot,
|
||||
renderOnly,
|
||||
} from './test-bodies';
|
||||
|
||||
export { getSnapshotFileName };
|
||||
|
||||
let storybook;
|
||||
let configPath;
|
||||
global.STORYBOOK_REACT_CLASSES = global.STORYBOOK_REACT_CLASSES || {};
|
||||
|
||||
const babel = require('babel-core');
|
||||
|
||||
const { pkg } = readPkgUp.sync();
|
||||
|
||||
const hasDependency = name =>
|
||||
(pkg.devDependencies && pkg.devDependencies[name]) ||
|
||||
(pkg.dependencies && pkg.dependencies[name]) ||
|
||||
fs.existsSync(path.join('node_modules', name, 'package.json'));
|
||||
imageSnapshot,
|
||||
};
|
||||
|
||||
export default function testStorySnapshots(options = {}) {
|
||||
addons.setChannel(createChannel());
|
||||
|
||||
const isStorybook =
|
||||
options.framework === 'react' || (!options.framework && hasDependency('@storybook/react'));
|
||||
const isRNStorybook =
|
||||
options.framework === 'react-native' ||
|
||||
(!options.framework && hasDependency('@storybook/react-native'));
|
||||
|
||||
if (isStorybook) {
|
||||
storybook = require.requireActual('@storybook/react');
|
||||
// eslint-disable-next-line
|
||||
const loadBabelConfig = require('@storybook/react/dist/server/babel_config')
|
||||
.default;
|
||||
const configDirPath = path.resolve(options.configPath || '.storybook');
|
||||
configPath = path.join(configDirPath, 'config.js');
|
||||
|
||||
const babelConfig = loadBabelConfig(configDirPath);
|
||||
const content = babel.transformFileSync(configPath, babelConfig).code;
|
||||
const contextOpts = {
|
||||
filename: configPath,
|
||||
dirname: configDirPath,
|
||||
};
|
||||
|
||||
runWithRequireContext(content, contextOpts);
|
||||
} else if (isRNStorybook) {
|
||||
storybook = require.requireActual('@storybook/react-native');
|
||||
|
||||
configPath = path.resolve(options.configPath || 'storybook');
|
||||
require.requireActual(configPath);
|
||||
} else {
|
||||
throw new Error('storyshots is intended only to be used with storybook');
|
||||
}
|
||||
|
||||
if (typeof describe !== 'function') {
|
||||
throw new Error('testStorySnapshots is intended only to be used inside jest');
|
||||
}
|
||||
|
||||
// NOTE: keep `suit` typo for backwards compatibility
|
||||
const suite = options.suite || options.suit || 'Storyshots';
|
||||
addons.setChannel(createChannel());
|
||||
|
||||
const { storybook, framework, renderTree, renderShallowTree } = loadFramework(options);
|
||||
const stories = storybook.getStorybook();
|
||||
|
||||
if (stories.length === 0) {
|
||||
throw new Error('storyshots found 0 stories');
|
||||
}
|
||||
|
||||
// Added not to break existing storyshots configs (can be removed in a future major release)
|
||||
// eslint-disable-next-line
|
||||
options.storyNameRegex = options.storyNameRegex || options.storyRegex;
|
||||
// NOTE: keep `suit` typo for backwards compatibility
|
||||
const suite = options.suite || options.suit || 'Storyshots';
|
||||
// NOTE: Added not to break existing storyshots configs (can be removed in a future major release)
|
||||
const storyNameRegex = options.storyNameRegex || options.storyRegex;
|
||||
|
||||
const snapshotOptions = {
|
||||
renderer: options.renderer,
|
||||
serializer: options.serializer,
|
||||
};
|
||||
// eslint-disable-next-line
|
||||
options.test =
|
||||
options.test || snapshotWithOptions({ options: snapshotOptions });
|
||||
|
||||
const testMethod = options.test || snapshotWithOptions({ options: snapshotOptions });
|
||||
|
||||
// eslint-disable-next-line
|
||||
for (const group of stories) {
|
||||
@ -100,19 +64,35 @@ export default function testStorySnapshots(options = {}) {
|
||||
}
|
||||
|
||||
describe(suite, () => {
|
||||
beforeEach(() => {
|
||||
if (typeof testMethod.beforeEach === 'function') {
|
||||
return testMethod.beforeEach();
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (typeof testMethod.afterEach === 'function') {
|
||||
return testMethod.afterEach();
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
describe(kind, () => {
|
||||
// eslint-disable-next-line
|
||||
for (const story of group.stories) {
|
||||
if (options.storyNameRegex && !story.name.match(options.storyNameRegex)) {
|
||||
if (storyNameRegex && !story.name.match(storyNameRegex)) {
|
||||
// eslint-disable-next-line
|
||||
continue;
|
||||
}
|
||||
|
||||
it(story.name, () => {
|
||||
const context = { fileName, kind, story: story.name };
|
||||
return options.test({
|
||||
const context = { fileName, kind, story: story.name, framework };
|
||||
return testMethod({
|
||||
story,
|
||||
context,
|
||||
renderTree,
|
||||
renderShallowTree,
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -122,14 +102,13 @@ export default function testStorySnapshots(options = {}) {
|
||||
}
|
||||
|
||||
describe('Storyshots Integrity', () => {
|
||||
describe('Abandoned Storyshots', () => {
|
||||
test('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);
|
||||
});
|
||||
});
|
||||
|
28
addons/storyshots/src/react/loader.js
Normal file
28
addons/storyshots/src/react/loader.js
Normal file
@ -0,0 +1,28 @@
|
||||
import runWithRequireContext from '../require_context';
|
||||
import hasDependency from '../hasDependency';
|
||||
import loadConfig from '../config-loader';
|
||||
|
||||
function test(options) {
|
||||
return options.framework === 'react' || (!options.framework && hasDependency('@storybook/react'));
|
||||
}
|
||||
|
||||
function load(options) {
|
||||
const { content, contextOpts } = loadConfig({
|
||||
configDirPath: options.configPath,
|
||||
babelConfigPath: '@storybook/react/dist/server/babel_config',
|
||||
});
|
||||
|
||||
runWithRequireContext(content, contextOpts);
|
||||
|
||||
return {
|
||||
framework: 'react',
|
||||
renderTree: require.requireActual('./renderTree').default,
|
||||
renderShallowTree: require.requireActual('./renderShallowTree').default,
|
||||
storybook: require.requireActual('@storybook/react'),
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
load,
|
||||
test,
|
||||
};
|
11
addons/storyshots/src/react/renderShallowTree.js
Normal file
11
addons/storyshots/src/react/renderShallowTree.js
Normal file
@ -0,0 +1,11 @@
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import shallow from 'react-test-renderer/shallow';
|
||||
|
||||
function getRenderedTree(story, context, { renderer, serializer }) {
|
||||
const storyElement = story.render(context);
|
||||
const shallowRenderer = renderer || shallow.createRenderer();
|
||||
const tree = shallowRenderer.render(storyElement);
|
||||
return serializer ? serializer(tree) : tree;
|
||||
}
|
||||
|
||||
export default getRenderedTree;
|
11
addons/storyshots/src/react/renderTree.js
Normal file
11
addons/storyshots/src/react/renderTree.js
Normal file
@ -0,0 +1,11 @@
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import reactTestRenderer from 'react-test-renderer';
|
||||
|
||||
function getRenderedTree(story, context, { renderer, serializer, ...rendererOptions }) {
|
||||
const storyElement = story.render(context);
|
||||
const currentRenderer = renderer || reactTestRenderer.create;
|
||||
const tree = currentRenderer(storyElement, rendererOptions);
|
||||
return serializer ? serializer(tree) : tree;
|
||||
}
|
||||
|
||||
export default getRenderedTree;
|
29
addons/storyshots/src/rn/loader.js
Normal file
29
addons/storyshots/src/rn/loader.js
Normal file
@ -0,0 +1,29 @@
|
||||
/* eslint-disable global-require */
|
||||
import path from 'path';
|
||||
import hasDependency from '../hasDependency';
|
||||
|
||||
function test(options) {
|
||||
return (
|
||||
options.framework === 'react-native' ||
|
||||
(!options.framework && hasDependency('@storybook/react-native'))
|
||||
);
|
||||
}
|
||||
|
||||
function load(options) {
|
||||
const storybook = require.requireActual('@storybook/react-native');
|
||||
|
||||
const configPath = path.resolve(options.configPath || 'storybook');
|
||||
require.requireActual(configPath);
|
||||
|
||||
return {
|
||||
renderTree: require('../react/renderTree').default,
|
||||
renderShallowTree: require('../react/renderShallowTree').default,
|
||||
framework: 'rn',
|
||||
storybook,
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
load,
|
||||
test,
|
||||
};
|
@ -1,43 +1,54 @@
|
||||
import reactTestRenderer from 'react-test-renderer';
|
||||
import shallow from 'react-test-renderer/shallow';
|
||||
import 'jest-specific-snapshot';
|
||||
import { getSnapshotFileName } from './utils';
|
||||
|
||||
function getRenderedTree(story, context, { renderer, serializer, ...rendererOptions }) {
|
||||
const currentRenderer = renderer || reactTestRenderer.create;
|
||||
const storyElement = story.render(context);
|
||||
const tree = currentRenderer(storyElement, rendererOptions);
|
||||
return serializer ? serializer(tree) : tree;
|
||||
export const snapshotWithOptions = options => ({
|
||||
story,
|
||||
context,
|
||||
renderTree,
|
||||
snapshotFileName,
|
||||
}) => {
|
||||
const result = renderTree(story, context, options);
|
||||
|
||||
function match(tree) {
|
||||
if (snapshotFileName) {
|
||||
expect(tree).toMatchSpecificSnapshot(snapshotFileName);
|
||||
} else {
|
||||
expect(tree).toMatchSnapshot();
|
||||
}
|
||||
|
||||
if (typeof tree.unmount === 'function') {
|
||||
tree.unmount();
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof result.then === 'function') {
|
||||
return result.then(match);
|
||||
}
|
||||
|
||||
return match(result);
|
||||
};
|
||||
|
||||
export const multiSnapshotWithOptions = options => ({ story, context, renderTree }) =>
|
||||
snapshotWithOptions(options)({
|
||||
story,
|
||||
context,
|
||||
renderTree,
|
||||
snapshotFileName: getSnapshotFileName(context),
|
||||
});
|
||||
|
||||
export function shallowSnapshot({ story, context, renderShallowTree, options = {} }) {
|
||||
const result = renderShallowTree(story, context, options);
|
||||
expect(result).toMatchSnapshot();
|
||||
}
|
||||
|
||||
export const snapshotWithOptions = options => ({ story, context, snapshotFileName }) => {
|
||||
const tree = getRenderedTree(story, context, options);
|
||||
export function renderOnly({ story, context, renderTree }) {
|
||||
const result = renderTree(story, context, {});
|
||||
|
||||
if (snapshotFileName) {
|
||||
expect(tree).toMatchSpecificSnapshot(snapshotFileName);
|
||||
} else {
|
||||
expect(tree).toMatchSnapshot();
|
||||
if (typeof result.then === 'function') {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (typeof tree.unmount === 'function') {
|
||||
tree.unmount();
|
||||
}
|
||||
};
|
||||
|
||||
export const multiSnapshotWithOptions = options => ({ story, context }) => {
|
||||
snapshotWithOptions(options)({ story, context, snapshotFileName: getSnapshotFileName(context) });
|
||||
};
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export const snapshot = snapshotWithOptions({});
|
||||
|
||||
export function shallowSnapshot({ story, context, options: { renderer, serializer } }) {
|
||||
const shallowRenderer = renderer || shallow.createRenderer();
|
||||
const tree = shallowRenderer.render(story.render(context));
|
||||
const serializedTree = serializer ? serializer(tree) : tree;
|
||||
expect(serializedTree).toMatchSnapshot();
|
||||
}
|
||||
|
||||
export function renderOnly({ story, context }) {
|
||||
const storyElement = story.render(context);
|
||||
reactTestRenderer.create(storyElement);
|
||||
}
|
||||
|
67
addons/storyshots/src/test-body-image-snapshot.js
Normal file
67
addons/storyshots/src/test-body-image-snapshot.js
Normal file
@ -0,0 +1,67 @@
|
||||
import puppeteer from 'puppeteer';
|
||||
import { toMatchImageSnapshot } from 'jest-image-snapshot';
|
||||
|
||||
expect.extend({ toMatchImageSnapshot });
|
||||
|
||||
export const imageSnapshot = ({
|
||||
storybookUrl = 'http://localhost:6006',
|
||||
getMatchOptions = () => {},
|
||||
}) => {
|
||||
let browser; // holds ref to browser. (ie. Chrome)
|
||||
let page; // Hold ref to the page to screenshot.
|
||||
|
||||
const testFn = ({ context }) => {
|
||||
if (context.framework === 'rn') {
|
||||
// Skip tests since we de not support RN image snapshots.
|
||||
console.error(
|
||||
"It seems you are running imageSnapshot on RN app and it's not supported. Skipping test."
|
||||
);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const encodedKind = encodeURIComponent(context.kind);
|
||||
const encodedStoryName = encodeURIComponent(context.story);
|
||||
const storyUrl = `/iframe.html?selectedKind=${encodedKind}&selectedStory=${encodedStoryName}`;
|
||||
const url = storybookUrl + storyUrl;
|
||||
if (!browser || !page) {
|
||||
console.error(
|
||||
`Error when generating image snapshot for test ${context.kind} - ${
|
||||
context.story
|
||||
} : It seems the headless browser is not running.`
|
||||
);
|
||||
return Promise.reject(new Error('no-headless-browser-running'));
|
||||
}
|
||||
|
||||
expect.assertions(1);
|
||||
return page
|
||||
.goto(url)
|
||||
.catch(e => {
|
||||
console.error(
|
||||
`ERROR WHILE CONNECTING TO ${url}, did you start or build the storybook first ? A storybook instance should be running or a static version should be built when using image snapshot feature.`,
|
||||
e
|
||||
);
|
||||
throw e;
|
||||
})
|
||||
.then(() =>
|
||||
page.screenshot().then(image => {
|
||||
expect(image).toMatchImageSnapshot(getMatchOptions({ context, url }));
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
testFn.beforeEach = () =>
|
||||
puppeteer
|
||||
// add some options "no-sandbox" to make it work properly on some Linux systems as proposed here: https://github.com/Googlechrome/puppeteer/issues/290#issuecomment-322851507
|
||||
.launch({ args: ['--no-sandbox ', '--disable-setuid-sandbox'] })
|
||||
.then(b => {
|
||||
browser = b;
|
||||
})
|
||||
.then(() => browser.newPage())
|
||||
.then(p => {
|
||||
page = p;
|
||||
});
|
||||
|
||||
testFn.afterEach = () => browser.close();
|
||||
|
||||
return testFn;
|
||||
};
|
46
addons/storyshots/src/utils.test.js
Normal file
46
addons/storyshots/src/utils.test.js
Normal file
@ -0,0 +1,46 @@
|
||||
import { getPossibleStoriesFiles, getSnapshotFileName } from './utils';
|
||||
|
||||
describe('getSnapshotFileName', () => {
|
||||
it('fileName is provided - snapshot is stored in __snapshots__ dir', () => {
|
||||
const context = { fileName: 'foo.js' };
|
||||
|
||||
const result = getSnapshotFileName(context);
|
||||
const platformAgnosticResult = result.replace(/\\|\//g, '/');
|
||||
|
||||
expect(platformAgnosticResult).toBe('__snapshots__/foo.storyshot');
|
||||
});
|
||||
|
||||
it('fileName with multiple extensions is provided - only the last extension is replaced', () => {
|
||||
const context = { fileName: 'foo.web.stories.js' };
|
||||
|
||||
const result = getSnapshotFileName(context);
|
||||
const platformAgnosticResult = result.replace(/\\|\//g, '/');
|
||||
|
||||
expect(platformAgnosticResult).toBe('__snapshots__/foo.web.stories.storyshot');
|
||||
});
|
||||
|
||||
it('fileName with dir is provided - __snapshots__ dir is created inside another dir', () => {
|
||||
const context = { fileName: 'test/foo.js' };
|
||||
|
||||
const result = getSnapshotFileName(context);
|
||||
const platformAgnosticResult = result.replace(/\\|\//g, '/');
|
||||
|
||||
expect(platformAgnosticResult).toBe('test/__snapshots__/foo.storyshot');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPossibleStoriesFiles', () => {
|
||||
it('storyshots is provided and all the posible stories file names are returned', () => {
|
||||
const storyshots = 'test/__snapshots__/foo.web.stories.storyshot';
|
||||
|
||||
const result = getPossibleStoriesFiles(storyshots);
|
||||
const platformAgnosticResult = result.map(path => path.replace(/\\|\//g, '/'));
|
||||
|
||||
expect(platformAgnosticResult).toEqual([
|
||||
'test/foo.web.stories.js',
|
||||
'test/foo.web.stories.jsx',
|
||||
'test/foo.web.stories.ts',
|
||||
'test/foo.web.stories.tsx',
|
||||
]);
|
||||
});
|
||||
});
|
38
addons/storyshots/src/vue/loader.js
Normal file
38
addons/storyshots/src/vue/loader.js
Normal file
@ -0,0 +1,38 @@
|
||||
import global from 'global';
|
||||
import runWithRequireContext from '../require_context';
|
||||
import hasDependency from '../hasDependency';
|
||||
import loadConfig from '../config-loader';
|
||||
|
||||
function mockVueToIncludeCompiler() {
|
||||
jest.mock('vue', () => require.requireActual('vue/dist/vue.common.js'));
|
||||
}
|
||||
|
||||
function test(options) {
|
||||
return options.framework === 'vue' || (!options.framework && hasDependency('@storybook/vue'));
|
||||
}
|
||||
|
||||
function load(options) {
|
||||
global.STORYBOOK_ENV = 'vue';
|
||||
mockVueToIncludeCompiler();
|
||||
|
||||
const { content, contextOpts } = loadConfig({
|
||||
configDirPath: options.configPath,
|
||||
babelConfigPath: '@storybook/vue/dist/server/babel_config',
|
||||
});
|
||||
|
||||
runWithRequireContext(content, contextOpts);
|
||||
|
||||
return {
|
||||
framework: 'vue',
|
||||
renderTree: require.requireActual('./renderTree').default,
|
||||
renderShallowTree: () => {
|
||||
throw new Error('Shallow renderer is not supported for vue');
|
||||
},
|
||||
storybook: require.requireActual('@storybook/vue'),
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
load,
|
||||
test,
|
||||
};
|
13
addons/storyshots/src/vue/renderTree.js
Normal file
13
addons/storyshots/src/vue/renderTree.js
Normal file
@ -0,0 +1,13 @@
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import Vue from 'vue';
|
||||
|
||||
function getRenderedTree(story, context) {
|
||||
const storyElement = story.render(context);
|
||||
|
||||
const Constructor = Vue.extend(storyElement);
|
||||
const vm = new Constructor().$mount();
|
||||
|
||||
return vm.$el;
|
||||
}
|
||||
|
||||
export default getRenderedTree;
|
@ -0,0 +1,122 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots Another Button with some emoji 1`] = `
|
||||
<Unknown
|
||||
className="css-1yjiefr"
|
||||
onClick={[Function]}
|
||||
>
|
||||
😀 😎 👍 💯
|
||||
</Unknown>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Another Button with text 1`] = `
|
||||
<Unknown
|
||||
className="css-1yjiefr"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Hello Button
|
||||
</Unknown>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Button with some emoji 1`] = `
|
||||
<Unknown
|
||||
className="css-1yjiefr"
|
||||
onClick={[Function]}
|
||||
>
|
||||
😀 😎 👍 💯
|
||||
</Unknown>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Button with text 1`] = `
|
||||
<Unknown
|
||||
className="css-1yjiefr"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Hello Button
|
||||
</Unknown>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Welcome to Storybook 1`] = `
|
||||
<glamorous(article)>
|
||||
<glamorous(h1)>
|
||||
Welcome to storybook
|
||||
</glamorous(h1)>
|
||||
<p>
|
||||
This is a UI component dev environment for your app.
|
||||
</p>
|
||||
<p>
|
||||
We've added some basic stories inside the
|
||||
|
||||
<glamorous(code)>
|
||||
src/stories
|
||||
</glamorous(code)>
|
||||
|
||||
directory.
|
||||
<br />
|
||||
A story is a single state of one or more UI components. You can have as many stories as you want.
|
||||
<br />
|
||||
(Basically a story is like a visual test case.)
|
||||
</p>
|
||||
<p>
|
||||
See these sample
|
||||
|
||||
<glamorous(a)
|
||||
onClick={[Function]}
|
||||
role="button"
|
||||
tabIndex="0"
|
||||
>
|
||||
stories
|
||||
</glamorous(a)>
|
||||
|
||||
for a component called
|
||||
|
||||
<glamorous(code)>
|
||||
Button
|
||||
</glamorous(code)>
|
||||
.
|
||||
</p>
|
||||
<p>
|
||||
Just like that, you can add your own components as stories.
|
||||
<br />
|
||||
You can also edit those components and see changes right away.
|
||||
<br />
|
||||
(Try editing the
|
||||
<glamorous(code)>
|
||||
Button
|
||||
</glamorous(code)>
|
||||
stories located at
|
||||
<glamorous(code)>
|
||||
src/stories/index.js
|
||||
</glamorous(code)>
|
||||
.)
|
||||
</p>
|
||||
<p>
|
||||
Usually we create stories with smaller UI components in the app.
|
||||
<br />
|
||||
Have a look at the
|
||||
|
||||
<glamorous(a)
|
||||
href="https://storybook.js.org/basics/writing-stories"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Writing Stories
|
||||
</glamorous(a)>
|
||||
|
||||
section in our documentation.
|
||||
</p>
|
||||
<glamorous(p)>
|
||||
<b>
|
||||
NOTE:
|
||||
</b>
|
||||
<br />
|
||||
Have a look at the
|
||||
|
||||
<glamorous(code)>
|
||||
.storybook/webpack.config.js
|
||||
</glamorous(code)>
|
||||
|
||||
to add webpack loaders and plugins you are using in this project.
|
||||
</glamorous(p)>
|
||||
</glamorous(article)>
|
||||
`;
|
@ -0,0 +1,11 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots Another Button with some emoji 1`] = `"{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"😀 😎 👍 💯\\",\\"className\\":\\"css-1yjiefr\\"},\\"_owner\\":null,\\"_store\\":{}}"`;
|
||||
|
||||
exports[`Storyshots Another Button with text 1`] = `"{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"Hello Button\\",\\"className\\":\\"css-1yjiefr\\"},\\"_owner\\":null,\\"_store\\":{}}"`;
|
||||
|
||||
exports[`Storyshots Button with some emoji 1`] = `"{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"😀 😎 👍 💯\\",\\"className\\":\\"css-1yjiefr\\"},\\"_owner\\":null,\\"_store\\":{}}"`;
|
||||
|
||||
exports[`Storyshots Button with text 1`] = `"{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"Hello Button\\",\\"className\\":\\"css-1yjiefr\\"},\\"_owner\\":null,\\"_store\\":{}}"`;
|
||||
|
||||
exports[`Storyshots Welcome to Storybook 1`] = `"{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":[{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"Welcome to storybook\\"},\\"_owner\\":null,\\"_store\\":{}},{\\"type\\":\\"p\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"This is a UI component dev environment for your app.\\"},\\"_owner\\":null,\\"_store\\":{}},{\\"type\\":\\"p\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":[\\"We've added some basic stories inside the\\",\\" \\",{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"src/stories\\"},\\"_owner\\":null,\\"_store\\":{}},\\" \\",\\"directory.\\",{\\"type\\":\\"br\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{},\\"_owner\\":null,\\"_store\\":{}},\\"A story is a single state of one or more UI components. You can have as many stories as you want.\\",{\\"type\\":\\"br\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{},\\"_owner\\":null,\\"_store\\":{}},\\"(Basically a story is like a visual test case.)\\"]},\\"_owner\\":null,\\"_store\\":{}},{\\"type\\":\\"p\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":[\\"See these sample\\",\\" \\",{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"role\\":\\"button\\",\\"tabIndex\\":\\"0\\",\\"children\\":\\"stories\\"},\\"_owner\\":null,\\"_store\\":{}},\\" \\",\\"for a component called\\",\\" \\",{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"Button\\"},\\"_owner\\":null,\\"_store\\":{}},\\".\\"]},\\"_owner\\":null,\\"_store\\":{}},{\\"type\\":\\"p\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":[\\"Just like that, you can add your own components as stories.\\",{\\"type\\":\\"br\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{},\\"_owner\\":null,\\"_store\\":{}},\\"You can also edit those components and see changes right away.\\",{\\"type\\":\\"br\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{},\\"_owner\\":null,\\"_store\\":{}},\\"(Try editing the \\",{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"Button\\"},\\"_owner\\":null,\\"_store\\":{}},\\" stories located at \\",{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"src/stories/index.js\\"},\\"_owner\\":null,\\"_store\\":{}},\\".)\\"]},\\"_owner\\":null,\\"_store\\":{}},{\\"type\\":\\"p\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":[\\"Usually we create stories with smaller UI components in the app.\\",{\\"type\\":\\"br\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{},\\"_owner\\":null,\\"_store\\":{}},\\"Have a look at the\\",\\" \\",{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"href\\":\\"https://storybook.js.org/basics/writing-stories\\",\\"target\\":\\"_blank\\",\\"rel\\":\\"noopener noreferrer\\",\\"children\\":\\"Writing Stories\\"},\\"_owner\\":null,\\"_store\\":{}},\\" \\",\\"section in our documentation.\\"]},\\"_owner\\":null,\\"_store\\":{}},{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":[{\\"type\\":\\"b\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"NOTE:\\"},\\"_owner\\":null,\\"_store\\":{}},{\\"type\\":\\"br\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{},\\"_owner\\":null,\\"_store\\":{}},\\"Have a look at the\\",\\" \\",{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\".storybook/webpack.config.js\\"},\\"_owner\\":null,\\"_store\\":{}},\\" \\",\\"to add webpack loaders and plugins you are using in this project.\\"]},\\"_owner\\":null,\\"_store\\":{}}]},\\"_owner\\":null,\\"_store\\":{}}"`;
|
8
addons/storyshots/stories/storyshot.renderOnly.test.js
Normal file
8
addons/storyshots/stories/storyshot.renderOnly.test.js
Normal file
@ -0,0 +1,8 @@
|
||||
import path from 'path';
|
||||
import initStoryshots, { renderOnly } from '../src';
|
||||
|
||||
initStoryshots({
|
||||
framework: 'react',
|
||||
configPath: path.join(__dirname, '..', '.storybook'),
|
||||
test: renderOnly,
|
||||
});
|
8
addons/storyshots/stories/storyshot.shallow.test.js
Normal file
8
addons/storyshots/stories/storyshot.shallow.test.js
Normal file
@ -0,0 +1,8 @@
|
||||
import path from 'path';
|
||||
import initStoryshots, { shallowSnapshot } from '../src';
|
||||
|
||||
initStoryshots({
|
||||
framework: 'react',
|
||||
configPath: path.join(__dirname, '..', '.storybook'),
|
||||
test: shallowSnapshot,
|
||||
});
|
@ -0,0 +1,14 @@
|
||||
import path from 'path';
|
||||
import initStoryshots, { shallowSnapshot } from '../src';
|
||||
|
||||
initStoryshots({
|
||||
framework: 'react',
|
||||
configPath: path.join(__dirname, '..', '.storybook'),
|
||||
test: data =>
|
||||
shallowSnapshot({
|
||||
...data,
|
||||
options: {
|
||||
serializer: JSON.stringify,
|
||||
},
|
||||
}),
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-viewport",
|
||||
"version": "3.4.0-alpha.1",
|
||||
"version": "3.4.0-alpha.5",
|
||||
"description": "Storybook addon to change the viewport size to mobile",
|
||||
"main": "register.js",
|
||||
"keywords": [
|
||||
@ -11,7 +11,7 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@storybook/components": "^3.4.0-alpha.1",
|
||||
"@storybook/components": "^3.4.0-alpha.5",
|
||||
"global": "^4.3.2",
|
||||
"prop-types": "^15.5.10"
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/angular",
|
||||
"version": "3.4.0-alpha.1",
|
||||
"version": "3.4.0-alpha.5",
|
||||
"description": "Storybook for Angular: Develop Angular Components in isolation with Hot Reloading.",
|
||||
"homepage": "https://github.com/storybooks/storybook/tree/master/apps/angular",
|
||||
"bugs": {
|
||||
@ -23,36 +23,37 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addon-actions": "^3.4.0-alpha.1",
|
||||
"@storybook/addon-links": "^3.4.0-alpha.1",
|
||||
"@storybook/addons": "^3.4.0-alpha.1",
|
||||
"@storybook/channel-postmessage": "^3.4.0-alpha.1",
|
||||
"@storybook/core": "^3.4.0-alpha.1",
|
||||
"@storybook/ui": "^3.4.0-alpha.1",
|
||||
"@storybook/addon-actions": "^3.4.0-alpha.5",
|
||||
"@storybook/addon-links": "^3.4.0-alpha.5",
|
||||
"@storybook/addons": "^3.4.0-alpha.5",
|
||||
"@storybook/channel-postmessage": "^3.4.0-alpha.5",
|
||||
"@storybook/core": "^3.4.0-alpha.5",
|
||||
"@storybook/node-logger": "^3.4.0-alpha.5",
|
||||
"@storybook/ui": "^3.4.0-alpha.5",
|
||||
"airbnb-js-shims": "^1.1.1",
|
||||
"angular2-template-loader": "^0.6.2",
|
||||
"autoprefixer": "^7.2.4",
|
||||
"autoprefixer": "^7.2.5",
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-loader": "^7.0.0",
|
||||
"babel-plugin-react-docgen": "^1.6.0",
|
||||
"babel-plugin-react-docgen": "^1.8.2",
|
||||
"babel-preset-env": "^1.6.0",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"babel-preset-react-app": "^3.0.0",
|
||||
"babel-preset-react-app": "^3.1.1",
|
||||
"babel-preset-stage-0": "^6.24.1",
|
||||
"babel-runtime": "^6.23.0",
|
||||
"case-sensitive-paths-webpack-plugin": "^2.0.0",
|
||||
"chalk": "^2.1.0",
|
||||
"commander": "^2.11.0",
|
||||
"common-tags": "^1.7.0",
|
||||
"commander": "^2.13.0",
|
||||
"common-tags": "^1.7.2",
|
||||
"configstore": "^3.1.0",
|
||||
"core-js": "^2.4.1",
|
||||
"cross-env": "^5.1.1",
|
||||
"css-loader": "^0.28.8",
|
||||
"css-loader": "^0.28.9",
|
||||
"express": "^4.15.3",
|
||||
"file-loader": "^0.11.1",
|
||||
"find-cache-dir": "^1.0.0",
|
||||
"global": "^4.3.2",
|
||||
"html-loader": "^0.5.4",
|
||||
"html-loader": "^0.5.5",
|
||||
"html-webpack-plugin": "^2.30.1",
|
||||
"json-loader": "^0.5.4",
|
||||
"json-stringify-safe": "^5.0.1",
|
||||
@ -60,7 +61,7 @@
|
||||
"lodash.pick": "^4.4.0",
|
||||
"markdown-loader": "^2.0.2",
|
||||
"node-sass": "^4.7.2",
|
||||
"postcss-flexbugs-fixes": "^3.0.0",
|
||||
"postcss-flexbugs-fixes": "^3.3.0",
|
||||
"postcss-loader": "^2.0.10",
|
||||
"prop-types": "^15.5.10",
|
||||
"qs": "^6.5.1",
|
||||
@ -73,17 +74,17 @@
|
||||
"rxjs": "^5.4.2",
|
||||
"sass-loader": "^6.0.6",
|
||||
"serve-favicon": "^2.4.3",
|
||||
"shelljs": "^0.7.8",
|
||||
"shelljs": "^0.8.0",
|
||||
"style-loader": "^0.18.2",
|
||||
"ts-loader": "^2.2.2",
|
||||
"uglifyjs-webpack-plugin": "^1.1.6",
|
||||
"url-loader": "^0.5.8",
|
||||
"util-deprecate": "^1.0.2",
|
||||
"uuid": "^3.1.0",
|
||||
"uuid": "^3.2.1",
|
||||
"webpack": "^2.5.1 || ^3.0.0",
|
||||
"webpack-dev-middleware": "^1.10.2",
|
||||
"webpack-hot-middleware": "^2.18.0",
|
||||
"zone.js": "^0.8.19"
|
||||
"zone.js": "^0.8.20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.26.0",
|
||||
@ -91,7 +92,7 @@
|
||||
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||
"codelyzer": "^3.1.2",
|
||||
"mock-fs": "^4.3.0",
|
||||
"nodemon": "^1.14.10",
|
||||
"nodemon": "^1.14.11",
|
||||
"typescript": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
2
app/angular/src/client/manager/provider.js
vendored
2
app/angular/src/client/manager/provider.js
vendored
@ -11,6 +11,8 @@ export default class ReactProvider extends Provider {
|
||||
super();
|
||||
this.channel = createChannel({ page: 'manager' });
|
||||
addons.setChannel(this.channel);
|
||||
|
||||
this.channel.emit('channelCreated');
|
||||
}
|
||||
|
||||
getPanels() {
|
||||
|
@ -53,7 +53,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
const value = props[key];
|
||||
const instanceProperty = instance[key];
|
||||
|
||||
if (!(instanceProperty instanceof EventEmitter) && !!value) {
|
||||
if (!(instanceProperty instanceof EventEmitter) && (value !== undefined && value !== null)) {
|
||||
instance[key] = value;
|
||||
if (hasNgOnChangesHook) {
|
||||
changes[key] = new SimpleChange(undefined, value, instanceProperty === undefined);
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
export interface NgModuleMetadata {
|
||||
declarations?: Array<any>;
|
||||
entryComponents?: Array<any>;
|
||||
|
86
app/angular/src/server/angular-cli_config.js
vendored
Normal file
86
app/angular/src/server/angular-cli_config.js
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { logger } from '@storybook/node-logger';
|
||||
|
||||
function isAngularCliInstalled() {
|
||||
try {
|
||||
require.resolve('@angular/cli');
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function getAngularCliWebpackConfigOptions(dirToSearch, appIndex = 0) {
|
||||
const fname = path.join(dirToSearch, '.angular-cli.json');
|
||||
if (!fs.existsSync(fname)) {
|
||||
return null;
|
||||
}
|
||||
const cliConfig = JSON.parse(fs.readFileSync(fname, 'utf8'));
|
||||
if (!cliConfig.apps || !cliConfig.apps.length) {
|
||||
throw new Error('.angular-cli.json must have apps entry.');
|
||||
}
|
||||
const appConfig = cliConfig.apps[appIndex];
|
||||
|
||||
const cliWebpackConfigOptions = {
|
||||
projectRoot: dirToSearch,
|
||||
appConfig,
|
||||
buildOptions: {
|
||||
outputPath: 'outputPath', // It's dummy value to avoid to Angular CLI's error
|
||||
},
|
||||
supportES2015: false,
|
||||
};
|
||||
|
||||
return cliWebpackConfigOptions;
|
||||
}
|
||||
|
||||
export function applyAngularCliWebpackConfig(baseConfig, cliWebpackConfigOptions) {
|
||||
if (!cliWebpackConfigOptions) return baseConfig;
|
||||
|
||||
if (!isAngularCliInstalled()) {
|
||||
logger.info('=> Using base config because @angular/cli is not installed.');
|
||||
return baseConfig;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
||||
const ngcliConfigFactory = require('@angular/cli/models/webpack-configs');
|
||||
|
||||
let cliCommonConfig;
|
||||
let cliStyleConfig;
|
||||
try {
|
||||
cliCommonConfig = ngcliConfigFactory.getCommonConfig(cliWebpackConfigOptions);
|
||||
cliStyleConfig = ngcliConfigFactory.getStylesConfig(cliWebpackConfigOptions);
|
||||
} catch (e) {
|
||||
logger.warn('=> Failed to get angular-cli webpack config.');
|
||||
return baseConfig;
|
||||
}
|
||||
logger.info('=> Get angular-cli webpack config.');
|
||||
|
||||
// Don't use storybooks .css/.scss rules because we have to use rules created by @angualr/cli
|
||||
// because @angular/cli created rules have include/exclude for global style files.
|
||||
const rulesExcludingStyles = baseConfig.module.rules.filter(
|
||||
rule =>
|
||||
!rule.test || (rule.test.toString() !== '/\\.css$/' && rule.test.toString() !== '/\\.scss$/')
|
||||
);
|
||||
|
||||
// cliStyleConfig.entry adds global style files to the webpack context
|
||||
const entry = {
|
||||
...baseConfig.entry,
|
||||
...cliStyleConfig.entry,
|
||||
};
|
||||
|
||||
const mod = {
|
||||
...baseConfig.module,
|
||||
rules: [...cliStyleConfig.module.rules, ...rulesExcludingStyles],
|
||||
};
|
||||
|
||||
// We use cliCommonConfig plugins to serve static assets files.
|
||||
const plugins = [...cliStyleConfig.plugins, ...cliCommonConfig.plugins, ...baseConfig.plugins];
|
||||
|
||||
return {
|
||||
...baseConfig,
|
||||
entry,
|
||||
module: mod,
|
||||
plugins,
|
||||
};
|
||||
}
|
4
app/angular/src/server/babel_config.js
vendored
4
app/angular/src/server/babel_config.js
vendored
@ -1,11 +1,9 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import JSON5 from 'json5';
|
||||
import { logger } from '@storybook/node-logger';
|
||||
import defaultConfig from './config/babel';
|
||||
|
||||
// avoid ESLint errors
|
||||
const logger = console;
|
||||
|
||||
function removeReactHmre(presets) {
|
||||
const index = presets.indexOf('react-hmre');
|
||||
if (index > -1) {
|
||||
|
8
app/angular/src/server/build.js
vendored
8
app/angular/src/server/build.js
vendored
@ -4,6 +4,7 @@ import path from 'path';
|
||||
import fs from 'fs';
|
||||
import chalk from 'chalk';
|
||||
import shelljs from 'shelljs';
|
||||
import { logger } from '@storybook/node-logger';
|
||||
import packageJson from '../../package.json';
|
||||
import getBaseConfig from './config/webpack.config.prod';
|
||||
import loadConfig from './config';
|
||||
@ -11,9 +12,6 @@ import { parseList, getEnvConfig } from './utils';
|
||||
|
||||
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
|
||||
|
||||
// avoid ESLint errors
|
||||
const logger = console;
|
||||
|
||||
program
|
||||
.version(packageJson.version)
|
||||
.option('-s, --static-dir <dir-names>', 'Directory where to load static files from', parseList)
|
||||
@ -66,13 +64,13 @@ if (program.staticDir) {
|
||||
logger.error(`Error: no such directory to load static files: ${dir}`);
|
||||
process.exit(-1);
|
||||
}
|
||||
logger.log(`=> Copying static files from: ${dir}`);
|
||||
logger.info(`=> Copying static files from: ${dir}`);
|
||||
shelljs.cp('-r', `${dir}/*`, outputDir);
|
||||
});
|
||||
}
|
||||
|
||||
// compile all resources with webpack and write them to the disk.
|
||||
logger.log('Building storybook ...');
|
||||
logger.info('Building storybook ...');
|
||||
webpack(config).run((err, stats) => {
|
||||
if (err || stats.hasErrors()) {
|
||||
logger.error('Failed to build the storybook');
|
||||
|
19
app/angular/src/server/config.js
vendored
19
app/angular/src/server/config.js
vendored
@ -1,11 +1,13 @@
|
||||
/* eslint-disable global-require, import/no-dynamic-require */
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { logger } from '@storybook/node-logger';
|
||||
import loadBabelConfig from './babel_config';
|
||||
import loadTsConfig from './ts_config';
|
||||
|
||||
// avoid ESLint errors
|
||||
const logger = console;
|
||||
import {
|
||||
getAngularCliWebpackConfigOptions,
|
||||
applyAngularCliWebpackConfig,
|
||||
} from './angular-cli_config';
|
||||
|
||||
// `baseConfig` is a webpack configuration bundled with storybook.
|
||||
// Storybook will look in the `configDir` directory
|
||||
@ -39,6 +41,12 @@ export default function(configType, baseConfig, configDir) {
|
||||
config.entry.manager.unshift(storybookDefaultAddonsPath);
|
||||
}
|
||||
|
||||
// Check whether project has Angular CLI configuration file
|
||||
const cliWebpackConfigOptions = getAngularCliWebpackConfigOptions(process.cwd());
|
||||
if (cliWebpackConfigOptions) {
|
||||
logger.info('=> Loading angular-cli config.');
|
||||
}
|
||||
|
||||
// Check whether user has a custom webpack config file and
|
||||
// return the (extended) base configuration if it's not available.
|
||||
const customConfigPath = path.resolve(configDir, 'webpack.config.js');
|
||||
@ -48,15 +56,16 @@ export default function(configType, baseConfig, configDir) {
|
||||
const configPath = path.resolve(__dirname, './config/defaults/webpack.config.js');
|
||||
const customConfig = require(configPath);
|
||||
|
||||
return customConfig(config);
|
||||
return applyAngularCliWebpackConfig(customConfig(config), cliWebpackConfigOptions);
|
||||
}
|
||||
const customConfig = require(customConfigPath);
|
||||
|
||||
if (typeof customConfig === 'function') {
|
||||
logger.info('=> Loading custom webpack config (full-control mode).');
|
||||
return customConfig(config, configType);
|
||||
return customConfig(applyAngularCliWebpackConfig(config, cliWebpackConfigOptions), configType);
|
||||
}
|
||||
logger.info('=> Loading custom webpack config (extending mode).');
|
||||
|
||||
return {
|
||||
...customConfig,
|
||||
// We'll always load our configurations after the custom config.
|
||||
|
5
app/angular/src/server/index.js
vendored
5
app/angular/src/server/index.js
vendored
@ -8,14 +8,13 @@ import path from 'path';
|
||||
import fs from 'fs';
|
||||
import chalk from 'chalk';
|
||||
import shelljs from 'shelljs';
|
||||
import { logger } from '@storybook/node-logger';
|
||||
import storybook, { webpackValid } from './middleware';
|
||||
import packageJson from '../../package.json';
|
||||
import { parseList, getEnvConfig } from './utils';
|
||||
|
||||
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
||||
|
||||
const logger = console;
|
||||
|
||||
program
|
||||
.version(packageJson.version)
|
||||
.option('-p, --port [number]', 'Port to run Storybook (Required)', parseInt)
|
||||
@ -105,7 +104,7 @@ if (program.staticDir) {
|
||||
logger.error(`Error: no such directory to load static files: ${staticPath}`);
|
||||
process.exit(-1);
|
||||
}
|
||||
logger.log(`=> Loading static files from: ${staticPath} .`);
|
||||
logger.info(`=> Loading static files from: ${staticPath} .`);
|
||||
app.use(express.static(staticPath, { index: false }));
|
||||
|
||||
const faviconPath = path.resolve(staticPath, 'favicon.ico');
|
||||
|
4
app/angular/src/server/ts_config.js
vendored
4
app/angular/src/server/ts_config.js
vendored
@ -1,8 +1,6 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
// avoid ESLint errors
|
||||
const logger = console;
|
||||
import { logger } from '@storybook/node-logger';
|
||||
|
||||
function resolveTsConfig(tsConfigPath) {
|
||||
if (!fs.existsSync(tsConfigPath)) {
|
||||
|
3
app/polymer/.babelrc
Normal file
3
app/polymer/.babelrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": ["env", "stage-0", "react"]
|
||||
}
|
3
app/polymer/.npmignore
Normal file
3
app/polymer/.npmignore
Normal file
@ -0,0 +1,3 @@
|
||||
docs
|
||||
src
|
||||
.babelrc
|
41
app/polymer/README.md
Normal file
41
app/polymer/README.md
Normal file
@ -0,0 +1,41 @@
|
||||
# Storybook for Polymer
|
||||
|
||||
[](https://circleci.com/gh/storybooks/storybook)
|
||||
[](https://www.codefactor.io/repository/github/storybooks/storybook)
|
||||
[](https://snyk.io/test/github/storybooks/storybook/8f36abfd6697e58cd76df3526b52e4b9dc894847)
|
||||
[](https://bettercodehub.com/results/storybooks/storybook) [](https://codecov.io/gh/storybooks/storybook)
|
||||
[](https://now-examples-slackin-nqnzoygycp.now.sh/)
|
||||
[](#backers) [](#sponsors)
|
||||
|
||||
* * *
|
||||
|
||||
Storybook for polymer is a UI development environment for your Polymer components.
|
||||
With it, you can visualize different states of your UI components and develop them interactively.
|
||||
|
||||
> Storybook for Polymer is at the **EXPERIMENTAL** stage!
|
||||
|
||||

|
||||
|
||||
Storybook runs outside of your app.
|
||||
So you can develop UI components in isolation without worrying about app specific dependencies and requirements.
|
||||
|
||||
## Getting Started
|
||||
|
||||
```sh
|
||||
npm i -g @storybook/cli
|
||||
cd my-polymer-app
|
||||
getstorybook
|
||||
```
|
||||
|
||||
For more information visit: [storybook.js.org](https://storybook.js.org)
|
||||
|
||||
* * *
|
||||
|
||||
Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish.
|
||||
You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want.
|
||||
|
||||
## Polymer Notes
|
||||
|
||||
- This is super super experimental, if you want to use this, expect some bugs, and missing features.
|
||||
- We're looking for help to support this. If you're a member of the Polymer community and like this project, please help us!
|
||||
If you need any onboarding from us, we're happy to help you in any way!
|
3
app/polymer/bin/build.js
Executable file
3
app/polymer/bin/build.js
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
require('../dist/server/build');
|
3
app/polymer/bin/index.js
Executable file
3
app/polymer/bin/index.js
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
require('../dist/server');
|
BIN
app/polymer/docs/demo.gif
Normal file
BIN
app/polymer/docs/demo.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 MiB |
BIN
app/polymer/docs/react_storybook_screenshot.png
Normal file
BIN
app/polymer/docs/react_storybook_screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 245 KiB |
BIN
app/polymer/docs/storybooks_io_logo.png
Normal file
BIN
app/polymer/docs/storybooks_io_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
87
app/polymer/package.json
Normal file
87
app/polymer/package.json
Normal file
@ -0,0 +1,87 @@
|
||||
{
|
||||
"name": "@storybook/polymer",
|
||||
"version": "3.4.0-alpha.5",
|
||||
"description": "Storybook for Polymer: Develop Polymer components in isolation with Hot Reloading.",
|
||||
"homepage": "https://github.com/storybooks/storybook/tree/master/apps/polymer",
|
||||
"bugs": {
|
||||
"url": "https://github.com/storybooks/storybook/issues"
|
||||
},
|
||||
"license": "MIT",
|
||||
"main": "dist/client/index.js",
|
||||
"bin": {
|
||||
"build-storybook": "./bin/build.js",
|
||||
"start-storybook": "./bin/index.js",
|
||||
"storybook-server": "./bin/index.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/storybooks/storybook.git"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "DEV_BUILD=1 nodemon --watch ./src --exec 'yarn prepare'",
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "^3.4.0-alpha.5",
|
||||
"@storybook/channel-postmessage": "^3.4.0-alpha.5",
|
||||
"@storybook/client-logger": "^3.4.0-alpha.5",
|
||||
"@storybook/core": "^3.4.0-alpha.5",
|
||||
"@storybook/node-logger": "^3.4.0-alpha.5",
|
||||
"@storybook/ui": "^3.4.0-alpha.5",
|
||||
"@webcomponents/webcomponentsjs": "^1.0.17",
|
||||
"airbnb-js-shims": "^1.3.0",
|
||||
"autoprefixer": "^7.1.6",
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-plugin-react-docgen": "^1.8.2",
|
||||
"babel-plugin-transform-regenerator": "^6.26.0",
|
||||
"babel-plugin-transform-runtime": "^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-react-app": "^3.1.1",
|
||||
"babel-preset-stage-0": "^6.24.1",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"case-sensitive-paths-webpack-plugin": "^2.1.1",
|
||||
"chalk": "^2.3.0",
|
||||
"commander": "^2.11.0",
|
||||
"common-tags": "^1.4.0",
|
||||
"configstore": "^3.1.1",
|
||||
"copy-webpack-plugin": "^4.2.0",
|
||||
"core-js": "^2.5.1",
|
||||
"css-loader": "^0.28.9",
|
||||
"express": "^4.16.2",
|
||||
"file-loader": "^0.11.2",
|
||||
"find-cache-dir": "^1.0.0",
|
||||
"global": "^4.3.2",
|
||||
"html-webpack-plugin": "^2.30.1",
|
||||
"json-loader": "^0.5.7",
|
||||
"json-stringify-safe": "^5.0.1",
|
||||
"json5": "^0.5.1",
|
||||
"lodash.pick": "^4.4.0",
|
||||
"polymer-webpack-loader": "2.0.1",
|
||||
"postcss-flexbugs-fixes": "^3.3.0",
|
||||
"postcss-loader": "^2.0.8",
|
||||
"prop-types": "^15.6.0",
|
||||
"qs": "^6.5.1",
|
||||
"react": "^16.0.0",
|
||||
"react-dom": "^16.0.0",
|
||||
"react-modal": "^2.4.1",
|
||||
"redux": "^3.7.2",
|
||||
"request": "^2.83.0",
|
||||
"serve-favicon": "^2.4.5",
|
||||
"shelljs": "^0.7.8",
|
||||
"style-loader": "^0.18.2",
|
||||
"url-loader": "^0.6.2",
|
||||
"util-deprecate": "^1.0.2",
|
||||
"uuid": "^3.2.1",
|
||||
"webpack": "^3.6.0",
|
||||
"webpack-dev-middleware": "^1.12.0",
|
||||
"webpack-hot-middleware": "^2.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.26.0",
|
||||
"nodemon": "^1.12.1"
|
||||
}
|
||||
}
|
17
app/polymer/src/client/index.js
Normal file
17
app/polymer/src/client/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
// import deprecate from 'util-deprecate';
|
||||
|
||||
// NOTE export these to keep backwards compatibility
|
||||
// import { action as deprecatedAction } from '@storybook/addon-actions';
|
||||
// import { linkTo as deprecatedLinkTo } from '@storybook/addon-links';
|
||||
|
||||
export { storiesOf, setAddon, addDecorator, configure, getStorybook } from './preview';
|
||||
|
||||
// export const action = deprecate(
|
||||
// deprecatedAction,
|
||||
// '@storybook/react action is deprecated. See: https://github.com/storybooks/storybook/tree/master/addons/actions'
|
||||
// );
|
||||
|
||||
// export const linkTo = deprecate(
|
||||
// deprecatedLinkTo,
|
||||
// '@storybook/react linkTo is deprecated. See: https://github.com/storybooks/storybook/tree/master/addons/links'
|
||||
// );
|
7
app/polymer/src/client/manager/index.js
Normal file
7
app/polymer/src/client/manager/index.js
Normal file
@ -0,0 +1,7 @@
|
||||
/* global document */
|
||||
|
||||
import renderStorybookUI from '@storybook/ui';
|
||||
import Provider from './provider';
|
||||
|
||||
const rootEl = document.getElementById('root');
|
||||
renderStorybookUI(rootEl, new Provider());
|
39
app/polymer/src/client/manager/preview.js
Normal file
39
app/polymer/src/client/manager/preview.js
Normal file
@ -0,0 +1,39 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
const iframeStyle = {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
border: 0,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
};
|
||||
|
||||
class Preview extends Component {
|
||||
shouldComponentUpdate() {
|
||||
// When the manager is re-rendered, due to changes in the layout (going full screen / changing
|
||||
// addon panel to right) Preview section will update. If its re-rendered the whole html page
|
||||
// inside the html is re-rendered making the story to re-mount.
|
||||
// We dont have to re-render this component for any reason since changes are communicated to
|
||||
// story using the channel and necessary changes are done by it.
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<iframe
|
||||
id="storybook-preview-iframe"
|
||||
title="preview"
|
||||
style={iframeStyle}
|
||||
src={this.props.url}
|
||||
allowFullScreen
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Preview.propTypes = {
|
||||
url: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default Preview;
|
53
app/polymer/src/client/manager/provider.js
Normal file
53
app/polymer/src/client/manager/provider.js
Normal file
@ -0,0 +1,53 @@
|
||||
import { location } from 'global';
|
||||
import qs from 'qs';
|
||||
import React from 'react';
|
||||
import { Provider } from '@storybook/ui';
|
||||
import addons from '@storybook/addons';
|
||||
import createChannel from '@storybook/channel-postmessage';
|
||||
import Preview from './preview';
|
||||
|
||||
export default class ReactProvider extends Provider {
|
||||
constructor() {
|
||||
super();
|
||||
this.channel = createChannel({ page: 'manager' });
|
||||
addons.setChannel(this.channel);
|
||||
|
||||
this.channel.emit('channelCreated');
|
||||
}
|
||||
|
||||
getPanels() {
|
||||
return addons.getPanels();
|
||||
}
|
||||
|
||||
renderPreview(selectedKind, selectedStory) {
|
||||
const queryParams = {
|
||||
selectedKind,
|
||||
selectedStory,
|
||||
};
|
||||
|
||||
// Add the react-perf query string to the iframe if that present.
|
||||
if (/react_perf/.test(location.search)) {
|
||||
queryParams.react_perf = '1';
|
||||
}
|
||||
|
||||
const queryString = qs.stringify(queryParams);
|
||||
const url = `iframe.html?${queryString}`;
|
||||
return <Preview url={url} />;
|
||||
}
|
||||
|
||||
handleAPI(api) {
|
||||
api.onStory((kind, story) => {
|
||||
this.channel.emit('setCurrentStory', { kind, story });
|
||||
});
|
||||
this.channel.on('setStories', data => {
|
||||
api.setStories(data.stories);
|
||||
});
|
||||
this.channel.on('selectStory', data => {
|
||||
api.selectStory(data.kind, data.story);
|
||||
});
|
||||
this.channel.on('applyShortcut', data => {
|
||||
api.handleShortcut(data.event);
|
||||
});
|
||||
addons.loadAddons(api);
|
||||
}
|
||||
}
|
37
app/polymer/src/client/preview/errorpreview.js
Normal file
37
app/polymer/src/client/preview/errorpreview.js
Normal file
@ -0,0 +1,37 @@
|
||||
export const errorpreview = (message, stack) => `
|
||||
<style>
|
||||
.errordisplay_main {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 20;
|
||||
background-color: rgb(187, 49, 49);
|
||||
color: #FFF;
|
||||
webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.errordisplay_heading {
|
||||
font-size: 20;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.2;
|
||||
margin: 10px 0;
|
||||
font-family: -apple-system, ".SFNSText-Regular", "San Francisco", Roboto, "Segoe UI", "Helvetica Neue", "Lucida Grande", sans-serif;
|
||||
}
|
||||
|
||||
.errordisplay_code {
|
||||
font-size: 14;
|
||||
width: 100vw;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
<div class="errordisplay_main">
|
||||
<div class="errordisplay_heading">${message}</div>
|
||||
<pre class="errordisplay_code">
|
||||
<code>
|
||||
${stack}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
`;
|
61
app/polymer/src/client/preview/index.js
Normal file
61
app/polymer/src/client/preview/index.js
Normal file
@ -0,0 +1,61 @@
|
||||
import { createStore } from 'redux';
|
||||
import addons from '@storybook/addons';
|
||||
import { navigator, window } from 'global';
|
||||
import createChannel from '@storybook/channel-postmessage';
|
||||
import { handleKeyboardShortcuts } from '@storybook/ui/dist/libs/key_events';
|
||||
import {
|
||||
StoryStore,
|
||||
ClientApi,
|
||||
ConfigApi,
|
||||
Actions,
|
||||
reducer,
|
||||
syncUrlWithStore,
|
||||
} from '@storybook/core/client';
|
||||
|
||||
import render from './render';
|
||||
|
||||
// check whether we're running on node/browser
|
||||
const isBrowser =
|
||||
navigator &&
|
||||
navigator.userAgent &&
|
||||
navigator.userAgent !== 'storyshots' &&
|
||||
!(navigator.userAgent.indexOf('Node.js') > -1) &&
|
||||
!(navigator.userAgent.indexOf('jsdom') > -1);
|
||||
|
||||
const storyStore = new StoryStore();
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
const reduxStore = createStore(reducer);
|
||||
/* eslint-enable */
|
||||
const context = { storyStore, reduxStore };
|
||||
|
||||
if (isBrowser) {
|
||||
// setup preview channel
|
||||
const channel = createChannel({ page: 'preview' });
|
||||
channel.on('setCurrentStory', data => {
|
||||
reduxStore.dispatch(Actions.selectStory(data.kind, data.story));
|
||||
});
|
||||
addons.setChannel(channel);
|
||||
Object.assign(context, { channel });
|
||||
|
||||
syncUrlWithStore(reduxStore);
|
||||
|
||||
// Handle keyboard shortcuts
|
||||
window.onkeydown = handleKeyboardShortcuts(channel);
|
||||
}
|
||||
|
||||
const clientApi = new ClientApi(context);
|
||||
export const { storiesOf, setAddon, addDecorator, clearDecorators, getStorybook } = clientApi;
|
||||
|
||||
const configApi = new ConfigApi({ clearDecorators, ...context });
|
||||
export const { configure } = configApi;
|
||||
|
||||
// initialize the UI
|
||||
const renderUI = () => {
|
||||
if (isBrowser) {
|
||||
render(context);
|
||||
}
|
||||
};
|
||||
|
||||
reduxStore.subscribe(renderUI);
|
||||
|
||||
export const forceReRender = () => render(context, true);
|
42
app/polymer/src/client/preview/nopreview.js
Normal file
42
app/polymer/src/client/preview/nopreview.js
Normal file
@ -0,0 +1,42 @@
|
||||
export const nopreview = `
|
||||
<style>
|
||||
.nopreview_wrapper {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 20;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
font-family: -apple-system, ".SFNSText-Regular", "San Francisco", Roboto, "Segoe UI", "Helvetica Neue", "Lucida Grande", sans-serif;
|
||||
webkit-font-smoothing: antialiased;
|
||||
}
|
||||
.nopreview_main {
|
||||
margin: auto;
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
background: rgba(0,0,0,0.03);
|
||||
}
|
||||
|
||||
.nopreview_heading {
|
||||
font-size: 20;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.2;
|
||||
margin: 10px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
<div class="nopreview_wrapper">
|
||||
<div class="nopreview_main">
|
||||
<h1 class="nopreview_heading">No Preview</h1>
|
||||
<p>Sorry, but you either have no stories or none are selected somehow.</p>
|
||||
<ul>
|
||||
<li>Please check the storybook config.</li>
|
||||
<li>Try reloading the page.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
66
app/polymer/src/client/preview/render.js
Normal file
66
app/polymer/src/client/preview/render.js
Normal file
@ -0,0 +1,66 @@
|
||||
import { document } from 'global';
|
||||
import { stripIndents } from 'common-tags';
|
||||
import { logger } from '@storybook/client-logger';
|
||||
import { nopreview } from './nopreview';
|
||||
import { errorpreview } from './errorpreview';
|
||||
|
||||
let previousKind = '';
|
||||
let previousStory = '';
|
||||
|
||||
const rootElement = document.getElementById('root');
|
||||
|
||||
export function renderError(error) {
|
||||
rootElement.innerHTML = errorpreview(error.message, error.stack);
|
||||
}
|
||||
|
||||
export function renderException(error) {
|
||||
renderError(error);
|
||||
logger.error(error.stack);
|
||||
}
|
||||
|
||||
export function renderMain(data, storyStore) {
|
||||
if (storyStore.size() === 0) return;
|
||||
const { selectedKind, selectedStory } = data;
|
||||
const story = storyStore.getStory(selectedKind, selectedStory);
|
||||
|
||||
if (selectedKind !== previousKind || previousStory !== selectedStory) {
|
||||
previousKind = selectedKind;
|
||||
previousStory = selectedStory;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
const context = {
|
||||
kind: selectedKind,
|
||||
story: selectedStory,
|
||||
};
|
||||
const component = story ? story(context) : nopreview;
|
||||
|
||||
if (!component) {
|
||||
renderError({
|
||||
message: `Expecting a Polymer component from the story: "${selectedStory}" of "${selectedKind}".`,
|
||||
stack: stripIndents`
|
||||
Did you forget to return the Polymer component from the story?
|
||||
Use "() => '<your-component-name></your-component-name\>'" when defining the story.
|
||||
`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (typeof component === 'string') {
|
||||
rootElement.innerHTML = component;
|
||||
} else {
|
||||
rootElement.innerHTML = '';
|
||||
rootElement.appendChild(component);
|
||||
}
|
||||
}
|
||||
|
||||
export default function renderPreview({ reduxStore, storyStore }) {
|
||||
const state = reduxStore.getState();
|
||||
if (state.error) {
|
||||
return renderException(state.error);
|
||||
}
|
||||
try {
|
||||
return renderMain(state, storyStore);
|
||||
} catch (ex) {
|
||||
return renderException(ex);
|
||||
}
|
||||
}
|
2
app/polymer/src/server/addons.js
Normal file
2
app/polymer/src/server/addons.js
Normal file
@ -0,0 +1,2 @@
|
||||
// import '@storybook/addon-actions/register';
|
||||
// import '@storybook/addon-links/register';
|
82
app/polymer/src/server/babel_config.js
Normal file
82
app/polymer/src/server/babel_config.js
Normal file
@ -0,0 +1,82 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import JSON5 from 'json5';
|
||||
import { logger } from '@storybook/node-logger';
|
||||
import defaultConfig from './config/babel';
|
||||
|
||||
function removeReactHmre(presets) {
|
||||
const index = presets.indexOf('react-hmre');
|
||||
if (index > -1) {
|
||||
presets.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Tries to load a .babelrc and returns the parsed object if successful
|
||||
function loadFromPath(babelConfigPath) {
|
||||
let config;
|
||||
if (fs.existsSync(babelConfigPath)) {
|
||||
const content = fs.readFileSync(babelConfigPath, 'utf-8');
|
||||
try {
|
||||
config = JSON5.parse(content);
|
||||
config.babelrc = false;
|
||||
logger.info('=> Loading custom .babelrc');
|
||||
} catch (e) {
|
||||
logger.error(`=> Error parsing .babelrc file: ${e.message}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (!config) return null;
|
||||
|
||||
// Remove react-hmre preset.
|
||||
// It causes issues with react-storybook.
|
||||
// We don't really need it.
|
||||
// Earlier, we fix this by running storybook in the production mode.
|
||||
// But, that hide some useful debug messages.
|
||||
if (config.presets) {
|
||||
removeReactHmre(config.presets);
|
||||
}
|
||||
|
||||
if (config.env && config.env.development && config.env.development.presets) {
|
||||
removeReactHmre(config.env.development.presets);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
export default function(configDir) {
|
||||
let babelConfig = loadFromPath(path.resolve(configDir, '.babelrc'));
|
||||
let inConfigDir = true;
|
||||
|
||||
if (!babelConfig) {
|
||||
babelConfig = loadFromPath('.babelrc');
|
||||
inConfigDir = false;
|
||||
}
|
||||
|
||||
if (babelConfig) {
|
||||
// If the custom config uses babel's `extends` clause, then replace it with
|
||||
// an absolute path. `extends` will not work unless we do this.
|
||||
if (babelConfig.extends) {
|
||||
babelConfig.extends = inConfigDir
|
||||
? path.resolve(configDir, babelConfig.extends)
|
||||
: path.resolve(babelConfig.extends);
|
||||
}
|
||||
}
|
||||
|
||||
const finalConfig = babelConfig || defaultConfig;
|
||||
// Ensure plugins are defined or fallback to an array to avoid empty values.
|
||||
const babelConfigPlugins = finalConfig.plugins || [];
|
||||
const extraPlugins = [
|
||||
[
|
||||
require.resolve('babel-plugin-react-docgen'),
|
||||
{
|
||||
DOC_GEN_COLLECTION_NAME: 'STORYBOOK_REACT_CLASSES',
|
||||
},
|
||||
],
|
||||
];
|
||||
// If `babelConfigPlugins` is not an `Array`, calling `concat` will inject it
|
||||
// as a single value, if it is an `Array` it will be spreaded.
|
||||
finalConfig.plugins = [].concat(babelConfigPlugins, extraPlugins);
|
||||
|
||||
return finalConfig;
|
||||
}
|
110
app/polymer/src/server/babel_config.test.js
Normal file
110
app/polymer/src/server/babel_config.test.js
Normal file
@ -0,0 +1,110 @@
|
||||
import loadBabelConfig from './babel_config';
|
||||
|
||||
// eslint-disable-next-line global-require
|
||||
jest.mock('fs', () => require('../../../../__mocks__/fs'));
|
||||
jest.mock('path', () => ({
|
||||
resolve: () => '.babelrc',
|
||||
}));
|
||||
|
||||
const setup = ({ files }) => {
|
||||
// eslint-disable-next-line no-underscore-dangle, global-require
|
||||
require('fs').__setMockFiles(files);
|
||||
};
|
||||
|
||||
describe('babel_config', () => {
|
||||
// As the 'fs' is going to be mocked, let's call require.resolve
|
||||
// so the require.cache has the correct route to the file.
|
||||
// In fact let's use it in the tests :)
|
||||
const babelPluginReactDocgenPath = require.resolve('babel-plugin-react-docgen');
|
||||
|
||||
it('should return the config with the extra plugins when `plugins` is an array.', () => {
|
||||
setup({
|
||||
files: {
|
||||
'.babelrc': `{
|
||||
"presets": [
|
||||
"env",
|
||||
"foo-preset"
|
||||
],
|
||||
"plugins": [
|
||||
"foo-plugin"
|
||||
]
|
||||
}`,
|
||||
},
|
||||
});
|
||||
|
||||
const config = loadBabelConfig('.foo');
|
||||
|
||||
expect(config).toEqual({
|
||||
babelrc: false,
|
||||
plugins: [
|
||||
'foo-plugin',
|
||||
[
|
||||
babelPluginReactDocgenPath,
|
||||
{
|
||||
DOC_GEN_COLLECTION_NAME: 'STORYBOOK_REACT_CLASSES',
|
||||
},
|
||||
],
|
||||
],
|
||||
presets: ['env', 'foo-preset'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the config with the extra plugins when `plugins` is not an array.', () => {
|
||||
setup({
|
||||
files: {
|
||||
'.babelrc': `{
|
||||
"presets": [
|
||||
"env",
|
||||
"foo-preset"
|
||||
],
|
||||
"plugins": "bar-plugin"
|
||||
}`,
|
||||
},
|
||||
});
|
||||
|
||||
const config = loadBabelConfig('.bar');
|
||||
|
||||
expect(config).toEqual({
|
||||
babelrc: false,
|
||||
plugins: [
|
||||
'bar-plugin',
|
||||
[
|
||||
babelPluginReactDocgenPath,
|
||||
{
|
||||
DOC_GEN_COLLECTION_NAME: 'STORYBOOK_REACT_CLASSES',
|
||||
},
|
||||
],
|
||||
],
|
||||
presets: ['env', 'foo-preset'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the config only with the extra plugins when `plugins` is not present.', () => {
|
||||
// Mock a `.babelrc` config file with no plugins key.
|
||||
setup({
|
||||
files: {
|
||||
'.babelrc': `{
|
||||
"presets": [
|
||||
"env",
|
||||
"foo-preset"
|
||||
]
|
||||
}`,
|
||||
},
|
||||
});
|
||||
|
||||
const config = loadBabelConfig('.biz');
|
||||
|
||||
expect(config).toEqual({
|
||||
babelrc: false,
|
||||
plugins: [
|
||||
[
|
||||
babelPluginReactDocgenPath,
|
||||
{
|
||||
DOC_GEN_COLLECTION_NAME: 'STORYBOOK_REACT_CLASSES',
|
||||
},
|
||||
],
|
||||
],
|
||||
presets: ['env', 'foo-preset'],
|
||||
});
|
||||
});
|
||||
});
|
83
app/polymer/src/server/build.js
Executable file
83
app/polymer/src/server/build.js
Executable file
@ -0,0 +1,83 @@
|
||||
import webpack from 'webpack';
|
||||
import program from 'commander';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import chalk from 'chalk';
|
||||
import shelljs from 'shelljs';
|
||||
import { logger } from '@storybook/node-logger';
|
||||
import packageJson from '../../package.json';
|
||||
import getBaseConfig from './config/webpack.config.prod';
|
||||
import loadConfig from './config';
|
||||
import { parseList, getEnvConfig } from './utils';
|
||||
|
||||
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
|
||||
|
||||
program
|
||||
.version(packageJson.version)
|
||||
.option('-s, --static-dir <dir-names>', 'Directory where to load static files from', parseList)
|
||||
.option('-o, --output-dir [dir-name]', 'Directory where to store built files')
|
||||
.option('-c, --config-dir [dir-name]', 'Directory where to load Storybook configurations from')
|
||||
.option('-d, --db-path [db-file]', 'DEPRECATED!')
|
||||
.option('--enable-db', 'DEPRECATED!')
|
||||
.parse(process.argv);
|
||||
|
||||
logger.info(chalk.bold(`${packageJson.name} v${packageJson.version}\n`));
|
||||
|
||||
if (program.enableDb || program.dbPath) {
|
||||
logger.error(
|
||||
[
|
||||
'Error: the experimental local database addon is no longer bundled with',
|
||||
'react-storybook. Please remove these flags (-d,--db-path,--enable-db)',
|
||||
'from the command or npm script and try again.',
|
||||
].join(' ')
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// The key is the field created in `program` variable for
|
||||
// each command line argument. Value is the env variable.
|
||||
getEnvConfig(program, {
|
||||
staticDir: 'SBCONFIG_STATIC_DIR',
|
||||
outputDir: 'SBCONFIG_OUTPUT_DIR',
|
||||
configDir: 'SBCONFIG_CONFIG_DIR',
|
||||
});
|
||||
|
||||
const configDir = program.configDir || './.storybook';
|
||||
const outputDir = program.outputDir || './storybook-static';
|
||||
|
||||
// create output directory if not exists
|
||||
shelljs.mkdir('-p', path.resolve(outputDir));
|
||||
// clear the static dir
|
||||
shelljs.rm('-rf', path.resolve(outputDir, 'static'));
|
||||
shelljs.cp(path.resolve(__dirname, 'public/favicon.ico'), outputDir);
|
||||
|
||||
// Build the webpack configuration using the `baseConfig`
|
||||
// custom `.babelrc` file and `webpack.config.js` files
|
||||
// NOTE changes to env should be done before calling `getBaseConfig`
|
||||
const config = loadConfig('PRODUCTION', getBaseConfig(), configDir);
|
||||
config.output.path = path.resolve(outputDir);
|
||||
|
||||
// copy all static files
|
||||
if (program.staticDir) {
|
||||
program.staticDir.forEach(dir => {
|
||||
if (!fs.existsSync(dir)) {
|
||||
logger.error(`Error: no such directory to load static files: ${dir}`);
|
||||
process.exit(-1);
|
||||
}
|
||||
logger.log(`=> Copying static files from: ${dir}`);
|
||||
shelljs.cp('-r', `${dir}/*`, outputDir);
|
||||
});
|
||||
}
|
||||
|
||||
// compile all resources with webpack and write them to the disk.
|
||||
logger.log('Building storybook ...');
|
||||
webpack(config).run((err, stats) => {
|
||||
if (err || stats.hasErrors()) {
|
||||
logger.error('Failed to build the storybook');
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
err && logger.error(err.message);
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
stats.hasErrors() && stats.toJson().errors.forEach(e => logger.error(e));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
86
app/polymer/src/server/config.js
Normal file
86
app/polymer/src/server/config.js
Normal file
@ -0,0 +1,86 @@
|
||||
/* eslint-disable global-require, import/no-dynamic-require */
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import findCacheDir from 'find-cache-dir';
|
||||
import { logger } from '@storybook/node-logger';
|
||||
import loadBabelConfig from './babel_config';
|
||||
|
||||
// `baseConfig` is a webpack configuration bundled with storybook.
|
||||
// Storybook will look in the `configDir` directory
|
||||
// (inside working directory) if a config path is not provided.
|
||||
export default function(configType, baseConfig, configDir) {
|
||||
const config = baseConfig;
|
||||
|
||||
const babelConfig = loadBabelConfig(configDir);
|
||||
config.module.rules[0].query = {
|
||||
// This is a feature of `babel-loader` for webpack (not Babel itself).
|
||||
// It enables a cache directory for faster-rebuilds
|
||||
// `find-cache-dir` will create the cache directory under the node_modules directory.
|
||||
cacheDirectory: findCacheDir({ name: 'react-storybook' }),
|
||||
...babelConfig,
|
||||
};
|
||||
|
||||
// Check whether a config.js file exists inside the storybook
|
||||
// config directory and throw an error if it's not.
|
||||
const storybookConfigPath = path.resolve(configDir, 'config.js');
|
||||
if (!fs.existsSync(storybookConfigPath)) {
|
||||
const err = new Error(`=> Create a storybook config file in "${configDir}/config.js".`);
|
||||
throw err;
|
||||
}
|
||||
config.entry.preview.push(require.resolve(storybookConfigPath));
|
||||
|
||||
// Check whether addons.js file exists inside the storybook.
|
||||
// Load the default addons.js file if it's missing.
|
||||
// Insert it after polyfills.js, but before client/manager.
|
||||
const storybookDefaultAddonsPath = path.resolve(__dirname, 'addons.js');
|
||||
const storybookCustomAddonsPath = path.resolve(configDir, 'addons.js');
|
||||
if (fs.existsSync(storybookCustomAddonsPath)) {
|
||||
logger.info('=> Loading custom addons config.');
|
||||
config.entry.manager.splice(1, 0, storybookCustomAddonsPath);
|
||||
} else {
|
||||
config.entry.manager.splice(1, 0, storybookDefaultAddonsPath);
|
||||
}
|
||||
|
||||
// Check whether user has a custom webpack config file and
|
||||
// return the (extended) base configuration if it's not available.
|
||||
const customConfigPath = path.resolve(configDir, 'webpack.config.js');
|
||||
|
||||
if (!fs.existsSync(customConfigPath)) {
|
||||
logger.info('=> Using default webpack setup based on "polymer-cli".');
|
||||
const configPath = path.resolve(__dirname, './config/defaults/webpack.config.js');
|
||||
const customConfig = require(configPath);
|
||||
|
||||
return customConfig(config);
|
||||
}
|
||||
const customConfig = require(customConfigPath);
|
||||
|
||||
if (typeof customConfig === 'function') {
|
||||
logger.info('=> Loading custom webpack config (full-control mode).');
|
||||
return customConfig(config, configType);
|
||||
}
|
||||
logger.info('=> Loading custom webpack config (extending mode).');
|
||||
return {
|
||||
...customConfig,
|
||||
// We'll always load our configurations after the custom config.
|
||||
// So, we'll always load the stuff we need.
|
||||
...config,
|
||||
// Override with custom devtool if provided
|
||||
devtool: customConfig.devtool || config.devtool,
|
||||
// We need to use our and custom plugins.
|
||||
plugins: [...config.plugins, ...(customConfig.plugins || [])],
|
||||
module: {
|
||||
...config.module,
|
||||
// We need to use our and custom rules.
|
||||
...customConfig.module,
|
||||
rules: [...config.module.rules, ...(customConfig.module.rules || [])],
|
||||
},
|
||||
resolve: {
|
||||
...config.resolve,
|
||||
...customConfig.resolve,
|
||||
alias: {
|
||||
...config.alias,
|
||||
...(customConfig.resolve && customConfig.resolve.alias),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
// @remove-on-eject-end
|
||||
|
||||
// This Webpack plugin ensures `npm install <library>` forces a project rebuild.
|
||||
// We’re not sure why this isn't Webpack's default behavior.
|
||||
// See https://github.com/facebookincubator/create-react-app/issues/186.
|
||||
|
||||
function WatchMissingNodeModulesPlugin(nodeModulesPath) {
|
||||
this.nodeModulesPath = nodeModulesPath;
|
||||
}
|
||||
|
||||
WatchMissingNodeModulesPlugin.prototype.apply = function apply(compiler) {
|
||||
compiler.plugin('emit', (compilation, callback) => {
|
||||
const missingDeps = compilation.missingDependencies;
|
||||
const { nodeModulesPath } = this;
|
||||
|
||||
// If any missing files are expected to appear in node_modules...
|
||||
if (missingDeps.some(file => file.indexOf(nodeModulesPath) !== -1)) {
|
||||
// ...tell webpack to watch node_modules recursively until they appear.
|
||||
compilation.contextDependencies.push(nodeModulesPath);
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = WatchMissingNodeModulesPlugin;
|
28
app/polymer/src/server/config/babel.js
Normal file
28
app/polymer/src/server/config/babel.js
Normal file
@ -0,0 +1,28 @@
|
||||
module.exports = {
|
||||
// Don't try to find .babelrc because we want to force this configuration.
|
||||
babelrc: false,
|
||||
presets: [
|
||||
[
|
||||
require.resolve('babel-preset-env'),
|
||||
{
|
||||
targets: {
|
||||
browsers: ['last 2 versions', 'safari >= 7'],
|
||||
},
|
||||
modules: process.env.NODE_ENV === 'test' ? 'commonjs' : false,
|
||||
},
|
||||
],
|
||||
require.resolve('babel-preset-stage-0'),
|
||||
require.resolve('babel-preset-react'),
|
||||
],
|
||||
plugins: [
|
||||
require.resolve('babel-plugin-transform-regenerator'),
|
||||
[
|
||||
require.resolve('babel-plugin-transform-runtime'),
|
||||
{
|
||||
helpers: true,
|
||||
polyfill: true,
|
||||
regenerator: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user